练习手脱  脱壳的文件是 手动脱壳进阶第一篇里的文件 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就可以了

上传的附件 Notepad.rar