好友
阅读权限 25
听众
最后登录 1970-1-1
公孙秒秒
发表于 2019-5-6 03:22
前言 :
———————————————————————————————
看了很多很多的TMD的脱文,终于把这个程序手脱了,TMD的壳对我这个新手确实有点难!!!!虽然这里不用补代码,但是TMD本身最难的部分就不是补代码,而是修复IAT,研究了狠多狠多帖子和教程,终于通关了,可以说超级激动了。
先说一下找到的教程通病吧,都只告诉你找几句关键代码,,然后脚本改哪里哪里,改完直接运行脚本,然后直接就是修复完成????看的时候以为自己懂了,换了一个程序不一样了照样处于懵比中。脱其他程序的时候稍微有点不一样的地方的话,就重新回到了起点,也不知道改干什么了,改完脚本运行完,程序各种弹窗不能运行。所以写了这个帖子记录一下我在研究tmd最后成功脱掉的思路吧,希望能够帮助到其他人提供思路战胜tmd吧,从此以后,回收站系列-1!
示例程序:xxx软件3.72(我是用户实战六的程序)
传送门 :
———————————————————————————————
本帖演示视频地址: https://www.bilibili.com/video/av51593945/
个人汇总贴: https://www.52pojie.cn/thread-932679-1-1.html
*视频只是做了操作演示,具体分析参考本帖,建议先看完帖子如果有需要再以视频辅助
第一部分:寻找OEP
———————————————————————————————
方法一:ZwFreeVirtualMemory
下ZwFreeVirtualMemory断点
EDI出现两次一样的地址
取消断点,代码段下内存访问断点
F9
F9 到达伪oep
方法二:bp GetProcessHeap+C
1. .text段下内存写入断点,shift+F9,取消内存断点.
2. bp GetProcessHeap+C,F9,取消断点.
3. .text段下F2断点,F9到oep或者oep的第一个call里面的位置.
方法三:.附加法
同时 StrongOD的设置需要注意 bp GetProcessHeap+C
然后启动OD
文件-附加 -选中刚刚的程序
返回到程序领口
然后单步往下走
je 往上的 修改下 让他跳过
马上就到了oep了
到了delphi的 最后一个call的地址
这里我比较推荐ZwFreeVirtualMemory法,亲测这个方法比较好用一点,不过我只测试了几个样本,可能也不是很准确,最好还是具体情况具体对待
这里ZwFreeVirtualMemory法师我直接拷贝出来的,但是实际使用中我发现这里描述的并不是非常的准确,这里只说了EDI出现两次一样的地址,实测时发现如果这里的地址如果不在程序领空,其实是没用的,准确的描述应该是:
步骤一:下ZwFreeVirtualMemory断点
步骤二:运行
步骤三:判断EDI是否被改变,如果没变的话,看看EDI里面的地址是否在程序的代码段当中,是的话取消断点开始下一步,不是的话跳到步骤二顺序执行
步骤四:取消断点,代码段下内存访问断点,运行,到达OEP,如果没到的话,在运行一次,大部分其实运行一次就到了
有了这个思路过后,其实我们就可以亲自动手来写一个到达OEP的脚本了,因为自己F9这件事情,第一很容易按错,第二,很多程序你不知道要按多少下才能到,就比如我脱的这个,要是几十次,很累是不是,能交给机器做的事情我们交给机器做就好了!!!!下面是我自己动手写的脚本:
[Asm] 纯文本查看 复制代码
//变量声明区域
var ZwFreeVirtualMemory//用这个变量直观的保存ZwFreeVirtualMemory的地址
//这里发现如果直接写bp ZwFreeVirtualMemory会出错,无法定位到API当中,要手动录入地址才行
var edil //定义一个变量保存下EDI的值,就可以在运行过后与当时的EDI进行比较了
var code//用来存放代码段开始的地址
var size//用来存放代码段尺寸
var code2//用来存放代码结束时候的地址
mov ZwFreeVirtualMemory,7C92D370 //在这里把ZwFreeVirtualMemory的地址直接赋值,不同机器确认这个地址是否相同就好了
mov code,401000 //代码段的起始地址,这里一般不会变
mov size,f7000//代码段大小记得改
//计算代码结束地址并给到code2
mov code2,code //先把代码段起始地址给code2
add code2,size//然后加上代码段大小也就得到了结束地址啦
//清除所有断点防止干扰
bc//清除所有普通断点
bphwcall//清除所有硬件断点
bpmc //清除所有内存断点
bp ZwFreeVirtualMemory //下断点
loop1: //这个段落主要负责判断edi改变了没有,如果发现edi没改变就跳到去判定edi里面的值是不是在代码段,否则就直接循环执行
run //这里相当于按了一下F9,如果要按SHIFT+F9,指令时esto
cmp edil,edi //比较edi是否改变
je xiangdeng//如果相等就跳去判断是否在代码段当中,相等说明EDI没有发生改变
mov edil,edi//如果没跳转就是不相等,那么直接把现在edi赋值给edil,用于下一次比较
jmp loop1//然后跳回去重新开始循环
xiangdeng: //这个片段用来判断代码段用来判断EDI的值是否在代码段当中
cmp edi,code //先将edi的值和代码段起始地址比较
jb loop1 //小于起始段就说明不在了,调回loop1里面继续F9
cmp edi,code2//上面如果没跳走就和代码段结束地址比较
ja loop1//如果大于代码段的结束地址那么肯定就不在了,跳走继续F9
//这里翻译成代码其实就是:
//if edi > 代码段结束地址 or edi < 代码段其实地址 : jmp loop1
//如果都没有跳走,那么生下来的edi必然是在代码段当中了
bc ZwFreeVirtualMemory //取消掉 ZwFreeVirtualMemory
bprm code,size //在代码段下内存访问断点
run //F9一下
//这里应该就到达OEP了
bpmc //然后取消内存断点
ret//结束脚本的运行,一定要写这个,不写的话,你就要记得手动结束脚本运行,不然你重新加载程序过后,脚本直接就开始突突突了
如果发现OEP被偷了代码的话,那么补OEP建议参考这个帖子:https://www.52pojie.cn/thread-37433-1-1.html
第二部分:寻找IAT
———————————————————————————————
众所周知,TMD脱壳 最难的部分其实是修复IAT,这里不会去谈什么esi表,因为我发现其实发现了这个表看了挺多帖子分析这个表,最后落实到脱壳的时候,跟这个表的关系讲真不大,中间我也去研究了这个表,但是最后落实到写脚本的时候,忘了他吧,完全用不上的,当然也可能是我没研究透彻。
说说我的思路,想修复IAT,其实就是一个逆推的过程,我们需要知道tmd对iat做了什么,我们就可以反过来着手再该回去了。
TMD对iat的修改其实可以分为三个部分:
第一个部分: 把真实的API地址写的壳里重重保护起来,让你在调用API的时候先经过他的壳。
第二个部分: 把IAT里API的地址全部换成了壳生成的伪API地址,让你在调用API的时候其实是访问了壳内置的函数。
未加壳程序的iat选段(这是我随便找的一个没加壳的程序复制出来的):
[Asm] 纯文本查看 复制代码
00459000 >77DA7AAB advapi32.RegQueryValueExA
00459004 >77DA7842 advapi32.RegOpenKeyExA
00459008 >77DB4280 advapi32.RegDeleteKeyA
0045900C >77DAE9E4 advapi32.RegCreateKeyExA
00459010 >77DA6C17 advapi32.RegCloseKey
00459014 >77DAEAD7 advapi32.RegSetValueExA
00459018 >77DAECD5 advapi32.RegDeleteValueA
tmd处理过的程序的IAT选段:
[Asm] 纯文本查看 复制代码
0048D000 03020000
0048D004 03030000
0048D008 03031015
0048D00C 03040000
0048D010 03050000
0048D014 00000000
第三个部分: 把程序主体当中call和jmp到API的地方,分别替换成了E8 伪iat地址 和 E9伪IAT地址,如果我们随便找一个没有壳的程序会发现,本来这些调用API的call和jmp分别是FF15类型的call和FF25类型的JMP这样的长跳,而经过TMD的加工过后,会变成E8类型的CALL和E9类型的JMP,如果大家去百度了解详细不难发现,FF15和FF25类型的跳转是一个长跳转,后面直接跟着的是你要跳转到的地址,而E8和E9后面跟的是偏移地址。这里搞不懂也没关系,可以简单理解为,原来的程序中是:FF 15 真实IAT地址 和FF 25 真实IAT地址 的地方,被分别替换成了 :E8 指向伪IAT的偏移量 和 E9 指向伪iat的偏移量。
未加壳程序调用api:
[Asm] 纯文本查看 复制代码
00407B74 FF15 00904500 call dword ptr ds:[<&ADVAPI32.RegQueryVa>; advapi32.RegQueryValueExA
tmd处理过后调用api:
[Asm] 纯文本查看 复制代码
0046A465 E8 BD696A02 call 02B10E27
这么一看的话,我们是不是马上就有了思路了对不对:
第一步:我们先从第一个部分获取到真实的API地址
第二步:再把第二个部分IAT中被改成了伪API地址的地方修复成相对应的真实API地址,这里真实的API地址我们在第一个部分获取到了
第三步:我们把被改成了E8 指向伪IAT的偏移量 ,E9 指向伪IAT的偏移量 这些地方全部修改回原来的 FF15 真实IAT地址 和 FF 25 真实IAT地址,而iat的地址我们在第二步其实已经获得了,他就是存放着真实API的地址
做完这三步,我们其实也就完成了对iat的修复,剩下的就是常规的脱壳手段脱下他的外衣就好了,下面我们来说说怎么去找到这些地址。
第二个部分的地址和第三个部分的地址其实我们都很容易找到,因为这里等于在修改我们的代码段,所以我们直接在整个代码段上下内存写入断点,不断的运行,记录下所有对代码段写入的语句然后分析就得到了所有关于API和iat已经他们被调用的地方了。
乍一听这个操作好像很难工作量很大,其实不然,因为tmd在修改iat的时候,也不过是调用了他自己的一个函数,用了特定的几处代码来回循环修改,我们找的也就是这几句关键代码而已,然后再去判断这几句代码分别是对应修改了哪里,以及他们执行的顺序及规律就能够得到足够我们去修复他的数据了。
而OD的脚本机制也让我们可以直接进行自动化操作,本来我们需要手动的运行,等到停在断点后判断这一句是不是已经被找到的地址,是的话继续运行,不是就记录下这个新地址,然后再运行,再记录。思路明确过后,我们就可以编写一个脚本代替我们来执行操作了,而我们只用等到脚本发现了新语句过后,记录下来新的地址,在脚本的过滤列表中加入这个新发现的地址,然后继续运行修改过的脚本,准备记录新语句就好了,这样的话我们的工作量其实并没有多少,大部分事情都丢给脚本这个勤劳务实五讲四美好少年吧!
当然下断点的事情我们最好手动操作,因为不然这个脚本要加挺多东西,不划算,本来就是为了偷懒的,所以我们编写脚本的思路就是:
步骤一:执行F9
步骤二:在程序停下来过后,判断这一句代码的地址是不是我们已经找到过的,是的话就跳到步骤一然后顺序执行,不是就停下来,我们手动记录这条新的指令,并把他的地址添加进入脚本,运行修改过的脚本
这里要注意,我们下内存访问断点过后呢,程序会有一个释放代码段正常代码的过程,也就是
[Asm] 纯文本查看 复制代码
007DB41B F3:A4 rep movs byte ptr es:[edi],byte ptr ds:[esi]
这里释放了其他的代码,这个地方我们其实手动执行就好了,因为就一次,没啥多大工作量,所以我们人工需要执行的步骤就是:
步骤一:OD加载程序
步骤二:代码段下内存写入断点,然后F9运行,这时候会听到一个rep循环上
步骤三:按F7,F8
步骤四:运行脚本
步骤五:脚本停下来过后,记录新语句,修改脚本,执行修改过后的新脚本
步骤六:重复上面的步骤直到出现的地址是在代码段当中,而不是在虚拟机 的代码段当中,那么说明虚拟机的修改工作已经完成了,这个时候其实程序应该是停在了oep附近的
下面是脚本的实现:
[Asm] 纯文本查看 复制代码
loop1:
esto
cmp eip,xxxxxxx//填入找到的地址一
je loop
cmp eip,xxxxxxx//填入找到的地址二
je loop
...
cmp eip,xxxxxxx//填入找到的地址n
je loop
ret
//每找到一个地址,就在中间加一个cmp 和je语句就好了,是不是非常的简单
程序最后停在了这里,很显然这个地址在我们的代码段当中,所以我们不用该继续记录了
[Asm] 纯文本查看 复制代码
0046A46F 8915 A41F6F00 mov dword ptr ds:[0x6F1FA4],edx
[Asm] 纯文本查看 复制代码
同时我们稍微向上寻找,发现OEP就在不远处:
0046A43F 55 push ebp
这样我们一共记录下来了9个地方,通过对这9个地方的分析发现他们分别做了如下操作:
第一处:
[Asm] 纯文本查看 复制代码
007EF98E 8928 mov dword ptr ds:[eax],ebp ; winmm.midiStreamOut
ebp=76B2A4EE (winmm.midiStreamOut)
ds:[0048D684]=00000000
//这里把EBP中的值写入了[eax]中,ebp有的时候是真实的api地址,有的时候是伪API地址,通过跟踪eax发现eax的地址属于iat
//这里获取到IAT地址:eax
第二处:
[Asm] 纯文本查看 复制代码
007EFDCC AA stos byte ptr es:[edi]
al=90
es:[edi]=[00431DDD]=90
//这里向需要修改的call或者jmp所在的地方写入90,其实也就是nop,是为了平衡FF 15变成E8或者FF 25变成E9过后减少的字节,这句后面必然和两句相连
//这里获取到调用的地址,不过我们不从这里获取,我们从他后面的写入E8的地方获取,写脚本的时候方便一点
第三处:
[Asm] 纯文本查看 复制代码
007EFDEB AA stos byte ptr es:[edi]
al=E8
es:[edi]=[00431DDE]=90
//这里向[ESI]写入了E8,完成了call类型的转入,因为没有研究全部数据,这里有没有写入E9未知
//从这里获取到被修改的CALL或者JMP语句的地址:edi
//为了方便后面区分,把第二处、第三处、第四处这样的方法成为stos三连法好了,后面方便形容
第四处:
[Asm] 纯文本查看 复制代码
007EFF29 AB stos dword ptr es:[edi]
eax=766F870B
es:[edi]=[00431DDF]=90909090
//这里写入了指向iat的偏移量,加上前面两句这里完成了一次调用的完整修改
//这里可以用来判定,到了这个地址在运行一次,那么也就完成了一次调用的修改,我们就可以把他刚刚修改的那个调用修复回去了
第五处:
[Asm] 纯文本查看 复制代码
007EFE32 AA stos byte ptr es:[edi]
al=E8
es:[edi]=[00431DB0]=90
//这里写入了E8,并且后面会跟上007EFF29 这句写入偏移量,完成一次改写,不过没有写入90(NOP),这是与上面三句的唯一区别
//这里可以获取到被修改的调用的地址:edi
//为了区分把第五处、第四处连在一起完成修改的方法叫做stos二连法
第六处:
[Asm] 纯文本查看 复制代码
00743A92 8F02 pop dword ptr ds:[edx] ; 02B00000
堆栈 [0012FF64]=02B00000 (02B00000)
ds:[0048D184]=00000000
//这里与007EFF29类似,向[EDX]写入了一个双字,不过这里有两种情况:
//一:直接对IAT进行操作,写入伪API,这种情况下我们获取到iat的地址:edx
//二:与下面的语句搭配,写入偏移量完成对调用的改写,这种情况下与第四处相似,可以用来判断改写完成,运行完他后开始修复
第七处:
[Asm] 纯文本查看 复制代码
007481A8 8822 mov byte ptr ds:[edx],ah
ah=E8
ds:[0046CC9C]=90
//这里向调用的地方写入了E8,因为没有分析每一条数据,有没有写入E9不知道,这一句后面必然跟着一句00743A92 写入偏移完成对调用的改写
//这一句和第六处一起是另一种改写的方式,所以这里我们也是获取到了被改写的调用的地址:edx
//为了区分把这一句和第六处连在一起修改调用的方法称为非STOS法
第八处:
[Asm] 纯文本查看 复制代码
007EFEB3 886F 04 mov byte ptr ds:[edi+0x4],ch
ch=16
ds:[00462341]=90
//这一句写入的内容并不确定,怀疑是壳一开始对程序的代码进行了修改,然后在修改IAT的过程中再修复回去
//修复的时候判断一下如果是这句就直接再运行一下就可以了,忽略就好
第九处:[Asm] 纯文本查看 复制代码
0046A46F 8915 A41F6F00 mov dword ptr ds:[0x6F1FA4],edx
//到达了程序的代码段,壳对IAT的修改也完成了,后面的也就不用记录了,停车的标志
这个时候我们需要修改的地方就都已经获得了,只要再找到真实的api地址,填进IAT,我们也就完成了对整个程序的修复了,下面我们开始找真实的API地址:
首先我们让程序停在非stos法的任意一句上面,也就是007481A8 或者00743A92的地方,至于怎么停到这,当然是把上面用来找地址的脚本里对于这两句的过滤直接注释掉然后跑一下脚本不就好了么,然后直接在堆栈中回溯:
[Asm] 纯文本查看 复制代码
0012FF64 02B00000
0012FF68 007F24E7 ASCII "h!\n"
0012FF6C 007EF8CC 返回到 PDF2Word.007EF8CC
直接返回到了壳的领空里面了:
[Asm] 纯文本查看 复制代码
007EF8CC /0F88 01000000 js PDF2Word.007EF8D3
然后往上面翻找到他上面最近的CMP和条件跳转:
[Asm] 纯文本查看 复制代码
007EF894 80BD 59E1C411 0>cmp byte ptr ss:[ebp+0x11C4E159],0x0
007EF89B 0F84 75000000 je PDF2Word.007EF916
然后对cmp语句里的左边这一项的地址也就是ebp+0x11C4E159这个地址下硬件写入断点byte,然后删除内存断点,不停的运行,知道一个下面全是条件跳转的地方:
[Asm] 纯文本查看 复制代码
007ECD8E C785 62E1C411 0>mov dword ptr ss:[ebp+0x11C4E162],0x0
007ECD98 B9 01000000 mov ecx,0x1
007ECD9D 85C9 test ecx,ecx
007ECD9F 74 51 je short PDF2Word.007ECDF2
007ECDA1 8B8D BE2FB711 mov ecx,dword ptr ss:[ebp+0x11B72FBE]
007ECDA7 8B09 mov ecx,dword ptr ds:[ecx]
007ECDA9 3B8D 8328B711 cmp ecx,dword ptr ss:[ebp+0x11B72883] ; user32.77D10000
007ECDAF 74 10 je short PDF2Word.007ECDC1
007ECDB1 3B8D 101BB711 cmp ecx,dword ptr ss:[ebp+0x11B71B10] ; advapi32.77DA0000
007ECDB7 74 08 je short PDF2Word.007ECDC1
007ECDB9 3B8D 6500B711 cmp ecx,dword ptr ss:[ebp+0x11B70065] ; kernel32.7C800000
007ECDBF 75 31 jnz short PDF2Word.007ECDF2
007ECDC1 C685 59E1C411 0>mov byte ptr ss:[ebp+0x11C4E159],0x1
007ECDC8 8D95 66E1C411 lea edx,dword ptr ss:[ebp+0x11C4E166]
007ECDCE 8D8D 3EE2C411 lea ecx,dword ptr ss:[ebp+0x11C4E23E]
007ECDD4 39CA cmp edx,ecx
007ECDD6 73 1A jnb short PDF2Word.007ECDF2
007ECDD8 833A 00 cmp dword ptr ds:[edx],0x0
007ECDDB 74 15 je short PDF2Word.007ECDF2
007ECDDD 3902 cmp dword ptr ds:[edx],eax
007ECDDF 75 0C jnz short PDF2Word.007ECDED
007ECDE1 C785 62E1C411 0>mov dword ptr ss:[ebp+0x11C4E162],0x1
007ECDEB EB 05 jmp short PDF2Word.007ECDF2
007ECDED 83C2 04 add edx,0x4
007ECDF0 ^ EB E2 jmp short PDF2Word.007ECDD4
007ECDF2 89C0 mov eax,eax
007ECDF4 E9 11000000 jmp PDF2Word.007ECE0A
007ECDF9 9D popfd
这个时候停下来,一路按F7,直到出现一个mov向一个双字的内存单元写入真实API地址的地方就是了:
[Asm] 纯文本查看 复制代码
007F0136 8985 57E2C411 mov dword ptr ss:[ebp+0x11C4E257],eax ; kernel32.HeapSize
eax=7C809136 (kernel32.HeapSize), ASCII "NTDLL.RtlSizeHeap"
ss:[007E926B]=7C81127A (kernel32.GetVersion)
//这里我们获取到了真实的API地址:eax
//我们下硬件执行断点就可以停到这里了
这里的eax里面的值就是我们需要的API的真实地址了,那么所有需要的地方都找到了,我们就可以开始着手写脚本修复了。
自写脚本开始修复:
———————————————————————————————
通过上面找到的代码我们就可以分析我们的脚本怎么写了:
一:声明变量已经初始化我们需要的变量
二:清除所有类型的所有断点,防止停在不必要的地方影响脚本运行
三:对代码段下内存写入断点以及后来我们找到的真实API地址语句处下硬件执行断点
四:让程序先走过其他代码释放段,然后我们开始对每一次停下来的地方进行判断,针对上面找出来的规律,我们逐句开始判断并跳转到不同的段落进行修复操作
五:停在了我们找到的代码之外的地方或者终点处,修复完成,脚本结束
下面是脚本的实现:
[Asm] 纯文本查看 复制代码
//变量声明区
var moveax //用来存放通过mov方法写入iat的地址(第一处)
var movapi //用来存放真实api出现的地方(007F0136这个地址)
var stos390 //存放stos三连方法中写入90的地方(第二处)
var stos3e8 //存放stos三连方法中写入E8的地方(第三处)
var ab //存放独一无二的ab的地方,就是stos三连和二连都用上了的那个写入双字的地方,他的命令机器码是AB,其他的都是AA(第四处)
var stos2 //stos二连方法中写入E8的地方(第五处)
var paichu //需要忽略的语句,我们找到的不需要修补和获取数据的地方(第八处)
var recall //用来放FF15的,这样直接写recall就代替写FF15,如果要改成别的也好修改
var rejmp //用来放FF25的,这样直接写rejmp就代替写FF25,如果要改成别的也好修改
var oep //用来存放OEP的,其实用不上oep,省略了吧
var iat //用来存放对应iat的值,也就是存放了api真实地址的地址,这个可能有点绕,如果你知道什么是iat就又会觉得这里很罗嗦其实
var api //用来存放真实API地址
var popbyte //这里其实mov,因为另一句是POP开始的,所以我写成这样好分辨,是非stos方法中写入E8的地方(第七处)
var popdword //用来存放非stos方法中写入双字的地方(第六处)
var mark //存放临时数据用的变量
var count //存放临时数据的变量
var lsg //存放临时数据的变量
var code //存放代码段起始地址,用于下断
var size //存放代码段大小,用于下断
var addr //用来存放获取到的需要修复的代码地址
//赋值区域,变量需要声明才能使用,下面开始把我们找到的数据进行赋值:
mov movapi,007F0136
mov stos390,007EFDCC
mov stos3e8,007EFDEB
mov ab,007EFF29
mov stos2,007EFE32
mov moveax,007EF98E
mov popdword,00743A92
mov paichu,007EFEB3
mov popbyte,007481A8
mov code,401000
mov size,2f3000
mov recall,15ff //这里要注意,要反着写,这里OD写入的方式是默认从右向左一个字节一个字节写的,我们要写入FF15这里就要按自己写成15ff
mov rejmp,25ff //这里也是一样,原理什么的其实汇编里数据也是这样的,大端模式和小端模式的问题
//清除所有类型的所有断点,防止停在不必要的地方影响脚本运行
bc //这句清楚普通断点
bphwcall //这句清楚所有硬件断点
//这里我没有写清楚内存断点的语句,因为每次重载过后其实内存断点就没了,需要的话也可以写上
//下断点及前置运行,因为直接运行会走到 rep movs byte ptr es:[edi],byte ptr ds:[esi] 这句,这里要F7一下,F8一下,才能继续F9
bphws movapi,"x" //这句是在movapi的位置下硬件执行断点
bpwm code,size //在code处,size大小上下内存写入断点
esto //shift+f9的意思,F9是RUN
sti//F7
sto//f8
esto
//上面这段执行完其实刚好停在了movapi的位置,我们可以直接获取API地址了
//这段获取API,然后运行一下直接跳去对比中心对比到了哪了,然后在跳到对应的地方
getapi:
mov api,eax //从eax里得到真实的api地址然后存在api这个变量里面
esto//运行一下
jmp cmps //执行完毕跳到对比中心
//这里处理所有可能出现的地址,然后丢到该去的地方处理
cmps:
cmp eip,movapi //对比eip,如果是movapi就跳去获取真实api
je getapi
cmp eip,stos390 //对比如果是stos三连中写入90的地方,跳过去执行一下再回来
je justrun
cmp eip,stos3e8 //STOS3连写入E8,这里其实可以开始处理了,他后面必然跟着一个ab语句写入双字完成修改调用
je clab
cmp eip,stos2 //STOS二连写入E8,他后面必然跟着一个ab语句写入双字完成修改调用,忽略90语句其实就是为了这里好写,可以把两处并做一处
je clab
cmp eip,popbyte//非stos方法写入E8的地方,要开始获取地址进行处理了
je clpop
cmp eip,popdword //这句上面说了有两种可能,我们在clpop里已经处理了一种可能,如果直接出现很显然是另一种可能,我们单独做处理
je cldw
cmp eip,moveax //这里不用说了吧
je clmoveax
cmp eip,paichu //这也没啥说的了吧
je justrun
//如果全部都不是,其实也就说明修复完了,脚本执行完毕了,直接清除所有断点结束脚本进入常规手段脱壳
bc
bphwcall
bpmc //清楚内存断点
msg "end" //弹个窗,可以不写
ret
//这个方法用来在需要忽略的时候运行一下程序到下一个点,然后对比
justrun:
esto
jmp cmps
//这里用来处理非stos方法中写入双字的另一种可能,也就是在对iat进行写入
cldw:
mov iat,edx //用iat保存edx,这个时候edx的值就是iat的地址
esto //然后运行一下,让壳先写,不然你修改了壳执行完语句又写回去了,白写了
mov [iat],api //这里是把真实的api地址写入到iat对应的地址单元
//这两句是分别把iat的值和api的值追加到"C:\ODLOG\u.txt"这个文件中,是我用来记录数据的,可以删除
wrta "C:\ODLOG\u.txt",iat
wrta "C:\ODLOG\u.txt",api
//这个语句如果填写中的文件不存在会创建,但是如果路径当中的文件夹不存在是不会创建的,直接报错
//因为是追加,所以记录之前要记得删掉以前的,也可以在一开始用个非追加的语句把这个文件清空,防止上次的干扰
jmp cmps //跳回对比中心
//处理moveax
clmoveax://这个其实跟上面是一模一样的,不过他们不同的是一个是从eax里取值,一个是从edx取值,所以写了两段
mov iat,eax
esto
mov [iat],api
wrta "C:\ODLOG\u.txt",iat
wrta "C:\ODLOG\u.txt",api
jmp cmps
//处理popbyte的地方,这里思路上首先获取到需要修补的地址,然后我们要先判断这个地址对不对,因为我们这里其实要写入6个字节的
clpop:
mov addr,edx//这里获取到要修改的地方
mov mark,addr //这里用mark先保存下addr的值,我们要进行判定写入的适当位置,因为壳修改调用其实是把6个字节的本来数据改成了5个字节并填充了一个nop
//但是这个nop有时候是在前面的,有时候是在后面的,那么我们就要判断下我们这个开始写入的位置究竟对不对了
//判断的方法其实就是读取从addr开始,后面有几个NOP,
//有4个的话就说明这个nop在这个E8前面,所以我们就要把addr减一了
//有五个的话我们就能直接写
//有6个的话就说明这里可能有一串调用连在一起,我们就在判断前面有没有NOP,前面没有那我们直接写没问题,前面有的话那我们就停下来手动跳过去数一下
//然后手动修正ADDR的值,然后继续执行脚本进行下一步,我一开始没有这个判断,很多修复错位,导致无法运行
mov count,0 //count置零用来保存后面的nop的数量
//这里写一个循环不停的对比内存单元中指令,如果是nop就把count加一,不是nop了就跳走,然后根据count的值来判定究竟有几个nop并做对应处理
clpop1:
inc mark //自增一
cmp [mark],#90#,1 //这句是取出mark所对应地址里面的值跟##中间的内容进行对比,需要对比几个字节由后面那个数字决定,这里对比一个字节就行了
jne clpopnop //不等于话才跳的!!!!
inc count //等于就向下执行,count+1
cmp count,6 //这里数到6了就要对前面进行判定了,为什么上面说了
je clpoperro
jmp clpop1
//这是大于5个nop处理的地方
clpoperro:
mov mark,addr //再把addr给mark
dec mark//mark减一
cmp [mark],#90#,1 //就是判断addr前面的这个值是不是nop
jne clpop2 //逻辑上已经说明了
pause //是nop就暂停下来,手工改
jmp clpop2
//这段根据nop的数据决定是否修改addr,也就是我们开始写入的地址,逻辑上面已经说过了,不解释了
clpopnop:
cmp count,4
jb clpoperro//这里如果小于4那肯定出错了,直接跳向手工处理的位置
cmp count,5
je clpop2
ja clpoperro //这里其实是等于6的情况了,也可以改上面省下来这句
dec addr //addr减一
clpop2:
esto //这里要先运行一下,然后判断走到哪了,因为有可能会走到需要排除的地方,所以加一个判断,同时stos方法中的东西其实可以也集成到这里
//从上面一段开始其实三种方法是共用的了,为了看的清楚我写了两段
clpop22:
cmp eip,popdword //如果后面是写入双字的地方,其实就可以进入下一环了
je clabpanduane9
cmp eip,paichu //如果是需要忽略的语句,就运行一下
je clpoprun
//如果都不是,那就是出错了,直接弹窗口吧,看看逻辑上哪里出错了
msg "error"
pause
//这里跟justrun其实是一样的,就是结尾他们要跳的地方不一样
clpoprun:
esto
jmp clpop22
//处理stos方法的地方了
clab:
mov addr,edi //获取要修改的地方的地址
mov mark,addr //这里开始跟上面是一样的其实,可以直接合并到上面去,然后在对比那里加一个判断是不是ab就后了
mov count,0
clab1:
inc mark
cmp [mark],#90#,1
jne clabnop
inc count
cmp count,6
je claberro
jmp clab1
clabnop:
cmp count,4
jb claberro
cmp count,5
je clstos2
ja claberro
dec addr
clstos2:
esto
cmp eip,ab
je clabpanduane9
cmp eip,paichu
je clstos2
msg "error"
pause
claberro:
mov mark,addr
dec mark
cmp [mark],#90#,1
jne clstos2
pause
jmp clstos2
//这里开始下一环,上面判断过后,这里必然就是改写调用写入偏移的地方了,我们直接运行一下,让壳完成对于调用的改写,这样我们才能开始往回改
clabpanduane9:
esto
//这里要判断一下,究竟是写入rejmp还是recall了,到了这里ADDR经过上面的判定必然是正确的地址了,我们对他的位置进行判定,如果是E9,那就说明
//这里是个jmp了,如果不是我们在判断后面一位是不是E9,都不是那肯定就是E8了,也可以判断是不是E8,都不是就肯定是E9了
mov mark,addr
cmp [mark],#e9#,1
je clabjmp
inc mark
cmp [mark],#e9#,1
je clabjmp
//这里开始写入,没什么好说的了,写完了直接跳会去进行下一轮判断
clabcall:
mov [addr],recall
wrta "C:\ODLOG\u.txt",addr
wrta "C:\ODLOG\u.txt",recall
jmp clabdw
clabjmp:
mov [addr],rejmp
wrta "C:\ODLOG\u.txt",addr
wrta "C:\ODLOG\u.txt",rejmp
clabdw:
add addr,2
mov [addr],iat
wrta "C:\ODLOG\u.txt",addr
wrta "C:\ODLOG\u.txt",iat
jmp cmps
免费评分
查看全部评分