羽夏壳世界如何实现代码压缩技术?

2026-05-17 08:171阅读0评论SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2083个文字,预计阅读时间需要9分钟。

《基于BD压缩算法的夏娃世界代码实现详解》

夏娃世界代码实现,详细介绍了BD压缩算法的实现细节。此系列文章将逐字逐句解析代码,展示代码实现的过程和效果截图。欢迎提出宝贵意见。代码实现难度适中,如需进一步讨论,请留言。

羽夏壳世界之压缩代码的实现,详细介绍压缩代码实现细节。 写在前面

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

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

压缩原理

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

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


本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可

本文共计2083个文字,预计阅读时间需要9分钟。

《基于BD压缩算法的夏娃世界代码实现详解》

夏娃世界代码实现,详细介绍了BD压缩算法的实现细节。此系列文章将逐字逐句解析代码,展示代码实现的过程和效果截图。欢迎提出宝贵意见。代码实现难度适中,如需进一步讨论,请留言。

羽夏壳世界之压缩代码的实现,详细介绍压缩代码实现细节。 写在前面

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

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

压缩原理

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

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


本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可