练习手脱 脱壳的文件是 手动脱壳进阶第一篇里的文件 98记事本
0040D042 > B8 00D04000 MOV EAX,Notepad.0040D000 ;最后一节的起始地址
0040D047 68 4C584000 PUSH Notepad.0040584C ;异常处理例程
0040D04C 64:FF35 0000000>PUSH DWORD PTR FS:[0]
0040D053 64:8925 0000000>MOV DWORD PTR FS:[0],ESP ;建立异常处理链
0040D05A 66:9C PUSHFW ;这里入栈2字节
0040D05C 60 PUSHAD ;入栈32字节
0040D05D 50 PUSH EAX ;入栈4字节
0040D05E 68 00004000 PUSH Notepad.00400000 ;IMAGEBASE 入栈4字节 一共入栈了2A H字节数 后面需要用到
0040D063 8B3C24 MOV EDI,DWORD PTR SS:[ESP] ;IMAGEBASE 送EDI
0040D066 8B30 MOV ESI,DWORD PTR DS:[EAX] ;[40d000]里存放的是USER32.DLL的IMAGE_IMPORT_DESCRIPTOR结构相对于最后一节的偏移2B4H
0040D068 66:81C7 8007 ADD DI,780 ;将要存放导入表部分内容的地址[400780]
0040D06D 8D7406 08 LEA ESI,DWORD PTR DS:[ESI+EAX+8]
0040D071 8938 MOV DWORD PTR DS:[EAX],EDI
0040D073 8B5E 10 MOV EBX,DWORD PTR DS:[ESI+10] ;得到VirtualProtect的地址 是在导入表中得到的
0040D076 50 PUSH EAX
0040D077 56 PUSH ESI
0040D078 6A 02 PUSH 2
0040D07A 68 80080000 PUSH 880
0040D07F 57 PUSH EDI
0040D080 6A 12 PUSH 12
0040D082 6A 06 PUSH 6
0040D084 56 PUSH ESI ;&oldProtect
0040D085 6A 04 PUSH 4 ;PAGE_READWRITE
0040D087 68 80080000 PUSH 880 ;size
0040D08C 57 PUSH EDI ;起始地址IMAGEBASE
0040D08D FFD3 CALL EBX ;修改内存属性为 PAGE_READWRITE // kernel32.VirtualProtect
0040D08F 83EE 08 SUB ESI,8
0040D092 59 POP ECX
0040D093 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI];复制导入表中函数地址
0040D095 59 POP ECX
0040D096 66:83C7 68 ADD DI,68
0040D09A 81C6 B6000000 ADD ESI,0B6
0040D0A0 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI];复制导入表中DLL名称
0040D0A2 FFD3 CALL EBX ;修改属性为只读
0040D0A4 58 POP EAX
从40D1B8开始的数据类似一个数据结构
struct _PETITE
{
+0 dd number;number的最高位为1的话 则低位字 为复制数据的DWORD数目
+4 dd start;复制数据的原始内存RVA 复制的数据其实就是压缩后的数据 (由于DF置位 复制是从后往前的)
+8 dd target;复制数据的目的内存RVA
+c dd start2;压缩数据的起始内存RVA 这个字段+IMAGEBASE后 实际上就是target复制完后EDI-4(就是复制后的数据的起始内存)
+10 dd ?? ;跟压缩算法有关 估计类似 解压过程 中循环的 次数 不熟悉压缩算法
+14 dd target2;压缩数据的目的内存RVA 这个字段+IMAGEBASE后 就是start复制完后ESI-4(复制前的数据起始内存)
+18 dd xx
}PETITE, *PPETITE;
0040D0A5 8D90 B8010000 LEA EDX,DWORD PTR DS:[EAX+1B8]
0040D0AB 8B0A MOV ECX,DWORD PTR DS:[EDX] ;[40D1B8] PETITE.number
0040D0AD 0FBAF1 1F BTR ECX,1F ;测试最高位 并清零
0040D0B1 73 16 JNB SHORT Notepad.0040D0C9
0040D0B3 8B0424 MOV EAX,DWORD PTR SS:[ESP] ;IMAGEBASE
0040D0B6 FD STD ;标志位置位
0040D0B7 8BF0 MOV ESI,EAX
0040D0B9 8BF8 MOV EDI,EAX
0040D0BB 0372 04 ADD ESI,DWORD PTR DS:[EDX+4] ;PETITE.start
0040D0BE 037A 08 ADD EDI,DWORD PTR DS:[EDX+8] ;PETITE.target
0040D0C1 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
0040D0C3 83C2 0C ADD EDX,0C
0040D0C6 FC CLD
0040D0C7 ^ EB E2 JMP SHORT Notepad.0040D0AB
0040D0C9 83C2 10 ADD EDX,10
0040D0CC 8B5A F4 MOV EBX,DWORD PTR DS:[EDX-C] ;PETITE.??
0040D0CF 85DB TEST EBX,EBX
0040D0D1 ^ 74 D8 JE SHORT Notepad.0040D0AB
0040D0D3 8B0424 MOV EAX,DWORD PTR SS:[ESP]
0040D0D6 8B7A F8 MOV EDI,DWORD PTR DS:[EDX-8] ;PETITE.target2
0040D0D9 03F8 ADD EDI,EAX
0040D0DB 52 PUSH EDX
0040D0DC 8D3401 LEA ESI,DWORD PTR DS:[ECX+EAX]
0040D0DF EB 17 JMP SHORT Notepad.0040D0F8
0040D0E1 58 POP EAX
0040D0E2 58 POP EAX
0040D0E3 58 POP EAX
0040D0E4 5A POP EDX
0040D0E5 ^ 74 C4 JE SHORT Notepad.0040D0AB
0040D0E7 ^ E9 1CFFFFFF JMP Notepad.0040D008
0040D0EC 02D2 ADD DL,DL
0040D0EE 75 07 JNZ SHORT Notepad.0040D0F7
0040D0F0 8A16 MOV DL,BYTE PTR DS:[ESI]
0040D0F2 83EE FF SUB ESI,-1
0040D0F5 12D2 ADC DL,DL
0040D0F7 C3 RETN
0040D0F8 81FB 00000100 CMP EBX,10000 ;
0040D0FE 73 0E JNB SHORT Notepad.0040D10E
0040D100 68 60C0FFFF PUSH -3FA0
0040D105 68 60FCFFFF PUSH -3A0
0040D10A B6 05 MOV DH,5
0040D10C EB 22 JMP SHORT Notepad.0040D130
0040D10E 81FB 00000400 CMP EBX,40000
0040D114 73 0E JNB SHORT Notepad.0040D124
0040D116 68 8081FFFF PUSH FFFF8180
0040D11B 68 80F9FFFF PUSH -680
0040D120 B6 07 MOV DH,7
0040D122 EB 0C JMP SHORT Notepad.0040D130
0040D124 68 0083FFFF PUSH FFFF8300
0040D129 68 00FBFFFF PUSH -500
0040D12E B6 08 MOV DH,8
0040D130 6A 00 PUSH 0
0040D132 32D2 XOR DL,DL
0040D134 4B DEC EBX ;PETITE.?? 自减 (猜测 循环次数为PETITE.??/4)
0040D135 A4 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] ;解压开始 PETITE结构共有2个 解压2次 最后这里触发一个异常
第一次解压起始地址40AF2C 目的地址为407C04
第二次解压起始地址404448 目的地址为401000
整个过程其实就是把压缩后的数据 复制到一块内存区 解压后再复制回来
两次解压将PE文件的 第一个节和第2个节 解压完 然后触发异常 转到异常
处理例程
第一次异常处理
0040584C E8 4F000000 CALL Notepad.004058A0
004058A0 33C0 XOR EAX,EAX ;EAX置0
004058A2 5E POP ESI ;405851
004058A3 64:8B18 MOV EBX,DWORD PTR FS:[EAX] ;FS:[0] Ptr32 _EXCEPTION_REGISTRATION_RECORD
004058A6 8B1B MOV EBX,DWORD PTR DS:[EBX] ;_EXCEPTION_REGISTRATION_RECORD.Next
;这里是找到第一次异常处理链堆栈
004058A8 8D63 D6 LEA ESP,DWORD PTR DS:[EBX-2A] ;重置ESP 注意这里的2A与开始的入栈的2A H对应
004058AB 5D POP EBP ;取出IMAGEBASE -4 H
004058AC 8D8E BD020000 LEA ECX,DWORD PTR DS:[ESI+2BD] ;新的异常处理例程地址
004058B2 894B 04 MOV DWORD PTR DS:[EBX+4],ECX ;直接写到了第一次异常发生前的_EXCEPTION_REGISTRATION_RECORD.Handler
004058B5 64:891D 0000000>MOV DWORD PTR FS:[0],EBX ;新的异常处理建立
004058BC 8B3C24 MOV EDI,DWORD PTR SS:[ESP] ;最后一节的起始VA
004058BF 81C7 39000000 ADD EDI,39 ;
004058C5 6A 0E PUSH 0E
004058C7 59 POP ECX
004058C8 F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] ;复制从405851开始的14个字节到 40D039处 执行完毕后EDI = 40D047
004058CA FF33 PUSH DWORD PTR DS:[EBX] ; 异常处理链的尾端的 _EXCEPTION_REGISTRATION_RECORD.Next
004058CC 56 PUSH ESI
004058CD 57 PUSH EDI ;三次PUSH +c H
004058CE 8DB7 71010000 LEA ESI,DWORD PTR DS:[EDI+171] ;ESI = 40D1B8 正好到了PETITE结构
004058D4 8BCE MOV ECX,ESI
004058D6 2BCF SUB ECX,EDI
004058D8 F3:AA REP STOS BYTE PTR ES:[EDI] ;内存以0填充 执行完毕后 EDI = ESI
004058DA 60 PUSHAD ;+ 20 H
004058DB 66:9C PUSHFW
004058DD 0FBA3C24 08 BTC DWORD PTR SS:[ESP],8 ;位测试并取反 传说中的单步异常 设置TF位
004058E2 66:9D POPFW
004058E4 5B POP EBX ;
004058E5 5A POP EDX ;单步异常
004058E6 64:8F05 0000000>POP DWORD PTR FS:[0]
第二次异常处理
00405B0E 33C0 XOR EAX,EAX
00405B10 64:8B18 MOV EBX,DWORD PTR FS:[EAX] ;FS[0] Ptr32 _EXCEPTION_REGISTRATION_RECORD
00405B13 8B1B MOV EBX,DWORD PTR DS:[EBX] ;_EXCEPTION_REGISTRATION_RECORD.Next
;这里找到建立第二次异常链后的堆栈
00405B15 8D63 AE LEA ESP,DWORD PTR DS:[EBX-52] ; 52H = 2AH -4H + 0CH +20H 定位到了第二次异常链建立好后
;pushad后的 堆栈
00405B18 61 POPAD
00405B19 833E 00 CMP DWORD PTR DS:[ESI],0
00405B1C ^ 0F84 C2FDFFFF JE Notepad.004058E4
00405B22 0FBA26 1F BT DWORD PTR DS:[ESI],1F ;测试最高位PETITE.number
00405B26 73 05 JNB SHORT Notepad.00405B2D ;CF 为0 则跳
00405B28 83C6 0C ADD ESI,0C
00405B2B ^ EB EC JMP SHORT Notepad.00405B19
00405B2D 8B7E 08 MOV EDI,DWORD PTR DS:[ESI+8] ;PETITE.target2
00405B30 03FD ADD EDI,EBP ;定位到解压后的节中
00405B32 8B4E 0C MOV ECX,DWORD PTR DS:[ESI+C] ;PETITE.xx
00405B35 D1F9 SAR ECX,1
00405B37 51 PUSH ECX
00405B38 72 15 JB SHORT Notepad.00405B4F
00405B3A 037E 04 ADD EDI,DWORD PTR DS:[ESI+4] ;PETITE.??(从这段代码看 ??可能还是解压后有效数据的大小 ?)
00405B3D C1F9 02 SAR ECX,2
00405B40 33C0 XOR EAX,EAX
00405B42 F3:AB REP STOS DWORD PTR ES:[EDI] ;清0 字节数为PETITE.xx/2(XX看来跟解压后的无效数据大小有关 ?)
00405B44 59 POP ECX
00405B45 83E1 03 AND ECX,3
00405B48 F3:AA REP STOS BYTE PTR ES:[EDI]
00405B4A 83C6 10 ADD ESI,10
00405B4D ^ EB CA JMP SHORT Notepad.00405B19
;下面就是最后的修正算法了
00405B4F 8B5E 04 MOV EBX,DWORD PTR DS:[ESI+4] ;PETITE.??
00405B52 83EB 06 SUB EBX,6 ;PETITE.??-6
00405B55 33D2 XOR EDX,EDX
00405B57 ^ 72 E1 JB SHORT Notepad.00405B3A
00405B59 8A043A MOV AL,BYTE PTR DS:[EDX+EDI]
00405B5C 3C E8 CMP AL,0E8 ;E8和E9是CALL和JMP的机器码 开始修正mov 的操作数
00405B5E 74 0E JE SHORT Notepad.00405B6E
00405B60 3C E9 CMP AL,0E9
00405B62 74 0A JE SHORT Notepad.00405B6E
00405B64 3C 0F CMP AL,0F ;0F80 - 0F8F 查的书上 似乎都是段内跳转指令的机器码 修改跳转指令的操作数
00405B66 74 12 JE SHORT Notepad.00405B7A
00405B68 42 INC EDX
00405B69 83EB 01 SUB EBX,1
00405B6C ^ EB E9 JMP SHORT Notepad.00405B57
00405B6E 29543A 01 SUB DWORD PTR DS:[EDX+EDI+1],EDX ;如果是MOV指令 就把后面的操作数机器码 - MOV指令机器码相对于解密起始处的偏 移
00405B72 83C2 05 ADD EDX,5
00405B75 83EB 05 SUB EBX,5
00405B78 ^ EB DD JMP SHORT Notepad.00405B57
00405B7A 8A443A 01 MOV AL,BYTE PTR DS:[EDX+EDI+1]
00405B7E 3C 80 CMP AL,80
00405B80 ^ 72 E6 JB SHORT Notepad.00405B68
00405B82 3C 8F CMP AL,8F
00405B84 ^ 77 E2 JA SHORT Notepad.00405B68
00405B86 29543A 02 SUB DWORD PTR DS:[EDX+EDI+2],EDX ;如果是段内跳转指令就把
00405B8A 83C2 06 ADD EDX,6
00405B8D 83EB 06 SUB EBX,6
00405B90 ^ EB C5 JMP SHORT Notepad.00405B57
00405B92 59 POP ECX
00405B93 5E POP ESI
00405B94 FD STD
00405B95 33C0 XOR EAX,EAX
00405B97 B9 56030000 MOV ECX,356
00405B9C E8 43B98624 CALL 24C714E4 ;解密后 从这里F7(解密后 这个CALL的转移地址要变的)
整个算法描述 有点啰嗦:如果碰到CALL和JMP指令或者段内转移指令 就把它们的操作数机器码 减去 本身相对于解密起始处的偏移
0040D039 5F POP EDI ; Notepad.00405BA1
0040D03A F3:AA REP STOS BYTE PTR ES:[EDI]
0040D03C 61 POPAD
0040D03D 66:9D POPFW
0040D03F 83C4 08 ADD ESP,8
0040D042 >- E9 8540FFFF JMP Notepad.004010CC ; 哈哈 回到加壳后的入口点地址 跳到OEP
总结:整个脱壳过程中 数据复制2次 解压2次 解密一次 2次异常 而且 关键跳 就在处EP
快速脱壳 除了最后一次异常法还可以 用多次内存断点 或者 直接在 载入后的入口点下个硬件执行断点F9就可以了
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)