• 羽夏壳世界——异或加密的实现


    写在前面

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

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

    加密原理

      由于展示最基本最简单的实现,使用算法加密就没用复杂的。如果使用比较复杂的加密,首先你在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位的操作系统也是十分注意堆栈平衡的。

    下一篇

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

  • 相关阅读:
    【HTML】web worker
    Go语言开源13周年啦,看看负责人说了啥
    【AI面试】CrossEntropy Loss 、Balanced Cross Entropy、 Dice Loss 和 Focal Loss 横评对比
    Linux 文本处理命令 - grep
    NetApp FAS2554故障灯常亮case处理过程分享
    工控机与普通电脑的区别对于工业自动化应用至关重要
    pdf转word文档方法
    计算机IO原理
    高精度电压源是什么意思
    内网穿透的应用-Cloudreve搭建云盘系统,并实现随时访问
  • 原文地址:https://www.cnblogs.com/wingsummer/p/16129215.html