• 标 题:attach法脱壳
  • 作 者:kongfoo
  • 时 间:004-05-12,10:27
  • 链 接:http://bbs.pediy.com

attach法脱壳
kongfoo/2004.5.10-12

  现在的壳anti都不弱,如果从壳入口一步步跟到OEP的话会很累,这里介绍
一种比较舒服的方法,不过有小小要求:掌握壳的一般知识,如IAT加密,stolen
code等。掌握PE文件知识,如一般地址表会放在什么地方,各种编译器生成的代码
入口点一般在什么地方等。掌握汇编语言的编写,因为我们面对的是加密后的IAT,
修复时通常都要写程序修复。
  快速脱壳方法有很多种,Memory Map断点法(我最先见到是二哥/David的文章
介绍这种方法),ESP定律法(fly大侠总结出来的方法)等。
  现在这种方法姑且叫attach法吧,当然这个方法不是我首创的,我只是总结一下。
相信很多大侠都用过这种方法脱壳这种方法有局限性:目标有anti attach的话就
很麻烦了,比如双进程,进程逃逸等。

  DFCG上Volx大侠提供的asp1.4壳目标程序。用这个程序做attach法脱壳例子不很
合适,因为这个程序在OD下可以运行起来,而attach法脱壳比较适用于对付在OD下不
能直接运行起来的壳。不管了 这里只介绍有这样一种办法,当然只靠这个方法
是不行的,几种方法结合起来才有效率。
壳入口:(OD设置:忽略全部异常,IsDebuggerPresent Hide)

代码:
00401000 >  68 01D07300     PUSH dskchkr.0073D001 00401005    E8 01000000     CALL dskchkr.0040100B 0040100A    C3              RETN 0040100B    C3              RETN

  用attach上的程序和壳比较一下就可以发现,壳的代码在程序运行后换成原程序的代码了。
我们要做的是恢复一份IAT。在进程空间bpx j,观察一下函数调用情况,都CALL到动态申请的
空间了,有部分API没有处理。
  看看一个地址项目:
call [cdca0c] cdca0c的内容是cdca18,去cdca18分析一下代码就可以找出原API地址。
(伪API代码cdca18紧接着cdca0c后面)
代码:
00CDCA18    33C0            XOR EAX,EAX 00CDCA1A    50              PUSH EAX 00CDCA1B    50              PUSH EAX 00CDCA1C    50              PUSH EAX 00CDCA1D    FF7424 10       PUSH DWORD PTR SS:[ESP+10] 00CDCA21    50              PUSH EAX 00CDCA22    F2:             PREFIX REPNE:                            ; Superfluous prefix 00CDCA23    EB 01           JMP SHORT 00CDCA26 00CDCA25    90              NOP  ==处理过花指令 00CDCA26    68 BF20C477     PUSH 77C420BF  ==根据这个地址很容易就找出原API地址了

  去77c420bf向上一看,API地址是77c420b0。去cdca0c写入地址(反序),原来是gdi32.CreateSlidBrush。

  分析一下其对API的处理,有几种情况:
  1、摘取API从开头到CALL之前的代码,到CALL时压入CALL之后的地址,后面接着摘取CALL里面的代码过来。
  2、整个API搬了过来。
  3、摘取API从开头到CALL之前的代码,再压入CALL的地址返回。
  4、分析的API不多,可能还有其它处理方式。

  伪地址表在cb0000段开始。

  bp VirtualAlloc,几次之后申请cb0000(连续申请2次,第2次才是),可以跟了。申请cb0000后回到这里:
代码:
00C815CE    85C0            TEST EAX,EAX 00C815DE    81FE 5024CA00   CMP ESI,0CA2450  ==本来想去ca2450看看的,眼花了看成是ca4250,去到一看,乖乖,是正确的API地址表!

立即复制一份下来(用binary copy)。ca4100开始。看看401000处,代码还没解码。还是
bp VirtualAlloc,在第2次申请cc0000时原程序解码了(用dump窗口监视)。所有ff15
的CALL的目的地址都用随机数改掉了。找第一个CALL下内存写断点,看看哪里进行处理。
代码:
00402676    FF15 6034CD00   CALL DWORD PTR DS:[672B6EAE]  ==在402678下内存写断 00C95755    8906            MOV DWORD PTR DS:[ESI],EAX  ==断下来,知道处理代码的地址就好办了。                             ==重来,bp VirtualAlloc原程序解码后去c95755写修复代码就行了

  此时的ebp是api地址。在原程序找一块地方,用来放刚才无意中得到的API地址表,
再用ebp放着的地址比较地址表里面的地址,就可以确定写回CALL目的地址的地址了。

=======这里到“代码修复API地址的小结”一段只是记录当时处理的过程,实际操作只需按小结的说明进行。

  地址表放到500000处(用binary paste)。将mov [esi],eax一句改成jmp 0ca3650。

代码:执行前先将400000段的access设为full access并把地址表放到500000。
代码:
00CA3650    50              PUSH EAX 00CA3651    B8 00044000     MOV EAX,500000 00CA3656    3B28            CMP EBP,DWORD PTR DS:[EAX] 00CA3658    74 0C           JE SHORT 00CA3666 00CA365A    83C0 04         ADD EAX,4 00CA365D    3D 04064000     CMP EAX,500204 00CA3662    77 0F           JA SHORT 00CA3673 00CA3664  ^ EB F0           JMP SHORT 00CA3656 00CA3666    8906            MOV DWORD PTR DS:[ESI],EAX 00CA3668    58              POP EAX 00CA3669    0FB74424 04     MOVZX EAX,WORD PTR SS:[ESP+4] 00CA366E  ^\E9 E920FFFF     JMP 00C9575C 00CA3673    8928            MOV DWORD PTR DS:[EAX],EBP 00CA3675    8305 5E36CA00 04    ADD DWORD PTR DS:[CA365E],4 00CA367C  ^ EB E8               JMP SHORT 00CA3666


  在0c95900处按F4就处理完了。好了,第一步,API地址修复完了,看看程序中
还有很多东西要修复。
  调用壳代码:
代码:
00401268    E8 8FA18D00     CALL 00CDCB18  ==地址是动态的。跟去分析一下,原来是把FF15的CALL变成E8的CALL,                       ==最后一字节随机,在修复API地址前在401268下内存写断,用代码修复。 00C9369A    C603 E8         MOV BYTE PTR DS:[EBX],0E8  ==这里断下,ebp是原API地址

  注意,这段代码运行前要做的工作有:1、设置full access。2、写好0CA3650的代码。3、API地址表放到500000。

代码:(把0c9369a的指令改成jmp 0ca3700)
代码:
00CA3700    50              PUSH EAX 00CA3701    B8 00044000     MOV EAX,500000 00CA3706    3B28            CMP EBP,DWORD PTR DS:[EAX] 00CA3708    74 0C           JE SHORT 00CA3716 00CA370A    83C0 04         ADD EAX,4 00CA370D    3D 04064000     CMP EAX,500204 00CA3712    77 0F           JA SHORT 00CA3723 00CA3714  ^ EB F0           JMP SHORT 00CA3706 00CA3716    66:C703 FF15    MOV WORD PTR DS:[EBX],15FF 00CA371B    8906            MOV DWORD PTR DS:[ESI],EAX 00CA371D    58              POP EAX 00CA371E  ^ E9 7DFFFEFF     JMP 00C936A0 00CA3723    8928            MOV DWORD PTR DS:[EAX],EBP 00CA3725    8305 5E36CA00 0>ADD DWORD PTR DS:[CA365E],4 00CA372C    8305 0E37CA00 0>ADD DWORD PTR DS:[CA370E],4 00CA3733  ^ EB E1           JMP SHORT 00CA3716

  在0c956a2按F4执行完修复代码之后大部分API调用都正常了。(在0c956a2按F4后0CA3650的修复代码也执行过了)
再来检查一下,bpx j,还有几个没修复。手动搞一下。
  前4个都是调用c93f78函数,去看一下,发现有一个CALL:
代码:
00C93F94    E8 6717FFFF     CALL 00C85700                            ; JMP to kernel32.GetProcAddress

  去c85700一看,DELPHI格式的跳转表。哪么OEP在代码的尾部啦。先不管这个,看看Intermodular calls窗口的
调用的函数表,还有很多API要修复。
  DELPHI格式的跳转表:
代码:
00C85700  - FF25 7042CA00   JMP DWORD PTR DS:[CA4270]                ; kernel32.GetProcAddress 00C85706    8BC0            MOV EAX,EAX

  最前面的4个是GetProcAddress啦,注意ca4270这个地址,我们无意中得到的地址表是从ca4100开始的,
搬到500000之后就是500170啦。把4个CALL都改成call [500170]就行了。
  另外的一些CALL到壳里的也用同样的方法修复:下内存写断找出哪里写入,再改代码修复。
  在402ace下内存写断。
代码:
00C9583A    8906              MOV DWORD PTR DS:[ESI],EAX  ==断下,和0C95755处的代码是一样的,所以代码很好写。

  代码修复API地址的小结:
  1、bp VirtualAlloc一直观察401000,直到解码完成。
  2、把0ca3646改为040250(反序的500204地址表尾)。
  2、改0C95755为jmp 0ca3650,去0ca3650写代码;改0c9583a为jmp 0ca3690,去0ca3690写代码;
    改0c9369a为jmp 0ca3700,去0ca3700写代码。(所有代码用后面的代码,有修改)
  3、设置400000段为full access。
  4、把API地址表写到500000处。
  5、到401268下内存写断。
  6、断下后去除中断再到0c956a2、0c95900按F4执行修复代码。
  7、手动修复4个GetProcAddress项目及其余项目。

0ca3650的代码:
代码:
00CA3650    50                  PUSH EAX 00CA3651    B8 00044000         MOV EAX,500000 00CA3656    3B28                CMP EBP,DWORD PTR DS:[EAX] 00CA3658    74 0D               JE SHORT 00CA3667 00CA365A    83C0 04             ADD EAX,4 00CA365D    3B05 4636CA00       CMP EAX,DWORD PTR DS:[CA3646]            ; dskchkr.00500000 00CA3663    77 0F               JA SHORT 00CA3674 00CA3665  ^ EB EF               JMP SHORT 00CA3656 00CA3667    8906                MOV DWORD PTR DS:[ESI],EAX 00CA3669    58                  POP EAX 00CA366A    0FB74424 04         MOVZX EAX,WORD PTR SS:[ESP+4] 00CA366F  ^ E9 E820FFFF         JMP 00C9575C 00CA3674    8928                MOV DWORD PTR DS:[EAX],EBP 00CA3676    8305 4636CA00 04    ADD DWORD PTR DS:[CA3646],4 00CA367D  ^ EB E8               JMP SHORT 00CA3667

0ca3690的代码:
代码:
00CA3690    50                  PUSH EAX 00CA3691    B8 00044000         MOV EAX,500000 00CA3696    3B28                CMP EBP,DWORD PTR DS:[EAX] 00CA3698    74 0D               JE SHORT 00CA36A7 00CA369A    83C0 04             ADD EAX,4 00CA369D    3B05 4636CA00       CMP EAX,DWORD PTR DS:[CA3646]            ; dskchkr.00500000 00CA36A3    77 0F               JA SHORT 00CA36B4 00CA36A5  ^ EB EF               JMP SHORT 00CA3696 00CA36A7    8906                MOV DWORD PTR DS:[ESI],EAX 00CA36A9    58                  POP EAX 00CA36AA    0FB74424 04         MOVZX EAX,WORD PTR SS:[ESP+4] 00CA36AF  ^ E9 8D21FFFF         JMP 00C95841 00CA36B4    8928                MOV DWORD PTR DS:[EAX],EBP 00CA36B6    8305 4636CA00 04    ADD DWORD PTR DS:[CA3646],4 00CA36BD  ^ EB E8               JMP SHORT 00CA36A7


0ca3700的代码:
代码:
00CA3700    50                  PUSH EAX 00CA3701    B8 00044000         MOV EAX,500000 00CA3706    3B28                CMP EBP,DWORD PTR DS:[EAX] 00CA3708    74 0D               JE SHORT 00CA3717 00CA370A    83C0 04             ADD EAX,4 00CA370D    3B05 4636CA00       CMP EAX,DWORD PTR DS:[CA3646]            ; dskchkr.00500000 00CA3713    77 0F               JA SHORT 00CA3724 00CA3715  ^ EB EF               JMP SHORT 00CA3706 00CA3717    66:C703 FF15        MOV WORD PTR DS:[EBX],15FF 00CA371C    8906                MOV DWORD PTR DS:[ESI],EAX 00CA371E    58                  POP EAX 00CA371F  ^ E9 7CFFFEFF         JMP 00C936A0 00CA3724    8928                MOV DWORD PTR DS:[EAX],EBP 00CA3726    8305 4636CA00 04    ADD DWORD PTR DS:[CA3646],4 00CA372D  ^ EB E8               JMP SHORT 00CA3717


0ca3646 全部修复代码
04 02 50 00 00 90 90 90 90 90 50 B8 00 00 50 00 3B 28 74 0D 83 C0 04 3B 05 46 36 CA 00 77 0F EB
EF 89 06 58 0F B7 44 24 04 E9 E8 20 FF FF 89 28 83 05 46 36 CA 00 04 EB E8 00 FF FF FF FF 01 00
00 00 FF FF FF FF 00 90 90 90 50 B8 00 00 50 00 3B 28 74 0D 83 C0 04 3B 05 46 36 CA 00 77 0F EB
EF 89 06 58 0F B7 44 24 04 E9 8D 21 FF FF 89 28 83 05 46 36 CA 00 04 EB E8 00 00 00 00 00 00 00
00 00 00 50 B8 00 00 50 00 3B 28 74 0D 83 C0 04 3B 05 46 36 CA 00 77 0E EB EF 89 06 58 8A 4C 24
06 E9 A0 21 FF FF 89 28 83 05 46 36 CA 00 04 EB E9 00 00 00 00 00 00 90 90 90 50 B8 00 00 50 00
3B 28 74 0D 83 C0 04 3B 05 46 36 CA 00 77 0F EB EF 66 C7 03 FF 15 89 06 58 E9 7C FF FE FF 89 28
83 05 46 36 CA 00 04 EB E8

04 02 50 00 00 90 90 90 90 90 50 B8 00 00 50 00 3B 28 74 0D 83 C0 04 3B 05 46 36 CA 00 77 0F EB
EF 89 06 58 0F B7 44 24 04 E9 E8 20 FF FF 89 28 83 05 46 36 CA 00 04 EB E8 00 FF FF FF FF 01 00
00 00 FF FF FF FF 00 90 90 90 50 B8 00 00 50 00 3B 28 74 0D 83 C0 04 3B 05 46 36 CA 00 77 0F EB
EF 89 06 58 0F B7 44 24 04 E9 8D 21 FF FF 89 28 83 05 46 36 CA 00 04 EB E8 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90 90 90 50 B8 00 00 50 00
3B 28 74 0D 83 C0 04 3B 05 46 36 CA 00 77 0F EB EF 66 C7 03 FF 15 89 06 58 E9 7C FF FE FF 89 28
83 05 46 36 CA 00 04 EB E8


  修复代码运行后(在0c95900按F4)还有些没修复,这些CALL是由动态生成的代码处理的,
不好用代码修复,反正也不多而且都有现成的地址参考,手动修复一下。比如
代码:
415ed1 call [cf865c] ;CloseHandle  ==CloseHandle地址在5000a4,把call [cf865c]改成call [5000a4]                   ==(Intermodular calls窗口双击项目即可定位到程序中)

  一轮修复后还有3个API没修复,comdlg32的GetOpenFileNameA和GetSaveFileNameA以及
comctl32的ImageList_GetImageCount,马上更新一下API地址专家,增加这2个库,得到地址
后去地址表尾部写入地址,再把call [xxxxx]改一下OK。
  用API地址专家取得的ImageList_GetImageCount地址与程序的不同,直接把程序的地址写到地址表
尾部。
  最后还有一个:
代码:
429ad8 call f042dd77  ==看样子是被破坏了,先不管。

  找OEP。虽然我们看到了DELPHI格式的跳转表,但并不代表这个就是DELPHI写的程序,
因为跳转表是在壳里面的,并不程序里面。从程序空隙用CC填充的情况来看,程序应该是
VC写的。

  先不动修复好API地址的程序,再开一个OD,bp VirtualAlloc,监视401000,解码后
去402ace下内存写断,再到0c95900按F4,此时壳已经完成API处理。对照修复了API地址
的程序,GetModuleHandleA的地址有3个,分别下断。
0042AFC8    E8 373B9400     CALL 00D6EB04  ==断下
  回到修复了API地址的程序,向上看,很快我们就找到OEP了:
代码:
0042AEF4    FA                 CLI     ==已经被破坏啦 0042AEF5    58                 POP EAX 0042AEF6    92                 XCHG EAX,EDX 0042AEF7    8F                 ???                                      ; Unknown command 0042AEF8    76 34              JBE SHORT dskchkr.0042AF2E 0042AEFA    DDC1               FFREE ST(1) 0042AEFC    D26F 64            SHR BYTE PTR DS:[EDI+64],CL 0042AEFF  ^ E3 84              JECXZ SHORT dskchkr.0042AE85 0042AF01    8F80 33E49FED      POP DWORD PTR DS:[EAX+ED9FE433] 0042AF07    D3CF               ROR EDI,CL 0042AF09    6A B5              PUSH -4B 0042AF0B    E5 35              IN EAX,35                                ; I/O command 0042AF0D    18D3               SBB BL,DL 0042AF0F    15 35C4E186        ADC EAX,86E1C435 0042AF14    DB07               FILD DWORD PTR DS:[EDI] 0042AF16    79 1C              JNS SHORT dskchkr.0042AF34 0042AF18    A8 9A              TEST AL,9A 0042AF1A    FF15 1C064000      CALL DWORD PTR DS:[40061C]               ; kernel32.GetVersion

  在第二个OD里面我们可以看到OEP处的情况:
代码:
0042AEF4  - E9 EE1E8900          JMP 00CBCDE7  ==对照样板分析一下cbcde7(动态地址,每次不同)                                                ==里面的代码就可以找到stloen code了

样板:ezip主程序,以前做pmview收集来的资料,有各种入口
代码:
0041FFAF    55                 PUSH EBP 0041FFB0    8BEC               MOV EBP,ESP 0041FFB2    6A FF              PUSH -1 0041FFB4    68 70C44200        PUSH ezip.0042C470 0041FFB9    68 A2014200        PUSH ezip.004201A2                       ; JMP to MSVCRT._except_handler3 0041FFBE    64:A1 00000000     MOV EAX,DWORD PTR FS:[0] 0041FFC4    50                 PUSH EAX 0041FFC5    64:8925 00000000   MOV DWORD PTR FS:[0],ESP 0041FFCC    83EC 20            SUB ESP,20 0041FFCF    53                 PUSH EBX 0041FFD0    56                 PUSH ESI 0041FFD1    57                 PUSH EDI 0041FFD2    8965 E8            MOV DWORD PTR SS:[EBP-18],ESP 0041FFD5    8365 FC 00         AND DWORD PTR SS:[EBP-4],0 0041FFD9    6A 01              PUSH 1 0041FFDB    FF15 ACF64400      CALL DWORD PTR DS:[44F6AC]               ; MSVCRT.__set_app_type

恢复的OEP stolen code:
代码:
0042AEF4    55                 PUSH EBP 0042AEF5    8BE5               MOV EBP,ESP 0042AEF7    6A FF              PUSH -1 0042AEF9    68 90444300        PUSH dskchkr.00434490 0042AEFE    68 3C054300        PUSH dskchkr.0043053C 0042AF03    64:A1 00000000     MOV EAX,DWORD PTR FS:[0] 0042AF09    50                 PUSH EAX 0042AF0A    64:8925 00000000   MOV DWORD PTR FS:[0],ESP 0042AF11    83EC 58            SUB ESP,58 0042AF14    53                 PUSH EBX 0042AF15    56                 PUSH ESI 0042AF16    57                 PUSH EDI 0042AF17    8965 E8            MOV DWORD PTR SS:[EBP-18],ESP

55 8B EC 6A FF 68 90 44 43 00 68 3C 05 43 00 64 A1 00 00 00 00 50 64 89 25 00 00 00 00 83 EC 58
53 56 57 89 65 E8

API地址表:
00 00 00 00 C4 7C E5 77 75 32 F5 77 00 E3 F7 77 1F E2 F7 77 08 99 E5 77 34 9E E5 77 0A 98 E5 77
45 9A E5 77 81 98 E5 77 44 F0 E5 77 24 99 E5 77 CE 7C E5 77 72 46 E5 77 EF 3B E5 77 67 31 E5 77
B8 05 E6 77 21 7F E5 77 7A 17 E4 77 FD A5 E5 77 93 9F E5 77 99 A0 E5 77 3C 51 E5 77 7D 15 F5 77
38 C9 E5 77 18 06 E6 77 9E 5D E5 77 AA 8E E5 77 B5 5C E5 77 8C 9D E5 77 84 9A E9 77 81 8C E5 77
92 01 E5 77 3E 18 F6 77 82 8B E5 77 06 D7 E4 77 3D 9C E5 77 EF 93 E5 77 08 16 E4 77 06 84 E5 77
37 A8 E5 77 63 79 E5 77 00 00 00 00 D6 E1 D3 77 50 72 D1 77 D7 AD D3 77 EC 72 D1 77 00 00 00 00
D7 23 DA 77 EA 22 DA 77 9A 18 DA 77 00 00 00 00 25 18 0F 77 E8 29 10 77 1D 15 0F 77 62 36 0F 77
E8 14 0F 77 A4 16 0F 77 00 00 00 00 39 9B E5 77 61 8B E5 77 29 2B E5 77 B4 C5 E5 77 45 9A E5 77
81 98 E5 77 99 A0 E5 77 00 00 00 00 F0 59 DA 77 54 91 DB 77 D7 23 DA 77 23 AE DA 77 EA 22 DA 77
2A 84 DA 77 9A 18 DA 77 00 00 00 00 44 F0 E5 77 84 9A E9 77 F7 15 E5 77 5B D7 E4 77 FC 02 E6 77
D8 05 E6 77 14 1B E5 77 99 1B E5 77 6F 16 E5 77 03 38 E5 77 A3 36 E5 77 EF 81 E4 77 57 C6 E5 77
87 30 E5 77 21 7F E5 77 08 16 E4 77 A5 C3 E5 77 FD A5 E5 77 93 9F E5 77 99 A0 E5 77 3C 51 E5 77
89 0F E5 77 E3 C0 E4 77 9B 86 E4 77 63 31 E5 77 9F 84 E5 77 B5 5C E5 77 62 92 EA 77 37 A8 E5 77
63 79 E5 77 00 00 00 00 7D 16 BD 77 E3 15 BD 77 2D 16 BD 77 00 00 00 00 6C BB D2 77 25 95 D1 77
2D 27 D3 77 4C BB D3 77 10 E3 D2 77 D7 AD D3 77 50 72 D1 77 09 D2 D2 77 C0 77 D1 77 4F 27 D3 77
46 81 D1 77 CB F5 D1 77 30 BB D3 77 06 B4 D3 77 03 E3 D2 77 00 00 00 00 75 16 19 77 00 00 00 00
57 C6 E5 77 00 00 00 00

代码修复后手动补上的API地址 500490
B3 22 32 76 D6 8B 32 76 00 00 00 00 7A E5 32 77

  
  LordPE full dump,ImpREC fix dump(OEP 2aef4),运行OK。