第五十三章-TPPpack脱壳 上一章中最后留的那个小比赛最后的获胜者是Ularteck童鞋。下面我们就用Ularteck童鞋编写的第一个脚本来定位OEP以及修复stolen bytes。脚本如下: /*################################ CracksLatinoS - 2006 作者: Ulaterck. 描述: 该脚本的功能的定位TPPpack的OEP以及修复其stolen bytes 目标程序: UnPackMe_TPPpack.exe 配置要求: ODBGScript 1.48 , HideDebugger 1.24 , HideOD, 停在入口点处,忽略Kernel32的异常,其他异常均不忽略. 因为我们这里要用到最后一次异常法,所以在执行该脚本之前首先要知道最后一次异常的地址,然后再执行该脚 本 以下关于该脚本的详细注释 ################################*/ var dir_excep var Newoep var dir_JMP var dir_CALL var oep var StartScan var Opcodes var temp var temp2 var temp3 Data: mov Newoep, eip // 将入口点保存到变量Newoep中 ask "最后一次异常的地址是多少?" // 弹出一个对话框让用户输入最后一次异常的地址 cmp $RESULT,0 // 判断用户是否输入了地址 je warning // 如果用户没有输入地址则跳转到warning标签处 mov dir_excep, $RESULT // 将用户输入的地址保存到变量dir_excep中 jmp Initiation // 跳转到Initiation标签处 warning: msg "请重新执行该脚本,再次输入一个有效的地址!" jmp final Initiation: run // 运行起来 eoe check // 如果发生异常断了下来,就跳转到check标签处 check: cmp eip,dir_excep // 判断断下来的地方是不是最后一次异常处 je last // 断下来的地方刚好是最后一次异常处,则跳转到last标签处 esto // 忽略掉异常继续执行,相当于在OD中按SHIFT+F9 jmp Initiation: // 跳转Initiation标签处继续定位最后一次异常处 last: findop eip,#FFE0# // 从最后一次异常处开始搜索JMP EAX指令,以便下面定位stolen bytes mov dir_JMP,$RESULT // 将JMP EAX指令的地址保存到变量dir_JMP中 bp dir_JMP // 对JMP EAX指令设置断点 esto // 忽略掉异常继续执行,相当于在OD中按了SHIFT+F9 bc dir_JMP // 删除掉JMP EAX指令处的断点 sti // 单步步入,相当于在OD中按F7,单步以后就到了stolen bytes处 mov oep,eip // 将stolen bytes的起始地址保存到变量oep中 mov StartScan,eip // 将stolen bytes的起始地址保存到变量StartScan中 LookForCall: // 开始搜索Stolen bytes中需要修正偏移量的CALL findop StartScan,#E8# // 搜索以机器码E8开头的CALL指令,即待修正偏移量的CALL指令 cmp $RESULT, 0 // 判断是否搜索到了待修正偏移量的CALL指令 je final // 没有搜索到的话,则跳转到final标签处 mov dir_CALL, $RESULT // 将待修正偏移量CALL的地址保存到变量dir_CALL中 mov StartScan, $RESULT // 将待修正偏移量的CALL指令的地址赋值给变量StartScan add dir_CALL,1 // 指向偏移量 mov Opcodes, [dir_CALL] // 获取待修正的偏移量并保存到变量Opcodes中 add Opcodes,StartScan // 将偏移量加上CALL指令所在的地址 add Opcodes,5 // 加上CALL指令的长度 // 这样就得到了CALL指令的目标地址 //修正CALL指令的偏移量 mov temp, StartScan // 将CALL指令的地址保存到临时变量temp中 sub temp, oep // 计算CALL指令距离stolen bytes起始地址的长度,并保存到临时变量temp中 mov temp2, Newoep // 将入口点的值保存到临时变量temp2中 add temp,temp2 // 计算CALL指令新的地址,并保存到变量temp中 sub Opcodes, temp // 将目标地址减去CALL指令新的地址 sub Opcodes, 5 // 然后减去5,就得到了CALL指令修正后的偏移量 edit: // 将CALL指令的偏移量修正 mov temp3, StartScan // 将CALL指令所在的地址保存到临时变量temp3中 add temp3,1 // 指向待修正的偏移量 mov [temp3], Opcodes // 修正偏移量 jmp LookForCall final: ret ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- 附脚本的截图: 下面我来给大家详细讲解这个脚本的作用。 Part1:定位OEP并修复stolen bytes(by Ularteck)。 详情: 首先我们需要配置一下反反调试插件。 OD加载目标程序以后直接运行起来,可以看到完美运行。 下面我们利用最后一次异常法来定位OEP,对于最后一次异常法大家应该很熟练了吧。我们经常会用到它。此法同样适用于ASProtect 2.1 SKE,2.2 SKE,2.3SKE以及带VM的版本。 这里我们先将所有忽略的异常选项都勾选上。 接着将程序运行起来,然后打开日志窗口,看看最后一次异常发生指令所在的地址是哪里。 我这里最后一次异常指令所在的地址为0046D36B,下面我们就可以利用脚本来定位OEP。 在使用脚本之前,我先演示一下如何手工定位OEP。 我们重启OD。 接着将忽略的异常选项的对勾都去掉。 接着直接按F9键运行起来。如果断在了不是0046D36B的异常处的话,就直接按SHIFT+F9忽略掉异常继续执行。 这里我们就断在了最后一次异常处。接下来按ALT+M打开区段列表窗口。 对代码段设置内存访问断点。 按SHIFT+F9忽略掉异常运行起来,断在了这里。 如果我们观察一下堆栈的话就会发现这里并不是真正的OEP,明显存在stolen bytes。 我们可以看到之前已经执行过stolen bytes了。返回地址为8B0EA4,该地址属于起始地址为8B0000的区段。 好,下面我们重启OD。 我们现在将脚本修改一下,让其自动定位到最后一次异常处。 将脚本修改成如下: 好,修改完毕以后。 我们重启OD以后,执行该脚本。 这里弹出了一个对话框要求我们输入最后一次异常指令所在的地址,这里我输入46D36B。 单击OK。 好了,我们可以看到脚本执行完毕了,我们可以看到刚好断在了最后一次异常处。 我们按ALT+M打开区段列表窗口。 接下来我们并不是跟刚才一样对代码段设置内存访问断点,这次我们对起始地址为8B0000的区段设置内存访问断点。 按SHIFT+F9忽略掉运行起来,断在了stolen bytes处。 如果我们按减号键可以看到回到了最后一次异常指令处。 以下脚本是根据Martian先生在他的教程中介绍的定位stolen bytes的思路编写的,定位stolen bytes的思路如下:首先定位到最后一次异常处,接着往下搜索机器码为FFE0的JMP EAX指令,搜到该指令以后,对其设置断点,接着运行起来,断到了JMP EAX处,然后按F7键单步一下,就可以到达stolen bytes处了。 我们在脚本中添加一个变量。 变量dir_JMP用于保存JMP EAX指令的地址。 接下来在last标签处添加以下内容: 我们执行该脚本看看效果。 我们可以看到成功定位到了stolen bytes处。下面我们要做的就是将stolen bytes拷贝到入口点处。 这里我们从8B0E48开始拷贝,一直到8B0EA4为止,注意是二进制复制。 粘贴到入口点处。 这里我们可以看到46B067处的这个CALL是一个间接CALL。 这里原stolen bytes应该是CALL 004293A0,目标地址是004293A0。 但是由于这是一个间接CALL,所以我们这里直接将其二进制复制到别的地方的话,目标地址就变了。 所以这个CALL被复制到别处的话,首先需要修正偏移量,我们来看看如何修正偏移量,首先我们在数据窗口中定位到该指令。 这里我们不用考虑前面的操作码,直接看后面的4个字节的偏移量。 FF B7 84 FC,为了下面列公式方便,这里我们将其命名为OPCODES。 这里我们将008B0E9F,即这个CALL指令所在的地址命名为DIR_CALL。 我们来算一下目标地址004293A0是如何得到的: 目标地址 = OPCODES + DIR_CALL + 5 目标地址 = FFB784FC + 008B0E9F + 5 = 004293A0 好了,现在我们已经知道004293A0这个目标地址是如何得来的了。下面我们来计算新的OPCODES。 目标地址- CALL指令新的地址 - 5 = 新的OPCODES ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- 何谓CALL指令新的地址:即该CALL指令被拷贝到的新的地址。 例如: 如果我们将Stolen bytes拷贝到入口点处。 这里我们看到46B067这个地址。这个地址的计算公式如下: 原地址 - Stolen bytes的起始地址 + 入口点 这里原地址为008B0E9F。 stolen bytes的起始地址 = 008B0E48 入口点为0046B010。 8B0E9F - 8B0E48 + 46B010 = 0046B067 所以说新地址为0046B067 好了,现在我们来计算新的OPCODES。 目标地址 - 新地址- 5 = FFFBE334 004293A0 - 0046B067 - 5 = FFFBE334 这里新的OPCODES我们有了。 现在我们来手动编辑它。 我们定位到stolen bytes处。 我们在数据窗口中定位到这个CALL: 这里我们跳过E8这个机器码,直接修改后面的4个字节的偏移量: 将其替换成新的OPCODES. 我们可以看到该CALL的OPCODE已经改变了,现在我们将stolen bytes拷贝到入口点处。 我们可以看到46B067处的CALL的目标地址这次正确了。 下面我们给脚本添加一些内容让其自动完成上述操作。 然后 执行该脚本。 这里我们可以看到执行了该脚本后,下面CALL的偏移量都被修正了。 下面我们要做的就是将stolen bytes二进制复制到入口点处。 好,现在我们定位到了入口点处,我们将EIP修改到入口点处。 我们选择是,下面来进行dump。 Martian先生的教程中提到了,这个大小也得修改,不然单击Dump按钮,会报错。 修改为: 这里我不使用OllyDump来修复IAT,所以我去掉了Rebuild Import的对勾。 然后按dump按钮进行dump。 好了,这里我们就dump完成了,但是肯定是无法正常运行的,因为IAT还没有修复。 下面我们来修复IAT。 如果我们直接用OD加载dump文件的话,直接就会报错。 我们尝试用PE编辑工具重建PE看看。 好,重建PE完毕了,我们再次用OD加载它。 这里提一句,HideOD这款插件有时候会出错导致程序正常运行,所以最好将其用HideDebugger和OllyAdvanced代替。 好了,第一个脚本已经给大家介绍完了,接下来给大家介绍第二个脚本。 var base var dir_VirtualAlloc var dir_VirtualProtect var dir_mov Initiation: gpa "VirtualAlloc", "kernel32.dll" //获取VirtualAlloc这个API函数的地址 mov dir_VirtualAlloc, $RESULT log dir_VirtualAlloc gpa "VirtualProtect", "kernel32.dll" mov dir_VirtualProtect, $RESULT bp dir_VirtualAlloc run eob info info: mov base, eax log base bc dir_VirtualAlloc bp dir_VirtualProtect Area: eob Section run Section: cmp esi, 00460000 je Return jmp Area Return: bc dir_VirtualProtect mov Reg_esp, [esp] bp Reg_esp eob Area_1 run Area_1: bc Reg_esp find base, #897C24188B4424# mov dir_mov, $RESULT log dir_mov jmp Nop Nop: bp dir_mov eob Nop2 run Nop2: bc dir_mov fill dir_mov, 4, 90 final: msg "NOP完毕,请按F9键运行." ret ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- 附第二个脚本截图: 这是修复IAT其中一个比较经典的方法,Martian先生在他的教程中详细介绍过。在到达OEP之前我们可以对IAT中重定向的项设置内存写入断点,断下来的地方就是写入重定向值的地方。但是要对IAT进行写入的话,首先得让IAT所在的内存单元具有写入权限,所以势必会调用VirtualProtect来修改IAT所在内存单元的内存访问属性,赋予其写入权限。所以我们可以先对VirtualProtect这个API函数设置一个断点,等执行完该函数赋予写入权限以后,我们再对IAT中重定向的项设置内存写入断点。 从Martian先生教程中这张截图我们可以看到断在了VirtualProtect这个API函数的入口处,其想修改IAT所在内存单元的访问属性。我们执行到返回,然后对重定向的IAT项设置内存写入断点,接着运行起来,断在了写入重定向值的地方。 这里就断在了写入重定向值的地方,仔细观察我们可以知道此时EAX中保存了重定向的值,而EBP指向的是对应的IAT项。 现在我们在前面几行处设置一个断点,跟踪一下,看看是什么情况。 我们可以看到前面几行会获取正确的IAT值,而接下来会将正确的IAT值覆盖为重定向的值。所以我们要做的就是将写入重定向值的语句NOP掉。 所以脚本要做的事情就是定位到写入重定向值的指令,并将其NOP掉。 首先脚本要做的第一件事情就是查找VirtualAlloc这个API函数的地址,首次断到VirtualAlloc这个API函数时,我们就可以获取需要NOP掉的指令所在内存单元的首地址了,因为在不同的机器上,待NOP掉的指令的地址是会变的,所以我们有必要动态获取它。 var base var dir_VirtualAlloc var dir_VirtualProtect var dir_mov Initiation: gpa "VirtualAlloc", "kernel32.dll" //获取VirtualAlloc这个API函数的地址 mov dir_VirtualAlloc, $RESULT log dir_VirtualAlloc 这里是获取VirtualAlloc这个API函数的地址,然后将其保存到变量dir_VirtualAlloc中,同理VirtualProtect也是一样。 gpa "VirtualProtect", "kernel32.dll" mov dir_VirtualProtect, $RESULT 获取VirtualProtect这个API函数的地址,然后将其保存到变量dir_VirtualProtect中。 bp dir_VirtualAlloc run eob info info: mov base, eax log base bc dir_VirtualAlloc bp dir_VirtualProtect 接着对VirtualAlloc设置断点,运行起来,如果断下来就跳转到info标签处,将该程序刚申请的内存单元的首地址保存到变量base中,下面我们需要NOP掉的指令将位于这块内存单元中。 接下来删除掉VirtualAlloc的断点,然后对VirutalProtect设置断点。 Area: eob Section run Section: cmp esi, 00460000 je Return jmp Area 断下来了的话,判断ESI的值是否等于460000(IAT的起始地址),因为该程序会调用VirtualProtect修改IAT所在内存单元的访问属性。如果ESI等于460000的话,就跳转到return标签处。 Return: bc dir_VirtualProtect mov Reg_esp, [esp] bp Reg_esp 删除掉VirtualProtect的断点,将ESP指向的内容(返回地址)保存到变量Reg_esp中,接着对返回地址处设置断点。 Area_1: bc Reg_esp find base, #897C24188B4424# mov dir_mov, $RESULT log dir_mov jmp Nop 当VirtualProtect调用返回后,下面就可以在之前申请的内存单元中搜索需要NOP掉的指令了。这里我们利用特征码来搜索。 特征码为:897C24188B4424。 搜索到了的话就跳转到Nop标签处。 Nop: bp dir_mov eob Nop2 run Nop2: bc dir_mov fill dir_mov, 4, 90 final: msg "NOP完毕,请按F9键运行." ret 好了,这里第二个脚本也介绍完了。感谢Ularteck童鞋提供的这两个脚本以及Martian先生提供的教程。我从Martian先生的教程中截了一张图片来解释第二个脚本。 本章,大家应该对于如何编写脚本更加了解了吧。 好,本章就到这里。
本系列文章汉化版转载看雪论坛
感谢原作者:RicardoNarvaja(西班牙人)
感谢热心翻译的朋友: 1~3章译者:BGCoder 4~58章译者:安于此生
全集配套程序下载地址:
|