• 标 题:睡不着,帖一个:aspr程序的脱壳修复体会 (8千字)
  • 作 者:Fpc
  • 时 间:2002-8-5 5:02:56
  • 链 接:http://bbs.pediy.com

aspr程序的脱壳修复体会
作者:Fpc/CCG

  工具:softice, fs0's aspr Loader, imprec 1.42, icedump, superbpm, fi2.45
 
  我们的目标是.....呵呵,不要条件反射,是脱壳。Mp3 TagClinic V2.5 Build 7。跟过以前版本的注册码,现在是用了aspr的Key注册方式,注册码是不用想了:( 只能脱壳,暴破。
 
  目标软件可在华军下到,8月1、2号更新的,1.8M。用fi245检查是Asprotect 1.2x的壳,谁知道呢。限制方式是一定的使用次数,好象是20次,注册码验证集成在aspr中。
 
  说说我明白的地方:
  找入口点用fs0写的loader,太牛了~~,dump可用softice + superbpm + icedump或trw2000 + superbpm,接下来是修复 ImportTable,用现成的工具ImpRec 1.42+(Revirgin不熟~) 这一版本的aspr无法全部修复,插件看来需要升级了。还有8个函数需要手工修复(不同机器上的偏移地址不同):

1.  0130E8DC
2.  0130E930
3.  0130E93C
4.  0130E8F8
5.  0130E94C
6.  0130E95C
7.  01321A0C
8.  01321A1C



  下面是修复过程,首先简单看一下aspr对一些函数的优待:

019F:0131253B  PUSH      0131290D
019F:01312540  PUSH      DWORD PTR FS:[EAX]
019F:01312543  MOV      FS:[EAX],ESP
019F:01312546  CALL      0130E70C        <- 特殊函数的处理

>>>>>>>>

019F:0130E70C  PUSHAD
019F:0130E70D  PUSH      01316716
019F:0130E712  PUSH      00
019F:0130E714  PUSH      0130E723
019F:0130E719  MOV      EAX,[013050B2]
019F:0130E71F  JMP      [EAX]
019F:0130E721  JMP      0130E748
019F:0130E723  PUSH      0130E72E
019F:0130E728  JMP      0130E7B2

... ....

<<<<<<<<


019F:0131254B  CALL      0131258A
019F:01312550  PUSH      01312559
019F:01312555  INC      DWORD PTR [ESP]
019F:01312558  RET                                                             
019F:01312559  MOV      ESP,0C24448B
019F:0131255E  JMP      01312561

  上面这个call不长,里面有花指令,它对几个特殊的函数作处理。举例来说,对于向GetVersion这类函数来说,返回值为一个DWORD,是在eax中。aspr在壳中执行GetVersion,将返回的eax放到内存的某个位置;而后程序再调用GetVersion时,由于Import Address Table作了处理,并不是再次执行这个函数,只是简单的从内存中赋值给eax,返回,算是反脱壳的一种方式。对付此种加密方式,对内存保留返回值的地方下bpm,倒追,就可以找到究竟是调用哪一个函数来的。

(1)来看第一个 0130E8DC :

019F:0040700A  RET
019F:0040700B  NOP
019F:0040700C  PUSH      EAX
019F:0040700D  PUSH      00                 <- 参数**
019F:0040700F  CALL      00406F0C            <- 指向 0130E8DC
019F:00407014  MOV      EDX,005D6108
019F:00407019  PUSH      EDX
019F:0040701A  MOV      [005DB4DC],EAX 


call的内容:

019F:0130E8DC  PUSH      EBP
019F:0130E8DD  MOV      EBP,ESP 
019F:0130E8DF  MOV      EAX,[EBP+08]            <- 参数**
019F:0130E8E2  TEST      EAX,EAX
019F:0130E8E4  JNZ      0130E8ED             <- 跟踪可知,eax不为0时,指向dll文件名
019F:0130E8E6  MOV      EAX,[01316624]            <- 是基址:400000,就是程序的Handle
019F:0130E8EB  JMP      0130E8F3

019F:0130E8ED  PUSH      EAX
019F:0130E8EE  CALL      KERNEL32!GetModuleHandleA

019F:0130E8F3  POP      EBP
019F:0130E8F4  RET      0004

[0130E8DC]=GetModuleHandleA

  注意最后多弹出4字节的堆栈,原因是:无论是否执行到 0130E8EE,由于在前面 0040700D 处的参数入栈是多余的,必须要保持平衡。

  根据上面程序看出,第一个函数是?GetModuleHandleA,下面是它的原型:

The GetModuleHandle function returns a module handle for the specified module if the file has been mapped into the address space of the calling process.

HMODULE GetModuleHandle(

    LPCTSTR lpModuleName     // address of module name to return handle for 
  );    

Parameters

lpModuleName

Points to a null-terminated string that names a Win32 module (either a .DLL or .EXE file). If the filename extension is omitted, the default library extension .DLL is appended. The filename string can include a trailing point character (.) to indicate that the module name has no extension. The string does not have to specify a path. The name is compared (case independently) to the names of modules currently mapped into the address space of the calling process.
If this parameter is NULL, GetModuleHandle returns a handle of the file used to create the calling process.


Return Values

If the function succeeds, the return value is a handle to the specified module.
If the function fails, the return value is NULL. To get extended error information, call GetLastError. ?              ?                  ?                                      ?                                                                              ?                                                                                                                            019F:0130E930  CALL      KERNEL32!GetVersion 


(2)函数2、3、4,aspr的诱惑??

019F:0130E930  CALL      KERNEL32!GetVersion        <- do nothing...
019F:0130E935  MOV      EAX,[01316710]         <- 倒追上面的 CALL 0130E70C,可知此处是GetCurrentProcessID
019F:0130E93A  RET 
019F:0130E93B  NOP

[0130E930]=GetCurrentProcessID

019F:0130E93C  PUSH      00
019F:0130E93E  CALL      KERNEL32!GetModuleHandleA    <- do nothing...
019F:0130E943  PUSH      DWORD PTR [01316714]        <- 倒追法,此处是 GetVersion
019F:0130E949  POP      EAX
019F:0130E94A  RET
019F:0130E94B  RET

[0130E93C]=GetVersion

同样的方法可得到:
[0130E8F8]=GetVersion

(3)函数5、6,什么都不作:
 
019F:0130E94C  PUSH      EBP
019F:0130E94D  MOV      EBP,ESP
019F:0130E94F  CALL      KERNEL32!GetCurrentProcess
019F:0130E954  MOV      EAX,[EBP+08]
019F:0130E957  POP      EBP                                                   
019F:0130E958  RET      0004

简化为:
mov eax, dword ptr [esp+4]
ret 04
整个函数相当于弹出压栈的参数到eax,即pop eax
解决办法:在 ImpRec 中修复时选一个特殊一点的函数,比如 MakeCriticalSectionGlobal 之类的。修复完成后用 wdasm 反汇编,将对这个函数的调用改为 pop eax 和几个 nop 。

[0130E94C]=pop eax

下面这个也一样:
                                                 
019F:0130E95C  PUSH      EBP                                                   
019F:0130E95D  MOV      EBP,ESP                                               
019F:0130E95F  CALL      KERNEL32!GetVersion                                   
019F:0130E964  POP      EBP                                                   
019F:0130E965  RET      0004

简化为:
call GetVersion
ret 04

注意它与GetVersion并不等价,原因在于 ret 04 != ret。跟踪对此处的调用发现,返回的 eax 值立刻被 mov eax, esi 覆盖掉,所以这个也同样处理为 pop eax + Nop

[0130E95C]=pop eax

(4)函数7、8,aspr的乾坤挪移??

我是第一次见到这种方式~~~先执行函数的几条指令,然后跳进函数体中继续~:

019F:01321A0C  PUSH      EBP 
019F:01321A0D  MOV      EBP,ESP
019F:01321A0F  PUSH      EBX
019F:01321A10  JMP      BFF9369            <- 跳走

跟下去会发现是到了这里:

KERNEL32!MulDiv
019F:BFF9368F  PUSH      EBP 
019F:BFF93690  MOV      EBP,ESP                                               
019F:BFF93692  PUSH      EBX
                                                 
019F:BFF93693  MOV      EBX,[EBP+10]            <- 在这里继续                                         
019F:BFF93696  MOV      ECX,EBX                                               
019F:BFF93698  OR        EBX,EBX                                               
019F:BFF9369A  JNS      BFF9369E                                             
019F:BFF9369C  NEG      EBX                                                   
019F:BFF9369E  MOV      EAX,[EBP+08]                                         
019F:BFF936A1  XOR      ECX,EAX                                               
019F:BFF936A3  OR        EAX,EAX                                               
019F:BFF936A5  JNS      BFF936A9                                             
019F:BFF936A7  NEG      EAX     
019F:BFF936A9  MOV      EDX,[EBP+0C]

...

[01321A0C]=MulDiv

同样:

019F:01321A1C  PUSH      EBP 
019F:01321A1D  MOV      EBP,ESP
019F:01321A1F  PUSH      EBX                                                   
019F:01321A20  JMP      BFF93693

[01321A1C]=MulDiv

  这种方式修复起来没有任何难度,而且也降低了加壳后程序运行的兼容性,是作者作出的一种尝试。

  上述修复完毕,脱后的文件变成未注册版,可正常运行,没有次数限制,但每次有NAG,软件也不想用,懒得跟下去。谁知道使用次数是存在哪的,半天也找不到:(
 
  希望能对大家水平提高有所帮助:)

编者注:此壳可以用Import REConstructor1.42+修复IAT。本文提供了如用Import REConstructor1.42+不能发现正确的函数时,手动修复的方法。