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

写在前面

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

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

压缩原理

  由于展示最基本最简单的实现,使用压缩算法就没用复杂的。如果使用比较复杂的压缩算法,首先你在C++代码层面和汇编层面要有配套的代码,C++负责压缩代码,汇编负责自我解压缩,否则你压缩完了,结果被压缩后的PE文件自己又解不了,这就很尴尬。
  我们本项目使用的算法被称之为RLE压缩算法,英文全称是run-length encoding,亦称行程长度编码。听起来高大上,下面我用比较通俗语言就行介绍。
  比如一个字符串:AABBCCDDDDEEEEEEEEE,一个二十个字符,我们如何使用该算法进行压缩呢?
  好,A有两个,就用2A表示;B有两个,用2B表示……最后我们得到下面的字符串:2A2B2C4D10E,可以看到长度被进行了压缩。
  这种算法有一个比较严重的弊端,如果每一组相邻的字符相同的少于2个,会导致负面影响,导致没有压缩效果甚至膨胀,但对于压缩代码比较足够了。因为里面会有大量的0xCC0x00这样的字节,可以忽略这种算法的缺陷导致的影响。

压缩的实现

  既然是使用该方式进行压缩,首先我们得进行编码。每一个压缩块定义如下:

#pragma pack(1)
    struct codata
    {
        BYTE code;
        BYTE count;
    };
#pragma pack()

  可以看出每一个压缩块的大小为双字,低字节放着是重复的代码,高字节放着是重复的个数。
  如果你细心的发现,这个压缩块最多一次放0xFF大小的重复字符,这个得考虑到,否则解压缩的时候会发生错误,与压缩代码相关的代码如下:

BOOL CWingProtect::CompressSeciton(BOOL NeedReloc, BOOL FakeCode)
{

    using namespace asmjit;

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

#pragma pack(1)
    struct codata
    {
        BYTE code;
        BYTE count;
    } cdata{};
#pragma pack()

    list<codata> datas;
    auto p = (BYTE*)OFFSET(packedPE, peinfo.PCodeSection->PointerToRawData);
    auto length = peinfo.PCodeSection->SizeOfRawData;
    CodeHolder holder;
    CodeHolder jmpholder;

    //开始进行压缩
    for (UINT i = 0; i < length; i++, p++)
    {
        cdata.count = 1;
        cdata.code = *p;
        while (true)
        {
            if (cdata.count < 0xFF && i + 1 < length && *(p + 1) == cdata.code)
            {
                cdata.count++;
                i++;
                p++;
            }
            else
            {
                datas.push_back(cdata);
                break;
            }
        }
    }

    auto wingSection = peinfo.WingSection;
    auto buffer = GetPointerByOffset(peinfo.WingSecitonBuffer, peinfo.PointerOfWingSeciton);
    encryptInfo.CompressedData = (UINT)peinfo.PointerOfWingSeciton;

    BYTE* shellcode;
    INT3264 codesize;
    INT3264 datasize;

    Environment envX64(Arch::kX64);

    // gs:[0x60]
    x86::Mem memX64;
    memX64.setSegment(x86::gs);
    memX64.setOffset(0x60);

    Environment envX86(Arch::kX86);
    //    fs:[0x30]
    x86::Mem memX86;
    memX86.setSegment(x86::fs);
    memX86.setOffset(0x30);

    auto rvabase = peinfo.AnalysisInfo.MinAvailableVirtualAddress;
#define AddRVABase(offset) ((UINT)offset + (UINT)rvabase)

    if (is64bit)
    {
        //生成汇编代码
        holder.init(envX64);
        x86::Assembler a(&holder);
        Label loop = a.newLabel();
        Label loop_d = a.newLabel();

        a.push(x86::rsi);
        a.push(x86::rdi);
        a.push(x86::rcx);
        a.push(x86::rdx);

        a.mov(x86::rax, memX64);
        a.mov(x86::rdx, x86::qword_ptr(x86::rax, 0x10));

        a.mov(x86::rsi, AddRVABase(encryptInfo.CompressedData));
        a.add(x86::rsi, x86::rdx);
        a.mov(x86::rdi, peinfo.PCodeSection->VirtualAddress);
        a.add(x86::rdi, x86::rdx);

        a.mov(x86::rcx, x86::qword_ptr(x86::rsi));
        a.add(x86::rsi, 8);

        a.xor_(x86::eax, x86::eax);

        a.bind(loop);
        a.mov(x86::ax, x86::word_ptr(x86::rsi));

        a.bind(loop_d);
        if (FakeCode) FakeProtect(a);
        a.mov(x86::byte_ptr(x86::rdi), x86::al);
        a.inc(x86::rdi);
        a.dec(x86::ah);
        a.test(x86::ah, x86::ah);
        a.jnz(loop_d);
        a.add(x86::rsi, 2);
        a.dec(x86::rcx);
        a.test(x86::rcx, x86::rcx);
        a.jnz(loop);

        a.mov(x86::rax, x86::rdx);    //此时执行完毕后 rax 存放的是 ImageBase
        a.pop(x86::rdx);
        a.pop(x86::rcx);
        a.pop(x86::rdi);
        a.pop(x86::rsi);

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

        a.ret();

        shellcode = a.bufferData();
        codesize = holder.codeSize();
        datasize = datas.size() * sizeof(codata) + sizeof(INT64);
    }
    else
    {
        holder.init(envX86);
        x86::Assembler a(&holder);
        Label loop = a.newLabel();
        Label loop_d = a.newLabel();

        a.push(x86::esi);
        a.push(x86::edi);
        a.push(x86::ecx);
        a.push(x86::edx);

        a.mov(x86::eax, memX86);
        a.mov(x86::edx, x86::qword_ptr(x86::eax, 0x8));

        a.mov(x86::esi, AddRVABase(encryptInfo.CompressedData));
        a.add(x86::esi, x86::edx);
        a.mov(x86::edi, peinfo.PCodeSection->VirtualAddress);
        a.add(x86::edi, x86::edx);

        a.mov(x86::ecx, x86::dword_ptr(x86::esi));
        a.add(x86::esi, 8);

        a.xor_(x86::eax, x86::eax);

        a.bind(loop);
        a.mov(x86::ax, x86::word_ptr(x86::rsi));

        a.bind(loop_d);
        if (FakeCode) FakeProtect(a);
        a.mov(x86::byte_ptr(x86::edi), x86::al);
        a.inc(x86::edi);
        a.dec(x86::ah);
        a.test(x86::ah, x86::ah);
        a.jnz(loop_d);
        a.add(x86::esi, 2);
        a.dec(x86::ecx);
        a.test(x86::ecx, x86::ecx);
        a.jnz(loop);

        a.mov(x86::eax, x86::edx);    //此时执行完毕后 rax 存放的是 ImageBase
        a.pop(x86::edx);
        a.pop(x86::ecx);
        a.pop(x86::edi);
        a.pop(x86::esi);

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

        a.ret();

        shellcode = a.bufferData();
        codesize = holder.codeSize();
        datasize = datas.size() * sizeof(codata) + sizeof(INT32);
    }

    encryptInfo.ShellCodeDeCompress = (UINT)(encryptInfo.CompressedData + datasize);
    peinfo.PointerOfWingSeciton += (datasize + codesize);

    codata* pc;

    if (is64bit)
    {
        auto bd = (INT64*)buffer;
        *bd = (INT64)datas.size();
        pc = (codata*)(bd + 1);
    }
    else
    {
        auto bd = (INT32*)buffer;    
        *bd = (INT32)datas.size();
        pc = (codata*)(bd + 1);
    }

    //生成数据
    for (auto i = datas.begin(); i != datas.end(); i++, pc++)
    {
        *pc = *i;
    }

    memcpy_s(pc, codesize, shellcode, codesize);        //拷贝 shellcode

    //清空代码段
    ::memset((LPVOID)OFFSET(packedPE, peinfo.PCodeSection->PointerToRawData), 0, peinfo.PCodeSection->SizeOfRawData);

    auto tmp = (PIMAGE_SECTION_HEADER)TranModPEWapper(peinfo.PCodeSection);
    tmp->Characteristics |= IMAGE_SCN_MEM_WRITE;

    return TRUE;
}

  对于以上代码你可能有一些疑问,我这里说一下:
  为什么有重定位的相关代码生成操作,这个原因我在上一篇说了,这里就不赘述了。
  为什么将被压缩的代码清空?因为压缩之后源代码还在,不清理的话这个和没被压缩有什么区别。
  为什么将代码块改为可写?因为我需要写啊,类似的原因在上一篇说过了。
  怎么用代码实现压缩和写ShellCode进行解密,这里就不唠叨了。

ShellCode 编写注意事项

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

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

下一篇

  羽夏壳世界——导入表加密的实现

posted @ 2022-04-11 12:12  寂静的羽夏  阅读(178)  评论(0编辑  收藏  举报