3

羽夏壳世界——异或加密的实现 - 寂静的羽夏

 2 years ago
source link: https://www.cnblogs.com/wingsummer/p/16129215.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

  此系列是本人一个字一个字码出来的,包括代码实现和效果截图。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏壳世界——序 ,方便学习本教程。

  由于展示最基本最简单的实现,使用算法加密就没用复杂的。如果使用比较复杂的加密,首先你在C++代码层面和汇编层面要有配套的代码,C++负责加密,汇编负责自我解密,否则你加密完了,结果加密后的PE文件自己又解密不了,这就很尴尬。
  在所有加密算法,异或加密是最简单的,也是最好是实现的。我们来介绍异或加密的原理。
  已知两个数AB,如果A xor B = C,则C xor B = A,其中xor表示异或运算符。如果不理解,这个是入门编程的最基本的知识,请自行补缺,这里我就不唠叨了。

异或加密的实现

  下面是我们实现异或加密的相关函数:

//
// GNU AFFERO GENERAL PUBLIC LICENSE
//Version 3, 19 November 2007
//
//Copyright(C) 2007 Free Software Foundation, Inc.
//Everyone is permitted to copyand distribute verbatim copies
//of this license document, but changing it is not allowed.
// Author : WingSummer (寂静的羽夏)
// 
//Warning: You can not use it for any commerical use,except you get 
// my AUTHORIZED FORM ME!This project is used for tutorial to teach
// the beginners what is the PE structure and how the packer of the PE files works.
// 
// 注意:你不能将该项目用于任何商业用途,除非你获得了我的授权!该项目用来
// 教初学者什么是 PE 结构和 PE 文件加壳程序是如何工作的。
//

BOOL CWingProtect::XORCodeSection(BOOL NeedReloc, BOOL FakeCode)
{
    using namespace asmjit;

    if (_lasterror != ParserError::Success) return FALSE;

    auto filesize = peinfo.FileSize.QuadPart;

    CodeHolder holder;

    /// <summary>
    /// PointerToRawData
    /// </summary>
    auto p = peinfo.PCodeSection->PointerToRawData;

    /// <summary>
    /// SizeOfRawData
    /// </summary>
    auto sizecode = peinfo.PCodeSection->SizeOfRawData;

    auto repeat = sizecode;

    BYTE* shellcode;
    INT3264 ccount;

    if (is64bit)
    {
        Environment env(Arch::kX64);
        holder.init(env);
        x86::Assembler a(&holder);
        Label loop = a.newLabel();

        x86::Mem mem;
        mem.setSegment(x86::gs);
        mem.setOffset(0x60);

        //生成加密 shellcode,此处的 rax = ImageBase
        a.push(x86::rcx);
        a.push(x86::rdi);

        //xor 解密
        a.mov(x86::rax, mem);
        a.mov(x86::rax, x86::qword_ptr(x86::rax, 0x10));
        a.mov(x86::rdi, x86::rax);
        a.add(x86::rdi, peinfo.PCodeSection->VirtualAddress);
        a.mov(x86::rcx, repeat);

        a.bind(loop);
        if (FakeCode) FakeProtect(a);
        a.xor_(x86::byte_ptr(x86::rdi), 0x55);
        a.inc(x86::rdi);
        a.dec(x86::rcx);
        a.test(x86::rcx, x86::rcx);
        a.jnz(loop);

        //确保此时 rax 或 eax 存放的是 ImageBase ,否则是未定义行为
        if (NeedReloc)
            RelocationSection(a);

        a.pop(x86::rdi);
        a.pop(x86::rcx);

        a.ret();

        shellcode = a.bufferData();
        ccount = holder.codeSize();
    }
    else
    {
        Environment env(Arch::kX86);
        holder.init(env);
        x86::Assembler a(&holder);
        Label loop = a.newLabel();

        x86::Mem mem;
        mem.setSegment(x86::fs);
        mem.setOffset(0x30);

        //生成加密 shellcode
        a.push(x86::ecx);
        a.push(x86::edi);
        a.mov(x86::eax, mem);
        a.mov(x86::eax, x86::dword_ptr(x86::eax, 0x8));
        a.mov(x86::edi, x86::eax);
        a.add(x86::edi, peinfo.PCodeSection->VirtualAddress);
        a.mov(x86::ecx, repeat);

        a.bind(loop);
        if (FakeCode) FakeProtect(a);
        a.xor_(x86::byte_ptr(x86::edi), 0x55);
        a.inc(x86::edi);
        a.dec(x86::ecx);
        a.test(x86::ecx, x86::ecx);
        a.jnz(loop);

        //确保此时 rax 或 eax 存放的是 ImageBase ,否则是未定义行为
        if (NeedReloc)
            RelocationSection(a);

        a.pop(x86::edi);
        a.pop(x86::ecx);

        a.ret();

        shellcode = a.bufferData();
        ccount = holder.codeSize();
    }

    //异或加密
    auto se = (BYTE*)b;
    for (UINT i = 0; i < repeat; i++)
    {
        se[i] ^= (BYTE)0x55;
    }

    //加密完毕,写 Shellcode
    encryptInfo.XORDecodeShellCode = (UINT)peinfo.PointerOfWingSeciton;
    auto ws = GetPointerByOffset(peinfo.WingSecitonBuffer, peinfo.PointerOfWingSeciton);
    memcpy_s(ws, ccount, shellcode, ccount);
    peinfo.PointerOfWingSeciton += ccount;

    if (!NeedReloc)
    {
        auto tmp = (PIMAGE_SECTION_HEADER)TranModPEWapper(peinfo.PCodeSection);
        tmp->Characteristics |= IMAGE_SCN_MEM_WRITE;
    }

    return TRUE;
}

  在C++代码层面,加密代码区内容相关的代码如下:

//异或加密
auto se = (BYTE*)b;
for (UINT i = 0; i < repeat; i++)
{
    se[i] ^= (BYTE)0x55;
}

  ^表示异或运算符,在汇编层面,以64位为例,实现如下所示:

a.mov(x86::rax, mem);
a.mov(x86::rax, x86::qword_ptr(x86::rax, 0x10));
a.mov(x86::rdi, x86::rax);
a.add(x86::rdi, peinfo.PCodeSection->VirtualAddress);
a.mov(x86::rcx, repeat);

a.bind(loop);
if (FakeCode) FakeProtect(a);
a.xor_(x86::byte_ptr(x86::rdi), 0x55);
a.inc(x86::rdi);
a.dec(x86::rcx);
a.test(x86::rcx, x86::rcx);
a.jnz(loop);

  可以看出来汇编写起来比写C++代码麻烦多了,里面有一些代码可能有一些其他的考虑,我们这里说一下:
  首先是FakeProtect,这个就是生成花指令,这里不多说,后面在介绍。还有一个函数比较注意RelocationSection,这个函数是用来生成做重定位的汇编代码的,为什么要有这个函数呢?
  比如我只有异或加密,我们是在硬编码的层面进行的加密,PE被加载进入的时候如果基址不和预想的那样,就会查是否有重定位表,如果有的话就解析并修复。但是,我们的代码是加密的,而重定位表没做修改,它就会错误的把被加密的硬编码进行重定位,这个是不能够允许的。所以我们需要摧毁重定位表,可以看到CWingProtect::Proctect里面有一个函数DestoryRelocation,这个作用就是用来销毁它的,不让PE加载器帮我们做重定位。
  综上所述,我们需要自己做重定位,我们需要在汇编层面来实现重定位表的修复,我们来看一下相关代码:

//
// GNU AFFERO GENERAL PUBLIC LICENSE
//Version 3, 19 November 2007
//
//Copyright(C) 2007 Free Software Foundation, Inc.
//Everyone is permitted to copyand distribute verbatim copies
//of this license document, but changing it is not allowed.
// Author : WingSummer (寂静的羽夏)
// 
//Warning: You can not use it for any commerical use,except you get 
// my AUTHORIZED FORM ME!This project is used for tutorial to teach
// the beginners what is the PE structure and how the packer of the PE files works.
// 
// 注意:你不能将该项目用于任何商业用途,除非你获得了我的授权!该项目用来
// 教初学者什么是 PE 结构和 PE 文件加壳程序是如何工作的。
//

void CWingProtect::RelocationSection(asmjit::x86::Assembler& a)
{
    using namespace asmjit;

    Label loop_xor = a.newLabel();
    Label loop_reloc = a.newLabel();
    Label loop_rt = a.newLabel();
    Label endproc = a.newLabel();
    auto rdd = peinfo.PDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    if (is64bit)
    {
        a.nop();
        a.push(x86::rdi);
        a.push(x86::rcx);

        a.push(x86::rsi);        //征用 rsi
        a.mov(x86::rsi, rdd.VirtualAddress);    //重定位表基址
        a.add(x86::rsi, x86::rax);

        a.push(x86::rdx);    //征用 rdx

        a.push(x86::r10);
        a.mov(x86::r10, peinfo.ImageBase);    //PE 加载后,该值会被重定位,只能写死
        a.sub(x86::r10, x86::rax);
        a.jz(endproc);

        a.bind(loop_rt);
        a.mov(x86::edi, x86::dword_ptr(x86::rsi));        //偏移基址地址
        a.add(x86::rdi, x86::rax);        //此时 rdi 为加载到内存的虚拟基址地址
        //计数
        a.mov(x86::ecx, x86::dword_ptr(x86::rsi, 4));
        a.sub(x86::ecx, 8);
        a.shr(x86::ecx, 1);    //此时为重定位表的真实项目个数
        a.add(x86::rsi, 8);    //将指针指向该索引下的第一个重定位项目

        a.bind(loop_reloc);
        a.dec(x86::rcx);
        a.mov(x86::dx, x86::word_ptr(x86::rsi, x86::rcx, 1));
        a.test(x86::dx, 0xF000);
        a.jz(loop_reloc);        //contine;
        a.and_(x86::edx, 0xFFF);
        a.add(x86::rdx, x86::rdi);
        a.sub(x86::qword_ptr(x86::rdx), x86::r10);    //修正
        a.cmp(x86::rcx, 0);
        a.ja(loop_reloc);

        a.sub(x86::rsi, 8);    //重新指向表头
        a.mov(x86::edx, x86::dword_ptr(x86::rsi, 4));
        a.add(x86::rsi, x86::rdx);        //指向下一个
        a.mov(x86::edx, x86::dword_ptr(x86::rsi));
        a.test(x86::edx, x86::edx);
        a.jnz(loop_rt);

        a.bind(endproc);

        a.pop(x86::r10);
        a.pop(x86::rdx);
        a.pop(x86::rsi);    //释放 rsi 自由身
        a.pop(x86::rcx);
        a.pop(x86::rdi);
    }
    else
    {
        a.push(x86::edi);
        a.push(x86::ecx);

        a.push(x86::esi);        //征用 rsi
        a.mov(x86::esi, rdd.VirtualAddress);    //重定位表基址
        a.add(x86::esi, x86::eax);

        a.push(x86::edx);    //征用 edx

        a.push((DWORD32)peinfo.ImageBase);    //x86寄存器没那么多,只能自己维护一个局部变量
        a.sub(x86::dword_ptr(x86::esp), x86::rax);
        a.jz(endproc);

        a.bind(loop_rt);
        a.mov(x86::edi, x86::dword_ptr(x86::esi));        //偏移基址地址
        a.add(x86::edi, x86::eax);        //此时 rdi 为加载到内存的虚拟基址地址
        //计数
        a.mov(x86::ecx, x86::dword_ptr(x86::esi, 4));
        a.sub(x86::ecx, 8);
        a.shr(x86::ecx, 1);    //此时为重定位表的真实项目个数
        a.add(x86::esi, 8);    //将指针指向该索引下的第一个重定位项目

        a.bind(loop_reloc);
        a.dec(x86::ecx);
        a.mov(x86::dx, x86::word_ptr(x86::rsi, x86::ecx, 1));
        a.test(x86::dx, 0xF000);
        a.jz(loop_reloc);        //contine;
        a.and_(x86::edx, 0xFFF);
        a.add(x86::edx, x86::edi);

        a.push(x86::eax);    //使用局部变量
        a.mov(x86::eax, x86::dword_ptr(x86::esp, 4));    //注意被 push 了一个,所以加个偏移
        a.sub(x86::dword_ptr(x86::edx), x86::eax);    //修正
        a.pop(x86::eax);

        a.cmp(x86::ecx, 0);
        a.ja(loop_reloc);

        a.sub(x86::esi, 8);    //重新指向表头
        a.mov(x86::edx, x86::dword_ptr(x86::esi, 4));
        a.add(x86::esi, x86::rdx);        //指向下一个
        a.mov(x86::edx, x86::dword_ptr(x86::esi));
        a.test(x86::edx, x86::edx);
        a.jnz(loop_rt);

        a.bind(endproc);

        a.add(x86::esp, 4);        //释放局部变量
        a.pop(x86::edx);
        a.pop(x86::esi);    //释放 rsi 自由身

        a.pop(x86::ecx);
        a.pop(x86::edi);
    }

    //将所有的节全部改为可写
    auto length = peinfo.NumberOfSections;
    for (UINT i = 0; i < length; i++)
    {
        ((PIMAGE_SECTION_HEADER)TranModPEWapper(&peinfo.PSectionHeaders[i]))
            ->Characteristics |= IMAGE_SCN_MEM_WRITE;
    }
}

  对于以上代码你可能有一些疑问,我这里说一下:
  为什么调用a.nop()来生成没有用的指令,这个是我用来方便调试我生成的ShellCode用的,否则会生成一大坨汇编到后来自己也不清楚自己在调试啥的,通过这个nop我就可以清楚的直到我到那里了,如果出错的话我也方便进行定位。
  此函数最后生成完ShellCode之后又将所有的节全部改为可写属性,这是为什么呢?因为线性内存是有属性的,如果我没有将其设置可写,如果它是只读内存,如果我对它做重定位修改的话,就会报内存访问错误,导致程序崩溃。
  怎么用汇编来解析重定位表,这里就不赘述了。

ShellCode 编写注意事项

  在编写ShellCode代码的时候,请一定保证如下原则,避免一些麻烦,否则会出现出乎意料的错误:

  1. 除了 eax / rax 其他寄存器用到的话,一定要注意保存好,因为其它函数调用有各种调用约定,一定不要影响它们,否则会出错。为什么要对 eax / rax 区别对待,因为通常来说它只用做返回值,调用函数返回结果一定会修改它,所以大可不必。
  2. 在使用 ASMJIT 生成汇编的时候,使用类似 MOV 的指令的时候,一定要注意如果要写入多大的数据一定要在目标操作数体现数来,比如要移动 WORD 大小的话,用 ax 就不要用 eax,否则它正常生成汇编指令不报错,结果和你想生成的代码不一样。
  3. 一定要注意堆栈平衡,这个是非常重要的东西,在64位尤甚,32位的操作系统也是十分注意堆栈平衡的。

  羽夏壳世界——压缩代码的实现


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK