软件保护壳技术专题 - 变形引擎的构建

变形引擎的构建
<YY>
    好久没有更新文章了。今年工作太忙,确实没有时间来发贴。有些朋友问我,专题会不会成为“太监”
按照我耐磨的性格,应该不会。
<目录>
0.哪些部分需要变形?
1.聊一聊花指令的构建
  1.1产生随机寄存器
  1.2产生随机的清0
  1.3产生随机的JMP
  1.4产生异常
  1.5利用MODRM/SIB
2.构建解密头
3.另一种变形
4.又是另一种变形
5.强调些东西
<内容>
0.哪些部分需要变形
壳的变形技术主要来自于病毒。病毒变形主要目的为了躲避杀毒软件的查杀。壳的变形也无非是这个。当然还有
目的是为了延长破解时间。消耗Cracker的精力。
现在我们来看一个经典的变形结构。也确认哪部分是用来变形的
壳加载段
----------
解密头
壳主体被加密的部分
密钥储存
----------
大多数情况下。壳加载器的结构就是这三个部分。解密头对壳主体进行解密后再跳入到壳主体进行运行
这里就可以很明显的看出 壳主体是很容易进行变形的。只要每次加密的密钥不一样最后的队列肯定不一样
现在解密头是固定的。所以这里是最需要进行变形的。
以上这个结构绝对是经典多态的结构。再复杂的变形也是从以上这个简单的结构开始的。下面看一段简单的CODE

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当然比较简单,当然在解密之前要首先保证代码段是可写属性
现在我们开始变形解密头要如何变形呢?这里可以看出其实总共解密头也没几个字节,提出两种通用方式
一个就是添加花指令构建将花指令丢到解密头每条指令中间。一个就是等价的代码替换,寄存器的替换。
总之解密头是我们自己编写的。我们可以精心的去构建一段代码。并将此按照我们自己的规则进行模板化
每当生成新的壳解密头时进行填充。当然也可以用一些复杂的规则去扩大这个结构。自定义的模板规则。复杂
的花指令生成,等等。产生解密模板的时候可以适当的运用一些编译原理的技术。首先产生一个自己可以理解的
中间指令,然后再翻译成X86代码。这样的好处是可以无限的扩充模板的形势。变形力度很大。在病毒上可能实现要麻烦
些。毕竟病毒要考虑到自身体积的大小。但是壳却不用考虑那么多。
另外一点要说明的是做变形的时候不要拘泥于此格式。可以尽量发挥想象来做。例如把解密分段,或者将解密先写入到栈中
在执行。总之达到恶心Cracker的目的就成。
1.聊一聊花指令的构建
花指令的构建方法有很多种,但是原理都是相同的在不影响运行上下文的情况下,产生一些无效的指令。同样下文以例子展出
如果自己做的,尽量发挥想象力。不用拘泥于这篇文章或者某篇文章。
1.1产生随机寄存器
每条指令对寄存器的操作的编码都有一些规则,具体是什么规则可以读下INTEL的编码规则。这个专题里也有一篇是讲反汇编引擎的
不过为了不产生歧义还是推荐去看INTEL的手册。这里也不讲IA32的编码规则了。直接来看些具体的例子$R表示寄存器,IMM(X)表示
立即数
例如MOV $R, IMM32,形如类似的移动语句。OPCODE是B8 IMM32 这样的。B8表示MOV EAX, 可以在OD里查看B9为MOV ECX,这里有个排序
从EAX开始为EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI这样的顺序。如果翻看MODRM/SIB规则可以看到这个排序规则,至于为什么要这样排
只能去问INTEL公司了。INTEL手册里也没说明。
总之MOV $R, IMM32可以变成为B8 B9 BA BB .... 按照以上那个排序选取寄存器。其他指令也一样例如
POP $R是从58开始的.所以每次选取之需要很简单的一个随机模8就可以了。 但是这里要排除一个意外就是esp。ESP控制着堆栈指针稍微有些不
细心就会造成程序运行错误。为了保险还是把它T出去好了。不过不是非要排除ESP。如果你的花指令产生器构建的够精密,模板够好。
也可以把ESP的变换也加进来。为了稳定,按照我谨慎的性格还是将它排除在外。SHOW段代码。C++的。

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
在编写代码过程中请0操作是常用操作。sub $R,$R ... xor $R,$R等都是常用的清0方式
我们把这些方式记录下来并保存为我们的模板,如果你不喜欢单指令的。也可以做一些复杂些
的模板 mov $R, IMM32; sub $R, IMM32-1; dec $R;都是可以的。以下展示段简单的C++代码

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 &gt;= 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 &gt;= 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; }

可以发挥想象自行编写。包括外部模板导入等等。
1.3产生随机的JMP
这里就不废话了。有了上面的基础,直接看实例代码吧

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产生异常
直接C代码

// 这个其中有些函数是产生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 &lt; 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
说说MODRM/SIB的邪恶之处。如果你不知道什么是MODRM/SIB去看看那篇反汇编引擎的构建吧或者直接去都INTEL手册。
TMD这个壳里运用了很多M/S这个结构构造的花指令。这种构造很恶心尤其是联合上JMP指令。每次你要想在OD里浏览代码
整体样子。都必须要去修复这段代码。相比脱过壳的朋友都遇上过这种。现在看一个基本型.其余复杂的形式靠自己想象了。
再次郑重想做好壳。要时刻保持丰富的想象力还有一颗“邪恶”的心。所谓是人至贱则无敌。壳至贱则无Cracker...
------
00401010:
JMP 004013E ;这里就跳入到了 dword ptr [4 * ebx + 0xFFFFEEEE]这部分
其余指令
004013D
mov eax, dword ptr [4 * ebx + 0xFFFFEEEE]
这里发挥一下想象力吧。dword ptr [4 * ebx + 0xFFFFEEEE] 这部分是可是有足足6个字节的空间可以发挥想象力
但是通常只能利用到SIB位。因为MODRM位产生不了。当Mod == 00 01 10 RM == 100 时,那么它的下一个字节就是SIB了。
通常JMP指令是个不错的选择。因为最长的JMP也就5个字节刚好满足。既有了花指令的作用,又可以对抗反汇编。一个不错的想法。
这的代码太长就不SHOW了自己发挥一下。不难利用。
2.构建解密头
看了以上的CODE,聪明的人应该早就想到如何去构建解密头了。和花指令构建一样。只需要按照事先定义好的模板随机选取适当的寄存器
填写花指令模板。重新修订JMP/CALL/JCC三种指令的偏移就好了。如果看到这里你还是没明白,那么先去补充一下基础知识吧。
3.另一种变形
这种变形主要目的是迷糊Cracker.名字我不知道怎么取,就叫另一种变形好了。让我们看一下这种变形的结构
首先看一下正常的代码段
正常情况
----------
指令段1
CALL C_A
指令段2
C_A:
指令段3
**********
壳段的某区域
----------
壳指令段1
P_A:
壳指令段2
非正常情况
----------
指令段1
CALL P_A
指令段2
C_A:
指令段3
**********
壳段的某区域
----------
壳指令段1
P_A:
花指令
JMP C_A:
壳指令段2
从上面的两种情况可以看出来这种变形了吧。其实这种变形主要是为了对抗一些流程图软件,例如“万恶”的IDA。“万恶”的F5
原理十分简单将属于偏移的CALL/JMP/JCC指令修改偏移首先跳到壳的某个区域,壳的这个区域可以做很多事情了。自动生成的花指令,
让Cracker可以看出来的反调试代码。或者跳入到自己的虚拟机内。执行完毕后JMP到原来正常代码跳入到的地方。这个技巧的运用要
注意的是8位的跳转尽量不要去动。16位的偏移最好计算一下是否偏移够长度。32位的则大胆的去做HOOK吧。
4.又是另一种变形
这种变形我也不知道是什么名字,不过姑且认为是一个替换吧。见结构
正常情况
----------
指令段1
CALL C_A
指令段2
C_A:
指令段3
JMP C_B
指令段4
C_B:
**********
壳段的某区域
----------
壳指令段1
P_A:
壳指令段2
看看做过替换后的结构
非正常情况
----------
指令段1
CALL C_A
JMP P_A
花指令
C_A:
指令段3
JMP C_B
指令段4
C_B:
**********
壳段的某区域
----------
壳指令段1
P_A:
指令段2
JMP C_A
壳指令段2
这里可以看出我们将两个JMP指令中间的指令段2搬运到了壳的某个地方。并将中间的指令用花指令进行填充。这样极大程度的改变了特征码
在指令执行完毕后又跳回了指令执行完毕的地址。这个搬运比较复杂。条件也有诸多限制。例如如果指令段1中有代码跳入到指令段2中就会出现错误
这个问题有一个非完美的解决方案。例如将指令段2内的每条指令的偏移计算得出。并利用JMP指令进行填充。填充后的结构如下
指令段2
----------
JMP P_A(指令段第一条指令)
JMP P_A+N(指令段第二条指令)
JMP P_A+N+N(指令段第三条指令)
.....
以此类推,保证每当跳入到替换后的代码区域可以正常的跳入到原本的指令继续执行。不过也会出现错误。就是空间的限制。指令段2的整体空间
在现实情况下不一定是以5字节对齐的。所以不可能每条指令都有一个入口JMP.最坏的情况的是在某个地方有一个动态跳转。例如CALL DWORD PTR [EAX]
这种形式。正好跳入到一个不以5字节对齐替换段的末尾地址。这会引起不稳定。当然还有一些其他解决方案。在生成之前首先生成整个PE文件的流程图
并进行分析。哪些是可以进行替换的哪些是不可以的。长度问题。偏移问题。是否有指令跳入到这片区域执行,这片区域是否有指令跳出。
我们为什么要截取两个跳转指令直接的代码段进行替换呢?很简单就是大多数JMP类指令都是以偏移做标准的。移动后的代码偏移量不变。跳转地址取发生了
改变。当然如果你有绝对好的设计。也可以考虑修复此偏移在替换后的情况下。我为了稳定在自己壳中,还是忽略了此项设计。原理就是这些。
越是复杂的壳越没有良好的兼容性。都是这样的“龌龊”技巧造成的。掌握了基本PE结构,基本壳原理后。发挥自己的想象力,让Crakcer郁闷去吧。
5.强调些东西
很多花指令生成的花指令都太明显了。可以一眼看出来是花指令,有些居然还出现浮点指令和MMX指令等。说实在的耗无意义。
花指令构建越是像真实指令越好。如果你在构造花指令模板的时候。可以直接构建一些正常代码的模板。例如一套很复杂的数学运行工作的函数。
花指令主要起迷惑作用。垃圾指令只适合病毒对抗杀毒软件。不适合长期与Crakcer做斗争的壳。
这里还要说的是,专题不会成为太监,但是真的不能保证更新速度。我的时间不是非常充裕。希望大家见谅。

在病毒扫描器中使用操作码模拟器解密非常多态化的病毒已不是新鲜想法了。更好的扫描器可以处理带有复杂译码器的病毒,它用一个通用的译码器从病毒上脱去保护壳,从而可以看清病毒的结构。

迄今为止,每个多态变形引擎都可以生成特定类型的译码器。这些译码器的一般行为可能会有差异,而普通的加密器则没有。

作为病毒编写者的我们,必须适应不断创新的编程技术。要让病毒得到进一步的进化,就要将编程普通化。普通化的程序设计可用于所有与病毒相关的手法中。

背景:

每个多态变形引擎基本上都是基于某些将以随机顺序使用的规则,垃圾指令以及一些可以组成不同操作码的指令。但是,如果你把一段代码输入给多态变形引擎,这个引擎如何生成这段代码的变形呢?真正的多态变形引擎将把每一个东西多态化,而不管它做什么。看下面的例子:

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) >&gt;-| 代码编码器 |----&gt;&gt;----| 变形机 |-&gt;&gt; (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段内生成一个解密代码。
.586p
.model flat,STDCALL
extrn ExitProcess: proc
VirusSize=100h
.data
DecodeMethod dd ?
DeCode:
pushad
call Encode
db 100h dup(11h)
Encode:
db 100h dup(0cch)
RndReg0 dd 0 eax
RndReg1 dd 0 ebx
RndCode dd 0 Rnd Code
RndMima dd 60932561 Rnd Password
.code
@@Start:
mov eax,RndMima
ror eax,7
mov RndCode,eax
mov eax,RndCode
mov ecx,eax
and eax,011b
mov RndReg0,eax
xor ecx,RndMima
and ecx,011b
cmp eax,ecx
jnz short ChooseRegOk
inc ecx
and ecx,011b
ChooseRegOk:
mov RndReg1,ecx
mov edi,offset Encode
ror RndCode,1
call GetBxCode,0,RndReg0,RndCode
mov esi,eax
ContFillStep0:
cld
lodsb
stosb
cmp al,0cch
jnz ContFillStep0
dec edi
ror RndCode,1
call GetBxCode,1,RndReg1,RndCode
mov esi,eax
ContFillStep1:
cld
lodsb
stosb
cmp al,0cch
jnz ContFillStep1
dec edi
mov ebx,edi //计算机Jmp指令用
ror RndCode,1
call GetBxCode,2,RndReg0,RndCode
mov esi,eax
ContFillStep2:
cld
lodsb
stosb
cmp al,0cch
jnz ContFillStep2
dec edi
mov eax,RndMima
mov [edi-4],eax //填写随机密码
mov eax,RndCode
and eax,01
mov DecodeMethod,eax //填写DeCode方法
ror RndCode,1
call GetBxCode,3,RndReg0,RndCode
mov esi,eax
ContFillStep3:
cld
lodsb
stosb
cmp al,0cch
jnz ContFillStep3
dec edi
ror RndCode,1
call GetBxCode,4,RndReg1,RndCode
mov esi,eax
ContFillStep4:
cld
lodsb
stosb
cmp al,0cch
jnz ContFillStep4
dec edi
ror RndCode,1
call GetBxCode,5,RndReg0,RndCode
mov esi,eax
ContFillStep5:
cld
lodsb
stosb
cmp al,0cch
jnz ContFillStep5
dec edi
mov al,0c3h
mov [edi],al //填写Ret指令
sub ebx,edi
mov [edi-1],bl //填写jmp指令
int 3;
jmp DeCode
ret
GetBxCode proc uses ebx ecx edx esi edi,Step:dword,Reg:dword,Rnd:dword
call GetBxCodeAddr
Step0_Eax:
mov eax,[esp]
int 3;
pop eax
push eax
int 3;
Step0_Ebx:
pop ebx
push ebx
int 3;
push dword ptr[esp]
pop ebx
int 3;
Step0_Ecx:
mov ecx,[esp]
int 3;
pop ecx
push ecx
int 3;
Step0_Edx:
mov edx,[esp]
int 3;
mov edx,esp
mov edx,[edx]
int 3
Step1_Eax:
mov eax,VirusSize
int 3
sub eax,eax
add ax,VirusSize+3081h
sub ax,3081h
int 3
Step1_Ebx:
mov ebx,VirusSize
int 3;
xor ebx,ebx
or bx,VirusSize
int 3;
Step1_Ecx:
sub ecx,ecx
xor ecx,(VirusSize xor 3181h)
xor ecx,(3181h)
int 3;
mov ecx,0
and cx,VirusSize
int 3
Step1_Edx:
and edx,0
xor dx,(VirusSize-0281h)
add dx,0281h
int 3;
xor edx,edx
sub edx,(0181h-VirusSize)
sub edx,-0181h
int 3;
Setp2_Eax:
xor [eax],12345678h
int 3
add [eax],12345678h
int 3
Setp2_Ebx:
xor [ebx],12345678h
int 3;
add [ebx],12345678h
int 3;
Setp2_Ecx:
xor [ecx],12345678h
int 3;
add [ecx],12345678h
int 3;
Setp2_Edx:
xor [edx],12345678h
int 3;
add [edx],12345678h
int 3;
Step3_Eax:
add eax,4
int 3
inc eax
inc eax
inc eax
inc eax
int 3;
Step3_Ebx:
add ebx,5
dec ebx
int 3
add ebx,2
add ebx,2
int 3;
Step3_Ecx:
sub ecx,-4
int 3
sub ecx,-5
dec ecx
int 3;
Step3_Edx:
inc edx
sub edx,-3
int 3
add edx,04
int 3;
Step4_Eax:
sub eax,4
int 3
dec eax
dec eax
dec eax
sub eax,1
int 3;
Step4_Ebx:
dec ebx
sub ebx,3
int 3;
dec ebx
dec ebx
sub ebx,2
int 3;
Step4_Ecx:
add cx,123
sub cx,123+4
int 3
sub cx,-4
dec cx
sub cx,7
int 3
Step4_Edx:
sub dx,2
dec dx
sub dx,1
int 3
inc edx
sub dx,5
int 3;
Step5_Eax:
jnz $
int 3
ja $
int 3
Step5_Ebx:
jg $
int 3
jnb $
int 3
Step5_Ecx:
jnl $
int 3
jnz $
int 3
Step5_Edx:
ja $
int 3
jg $
int 3
GetBxCodeAddr:
pop esi
mov al,0cch //指令分割符
mov ecx,Step
shl ecx,1
shl ecx,1
add ecx,Reg //计算机得到的指令位置
shl ecx,1
and Rnd,01b
add ecx,Rnd
jcxz short GetBxCodeOver
ContFindCode:
push ecx
ContFindCC:
inc esi
cmp [esi],al
jnz ContFindCC
pop ecx
loop ContFindCode
mov eax,esi
inc eax
ret
GetBxCodeOver:
mov eax,esi
ret
GetBxCode endp
end @@Start

你可能感兴趣的:(技术,软件,构建,引擎,专题)