首页
社区
课程
招聘
Obsidium外壳学习手记
发表于: 2005-12-18 08:31 38079

Obsidium外壳学习手记

2005-12-18 08:31
38079

己有2年时间没怎么研究壳了,为了不至于成为壳肓,准备花点时间学一下脱壳。将第一个目标锁定在Obsidium上,这款壳相对来说容易些。
首先声明一下,这篇文章没什么新的技术,Obsidium壳的分析论坛己有许多文章讨论了,写这篇文章主要是记录一下自己的学习过程,方便以
后查找。同时也将这篇文章送给刚入门的脱壳菜鸟们,所以文章中我将详细记录操作过程,同时尽可能说明为什么这么做,希望能通过这篇文
章让新手们举一反三。
  

第1篇  Obsidium 1.0.0.69学习手记

   用Obsidium 1.0.0.69建个项目,选中Encrypt resoures,Debugger checks,compression,Remove bytes at OEP,Runtime patching,
Runtime tracing等选项。
   为了降低难度,目标程序TraceMe1.exe没有重定位表,因此Obsidium加壳时没有重定位映像。
   实例下载:obsidiumtest1.0.rar

一.寻找OEP及Stole code

思路:现在外壳程序都大量了调用异常(SEH)产生非正常的跳转,用以干扰调试,正是外壳这些连续的SEH也给脱壳者指明了一条道路。
OD对异常处理相当灵活,因此在跟踪外壳的过程中,可以利用合适的异常做为一个路标来指导跟踪。用OD加载程序,除了“整数除以0异
常外”(为什么选这个异常?一个个异常试出来的),忽略其他所有异常。然后用OD隐藏插件将OD隐藏,这个版的Anti比较弱。同时按
Alt+B查看一个断点窗口,清除所有断点,有些断点Obsidium会发现被跟踪)
按F9运行程序,遇到异常按Shift+F9通过,5次异常后程序就运行起来了。

第1次异常:
009005EE    F7F0            div     eax
009005F0    EB 02           jmp     short 009005F4
009005F2    68 41EB01A2     push    A201EB41

第2次异常:
0090145E    F7F0            div     eax
00901460    EB 04           jmp     short 00901466

第3次异常:
009018C6    F7F0            div     eax
009018C8    EB 02           jmp     short 009018CC

第4次异常:
00901A62    F7F0            div     eax
00901A64    EB 04           jmp     short 00901A6A

第5次异常:
   
0040B5BC          F7F0               div     eax   //停在这里
0040B5BE          EB 04              jmp     short 0040B5C4

当第5次异常执行后,程序就运行起来了。因此重新加载程序,第5次异常后准备单步跟踪下去。
当第5次异常,停在0040B5BC时,查看堆栈窗口,数据如下:

0012FF98   0012FFE0  Pointer to next SEH record
0012FF9C   0040B5ED  SE handler//关键,如果程序异常后,系统将跳到此处执行

注:有关如何跟踪异常的技巧请参考加密与解密二版菜鸟学习笔记(2) - SEH 结构化异常处理

因此在命令行设断:bp 0040B5ED
0040B5ED         /EB 04              jmp     short 0040B5F3  //在这句设断点
0040B5EF         |6965 26 F7C80000   imul    esp, [ebp+26], 0C8F7

设好断点后,按Shift+F9让程序通过异常,就会在0040B5ED地址处中断。
中断后,别忘了取消断点。也不要忘了按Shift,而直接按F9了,在Obsidium1.3这样操作后,外壳后面可能会异常出错.此时会看到一堆代码很乱,因为这是有花指令的原故。
此时用花指令去除器,将花指令去除。



得到干净的代码如下:
0040B5ED          90                 nop
0040B5EE          90                 nop
0040B5EF          90                 nop
0040B5F0          90                 nop
0040B5F1          90                 nop
0040B5F2          90                 nop
0040B5F3          C8 000000          enter   0, 0
0040B5F7          8B45 08            mov     eax, [ebp+8]
0040B5FA          90                 nop
0040B5FB          90                 nop
0040B5FC          90                 nop
0040B5FD          90                 nop
0040B5FE          8B00               mov     eax, [eax]
0040B600          90                 nop
……
0040B660          8B45 10            mov     eax, [ebp+10]
0040B663          81EA F5C0B200      sub     edx, 0B2C0F5
0040B669          90                 nop
0040B66A          90                 nop
0040B66B          90                 nop
0040B66C          90                 nop
0040B66D          90                 nop
0040B66E          8D940A EFAB0CFF    lea     edx, [edx+ecx+FF0CABEF]
0040B675          90                 nop
0040B676          90                 nop
0040B677          90                 nop
0040B678          90                 nop
0040B679          90                 nop
0040B67A          8990 B8000000      mov     [eax+B8], edx             ; CONTEXT.EIP,此时EDX的值0040BAEA

向下跟踪,其中0040B67A 这句比较熟悉,也是SEH的一个处理形式,eax是指向CONTEXT结构,其偏移B8就是新的EIP,异常后,程序会
跳到这个EIP执行,因此对EDX(此时值0040BAEA)设断:

bp edx或bp0040BAEA

中断后来到这里:(中断后,别忘了取消断点。 )
0040BAEA          E8 AF000000        call    0040BB9E                  ; 这是一个加密CALL,按F7进去
        {
            //为了阅读方便,仅将相关代码重新整理排版列出,这段代码按F7走出即可
            //这段代码是SMC方法修改0040BAEA地址一段代码

           0040BB9E          60                 pushad
           0040BBA9          836C24 20 05       sub     dword ptr [esp+20], 5   //esp+20初始值为0xEF
           0040BBAE          8B4C24 20          mov     ecx, [esp+20]
           0040BBB7          C601 0E            mov     byte ptr [ecx], 0E      //此时ECX的值就是0040BAEA
           0040BBBE          C741 01 454EB4E7   mov     dword ptr [ecx+1], E7B44E45
           0040BBC5          BE B4000000        mov     esi, 0B4
           0040BBCA          F9                 stc
           0040BBCB          72 02              jb      short 0040BBCF
           0040BBCF          BB 14F9DC24        mov     ebx, 24DCF914
           0040BBD4          8131 E5445F5F      xor     dword ptr [ecx], 5F5F44E5
           0040BBDA          F9                 stc
           0040BBDB          72 04              jb      short 0040BBE1
           0040BBE1          83C1 04            add     ecx, 4
           0040BBE4          F9                 stc
           0040BBE5          72 06              jb      short 0040BBED
           0040BBED          C1C3 45            rol     ebx, 45
           0040BBF0          83EE 04            sub     esi, 4
           0040BBF3        ^ 0F85 DBFFFFFF      jnz     0040BBD4
           0040BBF9          F8                 clc
           0040BBFA          73 06              jnb     short 0040BC02
           0040BBFC          CE                 into
           0040BBFD          7E 12              jle     short 0040BC11
           0040BBFF          4F                 dec     edi
           0040BC00          AB                 stos    dword ptr es:[edi]
           0040BC01          58                 pop     eax
           0040BC02          61                 popad          //在这按F4,再按F8走出这个CALL
           0040BC03          C3                 retn
          }

走出上面的代码,重新来到0040BAEA 这个地址处:
0040BAEA         /EB 01              jmp     short 0040BAED            ; 加密CALL
0040BAEC         |11EB               adc     ebx, ebp
0040BAEE          0230               add     dh, [eax]

小技巧:你也可以一开始就在0040BAEA 这行,按一下F4,即可中断到这里

按F8单步走,这段有花指令,不要去除,会有自检验,来到如下代码:
0040BB7F          FFE7               jmp     edi                            ;  //关键!  跟进
0040BB81          EB 01              jmp     short 0040BB84
0040BB83          6A EB              push    -15
0040BB85          030C10             add     ecx, [eax+edx]

来到(己去除花指令):
00905AE4          E8 00000000        call    00905AE9
00905AE9          90                 nop
00905AEA          90                 nop
00905AEB          90                 nop
00905AEC          90                 nop
00905AED          90                 nop
00905AEE          5D                 pop     ebp
00905AEF          90                 nop
00905AF0          90                 nop
00905AF1          90                 nop
00905AF2          90                 nop
00905AF3          90                 nop
00905AF4          81ED A0C1B200      sub     ebp, 0B2C1A0
00905AFA          90                 nop
00905AFB          90                 nop
00905AFC          90                 nop
00905AFD          90                 nop
00905AFE          90                 nop
00905AFF          64:67:8F06 0000    pop     dword ptr fs:[0]
……
00905EA9          61                 popad
00905EAA          90                 nop
00905EAB          90                 nop
00905EAC          90                 nop
00905EAD          90                 nop
00905EAE          90                 nop
00905EAF          9D                 popfd
00905EB0          90                 nop
00905EB1          90                 nop
00905EB2          90                 nop
00905EB3          90                 nop
00905EB4          90                 nop
00905EB5          90                 nop
00905EB6          90                 nop
00905EB7          90                 nop
00905EB8          90                 nop
00905EB9          90                 nop
00905EBA          55                 push    ebp           //Stole code第1句
00905EBB          90                 nop
00905EBC          90                 nop
00905EBD          90                 nop
00905EBE          90                 nop
00905EBF          8BEC               mov     ebp, esp      //Stole code第2句
00905EC1          90                 nop
00905EC2          90                 nop
00905EC3          90                 nop
00905EC4          90                 nop
00905EC5          90                 nop
00905EC6          6A FF              push    -1            //Stole code第3句
00905EC8          90                 nop
00905EC9          90                 nop
00905ECA          90                 nop
00905ECB          68 D0404000        push    4040D0        //Stole code第4句
00905ED0          90                 nop
00905ED1          90                 nop
00905ED2          90                 nop
00905ED3          90                 nop
00905ED4          68 D41E4000        push    401ED4        //Stole code第5句                  
00905ED9          90                 nop
00905EDA          90                 nop
00905EDB          90                 nop
00905EDC          64:A1 00000000     mov     eax, fs:[0]   //Stole code第6句
00905EE2          90                 nop
00905EE3          90                 nop
00905EE4          90                 nop
00905EE5          90                 nop
00905EE6          90                 nop
00905EE7          90                 nop
00905EE8          50                 push    eax           //Stole code第7句
00905EE9          90                 nop
00905EEA          90                 nop
00905EEB          90                 nop
00905EEC          90                 nop
00905EED          64:8925 00000000   mov     fs:[0], esp   //Stole code第8句
00905EF4          90                 nop
00905EF5          90                 nop
00905EF6          90                 nop
00905EF7          90                 nop
00905EF8          90                 nop
00905EF9          90                 nop
00905EFA          83EC 58            sub     esp, 58       //Stole code第9句
00905EFD          90                 nop
00905EFE          90                 nop
00905EFF          90                 nop
00905F00          90                 nop
00905F01          90                 nop
00905F02          53                 push    ebx           //Stole code第10句
00905F03          90                 nop
00905F04          90                 nop
00905F05          90                 nop
00905F06          90                 nop
00905F07          90                 nop
00905F08          90                 nop
00905F09          56                 push    esi           //Stole code第11句
00905F0A          90                 nop
00905F0B          90                 nop
00905F0C          90                 nop
00905F0D          90                 nop
00905F0E          57                 push    edi           //Stole code第12句
00905F0F          90                 nop
00905F10          90                 nop
00905F11          90                 nop
00905F12          90                 nop
00905F13          90                 nop
00905F14          8965 E8            mov     [ebp-18], esp //Stole code第13句
00905F17          90                 nop
00905F18          90                 nop
00905F19          90                 nop
00905F1A          90                 nop
00905F1B        - E9 A6B4AFFF        jmp     TraceMe_.004013C6  //跳到伪OEP

伪OEP处的代码如下:
004013BF      A9            db      A9
004013C0      8B            db      8B
004013C1      A4            db      A4
004013C2      7F            db      7F
004013C3      6D            db      6D                               ;  CHAR 'm'
004013C4      E4            db      E4
004013C5      EE            db      EE
004013C6      FF            db      FF          //跳到此处,伪OEP
004013C7      15            db      15
004013C8      44            db      44                               ;  CHAR 'D'
004013C9      40            db      40                               ;  CHAR '@'
004013CA      40            db      40                               ;  CHAR '@'
004013CB      00            db      00

此时按一下Ctrl+A让OD重新分析一下代码:
004013A0      7A            db      7A                               ;  CHAR 'z'
004013A1      4A            db      4A                               ;  CHAR 'J'
004013A2      1D            db      1D
004013A3      66            db      66                               ;  CHAR 'f'
004013A4      83            db      83
004013A5      29            db      29                               ;  CHAR ')'
004013A6   .  C3            retn
004013A7      DA            db      DA
004013A8      05            db      05
004013A9      50            db      50                               ;  CHAR 'P'
004013AA      F3            db      F3
004013AB      7F            db      7F
004013AC      52            db      52                               ;  CHAR 'R'
004013AD      83            db      83
004013AE      59            db      59                               ;  CHAR 'Y'
004013AF      07            db      07
004013B0      65            db      65                               ;  CHAR 'e'
004013B1      39            db      39                               ;  CHAR '9'
004013B2      C4            db      C4
004013B3      50            db      50                               ;  CHAR 'P'
004013B4      18            db      18
004013B5      18            db      18
004013B6      5E            db      5E                               ;  CHAR '^'
004013B7      24            db      24                               ;  CHAR '$'
004013B8      5D            db      5D                               ;  CHAR ']'
004013B9      0F            db      0F
004013BA   .  8DAC4B 375AA9>lea     ebp, [ebx+ecx*2+8BA95A37]
004013C1   .  A4            movs    byte ptr es:[edi], byte ptr [esi>
004013C2   .  7F 6D         jg      short 00401431
004013C4   .  E4 EE         in      al, 0EE
004013C6   .  FF15 44404000 call    [404044]  //伪OEP
004013CC   .  33D2          xor     edx, edx
004013CE   .  8AD4          mov     dl, ah
004013D0   .  8915 28554000 mov     [405528], ed

将被抽去的OEP代码填上去,正确的代码如下:
004013A0      55            push    ebp
004013A1      8BEC          mov     ebp, esp
004013A3      6A FF         push    -1
004013A5      68 D0404000   push    004040D0
004013AA      68 D41E4000   push    00401ED4                        
004013AF      64:A1 0000000>mov     eax, fs:[0]
004013B5      50            push    eax
004013B6      64:8925 00000>mov     fs:[0], esp
004013BD      83EC 58       sub     esp, 58
004013C0      53            push    ebx
004013C1      56            push    esi
004013C2      57            push    edi
004013C3      8965 E8       mov     [ebp-18], esp
004013C6   .  FF15 44404000 call    [404044]

修补OEP代码后,就可以用LordPE将内存境像full dump另存为dumped.exe。
另外,Obsidium 的Stole code处理非常弱的,没有一点变形,平时只要根据程序的语言特征即可恢复。例如本例跟到伪OEP后,
记下堆栈数据:

0012FF4C   7C930738  ntdll.7C930738
0012FF50   FFFFFFFF
0012FF54   7FFD4000
0012FF58   A4653690
…………………………
0012FFA4   FFFFFFFF
0012FFA8   0012FF4C
0012FFAC   0012FFC0
0012FFB0   0012FFE0  Pointer to next SEH record  
0012FFB4   00401ED4  SE handler                   //这个值记下来
0012FFB8   004040D0  TraceMe_.004040D0           //这个值记下来

再找一个VC的程序,将OEP的代码复制过来,将push    4040D0 ,push    401ED4  填上即可。

二.IAT的处理

   继续跟踪程序,“004013C6  call    [404044]”这句就是调用系统的某个API,其中404044就是IAT中的某个地址。
在数据窗口下命令:d 404044

00403FF4  00 00 00 00 00 00 00 00 00 00 00 00 DC 4B 90 00  ............芩?
00404004  E8 4B 90 00 F4 4B 90 00 00 4C 90 00 0C 4C 90 00  杷?羲?.L?.L?
00404014  18 4C 90 00 24 4C 90 00 30 4C 90 00 3C 4C 90 00  L?$L?0L?<L?
00404024  48 4C 90 00 54 4C 90 00 60 4C 90 00 6C 4C 90 00  HL?TL?`L?lL?
00404034  78 4C 90 00 84 4C 90 00 90 4C 90 00 DC 4D 90 00  xL?????芡?
00404044  E6 4D 90 00 B4 4C 90 00 C0 4C 90 00 F0 4D 90 00  嫱?刺?捞?鹜?
00404054  D8 4C 90 00 E4 4C 90 00 F0 4C 90 00 FC 4C 90 00  靥?涮?鹛???
00404064  08 4D 90 00 14 4D 90 00 20 4D 90 00 2C 4D 90 00  M?M? M?,M?
00404074  38 4D 90 00 44 4D 90 00 50 4D 90 00 5C 4D 90 00  8M?DM?PM?\M?
00404084  68 4D 90 00 74 4D 90 00 80 4D 90 00 8C 4D 90 00  hM?tM?????
00404094  98 4D 90 00 A4 4D 90 00 FA 4D 90 00 06 4E 90 00  ??ね???N?
004040A4  12 4E 90 00 1E 4E 90 00 2A 4E 90 00 36 4E 90 00  N?N?*N?6N?
004040B4  42 4E 90 00 4E 4E 90 00 5A 4E 90 00 66 4E 90 00  BN?NN?ZN?fN?
004040C4  72 4E 90 00 7E 4E 90 00 8A 4E 90 00
FF FF FF FF  rN?~N?????

现在每个DWORD值都是0090xxxx的形式,其指向外壳代码里,程序调用API函数都由外壳程序代为处理。
为了让大家理解清楚,请看这张图:



   这张图就是输入表的结构,程序加载内存后,只需要IAT部分,其他部分就不需要了,IAT中的每项都指向一个函数。没加壳的程序,IAT部分
是Windows系统来填充的。加壳程序情况不同了,外壳程序自己模拟Windows系统来填充IAT表(图中红圈,输入表其他部分在外壳里是没有的)
,在填充过程中,外壳可填充HOOK-API代码的地址,这样可间接地获得程序的控制权。
   我们平时讨论的输入表重建工具ImpREC,就是通过这个IAT,重建整个输入表结构。由于Obsidium外壳己将IAT指向外壳代码里,用ImpREC
是不能获得正确的函数名了。我们必须想办法将各函数的真实地址填进IAT表里。例如:



   图中的地址7C81485F 就是 kernel32 中的FreeEnvironmentStringsW 函数,地址7C81EE79就是kernel32中的lstrcmpA函数。
这些IAT的地址与你当前的操作系统版本有关,等得到这些真实函数地址后,用ImpREC重建一张完整的输入表。
   再来确定IAT表的大小范围,IAT这张表是一个连续的表,其起始及结束应为0000 0000.上图的红色范围就是IAT表大小,起始地址就
404000,结束地址是4040D0。4040D0后面的地址虽然有数据,但其己不是00904xxx形式了,因此是其他数据。

    IAT的地址:0x404000~0x4040D0  大小0xD0

    获得IAT的范围后,下面就是想办法让Obsidium向这张表里填充正确的API函数地址。关闭程序,重新调试,加载程序,在命令行:
d 404000,此时的数据区全是0.按F9运行程序,当第5次异常后,会发现数据窗口己重新填充数据了:

00404000  DC 4B 90 00 E8 4B 90 00 F4 4B 90 00 00 4C 90 00  芩?杷?羲?.L?
00404010  0C 4C 90 00 18 4C 90 00 24 4C 90 00 30 4C 90 00  .L?L?$L?0L?

因此再重新加载程序来过,当第4次整除异常时:
00901A62    F7F0            div     eax//第4次异常
00901A64    EB 04           jmp     short 00901A6A

对00404000 下内在断点,在数据窗口00404000,右键/断点/内存写入。按Shit+F9跳过异常继续执行,会中断如下:
00904808    893E            mov     [esi], edi
0090480A    EB 01           jmp     short 0090480D

别忘记删除内存断点,此时单步跟踪即可。下面代码己将花指令去除,并重新排版整理过:
009047B6    C607 60         mov     byte ptr [edi], 60               ;  pushad
009047BD    66:895F 01      mov     [edi+1], bx
009047C4    66:894F 03      mov     [edi+3], cx                      ; mov bp,cx
009047CC    C1CB 10         ror     ebx, 10                          ; 取bd指定(即mov)
009047D3    8857 06         mov     [edi+6], dl
009047D9    885F 05         mov     [edi+5], bl                      ;  B3 00  mov     bl, 0
009047E2    C647 07 E9      mov     byte ptr [edi+7], 0E9            ; jmp
009047EB    C1CB 10         ror     ebx, 10                          ; ebx=00B3BD66
009047F1    8B45 14         mov     eax, [ebp+14]
009047F4    2BC7            sub     eax, edi
009047F9    83E8 0C         sub     eax, 0C
00904800    8947 08         mov     [edi+8], eax                     ;  jmp     [ebp+14]
00904808    893E            mov     [esi], edi                       ; 写IAT (开始就中断此行)
0090480D    83C7 0C         add     edi, 0C                          ; EDI->buff{0}
00904814    83C6 04         add     esi, 4                           ; IAT+4
0090481A    41              inc     ecx                              ; 计数器
0090481E    3B4D 0C         cmp     ecx, [ebp+C]                     ; =26
00904826  ^ 72 8B           jb      short 009047B3

上面这段代码就是将己加密的API入口地址填进IAT中(上面这段代码不必看懂,只要知道其运行结果是构造如下的代码就行),各API入口代
码的形式:
xxxxxxxx    60              pushad
xxxxxxxx    66:BD 0200      mov     bp, cx (CX是计数器,循环一次加1)
xxxxxxxx    B3 00           mov     bl, 0
xxxxxxxx  ^ E9 56F0FFFF     jmp     [ebp+14]

过完00904826 一行,Ctrl+G,查看EDI处的汇编代码。如下
00904BDC    60              pushad
00904BDD    66:BD 0000      mov     bp, 0
00904BE1    B3 00           mov     bl, 0
00904BE3  ^ E9 6EF0FFFF     jmp     00903C56
00904BE8    60              pushad
00904BE9    66:BD 0100      mov     bp, 1
00904BED    B3 00           mov     bl, 0
00904BEF  ^ E9 62F0FFFF     jmp     00903C56

下面还有一些语句继续处理:
00904830    833E 00         cmp     dword ptr [esi], 0
00904837    75 64           jnz     short 0090489D
0090483C    893E            mov     [esi], edi                       ; 存进IAT
00904843    C607 60         mov     byte ptr [edi], 60               ; pushad
0090484C    66:C747 01 66B8 mov     word ptr [edi+1], 0B866          ;  mov     ax, 0
00904858    66:894F 03      mov     [edi+3], cx                      ; mov     ax,cx(初值是 26)
00904862    C647 05 B2      mov     byte ptr [edi+5], 0B2            ;  mov     dl, 0
0090486C    8857 06         mov     [edi+6], dl
00904874    C647 07 E9      mov     byte ptr [edi+7], 0E9            ; jmp
0090487C    8B45 14         mov     eax, [ebp+14]
0090487F    2BC7            sub     eax, edi
00904886    83E8 0C         sub     eax, 0C
0090488F    8947 08         mov     [edi+8], eax                     ; jmp     00903C56
00904896    90              nop
00904897    83C7 0C         add     edi, 0C
009048A2    8BC7            mov     eax, edi
009048A7    2B45 18         sub     eax, [ebp+18]
009048AF    5F              pop     edi
009048B0    5E              pop     esi
009048B1    5B              pop     ebx
009048B2    C9              leave
009048B3    C2 1400         retn    14

上面这段代码构造当前DLL中的最后一个IAT中的值:
00904DA4    60              pushad
00904DA5    66:B8 2600      mov     ax, 26
00904DA9    B2 00           mov     dl, 0
00904DAB  ^ E9 A6EEFFFF     jmp     00903C56

走出上面的CALL,来到如下(己去除花指令):
00904485    E8 FA020000     call    00904784                         //我们从这返回
0090448F    0143 04         add     [ebx+4], eax
0090449E    8B46 14         mov     eax, [esi+14]                  
009044A6    8B56 10         mov     edx, [esi+10]                  
009044AE    0303            add     eax, [ebx]                       
009044B3    0353 28         add     edx, [ebx+28]
009044BB    FF76 04         push    dword ptr [esi+4]                ; DLL基址
009044BE    53              push    ebx                              ;
009044BF    52              push    edx                              ;
009044C0    50              push    eax                              ; IAT地址
009044C1    FF76 0C         push    dword ptr [esi+C]                ; API函数个数
009044C4    E8 6D010000     call    00904636                         //对普通函数解密
009044CC    85C0            test    eax, eax
009044D1    0F84 BB000000   je      00904592
009044DF    837D F0 00      cmp     dword ptr [ebp-10], 0
009044E9    74 37           je      short 00904522
009044F2    8B46 14         mov     eax, [esi+14]                  
009044FB    8B56 10         mov     edx, [esi+10]                  
00904504    0303            add     eax, [ebx]                     
0090450C    0353 28         add     edx, [ebx+28]                    ;
00904514    FF75 F4         push    dword ptr [ebp-C]                ;
00904517    53              push    ebx                              ;
00904518    52              push    edx                              ;
00904519    50              push    eax                              ; IAT
0090451A    FF76 0C         push    dword ptr [esi+C]                ; API函数个数
0090451D    E8 92010000     call    009046B4                         //对特殊函数解密
00904528    83C6 18         add     esi, 18
00904531    FF45 F8         inc     dword ptr [ebp-8]
00904538    FF4D FC         dec     dword ptr [ebp-4]
00904541  ^ 0F85 1CFDFFFF   jnz     00904263                         //向上跳就继续处理下一个DLL的API函数
0090454A    33C0            xor     eax, eax
00904552    5F              pop     edi
00904553    5E              pop     esi
00904554    5B              pop     ebx
00904555    C9              leave
00904556    C3              retn

   上面这段代码,是Obsidium外壳为防止某些函数指针不能加密,而进行解密的函数。因此下面要做的就是将其修改一下,
让其成所有函数指针的解密器。

   下面的修改方法来自辉仔Yock的文章(好像是他的文章首先提出这种修改方法)。

1.先来看看如何对普通函数解密的

009044C4    E8 6D010000     call    00904636 //按F7跟进
{
     00904636    C8 000000       enter   0, 0
     0090463A    53              push    ebx
     0090463B    56              push    esi
     0090463C    57              push    edi
     0090463D    8B5D 14         mov     ebx, [ebp+14]
     00904640    8B75 10         mov     esi, [ebp+10]
     00904643    8B7D 0C         mov     edi, [ebp+C]
     00904646    8B5B 04         mov     ebx, [ebx+4]
     00904649    66:F706 2000    test    word ptr [esi], 20   //这里改成test    [esi], 8
     0090464E    74 46           je      short 00904696       //这里改成jne      short 00904696
     00904650    66:F706 0200    test    word ptr [esi], 2
     00904655    75 1F           jnz     short 00904676
     00904657    66:C706 0400    mov     word ptr [esi], 4
     0090465C    8B45 14         mov     eax, [ebp+14]
     0090465F    6A 01           push    1
     00904661    6A 00           push    0
     00904663    FF76 04         push    dword ptr [esi+4]
     00904666    6A 00           push    0
     00904668    FF75 18         push    dword ptr [ebp+18]
     0090466B    FF50 40         call    [eax+40]             //解密函数
     0090466E    85C0            test    eax, eax
     00904670    74 39           je      short 009046AB       //这里改成 je      short 00904696
     00904672    8907            mov     [edi], eax
     00904674    EB 20           jmp     short 00904696
     00904676    66:C706 0400    mov     word ptr [esi], 4
     0090467B    8B45 14         mov     eax, [ebp+14]
     0090467E    0FB756 02       movzx   edx, word ptr [esi+2]
     00904682    6A 01           push    1
     00904684    52              push    edx
     00904685    6A 00           push    0
     00904687    FF76 04         push    dword ptr [esi+4]
     0090468A    FF75 18         push    dword ptr [ebp+18]
     0090468D    FF50 40         call    [eax+40]             //解密函数
     00904690    85C0            test    eax, eax
     00904692    74 17           je      short 009046AB       //这里改成 je      short 00904696
     00904694    8907            mov     [edi], eax
     00904696    83C6 08         add     esi, 8
     00904699    83C7 04         add     edi, 4
     0090469C    FF4D 08         dec     dword ptr [ebp+8]
     0090469F  ^ 75 A8           jnz     short 00904649
     009046A1    33C0            xor     eax, eax
     009046A3    40              inc     eax
     009046A4    5F              pop     edi
     009046A5    5E              pop     esi
     009046A6    5B              pop     ebx
     009046A7    C9              leave
     009046A8    C2 1400         retn    14
}

分析一下,当执行到00904649 test    word ptr [esi], 20 这句时,查看ESI指向的数据:
00900B10  02 00 46 00 3A 8E 2F 1C 02 00 6C 00 3D 54 3F 6B  .F.:?.l.=T?k
00900B20  02 00 47 00 4F 2E B8 88 02 00 57 00 89 E5 80 9A  .G.O.?.W.?
00900B30  02 00 47 00 96 84 F8 62 02 00 47 00 C7 31 2C 96  .G.??.G.?,?
00900B40  02 00 4C 00 51 A3 52 57 02 00 4C 00 00 16 86 A3  .L.QRW.L..?
00900B50  02 00 4D 00 39 1E F1 72 02 00 4C 00 8D BD C1 3F  .M.9耱.L.??
00900B60  02 00 47 00 FF 1F 7C C9 02 00 48 00 FE A8 89 58  .G.?|?.H.??
00900B70  02 00 56 00 4A 0D CE 09 02 00 48 00 72 1D DB 5E  .V.J.?.H.r坜
00900B80  02 00 47 00 70 65 86 B1 02 00 47 00 95 CB E2 AD  .G.pe?.G.?猸
00900B90  08 00  00 00 03 00 00 00 08 00  00 00 00 00 00 00  .............
00900BA0  02 00 45 00 CC 97 10 25 02 00 54 00 8D BF 40 AB  .E.?%.T.?@?

  其中红色的“08”是特殊函数标志,其他的02是普通函数标志。将00904649 这句改成test    [esi], 8,
目的是比较当前是否为特殊函数,如是特殊函数不处理。
  经过上面的修改,就能将普通的API函数解码出来,并填充到IAT中去。执行完009046A1    xor     eax, eax一句后,
下命令:d 404000 就能查看出己被恢复的IAT数据。

(在数据窗口右键/长型/地址的方式查看)
00404000  7C81485F  kernel32.FreeEnvironmentStringsW   <------这些是解密的函数,函数的地址7C81485F己填充到IAT中
00404004  7C81EE79  kernel32.lstrcmpA
00404008  7C81CC23  kernel32.GetEnvironmentStringsA
0040400C  7C80A0C7  kernel32.WideCharToMultiByte
00404010  7C80A480  kernel32.GetStringTypeW
00404014  7C838CB9  kernel32.GetStringTypeA
00404018  7C80CEC4  kernel32.LCMapStringW
0040401C  7C832E2B  kernel32.LCMapStringA
00404020  7C809CAD  kernel32.MultiByteToWideChar
00404024  7C801D77  kernel32.LoadLibraryA
00404028  7C80AC28  kernel32.GetProcAddress
0040402C  7C9379FD  ntdll.RtlReAllocateHeap
00404030  7C809A81  kernel32.VirtualAlloc
00404034  7C9305D4  ntdll.RtlAllocateHeap
00404038  7C80B529  kernel32.GetModuleHandleA
0040403C  7C801EEE  kernel32.GetStartupInfoA
00404040  00904C9C                                      <------ 这2个是特殊函数                  
00404044  00904CA8
…………

2.再来看看如何对特殊函数解密的
0090451D    E8 92010000     call    009046B4
{
     009046B4    C8 000000       enter   0, 0
     009046B8    53              push    ebx
     009046B9    56              push    esi
     009046BA    57              push    edi
     009046BB    8B5D 14         mov     ebx, [ebp+14]
     009046BE    8B75 10         mov     esi, [ebp+10]
     009046C1    8B7D 0C         mov     edi, [ebp+C]
     009046C4    8B5B 04         mov     ebx, [ebx+4]
     009046C7    66:833E 08      cmp     word ptr [esi], 8
     009046CB    0F85 97000000   jnz     00904768
     009046D1    8B46 04         mov     eax, [esi+4]
     009046D4    83F8 00         cmp     eax, 0
     009046D7    74 44           je      short 0090471D
     009046D9    83F8 01         cmp     eax, 1
     009046DC    74 4E           je      short 0090472C
     009046DE    83F8 02         cmp     eax, 2
     009046E1    74 58           je      short 0090473B
     009046E3    83F8 03         cmp     eax, 3
     009046E6    74 11           je      short 009046F9
     009046E8    83F8 04         cmp     eax, 4
     009046EB    75 7B           jnz     short 00904768
     009046ED    8B45 18         mov     eax, [ebp+18]
     009046F0    05 EE1DB300     add     eax, 0B31DEE
     009046F5    8907            mov     [edi], eax
     009046F7    EB 6F           jmp     short 00904768
     009046F9    8B45 14         mov     eax, [ebp+14]
     009046FC    68 C5B1662D     push    2D66B1C5
     00904701    6A 00           push    0
     00904703    FF50 20         call    [eax+20]  //关键,解密函数,跟进
     {    //己去除花指令并重新整理
          004134F2    60              pushad
          004134F7    E8 00000000     call    004134FC
          00413500    5B              pop     ebx
          00413507    81EB C85AB300   sub     ebx, 0B35AC8
          00413511    8B9B B45AB300   mov     ebx, [ebx+B35AB4]
          0041351F    8B4424 24       mov     eax, [esp+24]
          0041352D    33C9            xor     ecx, ecx
          00413535    8B4483 48       mov     eax, [ebx+eax*4+48]
          0041353E    8B5424 28       mov     edx, [esp+28]
          00413547    51              push    ecx
          00413548    51              push    ecx
          00413549    51              push    ecx
          0041354A    52              push    edx
          0041354B    50              push    eax
          0041354C    FF53 40         call    [ebx+40]  //其实这个CALL是Obsidium自己实现的GetProcAddress
          00413554    85C0            test    eax, eax  //解密后,EAX保存的就是函数地址
          0041355A   /0F84 DE010000   je      0041373E
          00413560    90              nop               //在这句加上:mov     [edi], eax
          00413561    90              nop               //将正确的指针放进去IAT中
          00413562    90              nop
          00413563    90              nop
          00413564    90              nop
          00413565    90              nop
          00413566    8BF0            mov     esi, eax
          ……
     }
     00904706    50              push    eax
     00904707    53              push    ebx
     00904708    E8 D7030000     call    00904AE4
     ……
     0090475A    2BD0            sub     edx, eax
     0090475C    C643 05 E9      mov     byte ptr [ebx+5], 0E9
     00904760    8953 06         mov     [ebx+6], edx
     00904763    891F            mov     [edi], ebx   //必须将这句NOP掉,不然会将IAT中原来正确API地址覆盖
     00904765    83C3 0A         add     ebx, 0A
     00904768    83C6 08         add     esi, 8
     0090476B    83C7 04         add     edi, 4
     0090476E    8B45 14         mov     eax, [ebp+14]
     00904771    8958 04         mov     [eax+4], ebx   
     00904774    FF4D 08         dec     dword ptr [ebp+8]
     00904777  ^ 0F85 4AFFFFFF   jnz     009046C7
     0090477D    5F              pop     edi
     0090477E    5E              pop     esi
     0090477F    5B              pop     ebx
     00904780    C9              leave
     00904781    C2 1400         retn    14
}

经过修改后,就能得到正确的IAT了。

00904541  ^ 0F85 1CFDFFFF   jnz     00904263                         //向上跳就继续处理下一个DLL的API函数
0090454A    33C0            xor     eax, eax                          //将断点设在,走出IAT处理过程
00904552    5F              pop     edi
00904553    5E              pop     esi
00904554    5B              pop     ebx
00904555    C9              leave
00904556    C3              retn

此时就可以用ImportREC重建输入表,将IAT的地址RVA:4000,大小D0填进,Get Imports后就能得到正确的输入表。



上图明显一个地方出错,因为各DLL之间的数据应为0,因此此处kernel32.dll与user32.dll之间那项,点击右键Cut thunks将这项删除。
另外,再将user32.dll中的40cc此处的项Cut thunks。再将OEP的RVA 13A0填上,Fix Dump即可得到修复后的程序。

三.暗桩修复

  但是执行脱壳的程序,会出错,用OD加载脱壳后的程序,禁止各种异常。
执行后,会出现“不知如何回避位地地址00901FFC的命令,……”错误,按Shift+F9通过这个异常,会中断如下:
0901FFC    FFFF            ???                                      ; 未知命令
00901FFE    FFFF            ???                                      ; 未知命令

此时查看堆栈:
0012FED4   004026FA  返回到 dumped_.004026FA 来自 009016C3               //在这一行按回车键
0012FED8   004026A2  返回到 dumped_.004026A2 来自 dumped_.004026BE

会来到出错的代码行:
004026EC   .  FF15 34404000 call    [<&kernel32.HeapAlloc>]          ; \HeapAlloc
004026F2   >  5E            pop     esi
004026F3   .  EB FF         jmp     short 004026F4  //去除
004026F5   .  15 B6804000   adc     eax, 004080B6   //去除
004026FA   .  C3            retn

修改后再运行,再次出错:
00402581   .  FF15 84404000 call    [<&kernel32.HeapFree>]           ; \HeapFree
00402587   >  5E            pop     esi
00402588   .  EB FF         jmp     short 00402589  //nop
0040258A   .  15 B2804000   adc     eax, 004080B2   //nop
0040258F   .  C3            retn

退出时再次出错:
0040131C   .  FF15 B8404000 call    [<&user32.DestroyWindow>]        ; \DestroyWindow
00401322   .  5F            pop     edi
00401323   .  B8 01000000   mov     eax, 1
00401328   .  5E            pop     esi
00401329   .  81C4 F4000000 add     esp, 0F4
0040132F   .  EB FF         jmp     short 00401330   //nop
00401331   .  15 B6804000   adc     eax, 004080B6    //nop
00401336   .  C2 1000       retn    10

原来外壳将垃圾代码加进来了,可能程序其他地方还有这些代码,在OD反汇编窗口搜索命令“adc     eax, 004080B6”
00403B82   .  5F            pop     edi
00403B83   .  C9            leave
00403B84   .  EB FF         jmp     short 00403B85    //nop
00403B86   .  15 B6804000   adc     eax, 004080B6     //nop
00403B8B   .  C3            retn

kanxue
看雪技术论坛 www.pediy.com
2005.12.18


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 1
支持
分享
最新回复 (57)
雪    币: 50161
活跃值: (20645)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
第2篇 Obsidium 1.3.0.4学习手记

前面那篇文章,实例程序是没重定位表的,因此Obsidium对外壳没有重定位。将实例程序TraceMe.exe重新编译一下,加上重定位表,再用
Obsidium 1.304加壳保护。(由于重新编译过了,因此这个实例与上篇是2个不同的程序,请勿参照上篇的而误导)。
加壳时,将一些常用的保护都选上了,这篇教学没有SDK部分,有关SDK的处理,请参考heXer的笔记:Obsidium1.2.5.0主程序脱壳记录点滴
附件:obsidium1.304实例下载

一.OD反跟踪

Obsidium在1.304加上了许多反跟踪代码,可以用隐藏OD插件HideOD躲过这些反跟踪。插件下载:
http://bbs.pediy.com/showthread.php?s=&threadid=19170
只需要将HideNtDebgBit,CheckRemoteDebuggerPresent,Process32Next三项勾上即可隐藏OD。

除了用插件隐藏外,还必须将OD类标识改掉,下面的红字:
000B5FE0 CFC2 B5F7 CAD4 A1A3 0000 0000 004F 6C6C 7944 4247 .............OllyDBG
000B5FF4 0000 6F6C 6C79 6462 6700 2E69 6E69 0049 434F 5F41 ..ollydbg..ini.ICO_A
000B6008 4141 4D41 494E 004D 4149 4E4D 454E 5500 4F4C 4C59 AAMAIN.MAINMENU.OLLY
000B601C 4442 4700 CEDE B7A8 B4B4 BDA8 D6F7 D2AA B4B0 BFDA DBG .................

这段是Obsidium 1.3.0.4利用CheckRemoteDebuggerPresent检测调试器代码,是goldenegg以前整理的。
解码时会有长长的一列除零错误。大约有两百多个除0。
运行后会启动一个线程,伪代码是这样子的:
while(1)
{
Sleep(500);
if(CheckRemoteDebuggerPresent()) ExitProcess();
if(IsDebugPresent()) ExitProcess();
DebugBreak(); //异常时中seh自动把eip指向循环开始。
ExitProcess();
}
当然,这些API都是以加密形式运行的。
这样运行起来后,od的log中就可以看到长长的一大列int3中断在ntdll.DebugBreak,且一直不停的增多下去。

上面说的“API都是以加密形式运行的”是怎么回事呢?
如果用PE编辑工具查看加壳后的文件,会发现未加壳的文件和加壳后的文件的输入表不一样,加壳后的输入表一般所引入的DLL和API函数很
少,甚至只有Kernel32.dll以及GetProcAddress这个API函数,有些连这个函数都没有。壳实际上还需要其他的API函数来完成它的工作,为了
隐藏这些API,它一般只在壳的代码中用显式链接方式动态加载这些API函数。

一般的壳是通过LoadLibrary加载DLL,GetProcAddress加载函数的:
HMODULE GetModuleHandle(
LPCTSTR lpModuleName     // DLL文件名地址
);

FARPROC GetProcAddress(
HMODULE hModule,     // DLL模块句柄
LPCSTR lpProcName      // 函数名
);

如果外壳输入表里没有LoadLibrary,可能是通过暴力搜索获得Kernel32.dll基址的,相关文章请参考看雪论坛精华集。外壳调用函数,
一般是通过GetProcAddress函数实现的,因此对这设断能很快找到感兴趣的东西。但现在的加密壳为了更好了隐藏自己操作,己不调用系
统提供的GetProcAddress函数实现了,而是自己专门写一段代码来实现GetProcAddress功能。Obsidium 1.3.0.4就是这情况。为了让大家
更好的理解,我们先来寻找Obsidium自己的GetProcAddress函数。用VirtualAlloc来做为入点,因为外壳都用这函数分配临时空间放代码,
因此用的比较频繁。
记住,不要将断点设在VirtualAlloc第一字节,因为外壳程序会检测第一字节是否有断点的,我一般喜欢将断点设在函数尾部。按Ctrl+G
跳到VirtualAlloc:
7C809A81 k> 8BFF mov edi,edi
7C809A83 55 push ebp
7C809A84 8BEC mov ebp,esp
7C809A86 FF75 14 push dword ptr ss:[ebp+14]
7C809A89 FF75 10 push dword ptr ss:[ebp+10]
7C809A8C FF75 0C push dword ptr ss:[ebp+C]
7C809A8F FF75 08 push dword ptr ss:[ebp+8]
7C809A92 6A FF push -1
7C809A94 E8 09000000 call kernel32.VirtualAllocEx
7C809A99 5D pop ebp
7C809A9A C2 1000 retn 10 //在这按F2设个断点

设置内存访问异常,2次后会调用VirtualAlloc函数。返回到如下代码,取消花指令干扰后,得到的干净代码如下:
00403688 6A 00 push 0
0040368A 6A 56 push 56
0040368C 6A 00 push 0
0040368E 68 4A0DCE09 push 9CE0D4A
00403693 FFB6 98000000 push dword ptr [esi+98]
00403699 FF56 54 call [esi+54]
0040369C 90 nop
0040369D 90 nop
0040369E 90 nop
0040369F 90 nop
004036A0 8B55 0C mov edx, [ebp+C]
004036A3 90 nop
004036A4 90 nop
004036A5 90 nop
004036A6 90 nop
004036A7 81C2 00040000 add edx, 400
004036AD 90 nop
004036AE 90 nop
004036AF 90 nop
004036B0 90 nop
004036B1 6A 40 push 40
004036B3 68 00300000 push 3000
004036B8 52 push edx
004036B9 6A 00 push 0
004036BB 50 push eax
004036BC FF96 84000000 call [esi+84] //这里调用VirtualAlloc,我们从这里CALL返回

向上看,估计00403699就是Obsidium自己实现的GetProcAddress代码。
重新加载程序,通过2个内存异常后,bp 00403688 设断,会中断:
00403688 6A 00 push 0
0040368A 6A 56 push 56
0040368C 6A 00 push 0
0040368E 68 4A0DCE09 push 9CE0D4A
00403693 FFB6 98000000 push dword ptr [esi+98]
00403699 FF56 54 call [esi+54]

取消断点,按F7进入00403699 这个CALL:

    0040AC80 55 push ebp
    0040AC81 8BEC mov ebp, esp
    0040AC83 81EC 18010000 sub esp, 118

    ……
    0040ADA9 8B0483 mov eax, [ebx+eax*4]
    0040ADAC 03C1 add eax, ecx
    0040ADAE EB 57 jmp short 0040AE07

    ……
    0040AE88 5F pop edi
    0040AE89 5E pop esi
    0040AE8A 5B pop ebx
    0040AE8B 8BE5 mov esp, ebp
    0040AE8D 5D pop ebp
    0040AE8E C2 1400 retn 14 //函数地址通过EAX返回

上面这段代码就是Obsidium自己实现的GetProcAddress代码,整个外壳程序调用的APi函数几乎都是通过这个函数得到。
可以将断点设在0040AE8E这一行,直接运行外壳程序,观察EAX指向的字符串,这些就是外壳将要调用的API函数。

二.寻找OEP

设置内存访问异常,加载程序从头跟踪:

第1次异常:00403055 8B00 mov eax, [eax]
第11次异常 00404D73 8B00 mov eax, [eax]

为了能少跟踪些代码,我们尽量找到离OEP最近的异常,再设置整除异常,一次后来到:
(因为两次整除异常后程序界面己出来,因此一次就行了,此时再将整除异常取消)

00405354 F7F2 div edx //整除除异常

查看堆栈窗口:
0012FF88 0012FFE0
0012FF8C 00405391 //注意这里

在命令行设断:bp 00405391

按Shift+F9跳过异常来到:
00405391 55 push ebp
00405392 8BEC mov ebp, esp
00405394 90 nop
00405395 90 nop
00405396 90 nop
00405397 90 nop
00405398 8B4D 08 mov ecx, [ebp+8]
0040539B 90 nop
0040539C 90 nop
0040539D 90 nop
0040539E 90 nop
0040539F 8B01 mov eax, [ecx]
004053A1 90 nop
004053A2 90 nop
004053A3 90 nop
004053A4 90 nop
004053A5 90 nop
004053A6 90 nop
004053A7 90 nop
004053A8 90 nop
………………

00405431 8D940A EFAB0CFF lea edx, [edx+ecx+FF0CABEF]
00405438 90 nop
00405439 90 nop
0040543A 90 nop
0040543B 90 nop
0040543C 90 nop
0040543D 90 nop
0040543E 8990 B8000000 mov [eax+B8], edx //记下EDX值,设断跟进

0040543E这句中,EDX中就是 CONTEXT.EIP的值,记EDX的值00405563,对其设断,来到:
00405563 E8 CB000000 call 00405633 ; 这是一个加密CALL,按F7进去
{
    …………
    00405692 8BEC mov ebp, esp
    00405694 49 dec ecx
    00405695 C061 F9 72 shl byte ptr [ecx-7], 72
    00405699 0224FE add ah, [esi+edi*8]
    0040569C C3 retn //按F4,走出这段循环代码

}

走出上面的代码,重新来到00405563这个地址处,代码己被SMC处理成一个跳转:
00405563 /EB 04 jmp short 00405569 //所以开始这也可按一下F4即可

……………… (按F8单步走,这段有花指令,不要去除,会有自检验)
00405612 FFE7 jmp edi //来到此处,跟进

来到:
008FB40D E8 00000000 call 008FB412
008FB412 EB 01 jmp short 008FB415
008FB414 2F das
008FB415 5D pop ebp
008FB416 EB 03 jmp short 008FB41B

…………

//1.304己没有将Stole code放在这里

008FB480 - E9 4A5FAFFF jmp 009513CF//来到这里,跳到伪OEP

{

    009513CF 64:A1 00000000 mov eax, fs:[0]//伪OEP
    009513D5 50 push eax
    009513D6 64:8925 0000000>mov fs:[0], esp
    009513DD 83EC 58 sub esp, 58
    009513E0 53 push ebx
    009513E1 56 push esi
    009513E2 57 push edi
    009513E3 8965 E8 mov dword ptr ss:[ebp-18],esp
    009513E6 FF15 44503F00 call dword ptr ds:[955044]   
}

到OEP后,此时的基址己不是加壳前的00400000,Obsidium己将程序代码重定位了,这里的程序基址是950000,不同的系统此值不同,这
个值是Obsidium外壳调用VirtualAlloc函数分配的一空间。但同一系统,重复运行实例程序,其基址都是相同的。这就给我们跟踪调试带来
方便了。

停到009513CF伪OEP后,查看堆栈:
0012FFB4 0095208C
0012FFB8 009550D8
0012FFBC FFFFFFFF

此时只要根据VC6文件的头部特征就可还原出被抽取的代码:
push ebp
mov ebp, esp
push -1
push 009550D8
push 0095208C

接下来分析IAT的位置,这句"009513E6 call dword ptr ds:[955044] "就是调用系统的某个API,在数据窗口下命令:
d 955044

00954FF4 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B6 00 ..............?
00955004 0C 00 B6 00 18 00 B6 00 24 00 B6 00 30 00 B6 00 ..?.?$.?0.?
00955014 3C 00 B6 00 48 00 B6 00 54 00 B6 00 60 00 B6 00 <.?H.?T.?`.?
00955024 6C 00 B6 00 78 00 B6 00 84 00 B6 00 90 00 B6 00 l.?x.?????
00955034 9C 00 B6 00 A8 00 B6 00 B4 00 B6 00 18 02 B6 00 ???????
00955044 22 02 B6 00 D8 00 B6 00 E4 00 B6 00 2C 02 B6 00 "?????,?
00955054 FC 00 B6 00 08 01 B6 00 14 01 B6 00 20 01 B6 00 ???? ?
00955064 2C 01 B6 00 38 01 B6 00 44 01 B6 00 50 01 B6 00 ,?8?D?P?
00955074 5C 01 B6 00 68 01 B6 00 74 01 B6 00 80 01 B6 00 \?h?t???
00955084 8C 01 B6 00 98 01 B6 00 A4 01 B6 00 B0 01 B6 00 ????????
00955094 BC 01 B6 00 C8 01 B6 00 D4 01 B6 00 E0 01 B6 00 ????????
009550A4 36 02 B6 00 42 02 B6 00 4E 02 B6 00 5A 02 B6 00 6?B?N?Z?
009550B4 66 02 B6 00 72 02 B6 00 7E 02 B6 00 8A 02 B6 00 f?r?~???
009550C4 96 02 B6 00 A2 02 B6 00 AE 02 B6 00 BA 02 B6 00 ????????
009550D4 C6 02 B6 00 FF FF FF FF 97 14 95 00 AB 14 95 00 ??????.??.

初步猜测IAT的范围就在955000~9550D8这个范围。

修复OEP后,这时可以Dump取文件,然后重建输入表后即可脱壳完毕,由于是在基址为955000这个基址Dump取的,脱壳后的文件基址也得为这个值。因此总感觉不完美,程序原来基址是400000这个值,我们可以对外壳动点手脚,让其代码不重定位,这样就可得到基址是400000的文件,下面我们就来跳过外壳的重定位。

三.寻找Stole code

由于上一步己构造好OEP处的代码了,这一步可以省略,但为了教学完整性,在这将Obsidium 1.304处理Stole code的过程列出。
前面说过,到达伪OEP时,堆栈代码:

0012FFB4 0095208C
0012FFB8 009550D8
0012FFBC FFFFFFFF

定位处理Stole code方法就是根据外壳程序是何时生成这三个堆栈数据而跟踪的。经过跟踪,是在第10次异常后开始处理Stole code代码的。
设置内存访问异常,重新加载程序,10次内存访问异常:

008F6F91 8B02 mov eax, [edx] //第10次异常中断此处

查看堆栈数据:
0012FE40 0012FE60 指针到下一个 SEH 记录
0012FE44 008F7034 SE 句柄 //异常后将跳到008F7034地址处

因此,跳到008F7034地址,按F2设断,再按Shift F9运行程序,中断这里:(下面代码花指令己取消)
008F7034 55 push ebp
008F7035 8BEC mov ebp, esp
008F7037 90 nop
008F7038 90 nop
008F7039 90 nop
008F703A 90 nop
008F703B 90 nop
008F703C 90 nop
008F703D E8 00000000 call 008F7042
008F7042 90 nop
008F7043 90 nop
008F7044 90 nop
008F7045 90 nop
008F7046 58 pop eax
008F7047 90 nop
008F7048 90 nop
008F7049 90 nop
008F704A 90 nop
008F704B 8B55 10 mov edx, [ebp+10]
008F704E 90 nop
008F704F 90 nop
008F7050 90 nop
008F7051 90 nop
008F7052 90 nop
008F7053 8D40 87 lea eax, [eax-79]
008F7056 90 nop
008F7057 90 nop
008F7058 90 nop
008F7059 8982 B8000000 mov [edx+B8], eax//contex.eip关键点,对EAX设断

运行到008F7059一行,对EAX地址设断,中断后到这里:
008F6FC9 /EB 04 jmp short 008F6FCF

…………
008F6FE4 5B pop ebx
008F6FE5 5E pop esi
008F6FE6 8BE5 mov esp, ebp
008F6FE8 5D pop ebp
008F6FE9 C3 retn //从走这出这个CALL

来到:(以下代码花指令己去除)
00404A7B 90 nop
00404A7C 90 nop
00404A7D 90 nop
00404A7E F747 0C 0400000>test dword ptr [edi+C], 4
00404A85 90 nop
00404A86 90 nop
00404A87 90 nop
00404A88 74 26 je short 00404AB0
00404A8A 90 nop
00404A8B 90 nop

…………
00404AE7 8D7C07 14 lea edi, [edi+eax+14]
00404AEB 90 nop
00404AEC 90 nop
00404AED 90 nop
00404AEE 90 nop
00404AEF 90 nop
00404AF0 ^ E9 75FBFFFF jmp 0040466A//跟进

来到:(以下代码花指令己去除)
0040466A 90 nop
0040466B 90 nop
0040466C 90 nop
0040466D 90 nop
0040466E 90 nop
0040466F 90 nop
00404670 90 nop
00404671 90 nop
00404672 90 nop
00404673 90 nop
00404674 90 nop
00404675 90 nop
00404676 8B45 0C mov eax, [ebp+C]
00404679 90 nop
0040467A 90 nop
0040467B 90 nop
0040467C 90 nop
0040467D 90 nop
0040467E 90 nop
0040467F 8B80 A02AEE00 mov eax, [eax+EE2AA0]

…………
00404A6E /74 40 je short 00404AB0
00404A70 |90 nop
00404A71 |90 nop
00404A72 |90 nop
00404A73 |90 nop
00404A74 |90 nop
00404A75 |90 nop
00404A76 |90 nop
00404A77 |90 nop
00404A78 |FF55 FC call [ebp-4] ; 关键,跟进
{

//这段代码不得改动,有自校验,按F8跟

008F6F28 E8 1F040000 call 008F734C
008F6F2D 6B99 161A9156 A>imul ebx, [ecx+56911A16], -5B
008F6F34 4F dec edi
……

008F73DA ^\0F85 AAFFFFFF jnz 008F738A
008F73E0 EB 03 jmp short 008F73E5
008F73E2 5B pop ebx
008F73E3 216F 61 and [edi+61], ebp
008F73E6 C3 retn//如果看到这里,按F4设断,可来到这里
}

008F73E6返回后,来到:(以下代码花指令己去除)
008F6F28 90 nop
008F6F29 90 nop
008F6F2A 90 nop
008F6F2B 90 nop
008F6F2C 90 nop
008F6F2D E8 00000000 call 008F6F32
008F6F32 90 nop
008F6F33 90 nop
008F6F34 90 nop
008F6F35 90 nop
008F6F36 90 nop
………………
008F701B 61 popad
008F701C 90 nop
008F701D 90 nop
008F701E 90 nop
008F701F 9D popfd
008F7020 90 nop
008F7021 90 nop
008F7022 90 nop
008F7023 90 nop
008F7024 90 nop
008F7025 90 nop
008F7026 90 nop
008F7027 90 nop
008F7028 90 nop
008F7029 90 nop
008F702A 55 push ebp //stolen code第一句
008F702B 90 nop
008F702C 90 nop
008F702D 90 nop
008F702E 90 nop
008F702F 90 nop
008F7030 90 nop
008F7031 8BEC mov ebp, esp //stolen code第二 句
008F7033 90 nop
008F7034 90 nop
008F7035 90 nop
008F7036 90 nop
008F7037 6A FF push -1 //stolen code第三句
008F7039 90 nop
008F703A 90 nop
008F703B 90 nop
008F703C 90 nop
008F703D 68 D8503F00 push 9550D8 //stolen code第四句
008F7042 90 nop
008F7043 90 nop
008F7044 90 nop
008F7045 68 8C203F00 push 95208C //stolen code第五句
008F704A 90 nop
008F704B 90 nop
008F704C 90 nop
008F704D 90 nop
008F704E 90 nop
008F704F 9C pushfd
008F7050 60 pushad

四.跳过基址重定位,修复内存映像文件

重新加载程序,按Ctrl+G跳到VirtualAlloc:
7C809A81 ker> 8BFF mov edi,edi
7C809A83 55 push ebp
7C809A84 8BEC mov ebp,esp
7C809A86 FF75 14 push dword ptr ss:[ebp+14]
7C809A89 FF75 10 push dword ptr ss:[ebp+10]
7C809A8C FF75 0C push dword ptr ss:[ebp+C]
7C809A8F FF75 08 push dword ptr ss:[ebp+8]
7C809A92 6A FF push -1
7C809A94 E8 09000000 call kernel32.VirtualAllocEx
7C809A99 5D pop ebp
7C809A9A C2 1000 retn 10 //这里按F2设断

同一系统多次运行实例程序,外壳申请的映像空间的地址是一样的,笔者机子当时基址是:9500000,设好VirtualAlloc断点后,
运行程序,当第三次调用VirtualAlloc函数时,返回的值是9500000。
00404EEF FF93 84000000 call [ebx+84] //VirtualAlloc
00404EF5 90 nop 返回这里,eax=950000
00404EF6 90 nop
00404EF7 90 nop
00404EF8 90 nop
00404EF9 90 nop
00404EFA 90 nop
00404EFB 85C0 test eax, eax
00404EFD 90 nop
00404EFE 90 nop
00404EFF 90 nop
00404F00 90 nop
00404F01 90 nop
00404F02 90 nop
00404F03 0F84 B2030000 je 004052BB

下面开始解码过程。
解码后这里处理reloc:
00405234 8B43 10 mov eax, [ebx+10] ; 新imagebase
00405237 90 nop
00405238 90 nop
00405239 90 nop
0040523A 90 nop
0040523B 90 nop
0040523C 2B43 40 sub eax, [ebx+40] ; 求与原来的基址差值,以重定位相关代码要,改成 xor eax,eax
0040523F 90 nop
00405240 90 nop
00405241 90 nop
00405242 90 nop
00405243 FF77 08 push dword ptr [edi+8]
00405246 FF77 04 push dword ptr [edi+4]
00405249 FF73 7C push dword ptr [ebx+7C]
0040524C 50 push eax
0040524D FF73 10 push dword ptr [ebx+10]
00405250 FF53 74 call [ebx+74]

为了脱壳后使base定位在400000h,将0040523C这句改成:
0040523C 33C0 xor eax,eax

接下来就到OEP处,修复Stole code再Dump取程序。
此时取消VirtualAlloc断点,再经过7次内存访问异常,再单步跟踪就可到伪OEP了:

009513CF 64:A1 00000000 mov eax,dword ptr fs:[0]
009513D5 50 push eax
009513D6 64:8925 00000000 mov dword ptr fs:[0],esp
009513DD 83EC 58 sub esp,58
009513E0 53 push ebx
009513E1 56 push esi
009513E2 57 push edi
009513E3 8965 E8 mov dword ptr ss:[ebp-18],esp
009513E6 FF15 44504000 call dword ptr ds:[405044]

此时查看上面的红字[405044],己不是开始的[955044]了,外壳没有再重定位相关代码与数据。
在修复键入OEP代码前,先按Alt+M打开内存映射窗口,找到基址这个地址(我当前的基址950000),同时记下映像的大小0xB000,点击右键/设置权限/完整权限,这样操作后,就可在OD里键入代码了。

运行LordPE,选中实例进程,先用 Dump Full功能抓取映像,存为dumpedfull.exe。别忘设置LordPE的Options/Full dump:paste header from disk这项,从磁盘文件里取PE的各项信息。不然dumped.exe的程序一些PE头数据是错误的,例如没图标等。

接下来再抓取950000这个基址,大小是0xB000的映像,这个映像就是程序的代码段、数据段。同样用LordPE,在右键选Dump partial,将你程序OEP处的基址填进,大小是0xB000。保存为dumppartial.dmp文件。

下面就利用dumpedfull.exe和dumppartial.dmp构造一个完整的dumped.exe。

1).修正PE头
用LordPE打开dumpedfull.exe,查看区块:

ㄔNumber Name VirtSize RVA PhysSize Offset Flagㄔ
1 00001000 00001000 00001000 00001000 C0000040
2 .rsrc 00001000 00002000 00001000 00002000 C0000040
3 0000C000 00003000 0000C000 00003000 E0000060

我们只需要PE头这部分的数据,就是0x1000以前的数据,用Hex Workshop十六进制工具打开dumpedfull.exe,将光标定在文件头,菜单执行Edit/Select Block,输入1000,选中PE头部分,点击复制。



再用Hex Workshop接着打开dumppartial.dmp文件,这个文件前0x1000字节己被清空,我们要做的就是将dumpedfull.exe文件头部分复制过来。
同样的操作,将光标定在文件头,菜单执行Edit/Select Block,输入1000,选中PE头部分,点击粘贴,将刚才复制的头部数据粘贴过来。
将dumppartial.dmp改名为dumped.exe。

用LordPE打开修正过的dumped.exe,查看区块,点击右键wipe section header,将第2、3项删除。
同时将第1项修正,大小改为0xb000,属性选上“执行代码”,E0000040。

ㄔNumber Name VirtSize RVA PhysSize Offset Flagㄔ
1 .text 0000B000 00001000 0000B000 00001000 E0000040

2).资源修复
用dREAMtHEATER的DT_FixRes工具打开dumpedfull.exe,点击Dump标签,NewRVA填上C000,FileAlignment填上1000,点击Dump Resource,将资源文件提取出来rsrc.bin。
再用LordPE打开dumped.exe,查看区块,右键执行Load section from disk,将资源rsrc.bin导入,同时在数据目录表里修正资源项的RVA为C000.

ㄔNumber Name VirtSize RVA PhysSize Offset Flagㄔ
1 .text 0000B000 00001000 0000B000 00001000 E0000040
2 rsrc.bin 00002000 0000C000 00002000 0000C000 E00000E0

将修复好的dumped.exe先放一边,等获得正确的输入表再修复就可运行了。

五.输入表的修复

重新加载程序,仅设置内存访问异常。当外壳程序VirtualAlloc调用为映像申请内存后,就可以用命令:
D 955000 查看IAT的数据了。

注意:不同机子,VirtualAlloc申请的值会不同,根据前面的分析得知,基址+5000 就是IAT的起始地址。

当第11次异常后,D 955000 查看的数据己有值,因此重新来过,当第10次异常后,停下:
008F6F29 8B02 mov eax, [edx]

中断后数据窗口:
00955000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00955010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

在 00955000选中几个字节,点击鼠标右键,设置“断点/内在写入”
再按Shift+F9通过异常继续执行程序(请不要按F9,否则以后程序可能会出错的),停在如下:

008F76E2 893E mov [esi], edi

别忘取消内存断点。为了让大家看的明白,用花指令去除了垃圾代码,并且将多余的NOP也省略,剩下的代码为:
008F76E2 893E mov [esi], edi//不断这里,将IAT里填充数据
008F76E7 83C7 0C add edi, 0C
008F76EE 83C6 04 add esi, 4
008F76F7 41 inc ecx
008F76FD 3B4D 0C cmp ecx, [ebp+C]
008F7704 ^ 0F82 68FFFFFF jb 008F7672
008F7712 833E 00 cmp dword ptr [esi], 0
008F771B 0F85 7D000000 jnz 008F779E
008F7726 C607 60 mov byte ptr [edi], 60
008F772D 66:895F 01 mov [edi+1], bx
008F7734 8BC1 mov eax, ecx
008F773C 3345 FC xor eax, [ebp-4]
008F7743 66:8947 03 mov [edi+3], ax
008F774A C1CB 10 ror ebx, 10
008F7753 8857 06 mov [edi+6], dl
008F775B 885F 05 mov [edi+5], bl
008F7762 C647 07 E9 mov byte ptr [edi+7], 0E9
008F776A C1CB 10 ror ebx, 10
008F7772 8B45 14 mov eax, [ebp+14]
008F777A 2BC7 sub eax, edi
008F7781 83E8 0C sub eax, 0C
008F7787 8947 08 mov [edi+8], eax
008F7790 893E mov [esi], edi
008F7798 83C7 0C add edi, 0C
008F77A2 8BC7 mov eax, edi
008F77A8 2B45 18 sub eax, [ebp+18]
008F77AF 5F pop edi
008F77B0 5E pop esi
008F77B1 5B pop ebx
008F77B2 8BE5 mov esp, ebp//下面这几句没有花指令,中断可以向下翻,直接F4走出这段代码
008F77B4 5D pop ebp
008F77B5 C2 1400 retn 14

走出上面代码,来到如下(花指令己去除,代码重新排版整理):
008F738D FF73 34 push dword ptr [ebx+34]
008F7390 50 push eax
008F7391 FF76 0C push dword ptr [esi+C]
008F7394 FF75 F8 push dword ptr [ebp-8]
008F7397 E8 09040000 call 008F77A5 //我们从这个CALL出来
008F739F 0145 EC add [ebp-14], eax
008F73AF 8B46 10 mov eax, [esi+10]
008F73B5 8B56 14 mov edx, [esi+14]
008F73BE 0343 10 add eax, [ebx+10]
008F73C4 0353 48 add edx, [ebx+48]
008F73CB FF36 push dword ptr [esi]
008F73CD 53 push ebx
008F73CE 52 push edx
008F73CF 50 push eax
008F73D0 FF76 0C push dword ptr [esi+C]
008F73D3 E8 E4010000 call 008F75BC //普通加密函数
008F73DE 85C0 test eax, eax
008F73E3 0F84 18010000 je 008F7501
008F73F2 837D F0 00 cmp dword ptr [ebp-10], 0
008F73F9 74 43 je short 008F743E //如跳,则这个DLL里没有特殊函数
008F7405 8B46 10 mov eax, [esi+10]
008F740D 8B56 14 mov edx, [esi+14]
008F7414 0343 10 add eax, [ebx+10]
008F741D 0353 48 add edx, [ebx+48]
008F7426 FF75 EC push dword ptr [ebp-14]
008F7429 53 push ebx
008F742A 52 push edx
008F742B 50 push eax
008F742C FF76 0C push dword ptr [esi+C]
008F742F E8 A5020000 call 008F76D9 //特殊加密函数
008F7438 0145 EC add [ebp-14], eax
008F7445 33C0 xor eax, eax
008F744D 8946 0C mov [esi+C], eax
008F7455 8946 10 mov [esi+10], eax
008F745B 83C6 18 add esi, 18
008F7461 FF45 F8 inc dword ptr [ebp-8]
008F7467 FF4D FC dec dword ptr [ebp-4]
008F746F ^ 0F85 05FDFFFF jnz 008F717A
008F747B 33C0 xor eax, eax
008F7481 5F pop edi
008F7482 5E pop esi
008F7483 5B pop ebx
008F7484 8BE5 mov esp, ebp
008F7486 5D pop ebp
008F7487 C3 retn

1.处理普通加密函数

按F7进入008F73D3 call 008F75BC ,这段代码就是外壳为了防止某些函数指针加密出错,这里将加密的指针还原的。
按照以前版本的通用修改方法如下:(花指令己除及中间的NOP指令己忽略)
008F75BC 55 push ebp
008F75BD 8BEC mov ebp, esp
008F75BF 53 push ebx
008F75C0 56 push esi
008F75C1 57 push edi
008F75C7 8B75 10 mov esi, [ebp+10]
008F75D0 8B7D 0C mov edi, [ebp+C]
008F75D7 8B5D 14 mov ebx, [ebp+14]
008F75E4 test word ptr [esi], 20 // test [esi],8
008F75EC je 008F7685 // jnz 008F7685
008F75F8 66:F706 0200 test word ptr [esi], 2
008F7602 75 47 jnz short 008F764B
008F760E 33C0 xor eax, eax
008F7613 66:C706 0400 mov word ptr [esi], 4
008F761E 6A 01 push 1
008F7620 50 push eax
008F7621 FF76 04 push dword ptr [esi+4]
008F7624 50 push eax
008F7625 FF75 18 push dword ptr [ebp+18]
008F7628 FF53 54 call [ebx+54]
008F762F 85C0 test eax, eax
008F7635 0F84 86000000 je 008F76C1 // je 008F7685
008F7641 8907 mov [edi], eax
008F7646 EB 3D jmp short 008F7685
008F7650 66:C706 0400 mov word ptr [esi], 4
008F7659 0FB756 02 movzx edx, word ptr [esi+2]
008F7661 6A 01 push 1
008F7663 52 push edx
008F7664 6A 00 push 0
008F7666 FF76 04 push dword ptr [esi+4]
008F7669 FF75 18 push dword ptr [ebp+18]
008F766C FF53 54 call [ebx+54]
008F7674 85C0 test eax, eax
008F7679 74 0A je short 008F76C1 //je 008F7685
008F767E 8907 mov [edi], eax
008F7688 83C6 08 add esi, 8
008F768F 83C7 04 add edi, 4
008F7698 FF4D 08 dec dword ptr [ebp+8]
008F769E ^ 0F85 3CFFFFFF jnz 008F75E0

这种方法在以前版本有效,但在这个版本就失效了,生成的IAT中有部分函数出错:(在数据窗口,右键/长型/地址显示:)
00405000 >7C80C729 kernel32.lstrcpyA
00405004 >7C81EE79 kernel32.lstrcmpA
00405008 >7C838CB9 kernel32.GetStringTypeA
0040500C >7C80CEC4 kernel32.LCMapStringW
00405010 >7C832E2B kernel32.LCMapStringA
00405014 >7C809CAD kernel32.MultiByteToWideChar
00405018 >00B60048 <====这里放上的是错误函数
0040501C >00B60054 <====这里放上的是错误函数
00405020 >7C9379FD ntdll.RtlReAllocateHeap
00405024 >7C809A81 kernel32.VirtualAlloc
00405028 >7C9305D4 ntdll.RtlAllocateHeap
0040502C >7C81E82A kernel32.GetOEMCP
00405030 >7C809943 kernel32.GetACP
00405034 >7C812BE6 kernel32.GetCPInfo
00405038 >7C80B529 kernel32.GetModuleHandleA
0040503C >7C801EEE kernel32.GetStartupInfoA
00955040 00B6021B <====特殊函数,上面的008F729F call 008F7549 这个函数处理
00955044 00B60225 <====特殊函数
00405048 >00B600D8 <====这里放上的是错误函数
0040504C >7C801E16 kernel32.TerminateProcess
00955050 00B6022F <====特殊函数
00405054 >7C862B8A kernel32.UnhandledExceptionFilter
00405058 >7C80B357 kernel32.GetModuleFileNameA
0040505C >00B60114 <====这里放上的是错误函数
00405060 >00B60120 <====这里放上的是错误函数
00405064 >7C80A0C7 kernel32.WideCharToMultiByte
00405068 >00B60138 <====这里放上的是错误函数
0040506C >00B60144 <====这里放上的是错误函数
00405070 >7C80C6CF kernel32.SetHandleCount
00405074 >7C812CA9 kernel32.GetStdHandle
00405078 >7C811069 kernel32.GetFileType
0040507C >00B60174
00405080 >7C812851 kernel32.GetVersionExA
00405084 >7C811110 kernel32.HeapDestroy
00405088 >7C812929 kernel32.HeapCreate
0040508C >7C809B14 kernel32.VirtualFree
00405090 >7C93043D ntdll.RtlFreeHeap
00405094 >7C957A40 ntdll.RtlUnwind
00405098 >00B601C8 <====这里放上的是错误函数s
0040509C >7C80A480 kernel32.GetStringTypeW

经过分析,问题出在这:
008F75F8 66:F706 0200 test word ptr [esi], 2
008F7602 75 47 jnz short 008F764B

查看ESI指向的数据如下:

008F0984 02 00 6C 00 10 E4 37 3D 02 00 6C 00 16 68 FD 1F l??l栖>
008F0994 02 00 47 00 FA FD D2 A4 02 00 4C 00 2A 39 97 BA G??L??
008F09A4 02 00 4C 00 89 69 EB 25 02 00 4D 00 D6 68 87 83 L榉?M?>
008F09B4 80 00 00 00 04 00 00 00 80 00 00 00 0A 00 00 00 ?..?...
008F09C4 02 00 48 00 25 3C 50 91 02 00 56 00 28 A7 07 B5 H?酐V??
008F09D4 02 00 48 00 5A 56 8D 4C 02 00 47 00 4B AB 0D 19 H??G??
008F09E4 02 00 47 00 6D 2F CD E2 02 00 47 00 BA 5B 49 4E G??G?>
008F09F4 02 00 47 00 ED 52 B7 AF 02 00 47 00 8C 68 8D A0 G??G?>
008F0A04 08 00 00 00 03 00 00 00 08 00 00 00 00 00 00 00 .....
008F0A14 40 00 00 00 03 00 00 00 02 00 54 00 11 04 59 C8 @..TБ?

02:普通函数标志,用008F73D3 call 008F75BC函数解密
08:特殊函数标志,用008F742F call 008F76D9函数恢复
80:特殊函数标志

对于80这个标志,用“008F7498 call [ebx+54]”这个函数也处理,但得到的结果是错误,
猜测作者在Obsidium1.3里新增了一种处理函数方法。

在这,我们还是用以前的方法,将几处代码修改好:
008F75E4 test [esi],8
008F75EC jnz 008F7685
008F75F8 66:F706 0200 test word ptr [esi], 2
008F7602 /75 47 jnz short 008F764B
008F760E 33C0 xor eax, eax //设断,让程序执行,查看数据面板窗口
008F7613 66:C706 0400 mov word ptr [esi], 4
008F761E 6A 01 push 1
008F7620 50 push eax
008F7621 FF76 04 push dword ptr [esi+4]
008F7624 50 push eax
008F7625 FF75 18 push dword ptr [ebp+18]
008F7628 FF53 54 call [ebx+54]
008F762F 85C0 test eax, eax
008F7635 je 008F7685
008F7641 8907 mov [edi], eax
008F7646 EB 3D jmp short 008F7685
008F7650 66:C706 0400 mov word ptr [esi], 4
008F7659 0FB756 02 movzx edx, word ptr [esi+2]
008F7661 6A 01 push 1
008F7663 52 push edx
008F7664 6A 00 push 0
008F7666 FF76 04 push dword ptr [esi+4]
008F7669 FF75 18 push dword ptr [ebp+18]
008F766C FF53 54 call [ebx+54]
008F7674 85C0 test eax, eax
008F7679 74 0A je 008F7685

008F760E中断后,查看数据面板窗口:
00955000 7C80C729 kernel32.lstrcpyA
00955004 7C81EE79 kernel32.lstrcmpA
00955008 7C838CB9 kernel32.GetStringTypeA
0095500C 7C80CEC4 kernel32.LCMapStringW
00955010 7C832E2B kernel32.LCMapStringA
00955014 7C809CAD kernel32.MultiByteToWideChar
00955018 00B60048 //这个是80标志的函数
……

为了了解代码是如何处理80标志的函数,我们跟进00955018这个IAT地址所指的 00B60048函数。按Ctrl+G打开地址框,
输入0B60048(注意:地址首位如果是字母,别忘加个0):
00B60048 60 pushad //在这按右键新建EIP源,将当前EIP指针指到此处
00B60049 66:BF B55B mov di, 5BB5
00B6004D B2 80 mov dl, 80
00B6004F - E9 E350D9FF jmp 008F5137

除去花指令后的代码:
008F52C7 90 nop
008F52C8 90 nop
008F52C9 90 nop
008F52CA 90 nop
008F52CB 90 nop
008F52CC 0FB6D2 movzx edx, dl
008F52CF 90 nop
008F52D0 90 nop
008F52D1 90 nop
008F52D2 0FB7C7 movzx eax, di
……
008F5399 8D34C6 lea esi, [esi+eax*8] //来到这里,查看ESI
008F53A0 0FB706 movzx eax, word ptr [esi]
008F53AC 83F8 04 cmp eax, 4
008F53B5 0F84 78010000 je 008F5533
008F53C4 83F8 01 cmp eax, 1
008F53CA 0F84 11010000 je 008F54E1
008F53DA 3D 80000000 cmp eax, 80
008F53E3 /0F84 AE070000 je 008F5B97//这里会跳

到008F5209这句查看ESI:
008F09B4 80 00 00 00 04 00 00 00 80 00 00 00 0A 00 00 00 ?..?...

这个就是上面的那个数据表,80就是特殊函数标志,后面的04就是与函数名有关的参数。
008F5BBB 8BD5 mov edx, ebp
008F5BBD 90 nop
008F5BBE 90 nop
008F5BBF 90 nop
008F5BC0 90 nop
008F5BC1 90 nop
008F5BC2 90 nop
008F5BC3 039485 9401EC00 add edx, [ebp+eax*4+EC0194]
008F5BCA 90 nop
008F5BCB 90 nop
008F5BCC 90 nop
008F5BCD 90 nop
008F5BCE FFE2 jmp edx

继续走,来到如下:
008F5D23 90 nop
008F5D24 90 nop
008F5D25 90 nop
008F5D26 8B83 C4010000 mov eax, [ebx+1C4]
008F5D2C 90 nop
008F5D2D 90 nop
008F5D2E 90 nop
008F5D2F 90 nop
008F5D30 90 nop
008F5D31 90 nop
008F5D32 ^ E9 E7FDFFFF jmp 008F5B1E

查看 ebx+1C4,会发现一组代码的地址,原来程序是根据不同的函数参数确定函数名,你可以Ctrl+G输入下面的地址查看代码:
008F0390 008F223D 008F22C7 008F23AB 008F232D
008F03A0 008F2423 008F2489 008F24EF 008F2555
008F03B0 008F2633 008F269F 008F2705 008F27E9
008F03C0 008F285B 008F25C1 008F2777 008F28C7
008F03D0 008F2159 008F21CB 008F20A1 008F20FD
008F03E0 008F1FD5 008F203B 008F1E73 008F1EF1
008F03F0 008F1F63 008F2951 008F29C3 008F2A47
008F0400 008F2AC5 008F2B2B 008F1C98 00000000

上面每个地址是一段代码的入口,处理某个API函数,下面要做的就是找到这些代码共同出口点,为补丁程序做准备。继续:
008F5B26 8038 CC cmp byte ptr [eax], 0CC
008F5B2E 74 1B je short 008F5B4B
008F5B38 894424 1C mov [esp+1C], eax
008F5B3C 90 nop
008F5B3D 90 nop
008F5B3E 90 nop
008F5B3F 90 nop
008F5B40 61 popad
008F5B41 90 nop
008F5B42 90 nop
008F5B43 90 nop
008F5B44 90 nop
008F5B45 FFE0 jmp eax //根据参数不同,跳到不同代码里获取函数地址

这次跟进eax为008F2423值,jmp 008F2423地址,来到:
008F2423 55 push ebp
008F2424 8BEC mov ebp, esp
008F2426 83EC 08 sub esp, 8
008F2429 53 push ebx
008F242A 56 push esi
008F242B 57 push edi
008F242C E8 00000000 call 008F2431
008F2431 5B pop ebx
008F2432 8BF3 mov esi, ebx
008F2434 8B9B 4FF4FFFF mov ebx, [ebx-BB1]
008F243A 8975 F8 mov [ebp-8], esi
008F243D 8DBE A7070000 lea edi, [esi+7A7]
008F2443 BE 04000000 mov esi, 4
008F2448 8B07 mov eax, [edi]
008F244A 85C0 test eax, eax
008F244C 74 14 je short 008F2462
008F244E 8D55 FC lea edx, [ebp-4]
008F2451 FF75 08 push dword ptr [ebp+8]
008F2454 52 push edx
008F2455 FFD0 call eax
008F2457 83F8 01 cmp eax, 1
008F245A 74 21 je short 008F247D
008F245C 83C7 04 add edi, 4
008F245F 4E dec esi
008F2460 ^ 75 E6 jnz short 008F2448
008F2462 8B75 F8 mov esi, [ebp-8]
008F2465 FF75 08 push dword ptr [ebp+8]
008F2468 FFB6 57090000 push dword ptr [esi+957] //esi+957指向的就是正确的函数名,按F7跟进
008F246E FF93 84000000 call [ebx+84]
008F2474 5F pop edi
008F2475 5E pop esi
008F2476 5B pop ebx
008F2477 8BE5 mov esp, ebp
008F2479 5D pop ebp
008F247A C2 0400 retn 4

008F2468这个CALL进入会来到外壳自己的领空,其地址是4xxxxx:
0040C633 /EB 01 jmp short 0040C636 //在这可以用mov eax, [esp+4] 指令取得函数的地址
0040C635 |5D pop ebp
0040C636 \EB 01 jmp short 0040C639
0040C638 04 60 add al, 60
0040C63A EB 04 jmp short 0040C640

这就意味着不同的函数,最终都会来到这里,这为补丁程序取得函数名地址提供了可能。
补丁思路:
1.处理标志为80函数时,跳到IAT指定的地址处执行
2.补丁0040C633,让其跳会IAT普通函数处理代码里。

当然如果特殊函数不多,你不怕麻烦可以不用这方法来处理,手动跟踪那些没被识别出的函数,找到正确的函数指针。

按这思路构造的代码如下,红色部分是处理标志为80的函数:
(由于每次跟踪,外壳这段代码地址不同,下面这段代码是第二次运行抓取的,因此同一指令和上文提供的地址不同,大家以汇编代码来识别)
008F7564 test word ptr ds:[esi],8
008F756C jnz 008F7605
008F7578 test word ptr ds:[esi],2
008F7582 jnz short 008F75CB
008F7584 test word ptr ds:[esi],80 //如是80标志的函数就处理
008F7589 je short 008F7605
008F758B mov eax,dword ptr ds:[esi+4]
008F758E pushad
008F758F mov edx,954FF8 //954FF8地址是内存中的一空白,这里是IAT前面部分,用以保存临时变量
008F7594 mov dword ptr ds:[edx],esp //将esp的值保存在954FF8这个地址变量里
008F7596 jmp dword ptr ds:[edi] //跳到IAT指定的地址运行
008F7598 nop
008F7599 nop
008F759A nop //IAT指定地址运行结束后,让它返回到这里
008F759B mov eax,dword ptr ss:[esp+4] //取得函数地址
008F759F mov esp,dword ptr ds:[954FF8] //恢复堆栈
008F75A5 mov dword ptr ss:[esp+1C],eax //eax的结果放进堆栈里,以便popad时,会放到eax寄存器里
008F75A9 popad
008F75AA mov word ptr ds:[esi],4
008F75AF mov dword ptr ds:[edi],eax //将得到的函数地址放进IAT
008F75B1 nop
008F75B2 nop
……(中间全是NOP指令)
008F75C6 EB 3D jmp short 008F7605 //继续处理下一个函数
008F75D0 66:C706 0400 mov word ptr ds:[esi],4
008F75D9 0FB756 02 movzx edx,word ptr ds:[esi+2]
008F75E1 6A 01 push 1
008F75E3 52 push edx
008F75E4 6A 00 push 0
008F75E6 FF76 04 push dword ptr ds:[esi+4]
008F75E9 FF75 18 push dword ptr ss:[ebp+18]
008F75EC FF53 54 call dword ptr ds:[ebx+54]
008F75F4 85C0 test eax,eax
008F75F9 74 0A je short 008F7605
008F75FE 8907 mov dword ptr ds:[edi],eax
008F7608 83C6 08 add esi,8
008F760F 83C7 04 add edi,4
008F7618 FF4D 08 dec dword ptr ss:[ebp+8]
008F761E ^ 0F85 3CFFFFFF jnz 008F7560
008F7629 33C0 xor eax,eax //在这设个断点,执行完上述代码,80标志的函数就还原了
008F762F 40 inc eax
008F7636 5F pop edi
008F7637 5E pop esi
008F7638 5B pop ebx
008F7639 5D pop ebp
008F763A C2 1400 retn 14

上面这段代码有几点要注意的:
第一个就是954FF8地址,我这取的是IAT(IAT起始地址是955000)前面那个空间,
由于不同系统分配的内存地址可能不同,所以你得根据你的情况将这个值定好。
第二个就是008F7582~008F75C6之间代码别忘全部NOP掉,用新增的代码替换。
第三就是处理函数的每个代码入口参数不一样,因此必须将ESP保存到一变量了。

上面修改代码二进制如下,实际操作时,可以用OD的二进制粘贴功能将代码复制过去,别忘了还要根据你的情况修正954FF8变量地址。

66 F7 06 80 00 74 7A 8B 46 04 60 BA F8 4F 95 00 89 22 FF 27 90 90 90 8B 44 24 04 8B 25 F8 4F 95 00 89 44 24 1C 61 66 C7 06 04 00 89 07

另外,再按Ctrl+G跳到0040C633 ,这是所有代码的出口,将其改成:
0040C633 - E9 62AF4E00 jmp 008F759 //别忘设个断,所有函数处理完毕回调用这里的代码,你再撤消选择将其还原

2.处理特殊加密函数

走出上面的普通函数,进入特殊函数处理的CALL:
008F7424 90 nop
008F7425 90 nop
008F7426 FF75 EC push dword ptr [ebp-14]
008F7429 53 push ebx
008F742A 52 push edx
008F742B 50 push eax
008F742C FF76 0C push dword ptr [esi+C]
008F742F E8 A5020000 call 008F76D9//进入这个处理特殊函数的CALL

F7走进:
008F76D9 55 push ebp
008F76DA 8BEC mov ebp, esp
008F76DC 53 push ebx
008F76DD 56 push esi
008F76DE 57 push edi
008F76DF 8B75 10 mov esi, [ebp+10]

…………

008F771A 83F8 03 cmp eax, 3
008F771D 74 12 je short 008F7731
008F771F 83F8 04 cmp eax, 4
008F7722 ^ 75 CA jnz short 008F76EE
008F7724 8B45 14 mov eax, [ebp+14]
008F7727 8B90 E8000000 mov edx, [eax+E8]
008F772D 8917 mov [edi], edx
008F772F ^ EB BD jmp short 008F76EE
008F7731 8B45 14 mov eax, [ebp+14]
008F7734 68 C5B1662D push 2D66B1C5
008F7739 6A 00 push 0
008F773B FF50 18 call [eax+18]
008F773E 50 push eax
008F773F 53 push ebx
008F7740 E8 98020000 call 008F79DD
008F7745 53 push ebx
008F7746 E8 19020000 call 008F7964
008F774B 8BCB mov ecx, ebx
008F774D 8D5C03 01 lea ebx, [ebx+eax+1]
008F7751 8BC1 mov eax, ecx
008F7753 EB 2B jmp short 008F7780
008F7755 8B45 14 mov eax, [ebp+14]
008F7758 68 0F1ACF4C push 4CCF1A0F
008F775D 6A 00 push 0
008F775F FF50 18 call [eax+18] //解密函数,按F7进入
{
    0040CE93 90 nop
    0040CE94 90 nop
    0040CE95 90 nop
    0040CE96 60 pushad
    0040CE9B 83EC 04 sub esp, 4
    0040CEA3 E8 00000000 call 0040CEA8
    0040CEAC 5B pop ebx
    0040CEB1 8BEB mov ebp, ebx
    0040CEB7 8B5B E7 mov ebx, [ebx-19]
    0040CEC4 8B4424 28 mov eax, [esp+28]
    0040CED4 33C9 xor ecx, ecx
    0040CEDC 8B8483 98000000 mov eax, [ebx+eax*4+98]
    0040CEE9 8B5424 2C mov edx, [esp+2C]
    0040CEF3 51 push ecx
    0040CEF4 51 push ecx
    0040CEF5 51 push ecx
    0040CEF6 52 push edx
    0040CEF7 50 push eax
    0040CEF8 FF53 54 call [ebx+54] //Obsidium自己实现的GetProcAddress
    0040CF01 85C0 test eax, eax
    0040CF06 0F84 56020000 je 0040D162
    0040CF0C 90 nop //改成 MOV [EDI], EAX 同时设断
    0040CF0D 90 nop
    0040CF11 8BF0 mov esi, eax
}
008F7762 /EB 1C jmp short 008F7780
008F7764 |8B45 14 mov eax, [ebp+14]
008F7767 |68 A41A86D0 push D0861AA4
008F776C |6A 00 push 0
008F776E |FF50 18 call [eax+18]
008F7771 |EB 0D jmp short 008F7780
008F7773 |8B45 14 mov eax, [ebp+14]
008F7776 |68 E313B41D push 1DB413E3
008F777B |6A 00 push 0
008F777D |FF50 18 call [eax+18]
008F7780 \C603 B8 mov byte ptr [ebx], 0B8
008F7783 8943 01 mov [ebx+1], eax
008F7786 8B45 14 mov eax, [ebp+14]
008F7789 8B90 A4010000 mov edx, [eax+1A4]
008F778F 8D43 0A lea eax, [ebx+A]
008F7792 2BD0 sub edx, eax
008F7794 C643 05 E9 mov byte ptr [ebx+5], 0E9
008F7798 8953 06 mov [ebx+6], edx
008F779B 891F mov [edi], ebx //这句NOP掉,因为处理完,会用其地址覆盖[edi](edi指向IAT)

在:0040CF0C 90 nop //改成 MOV [EDI], EAX //同时设断
这一行,会中断三次,得到三个特殊函数:
GetCommandLineA
GetVersion
GetCurrentProcess

所有的特殊函数处理完毕后,再执行,程序返回的EDI值己不指向IAT了,因此必须将MOV [EDI], EAX 这句删除,还原代码。

处理完kernel32.dll后将处理user32.dll,程序会再循环回到那个解密普通函数的CALL程序再次执行的。user32.dll没有特殊函数,出来后:

08F73D3 E8 E4010000 call 008F75BC //普通加密函数
008F73DE 85C0 test eax, eax
008F73E3 0F84 18010000 je 008F7501
008F73F2 837D F0 00 cmp dword ptr [ebp-10], 0
008F73F9 74 43 je short 008F743E //如跳,则这个DLL里没有特殊函数
008F7405 8B46 10 mov eax, [esi+10]
008F740D 8B56 14 mov edx, [esi+14]
008F7414 0343 10 add eax, [ebx+10]
008F741D 0353 48 add edx, [ebx+48]
008F7426 FF75 EC push dword ptr [ebp-14]
008F7429 53 push ebx
008F742A 52 push edx
008F742B 50 push eax
008F742C FF76 0C push dword ptr [esi+C]
008F742F E8 A5020000 call 008F76D9 //特殊加密函数
008F7438 0145 EC add [ebp-14], eax
008F7445 33C0 xor eax, eax
008F744D 8946 0C mov [esi+C], eax
008F7455 8946 10 mov [esi+10], eax
008F745B 83C6 18 add esi, 18
008F7461 FF45 F8 inc dword ptr [ebp-8]
008F7467 FF4D FC dec dword ptr [ebp-4]
008F746F ^ 0F85 05FDFFFF jnz 008F717A
008F747B 33C0 xor eax, eax //将断点设在,处理所有的DLL就会中断在这
008F7481 5F pop edi
008F7482 5E pop esi
008F7483 5B pop ebx
008F7484 8BE5 mov esp, ebp
008F7486 5D pop ebp
008F7487 C3 retn

所有的DLL处理完毕查看一下IAT:
00955000 7C80C729 kernel32.lstrcpyA
00955004 7C81EE79 kernel32.lstrcmpA
00955008 7C838CB9 kernel32.GetStringTypeA
0095500C 7C80CEC4 kernel32.LCMapStringW
00955010 7C832E2B kernel32.LCMapStringA
00955014 7C809CAD kernel32.MultiByteToWideChar
00955018 7C801D77 kernel32.LoadLibraryA
0095501C 7C80AC28 kernel32.GetProcAddress
00955020 7C9379FD ntdll.RtlReAllocateHeap
00955024 7C809A81 kernel32.VirtualAlloc
00955028 7C9305D4 ntdll.RtlAllocateHeap
0095502C 7C81E82A kernel32.GetOEMCP
00955030 7C809943 kernel32.GetACP
00955034 7C812BE6 kernel32.GetCPInfo
00955038 7C80B529 kernel32.GetModuleHandleA
0095503C 7C801EEE kernel32.GetStartupInfoA
00955040 7C812C8D kernel32.GetCommandLineA
00955044 7C8114AB kernel32.GetVersion
00955048 00B600D8 //这个没找出,这个函数的特殊标志是40
0095504C 7C801E16 kernel32.TerminateProcess
00955050 7C80E00D kernel32.GetCurrentProcess
00955054 7C862B8A kernel32.UnhandledExceptionFilter
00955058 7C80B357 kernel32.GetModuleFileNameA
0095505C 7C81DC3F kernel32.FreeEnvironmentStringsA
00955060 7C81485F kernel32.FreeEnvironmentStringsW
00955064 7C80A0C7 kernel32.WideCharToMultiByte
00955068 7C81CC23 kernel32.GetEnvironmentStringsA
0095506C 7C812C78 kernel32.GetEnvironmentStringsW
00955070 7C80C6CF kernel32.SetHandleCount
00955074 7C812CA9 kernel32.GetStdHandle
00955078 7C811069 kernel32.GetFileType
0095507C 7C81486A kernel32.GetEnvironmentVariableA
00955080 7C812851 kernel32.GetVersionExA
00955084 7C811110 kernel32.HeapDestroy
00955088 7C812929 kernel32.HeapCreate
0095508C 7C809B14 kernel32.VirtualFree
00955090 7C93043D ntdll.RtlFreeHeap
00955094 7C957A40 ntdll.RtlUnwind
00955098 7C810F9F kernel32.WriteFile
0095509C 7C80A480 kernel32.GetStringTypeW
009550A0 00B601E0
009550A4 77D1A8AD USER32.wsprintfA
009550A8 77D6AC1E USER32.GetDlgItemTextA
009550AC 77D1BE71 USER32.EnableWindow
009550B0 77D1DA60 USER32.SetFocus
009550B4 77D31F4C USER32.MessageBeep
009550B8 77D21324 USER32.LoadIconA
009550BC 77D3C2BF USER32.SendDlgItemMessageA
009550C0 77D1DAEA USER32.DestroyWindow
009550C4 77D24816 USER32.GetDlgItem
009550C8 77D2F39A USER32.SendMessageA
009550CC 77D26250 USER32.EndDialog
009550D0 77D3B11C USER32.DialogBoxParamA

最后还有一个函数没获得:00955048 00B600D8
因此手工跟。在00B600D8新建EIP:
00B600D8 60 pushad
00B600D9 66:BF A15B mov di, 5BA1
00B600DD B2 80 mov dl, 80
00B600DF - E9 C351D9FF jmp 008F52A7

……
008F53D2 83F8 40 cmp eax, 40
008F53D5 90 nop
008F53D6 90 nop
008F53D7 90 nop
008F53D8 0F84 AB010000 je 008F5589

008F57E9 6A 00 push 0
008F57EB 6A 45 push 45
008F57ED 6A 00 push 0
008F57EF 68 CC971025 push 251097CC
008F57F4 FF37 push dword ptr [edi]
008F57F6 FF53 54 call [ebx+54]//经过这个CALL,获得EAX 7C81CAA2 kernel32.ExitProcess

执行到008F747B 33C0 xor eax, eax 这句后,就可不执行了,接下来运行ImportREC修复输入表。

将IAT的地址RVA:555000,大小D4填进,Get Imports后就能得到正确的输入表。
Obsidium这种重定位的壳,ImportREC计算是按脱壳前文件的基址,例如这个实例脱壳前的基址是00400000,IAT的VA地址是955000,则ImportREC里的RVA填955000-400000=555000。

如果外壳重定位的基址低于外壳基址,计算方法还是一样的。例假设这个实例重定位后的映像基址是3F0000,则IAT的VA为3F5000,ImportREC里的RVA填3F5000-00400000=FFFF5000(多余的F舍去)。
这样ImportREC就能工作了,但最后Fix Dump会出错,提示“Invalid dump file!Can't match RVA to Offset in the dump file”

解决办法很简单,此时点击Save Tree,将IAT保存到文件里,打开这个文本文件:
FThunk: FFFF5000    NbFunc: 00000028
1    FFFF5000    kernel32.dll    03AD    lstrcpy
1    FFFF5004    kernel32.dll    03A7    lstrcmp
1    FFFF5008    kernel32.dll    01B0    GetStringTypeA
1    FFFF500C    kernel32.dll    0235    LCMapStringW

将FFFF用0000全部替换:
FThunk: 00005000    NbFunc: 00000028
1    00005000    kernel32.dll    03AD    lstrcpy
1    00005004    kernel32.dll    03A7    lstrcmp
1    00005008    kernel32.dll    01B0    GetStringTypeA

最后ImportREC打开进程,点击Load Tree,这次Fix Dump就能成功修复文件了。
注:也可尝试在Options里将“Use PE Header From Disk”默认的选项去除,直接用内存中的基址进行计算。
kanxue
看雪技术论坛 www.pediy.com
2005.12.18
2005-12-18 08:34
0
雪    币: 2384
活跃值: (766)
能力值: (RANK:410 )
在线值:
发帖
回帖
粉丝
3
学习。
2005-12-18 08:40
0
雪    币: 389
活跃值: (912)
能力值: ( LV9,RANK:770 )
在线值:
发帖
回帖
粉丝
4
向老大学习。
2005-12-18 09:01
0
雪    币: 245
活跃值: (195)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
5
好-十分?蓖....??~
2005-12-18 09:15
0
雪    币: 50161
活跃值: (20645)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
6
最初由 小虾 发布
学习。

最初由 kyc 发布
向老大学习。

最初由 syscom 发布
-十分?蓖....


呵~各位早上好。
没什么技术含量,所以只好详细些,让文章好看些。
2005-12-18 09:19
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
老大辛苦啦!!!顺祝坛子里的大虾,中虾,小虾,还有偶等虾米新年快乐!!!
2005-12-18 09:28
0
雪    币: 303
活跃值: (476)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
好详细,多谢
2005-12-18 09:49
0
雪    币: 442
活跃值: (1241)
能力值: ( LV12,RANK:1130 )
在线值:
发帖
回帖
粉丝
9
good good study ,Day Day up
2005-12-18 09:52
0
雪    币: 211
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
ding!!!!!!!!!!!!!!!!!!!!!!!!!
2005-12-18 10:11
0
雪    币: 440
活跃值: (827)
能力值: ( LV9,RANK:690 )
在线值:
发帖
回帖
粉丝
11
学习!!!
2005-12-18 10:17
0
雪    币: 298
活跃值: (445)
能力值: ( LV12,RANK:450 )
在线值:
发帖
回帖
粉丝
12
老大出手了,学习!!
2005-12-18 10:22
0
雪    币: 494
活跃值: (629)
能力值: ( LV9,RANK:1210 )
在线值:
发帖
回帖
粉丝
13
把道理说得如此清楚,真是难得的好教程!
2005-12-18 10:28
0
雪    币: 398
活跃值: (343)
能力值: (RANK:650 )
在线值:
发帖
回帖
粉丝
14
好长啊...
2005-12-18 10:28
0
雪    币: 61
活跃值: (160)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
15
I support you,kanxue!
2005-12-18 10:52
0
雪    币: 603
活跃值: (617)
能力值: ( LV12,RANK:660 )
在线值:
发帖
回帖
粉丝
16
顶~
2005-12-18 11:10
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
好详细啊,老大出手果然不同```严重学习中。。。
2005-12-18 11:54
0
雪    币: 898
活跃值: (4039)
能力值: ( LV9,RANK:3410 )
在线值:
发帖
回帖
粉丝
18
学习
2005-12-18 12:05
0
雪    币: 214
活跃值: (15)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
19
最初由 kyc 发布
向老大学习。
2005-12-18 15:08
0
雪    币: 226
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
20

来龙去脉,一清二楚!不亏是坛主。
如果论坛文章均能如此,则我等菜鸟定能早日脱菜!
学习,学习,再学习!
2005-12-18 15:14
0
雪    币: 3841
活跃值: (4412)
能力值: (RANK:215 )
在线值:
发帖
回帖
粉丝
21
近水楼台先得月,一大群牛在一起,东西交流得就多了,学习不了,郁闷ing 。
2005-12-18 16:54
0
雪    币: 234
活跃值: (370)
能力值: ( LV9,RANK:530 )
在线值:
发帖
回帖
粉丝
22
学习:)
2005-12-18 20:09
0
雪    币: 229
活跃值: (70)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
23
又重新看了一遍,实在是好啊!!老大出手就是不凡。
2005-12-19 00:26
0
雪    币: 217
活跃值: (91)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
24
详细,学习。
2005-12-19 07:58
0
雪    币: 690
活跃值: (1841)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
25
2005-12-19 08:09
0
游客
登录 | 注册 方可回帖
返回
//