最近学习了天草视频。记录一下笔记。

Obsidium1.3.6.4DEMO版本主程序下载地址:http://download.csdn.net/detail/whatday/5244425

前期准备工作:

这里主要记录的完美脱壳的PE组装的一些步骤,脱壳部分用脚本带过。

一般脱壳的OD脚本如下:(跳过脚本)

/*  repair Obsidium 1.3.6.4 main program function: 1.Go to OEP 2.Fix IAT 3.Fix stolen OEP 4.Fix two type the encrypted code 5.Fix SDK API */  var iatStart var iatAddr var iatKey var iatValue var resApiBk var resApiBk1 var resApiBk2 var sysApiBk var number var oepAddr var dataBase var encrypteAddr  mov iatStart, 00581B96		; The code here because of different computer  // This variable is used for the reduction treatment the third type special API xor number,number  // Set VirtualProtect breakpoint gpa "VirtualProtect", "kernel32" find $RESULT, #E9??????# gci $RESULT, DESTINATION gci $RESULT, DESTINATION find $RESULT, #E8??????# cmp $RESULT, 0 jz err5 bp $RESULT mov sysApiBk,$RESULT  // Set the code section attributes loop0: esto cmp [esp + c], 1 jnz loop0 mov [esp + c], 40 esto findmem #66623A432B2B484F4F4B# cmp $RESULT,0 jz err6 find $RESULT, #E8??????# cmp $RESULT,0 jz err7 bprm $RESULT,1 mov oepAddr,$RESULT loop0_1: esto cmp eip,$RESULT jnz loop0_1   //Set noraml api breakpoint findmem #0FB746026A01506A00FF7604FF37FF535485C0# cmp $RESULT, 0 jz err1 mov resApiBk,$RESULT add resApiBk,e bp resApiBk  // 0f 84 je set breakpoint mov resApiBk1,resApiBk sub resApiBk1,60 find resApiBk1, #0f84????# cmp $RESULT,0 jz err3 cmp $RESULT,resApiBk jae err3 gci $RESULT,DESTINATION bp $RESULT  // 75 jnz set breakpoint mov resApiBk1,resApiBk sub resApiBk1,40 find resApiBk1, #75??????# cmp $RESULT,0 jz err2 cmp $RESULT,resApiBk jae err2 gci $RESULT,DESTINATION find $RESULT,#85c0????# cmp $RESULT, 0 jz err4 bp $RESULT mov resApiBk2, $RESULT  // 0F 85 jnz set breakpoint mov resApiBk1,resApiBk sub resApiBk1,40 loop1: find resApiBk1, #0F85????# cmp $RESULT,0 jz next1 cmp $RESULT,resApiBk jae next1 mov resApiBk1, $RESULT gci resApiBk1,DESTINATION bp $RESULT add resApiBk1,2 jmp loop1 next1:  // Set last 0f 85 breakpoint, becase it is a special breakpoint sub resApiBk1,2 gci resApiBk1,DESTINATION bc $RESULT gci $RESULT, DESTINATION mov resApiBk1,$RESULT add resApiBk1,3 bp resApiBk1   // Cycle to restore an API mov iatAddr, iatStart		 loop2: add iatAddr,6 cmp [iatAddr], 0 jz fun3 cmp [iatAddr], #FF25#, 2 jz next3 sub iatAddr, 5 jmp loop2 next3: mov iatKey, [iatAddr+2] gci iatAddr,DESTINATION mov iatValue, $RESULT  //Fix noraml api mov eip,iatValue cmp [eip], #60#, 1 jnz next5 esto cmp eip,resApiBk jnz next2 sto mov [iatKey],eax jmp loop2   //Fix first type special API next2: cmp eip,resApiBk2 jnz next4 mov [iatKey],eax jmp loop2   // Fix second type special API next4: cmp eip,resApiBk1 jnz loop2 cmp eax,0 jnz case2 gpa "VirtualQuery", "kernel32.dll" jmp default case2: cmp eax,2 jnz case3 gpa "FreeResource", "kernel32.dll" jmp default case3: cmp eax,3 jnz case4 gpa "ExitThread", "kernel32.dll" jmp default case4: cmp eax,4 jnz loop2 gpa "ExitProcess", "kernel32.dll" default: mov [iatKey], $RESULT jmp loop2  // Fix third type special API next5: cmp [eip], #B8#, 1 jnz test4 inc number cmp number,1 jnz test1 gpa "GetCommandLineA", "kernel32.dll" jmp default2 test1: cmp number,2 jnz test2 gpa "GetCurrentProcessId", "kernel32.dll" jmp default2 test2: cmp number,3 jnz test4 gpa "GetVersion", "kernel32.dll" jmp default2  test4: cmp [eip], #55#, 1 jnz loop2 gpa "lstrlenA", "kernel32.dll" default2: mov [iatKey], $RESULT jmp loop2   // Repair stolen OEP fun3: sub oepAddr,10 mov [oepAddr], #A18B904700C1E002A38F904700526A00# findmem #426F726C616E6420432B2B# cmp $RESULT,0 jz ret8 mov dataBase,$RESULT mov [oepAddr+1],dataBase+8b mov [oepAddr+9],dataBase+8f mov eip,oepAddr + 10 sub oepAddr, 13 mov [oepAddr], #90EB10#, 3 add oepAddr, 1   //clear breakpoints for the next run clean: bpmc bc   // Decrypting the encrypted code mov encrypteAddr,401000 loop: inc encrypteAddr find encrypteAddr,#68????????FF15F2006500# cmp $RESULT,0 je next7 mov encrypteAddr,$RESULT mov eip,encrypteAddr bphws  eip+B, "x" esto fill encrypteAddr,B,90 bphwc jmp loop  next7: mov encrypteAddr,401000 loop4: inc encrypteAddr find encrypteAddr,#68????????FF15FA006500# cmp $RESULT,0 je next8 mov encrypteAddr,$RESULT fill encrypteAddr,B,90 jmp loop4   // Fix the second type for encrypted code next8: mov encrypteAddr,401000 loop5: inc encrypteAddr find encrypteAddr, #68????????E8# cmp $RESULT,0 je next9 mov encrypteAddr,$RESULT mov number, [encrypteAddr+1] add number,a cmp number,1000 ja loop5 add number,encrypteAddr cmp [number],#EB08#,2 jnz loop5 mov number, [encrypteAddr+1] add number, 5 mov [encrypteAddr], #E9#, 1 mov [encrypteAddr+1],number jmp loop5    // Fix SDK API next9: add iatStart,6 /*   jmp     005829B8   nop   mov     eax, 0x1   retn   retn   nop   nop   nop   nop   nop */ mov [iatStart], #E9170E000090B801000000C3C39090909090# gci iatStart, DESTINATION /*   cmp     dword ptr [esp], 00403063   jnz L007   mov     dword ptr [esp+0x38], 0x7373694B   mov     dword ptr [esp+0x3C], 0x70555B79   mov     dword ptr [esp+0x40], 0x5D4B   mov     edx, 00583034   retn    0x8 L007:   mov     dword ptr [esp+0x18], 0x2E777777   mov     dword ptr [esp+0x1C], 0x61706E75   mov     dword ptr [esp+0x20], 0x632E6B63   mov     word ptr [esp+0x24], 0x6E   mov     edx, 00583034   retn    0x8 */ mov [$RESULT], #813C24633040007520C74424384B697373C744243C795B5570C74424404B5D0000BA34305800C20800C74424187777772EC744241C756E7061C7442420636B2E6366C74424246E00BA34305800C20800#   // Output log and positioning OEP eval "OEP地址:{oepAddr}" log $RESULT mov eip, oepAddr jmp ret1   err1: msg "没有找到模拟api的代码" jmp ret1  err2: msg "模拟api的代码已经改变" jmp ret1  err3: msg "模拟api的代码已经改变2" jmp ret1  err4: msg "模拟api的代码已经改变3" jmp ret1  err5: msg "定位VirtualProtect错误" jmp ret1  err6: msg "没有找到BC的特征码" jmp ret1  err7: msg "没有找到BC的入口的CALL" jmp ret1  err8: msg "没有找到数据段" jmp ret1  err9: msg "修复加密代码错误" jmp ret1  ret1: ret

运行此脚本后 OD停在OEP处



第一步:

此时用LordPE Deluxe dump 得到dumped.exe文件 在dump之前先要修复一下镜像大小,这也是anti dump的一种方法。

修复大小

dump


这里查看一下区段信息

发现资源段还存在这里可以备份一份文件,后边修复资源使用。

如果是一般脱壳到这里只需要修复IAT就完成了。接下来是找一个相同编译器的生成的无壳程序,把它的PE头覆盖复制到dump文件上 ,这里是BC程序所以找一个BC程序


第二步:

用C32Asm打开dump文件和无壳的BC程序 复制无壳程序PE头道dump文件


点击按钮是 然后保存

这个时候发现dump程序的图标不显示了

这是因为资源段找不到,本质就是PE头和PE节不匹配造成的,因为要完美脱壳,所以必须在原本的PE头基础上来拼装各个节表,这样才算完美。

这个时候用LordPE查看一下节表信息

可以看出各个节表都有了,这点和无壳程序完全一样了,但是这只是PE头,具体对应的节表还需要修复。


第三步:

接下来通过跟踪 VirtualProtect函数 可以得到原始的节区段信息,这个是OB的一个特点,其他壳不知道。

重新载入DEMO程序,bp VirtualProtect  循环shift+F9 得出原始区段信息


可以看出 第一张和最后一张不在范围了 所以实际上得到4个区段 转换为RVA分别是

001000 - 181A00 183000 - 31200 1B9000 - 200 1BB000 - 3C00

原本的头文件区段应该有

.text .data .tls .rdata .idata .edata .rsrc .reloc

通过无壳的BC文件发现 .tls  .rdata段一般都为1000 在回想下前边修复IAT的过程 IAT比较零散比较大,可以得到结论,3C00不是.rdata段的大小,应该是.idata段的大小 .rdata段和.edata没有出现 所以我们自己添加剩下的段,只有资源段和重定位段 资源段需要专门修复 重定位段可以随便添加。又由于PE头文件中一般设定的内存块对齐是1000h。这里得出的区段信息如下:

001000 - 182000	.text 183000 - 36000	.data 1B9000 - 1000	.tls 1BA000 - 1000	.rdata 1BB000 - 4000	.idata 1BF000 - 4000	.edata 1C3000 - 未知	.rsrc 未知		.reloc


第四步:

通过LordPE修改dump各区段信息

需要注意2点,第一点各个段的属性 可以改成E0000060(可读 可写 可执行 包含执行代码 包含初始化数据) 这样排除因为权限造成的错误 在脱壳最后还需要根据无壳程序改回来。第二点 修改地址大小的时候 一定要把VirtualAddress VirtualSize RwaOffset RwaSize都修改了,因为每个区段在磁盘文件中和虚拟内存上都需要正确的信息,这点我先前没有修改RwaOffset 后来造成数据错位,后来想想,既然是全部自己拼装PE,当然内存映射的 和 磁盘的都需要考虑到。

由于资源段和重定位段要自己修复添加 所以这里先删除


第五步:

导出表,导入表,资源表,TLS都需要我们自己修复 所以先清空数据目录

PE头清空以后 对应的区段也需要清空 这里用的是PE Tools

把.tls .rdata .idata .edata 全部00填充 在相应的节鼠标右键Fill section 00填充

到现在清空了数据,后边修复数据就不会被其他数据影响了


第六步:

修复IAT 需要注意的是 平时都是选了Add new section这个选项的 但是这里的IAT都在的区段我们已经准备好了 而且清空了所以这个不能勾选此选项

RVA填入1BB000(1BB000 - 4000    .idata)

修复后用LordPE查看一下 输入表信息 看是否正确



第七步:

修复资源表 使用DT FixResource 把前边的dump备份文件拖进去

NewRVA填入1C3000(1C3000 - 未知    .rsrc)由于我们的PE文件头设置的文件块对齐是200 所以这里也填200 然后 Dump Resource


在LordPE中load section from disk 选中刚才保存的资源段

修改PE数据目录的资源段信息 并验证

资源段的大小可以通过导入资源段后查看得到

返回PE头信息窗口 看看还有什么需要修改的 发现BaseOfData还有问题 修改成我们先前得到的00183000

保存后 刷新一下可以看到有图标了

这个时候在win7运行可以正常了 但是在XP有可能还需要修复TLS 现在继续修复TLS


第八步:

TLS的修复需要参照无壳程序和加壳的源程序来看 以OD本身为例 通过TLS和区段目录可以发现


DataBlockStartVA就是tls段的起始地址  DataBlockEndVA=DataBlockStartVA+9C
注意点1:这里的9C不是固定的 是编译器根据程序的不同而生成的 所以这里需要参考加壳的源程序

显然加壳程序中的大小比9C大 如果这里填入9C 程序也可以正常运行但是就会出现 退出时错误,

原因跟踪发现,就是 线程局部存储 块比程序中设计的小了,原程序中的各个程序设计都是按照CC来做的

比如memcpy设定的长度等等,如果是9C 这样一来就会造成数据的错误,从而在退出程序清理内存时出现错误。

继续来定位TLS各项

IndexVariableVA是OEP指向的指令的内存寻址值

这里是0x58308B

CallBackTableVA=下一个区段起始地址+10=5BA000+10=5BA010

总结一下tls的值为

tls 5B9000 5B909c 58308B 5BA010
修改dump文件的tls

运行下程序 可以运行了 PEID查看一下

发现有附加数据段 用File Format Identifier去除附加数据段

在用PEID看一下


第九步:

到目前为止还需要添加一个重定位段和修改各个段的属性 以及 块对齐 这样可以减少PE文件大小

重定位可以随便添加一个 但是需要注意用Import REConstructor添加段 只是在PE头中添加 不会有相应的磁盘段对应 可以利用上一个段的剩余 也可以用其他工具修改下

各个段属性的修改就是比对无壳程序进行的 。

块对齐使用的是 PE Optimizer 工具 选择我们的文件 点process

可以看出来 大小缩小了2%

对比下先前和现在文件的大小


的确有变化

这个时候运行发生错误 通过OD跟踪发现 call 49af5c 造成的 但是49af5c为00 初步怀疑是PE Optimizer优化造成的,

实验发现是 kill Relocation造成的。LordPE加载未优化的文件

把重定位RVA+400000=496000 再观察节表


显然这个重定位地址是在代码段,PE Optimizer的优化恰好把代码清0了,

回想下重定位信息是先前我们复制PE头自带的 我们没有修改它 也没有清除它

这里把重定位信息清除掉 在优化可以运行了。



以上步骤完成后 在关闭脱壳文件任然要报错,还需要修复。通过 注意点1 解决了错误。

总结一下:PE的拼装关键就是要把PE头和各个区段分开看,PE头和区段一一对应,然后还有PE头中一些基本选项的修改,以及数据目录的修改 。