《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结

  在学习《加密与解密》脱壳部分时,做了一次关于ASProtect的脱壳实验,但按照书上的步骤会报保护错误(Protect error)。为了明白原因,我 继续往下深入。除了分析结果,以下还记录了关于脱壳学习的小结,方便以后自己和坛友查看。

  •       寻找OEP
  •       Dump
  •       解除 “Emulate standard system functions”功能
  •       通过脚本解除 “Emulate standard system functions”功能

  1.寻找OEP
    关于ASProtect壳,找到原始入口点OEP(original entry point)有几种方法,坛论上也有很多相关的帖子,比如安于此生 翻译的 “使用OllyDbg从零开始Cracking 第五十一章-ASProtect v2.3.04.26脱壳-Part1  ” ,其中介绍了两种方法,一是Ollybone 插件;二是最后一次异常法。Ollybone这款插件很方便,在 401000 代码段使用break-on-execute断点,即可到达OEP。不过 ASProtect的外壳的起始地址也是401000,所以需要按F7,单步跟踪来到外壳的另一个段,再使用执行断点。注意Ollybone很久没 更新了,因此不支持现在的Windows高版本,比如Win8。在《加密与解密》一书中,也介绍了两种方法,一是使用VolX脚本,二是 分析外壳代码。在第二方法中,作者分析了外壳代码,来到了处理“Emulate standard system functions”处(之后会说到), 当外壳执行完“模拟系统函数  ”  的功能后,只需对401000代码段设内存断点,再点击F9,即可来到OEP。

    注:书中所述的方法需要在“Options->Debugging options->exceptions”中勾选“Memory access violation”,即忽略 内存访问异常断点。

  2.Dump
    在ASProtect这个例子中,加壳选项只设置了“Emulate standard system functions”(以下简称模拟系统函数)。如果直接到达OEP,那么Dump下来的程序是无法运行的。因为Dump的数据只包括IMAGE_SECTION_HEADER所指定的,即只有代码段、数据段等,没有外壳代码部分。(这部分外壳代码的地址是通过VirtualAlloc分配的,因此不会被Dump下来,所以当Dump下来的程序执行时,会因找不到外壳代码而出错。)接下来到正题了。

  3.解除 “Emulate standard system functions”功能
    为能够让大家也尝试一次,在讲述正题前,先说一下快速来到“模拟系统函数”代码的方法。首先用OD打开附件中的“TraceMe_Emulate standard system functions.exe”,点击一次F9,来到401000代码段。
    
     《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结_第1张图片
    
    对401000设内存写断点,点击F9,来到4074C3处。

     《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结_第2张图片

    连续点击F8数次,跳过rep指令,再点击F9,来到2BBCDE处。(这里不取消内存写断点,一直按住F8跳过rep要快点,也方便之后按F9再次断在401000段。)

     《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结_第3张图片

    按Ctrl+G,输入2DBA97,并对2DBA97设内存访问断点,点击F9,来到“模拟系统函数 ” 代码处。(由于2B0000这片空间是动态分配的,所以大家的OD可能不是2B0000,根据自己的地址来,为定位模拟系统函数的代码,计算地址:address = 0x2B0000 + 0x20000 + 0x0BA97 = 0x2DBA97。按Alt+M,查看内存窗口,可看到从2B0000开始的空间大小是0x00043000,这片地址会执行外壳的很多操作,包括模拟系统函数。)

     《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结_第4张图片

    好了,定位到目标地址处,现在来说正题。“模拟系统函数”的功能是复制系统函数的一小段代码到外壳代码中,并修改原程序 call 调用的目的地址,使代码重定位到外壳代码处。在执行几句代码后,便跳回到系统函数的内部代码继续执行(通过push retn)。书中介绍说为了解除模拟系统函数的功能,可执行一段脚本,并在分配的空间中写入一段代码,修改call所调用的地址。当在2DBA97设置硬件断点,脚本就会执行,直到修复完所有原程序 call 调用的目的地址 。(分配空间使用了HideOD插件的Alloc Memory,脚本和写入的代码在附件raw.zip的code.txt中)

外壳修改原程序的调用地址:

002DBAB5    2BC5            sub     eax, ebp
002DBAB7    83E8 05         sub     eax, 5
002DBABA    45              inc     ebp                    //外壳修改后的代码是call 0x????????,因此ebp是加1(第一个字节为 EB ),而在新写入的代码中是加2(前两个字节是 FF15)
002DBABB    8945 00         mov     dword ptr [ebp], eax   //此处的eax为外壳代码的地址,如0x1F0004,0x1F0000是VirtualAlloc给外壳分配的。
    
脚本部分:

LABEL:
  cmp eip,2DBA97 //0x2DBA97设硬件断点,使脚本在 jmp LABEL 后能循序执行
  jne END
  mov eip,470000  //用HideOD新分配的空间
  run
  jmp LABEL

END:
  pause
  
在新分配空间写入的代码部分:
00470000    60              pushad
00470001    66:C745 00 FF15 mov     word ptr [ebp], 15FF  //call dword ptr [0x????????] 机器码的前两个字节
00470007    BE 00404000     mov     esi, 404000                  //IAT起始地址
0047000C    3906            cmp     dword ptr [esi], eax            //在 0x2DBA97 处eax的值为即将修改的系统函数地址
0047000E    74 13           je      short 00470023
00470010    83C6 04         add     esi, 4
00470013    81FE D0404000   cmp     esi, 4040D0
00470019  ^ 72 F1           jb      short 0047000C
0047001B    FF05 50004700   inc     dword ptr [470050]      //检查是否在IAT中,根据实验,这行代码不会被执行,只是方便之后错误的排查
00470021    EB 03           jmp     short 00470026
00470023    8975 02         mov     dword ptr [ebp+2], esi      //call dword ptr [0x????????] 机器码的后四字节
00470026    61              popad
00470027  - E9 8DBAE8FF     jmp     002DBABE

    从以上代码可看到, 此过程跳过了2DBA97到2DBABE的代码。当eip为 0x2DBA97,执行脚本。运行中却弹出了“Protect Error”对话框。

    《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结_第5张图片

    经过分析,发现因为没有执行这两个函数,导致在2DADEC函数中检验时会出错。

    002DBAA3    E8 CCB6FFFF     call    002D7174  //这两个函数实现“模拟系统函数”
    002DBAB0    E8 EF010000     call    002DBCA4  //

    002DBA81    E8 66F3FFFF     call    002DADEC  //这个函数负责检查在执行“模拟系统函数”期间,是否有什么异常发生,比如INT3断点等,如果发现则报错

    可推测函数 0x002D7174 和 0x002DBCA4 在内部会动态更新某些参数,而 0x002DADEC 会检查这些参数,每当执行一次,这些参数就会改变一次,这些参数很可能保存在栈中。往回看 0x2DBA97 位置处,可发现外壳在每修改一个函数地址时,[esp+4]和[esp+8]都会动态更新,并作为参数传递给 0x2DADEC 函数以供检查,这也增加了以上推测的可能性。

     《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结_第6张图片

    这里我还是使用书中所述的方法,不过要修改脚本和写入的代码。基本思路是让外壳正常执行函数 0x002D7174 和 0x002DBCA4 ,只是不让它修改函数调用的地址。在 0x2DBA97 设置一个硬件断点,当执行脚本时跳到写入的代码,保存将被修改的函数的真正地址(该地址事先保存在eax中)。然后在 0x2DBABA设一个硬件断点,运行脚本修正原程序的调用地址。这和书上说的方法只有一点不同,即让外壳执行了函数 002D7174 和 002DBCA4。

修改后的脚本部分:

LABEL:
  cmp eip,2DBA97
  jne NEXT
  mov eip,470060
  run
  jmp LABEL
NEXT:
  cmp eip,2DBABA
  jne END
  mov eip,470000
  run
  jmp LABEL
END:
  pause

修改后的代码部分:

00470000    60              pushad
00470001    66:C745 00 FF15 mov     word ptr [ebp], 15FF
00470007    A1 70004700     mov     eax, dword ptr [470070]    //执行 0x470060 时保存的系统函数地址
0047000C    BE 00404000     mov     esi, 404000
00470011    3906            cmp     dword ptr [esi], eax
00470013    74 13           je      short 00470028
00470015    83C6 04         add     esi, 4
00470018    81FE D0404000   cmp     esi, 4040D0
0047001E  ^ 72 F1           jb      short 00470011
00470020    FF05 50004700   inc     dword ptr [470050]
00470026    EB 03           jmp     short 0047002B
00470028    8975 02         mov     dword ptr [ebp+2], esi
0047002B    61              popad
0047002C  - E9 8DBAE8FF     jmp     002DBABE

00470060    60              pushad                              //新添加的代码
00470061    A3 70004700     mov     dword ptr [470070], eax
00470066    61              popad
00470067    52              push    edx
00470068  - E9 2BBAE8FF     jmp     002DBA98
    
    结果本应美好,但又出现了问题。第一次来到2DBA97后,设置两个硬件断点,通过HideOD分配空间,并在分配空间中写入代码,然后使用脚本让其运行。当一气呵成地点击“Plugins->ODbgScript->运行脚本”后,结果弹出了如下对话框。

     《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结_第7张图片

    之前分析一个加了ASProtect壳(ASProtect 1.2x - 1.3x [Registered] -> Alexey Solodovnikov)的软件时,还没分析出OEP就会报这个错,无论想从什么地方跳转到OEP,都会弹出这个对话框。似乎把加壳的软件调成兼容模式“Windows XP(Service Pack 2)”,就不会弹出以上对话框,但我尝试之后还是不行。如果有知道的坛友,希望能指点指点。

    回到刚才报错的对话框。为了知道为什么会报这个错,我在新分配的空间中将写入代码的第一行(0x470060)设内存断点。当eip为 0x2DBA97 时,执行脚本,单步跟踪,当跳回 0x2DBA98 时,没有报错。那很可能是在第二个硬件断点处执行脚本时报错的,于是我重复之前的操作,在 0x470000 处设内存断点,当eip为 0x2DBABA 时,执行脚本,单步跟踪。当跳回 0x2DBABE 时,也没有报错。之后我试了几次,结果也是一样。因此总结当外壳修改第一个地址时,如果像刚才一样单步跟踪,就不会报错。好的,那么第一次就单步跟踪来修改原程序函数所调用的地址,之后当回到 0x2DBA97,准备修改原程序 第二个 函数所调用的地址时,执行脚本,让程序运行起来。

    看着OD的代码界面一闪一闪的,从外壳跳到写入代码,再跳回外壳部分,一次一次顺利地进行着,然而当 dword ptr [esi]为2时,即还剩两个地址需要修改,这时又弹出了一个错误,无法从 0x40?????? 读取数据,eip也跑飞了。不知道问题出在何处,打算继续单步跟踪。查看在新分配空间写入的代码,在 0x470070 处保存了系统函数的地址,该地址为 0x????6BB0 = k + 0x10000, k为Kernel32.dll的代码段起始地址,函数名为GetStringTypeExA。这说明是在修改GetStringTypeExA的地址时报“读错”的。因此跟之前一样,单步跟踪完第一次地址修改,可在 0x2DBA90 处加上条件断点 eax == 0x????6BB0,其中eax保存了即将修改的系统函数地址。这样当要修改该函数地址时,能断在 0x2DBA90 处。当设好条件断点后,执行脚本,程序运行起来,最后断在了 0x2DBA90 处。之后分别在 0x2DBA97 和 0x2DBABA 处执行脚本,单步跟踪。结果是什么也没发生,一切正常。和“Workstation, No servicePack”相似,手动单步跟踪就没问题了。最后,再次执行脚本修改完所有系统函数的地址后,对401000代码段设内存访问断点,点击F9,便可跳到OEP处,这时可看到函数都被修改为真正的系统函数地址。此处把文件Dump下来,使用ImportREC修复导入表后,脱壳就结束了。

《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结_第8张图片

    PS:关于“Workstation, No servicePack”错误 和 0x40?????? 读取错误,在我之后整理笔记,进行实验时,发现有些时候并不会报这两个错误,这可能和测试环境有关吧。所以如果没有遇到这两个错误,自然是好。如果遇到了,就手动单步跟踪,跳过这些节点即可。 

    4. 通过脚本解除“Emulate standard system functions”功能 
     以上是分析原代码来修正“模拟系统函数”,当然也可以直接来到OEP,然后Dump下来,之后通过写脚本的方式来修正这些函数调用,这里说下我的思路,如果以后有时间,可能会把脚本写出来。回到加壳的软件,重新加载,直接来到OEP,在代码区域右键“Search for->All intermodular calls”,可看到很多类似call 2010004, call 2030004这样的调用。

《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结_第9张图片

    可以发现,每个被外壳修改后的函数调用都是 call ??????04 这样的形式,因此只要修复这些函数即可。随便跳入一个函数中,比如 0x2010004,代码如下。

02010004    FF0424          inc     dword ptr [esp]            //将返回地址加1
02010007    68 00000002     push    2000000                    //这里有可能直接是jmp指令,如 0x1EB0004
0201000C    C3              retn                               //跳到 0x2000000处

01EB0004    FF0424          inc     dword ptr [esp]
01EB0007  - E9 F4FFFEFF     jmp     01EA0000


    通过以上代码可知,call 2010004 后的一个字节是垃圾数据,这也是之前修改函数为 call dword ptr []的原因,因为这种函数调用占6个字节,刚好把一字节的垃圾数据覆盖了。我们再来到 0x2000000 处。

02000000    8BFF            mov     edi, edi
02000002    55              push    ebp
02000003    8BEC            mov     ebp, esp
02000005    64:A1 30000000  mov     eax, dword ptr fs:[30]
0200000B    83EC 18         sub     esp, 18
0200000E    53              push    ebx
0200000F    8B58 10         mov     ebx, dword ptr [eax+10]
02000012    56              push    esi
02000013    8B35 9C07E275   mov     esi, dword ptr [75E2079C]
02000019    85F6            test    esi, esi                //以上的代码是系统函数的开头部分,即外壳复制的一小段代码
0200001B    68 FB8FD375     push    75D38FFB
02000020    C3              retn                            //此处返回系统函数内部

    类似 0x2010004、0x2000000组合,其他外壳修改的部分都一样。接下来是脚本的思路。

    1.检查每个函数调用,如果跳转的地址 A 最后一个字节是 0x04,则执行第二步,否则继续检查下一个函数。
    2.读取[A],若前三字节为0xFF0424(inc dword ptr [esp]),则A很可能是需要修正的地址,执行第三步,否则回到第一步。
    3.判断 byte ptr [A+3],如果是68(push),则读取后四字节放入地址B;如果是E9(jmp),则需要将jmp后一条指令地址加上E9的后四字节,结果放入地址B。
    4.循环判断 ([B]==0x68 && [B+4]==0xC3 ),如果不成立,B自加1,如果成立,则 dword ptr [B+1]是将要跳转的地址,将该地址放入地址C;如果找不到,则回到第一步,继续寻找。(因为有一些特例不需要修改 。)
    5.计算 (C - X) ,将计算结果放入地址D,其中X为B的第四个字节。在 0x2000000 的例子中,0x75D38FFB - 0x1B 为 0x75D38FE0,该地址为对应系统函数的首地址。
    6.让D与IAT中的每一项进行比较,如果找到,则对应项的地址就是我们想要的地址,将其放入地址E;如果找不到......。根据之前HideOD新分配空间的写入代码,可知不会有找不到的函数,如果真存在,那就只有手动查找了。
    7.结合FF15,将E放入 call dword ptr [E]中,之后回到第一步,继续修正地址。

    PS:在第五步中,有可能出现以下的情况。

01F60000    8BFF            mov     edi, edi
01F60002    55              push    ebp
01F60003    8BEC            mov     ebp, esp
01F60005    5D              pop     ebp
01F60006    8BFF            mov     edi, edi
01F60008    55              push    ebp
01F60009    8BEC            mov     ebp, esp
01F6000B    83EC 14         sub     esp, 14
01F6000E    A1 683B0476     mov     eax, dword ptr [76043B68]
01F60013    33C5            xor     eax, ebp
01F60015    8945 FC         mov     dword ptr [ebp-4], eax
01F60018    837D 08 00      cmp     dword ptr [ebp+8], 0
01F6001C    68 4630F975     push    75F93046
01F60021    C3              retn


    以上代码中有一次多余的mov push mov,这会导致第五步计算的地址出错。在这种情况下,就需要在第六步的开头添加一步,判断word ptr [D]是否是0x90(nop ),如果是,则需要执行一个循环:

while(byte ptr [D-1] != 0x90){
    D--;
}

    这样,计算后的D则为系统函数的地址。

你可能感兴趣的:(《加密与解密》ASProtect 2.1x SKE 脱壳过程中遇到的问题与解决方法及脱壳小结)