时间:2022-09-08 09:00:12 | 栏目:C代码 | 点击:次
由于展示最基本最简单的实现,使用算法加密就没用复杂的。如果使用比较复杂的加密,首先你在C++
代码层面和汇编层面要有配套的代码,C++
负责加密,汇编负责自我解密,否则你加密完了,结果加密后的PE
文件自己又解密不了,这就很尴尬。
在所有加密算法,异或加密是最简单的,也是最好是实现的。我们来介绍异或加密的原理。
已知两个数A
和B
,如果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. 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. 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
代码的时候,请一定保证如下原则,避免一些麻烦,否则会出现出乎意料的错误: