在学习《加密与解密》脱壳部分时,做了一次关于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代码段。
对401000设内存写断点,点击F9,来到4074C3处。
连续点击F8数次,跳过rep指令,再点击F9,来到2BBCDE处。(这里不取消内存写断点,一直按住F8跳过rep要快点,也方便之后按F9再次断在401000段。)
按Ctrl+G,输入2DBA97,并对2DBA97设内存访问断点,点击F9,来到“模拟系统函数 ” 代码处。(由于2B0000这片空间是动态分配的,所以大家的OD可能不是2B0000,根据自己的地址来,为定位模拟系统函数的代码,计算地址:address = 0x2B0000 + 0x20000 + 0x0BA97 = 0x2DBA97。按Alt+M,查看内存窗口,可看到从2B0000开始的空间大小是0x00043000,这片地址会执行外壳的很多操作,包括模拟系统函数。)
好了,定位到目标地址处,现在来说正题。“模拟系统函数”的功能是复制系统函数的一小段代码到外壳代码中,并修改原程序 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 ENDmov eip,470000  //用HideOD新分配的空间runjmp LABELEND: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”对话框。
    
    经过分析,发现因为没有执行这两个函数,导致在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 函数以供检查,这也增加了以上推测的可能性。
这里我还是使用书中所述的方法,不过要修改脚本和写入的代码。基本思路是让外壳正常执行函数 0x002D7174 和 0x002DBCA4 ,只是不让它修改函数调用的地址。在 0x2DBA97 设置一个硬件断点,当执行脚本时跳到写入的代码,保存将被修改的函数的真正地址(该地址事先保存在eax中)。然后在 0x2DBABA设一个硬件断点,运行脚本修正原程序的调用地址。这和书上说的方法只有一点不同,即让外壳执行了函数 002D7174 和 002DBCA4。
修改后的脚本部分:LABEL:cmp eip,2DBA97jne NEXTmov eip,470060runjmp LABEL
NEXT:cmp eip,2DBABAjne ENDmov eip,470000runjmp 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     002DBABE00470060    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壳(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修复导入表后,脱壳就结束了。
PS:关于“Workstation, No servicePack”错误 和 0x40?????? 读取错误,在我之后整理笔记,进行实验时,发现有些时候并不会报这两个错误,这可能和测试环境有关吧。所以如果没有遇到这两个错误,自然是好。如果遇到了,就手动单步跟踪,跳过这些节点即可。 
4. 通过脚本解除“Emulate standard system functions”功能 
以上是分析原代码来修正“模拟系统函数”,当然也可以直接来到OEP,然后Dump下来,之后通过写脚本的方式来修正这些函数调用,这里说下我的思路,如果以后有时间,可能会把脚本写出来。回到加壳的软件,重新加载,直接来到OEP,在代码区域右键“Search for->All intermodular calls”,可看到很多类似call 2010004, call 2030004这样的调用。
可以发现,每个被外壳修改后的函数调用都是 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 脱壳过程中遇到的问题与解决方法及脱壳小结相关推荐

  1. 加密字符 【问题描述】 在情报传递过程中,为了防止情报被截获,往往需要用一定的方式对情报进行加密。简单的加密算法虽然不足以完全避免情报被破译,但仍然能防止情报被轻易识别

    加密字符 [问题描述] 在情报传递过程中,为了防止情报被截获,往往需要用一定的方式对情报进行加密.简单的加密算法虽然不足以完全避免情报被破译,但仍然能防止情报被轻易识别.我们给出一种加密算法,对给定的 ...

  2. 如何修复win无线服务器,win10 无线802 1X认证故障处理 以 升级后网络故障常规解决方法...

    本帖最后由 郭友友 于 2016-7-5 14:05 编辑 [问题现象]当升级Windows10 后无法连接到的 WPA-2 企业网络(尤其是校园网),即使用证书进行服务器端或相互身份的验证 (EAP ...

  3. 探讨.NET Core数据进行3DES加密和解密问题

    前言 一直困扰着我关于数据加密这一块,24号晚上用了接近3个小时去完成一项任务,本以为立马能解决,但是为了保证数据的安全性,我们开始去对数据进行加密,然后接下来3个小时专门去研究加密这一块,然而用着用 ...

  4. Spring Cloud Config 加密和解密

    重要 先决条件:要使用加密和解密功能,您需要在JVM中安装全面的JCE(默认情况下不存在).您可以从Oracle下载"Java加密扩展(JCE)无限强度管理策略文件",并按照安装说 ...

  5. python输出字体的大小_Python密码学编程:文件的加密与解密

    在之前的章节中,编写的程序只能操作较少的信息,这些信息往往是以字符串的形式直接写在代码中的.但本章中的程序可以对整个文件进行加密和解密,文件的大小可以包括成千上万个字符. 本章要点 open()方法. ...

  6. Node之加密与解密处理

    crypto模块概述 在Node.js中,使用OpenSSL类库作为其内部实现加密与解密处理的基础手段,这是因为目前OpenSSL已经成为了一个经过严格测试的可靠的加密与解密算法的实现工具. 在Nod ...

  7. 加密、解密详解及CA的实现

    加密.解密详解及CA的实现 推荐 转载nmshuishui的文章,文章链接 http://blog.51cto.com/nmshuishui/1370917?utm_source=tuicool&am ...

  8. PHP实现RSA与RSA256加密,解密,加签,验签

    1.RSA加密简介 RSA加密是一种非对称加密.可以在不直接传递密钥的情况下,完成解密.这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险.是由一对密钥来进行加解密的过程,分别称为公钥和私 ...

  9. RSA加密、解密、签名、验签的原理及方法

    目录 一.RSA加密简介 二.公钥与私钥的理解 三.RSA加密解密 四.RSA签名和验证 五.加密的作用 六.两种不同的加密与解密 七.RSA加密.签名区别 八.RSA加密.签名的方法,代码例子如下: ...

最新文章

  1. 苹果要垄断?官方证实:T2安全芯片会限制新款Mac电脑第三方
  2. 互动整合营销_初识网络整合营销
  3. OpenGL Overdraw Count透支数的实例
  4. EOJ Monthly 2019.2 (based on February Selection) D.进制转换
  5. LeetCode-28 实现strStr()
  6. php表单密码由加密变明文,PHP 安全性漫谈 Linux+Apache+Mysql+PHP
  7. Linux下的文件共享全攻略系列之二:NFS快速配置教程与安全策略
  8. Vue的单页应用中如何引用单独的样式文件
  9. Shell子程序结构,函数
  10. 如何修复Sketchup经常遇到的错误报告问题
  11. 协方差矩阵-Covariance Matrix
  12. 基于FPGA的PWM加减速控制实现
  13. NOI 2.6 动态规划 6045:开餐馆
  14. PLSQL Developer几个可能的隐患
  15. Logistics回归模型
  16. 二维数组名再取地址是什么
  17. Bert模型做多标签文本分类
  18. 七夕祝福网页制作_七夕的七个冷知识:是情人节还是女儿节?
  19. ITeye,还IT淫一个简洁的首页吧!
  20. mysql社区版与cluster 区别_MySQL版本Enterprise/Community/Cluster有何区别

热门文章

  1. 数据库系统原理名词解释
  2. 虚拟机实现NAT上网问题
  3. web网站java实现QQ第三方登录
  4. 常见的linux、docker、kubectl命令
  5. 洗地机和吸尘器哪个实用,洗地机优点和缺点
  6. 数据结构与算法分析之---部分排序算法的实现
  7. [证券日报]阿里或收购东方证券,全行业颤抖 ?
  8. java.lang.NoSuchMethodError: javax.persistence.OneToMany.orphanRemoval()Z 两种解决方案
  9. 绩效考核中,领导该如何评估员工的投入度?
  10. android 8.0彩蛋插件,谷歌 Android 8.0 小彩蛋:免 Root 换主题