好友
阅读权限10
听众
最后登录1970-1-1
|
叶子青
发表于 2018-7-5 00:48
之前没有真正动手脱过壳,这边看了一些教程总是半懂不懂,看是有手上有个简单的例子,就拿来做练习的对象了,过程有点繁琐
首先进行查壳,可以发现他是RLPack壳
打开OD查看入口处,入口处存在一个pushad所以这种情况下直接使用直接ESP定律,在ESP上下断点执行会在popad时断下
此处被断下继续f8向下跟踪
紧接着又有一个Pushad,继续esp定律更随
断在了这个地方这里应该就是OEP的附近了
此时eax的值为0x446a52
可以看到上图中这里的代码所做的处理,将eax,ebx,ecx....这些通用寄存器存入了0x446a52+0x535e往后的地址中
这个地方我F8跟踪进,将各个寄存器的值写入了内存然后将ebp+0x535a也就是0x446a52+0x535a=44bda8的内容写入edi
并且将ebp+0x5356=0x446a52+0x5356=44bdac的内容存入esi
此时寄存器列表中ESI=00D70000,EDI=00D50000
在数据窗口更随这两个值
可以看到00D70000中存在一大堆的数据,00D50000里面什么也没有,我就没截图贴上来
继续在刚才EIP的基础上单步F8继续往下跟踪走到JMP跳过去
他先拿ESI所指向的地址也就是D70000里的数据和0作比较非0就跳到如下图的这个位置
可以看到这个下面他一直在拿esi所指向的内容和0,1,2.。。。做比较,我们当前esi指向的地址是D70000里面的内容为0x2,我们继续F8向下跟踪
当到达与0x2做比较的位置,不再跳转,当前保存了一下环境并清零ecx,然后将当前指针esi+4的内容压栈(也就是我们当前D70000向下的四字节的内容)
我们继续跟踪这个函数进到下面这个CALL里面
此时进入这个函数,清点下目前寄存器的值
ebp=eax=446a52(也就是我们之前保存那一堆寄存器的内存块0x446a52+0x535a的基址)
而此时ESI指向的是我们之前读到的数据块首地址(D70000),edi指向的是为0的数据块的首地址(D50000)
在449352这行
这几句话将之前push到堆栈的内容esi+4的内容复制给了edi指向的地址也就是D50000
然后向堆栈中压入两个值(-4和8),跟踪这个函数
在这个位置我们清点下手上寄存器的值
ecx为我们之前存储的基址0x446a52,EDX和EBX至今没怎么变过
继续向下跟踪
在448be7这个位置,将之前压栈的两个参数也就是 8给了eax -4给了ebx
然后接下来进行比较判断eax的值是多少继续往下跟踪
判断出eax=8后将ebx(-4)加给了ebp+0x537a
而此时EBP的值是我们之前存储一大堆寄存器的基址0x446a52,ebp+0x537a对应的就是之前存储的ESP
上图是之前存寄存器时的图,也就相当于对ESP存储的那块内存-4(模拟提升堆栈?)
紧接着在下一行也就是448f26这一行将edi的值(d50000)+4
可以猜测,他应该是用基址0x446a52+0x535e为起始地址后面的这些内存块动用做模拟我们计算机的8个通用寄存器使用,
然后将edi所指向的D50000作为了模拟的堆栈,而刚才那顿操作就相当于往虚拟的堆栈里压入了一个值并提升堆栈
那么相应的ESI所指向的D70000应该就是类似于我们的EIP指向代码的存在
那么假设这个是类似于压栈的操作那前面应该有对堆栈赋值的操作,所以我回头查找,在449352行,就是将esi+4位置的数据
存入我们的虚拟堆栈D50000的动作
那么当前的操作就像是我们硬编码执行的过程,首先识别最前面的操作指令,判断指令类型和指令长度,然后后面跟着的是
我们的指令操作数,
我们继续f8向下跟踪
发现在这个位置我们的esi加0xc然后向上跳转来到了刚才那个判断类型的位置
基本上可以确定这里模拟了我们计算机的工作原理,
内存D70000这里存放着我们即将执行的代码 ESI就像EIP一样在这个上面读取指令
内存D50000这里作为我们模拟的堆栈空间
然后将0x00446a52为基址如下图中的这些内存块当做我们的寄存器组
然后循环遍历D70000中的代码,
例如首先看到我们的第一个虚拟的指令操作数为0x2,将操作数0x2后4字节的数据作为操作数压入虚拟的堆栈
其中就包括将这个数据写入虚拟堆栈,并提升栈空间
然后虚拟的EIP加0xc(也许是因为在他设计的这个体系中他这个指令占三个DWORD)指向下一条指令继续循环
所以当前第一条指令就是PUSH 0
那好我们继续往下跟踪验证下我们的猜想
此时ESI指向了我们D70000+0C也就是之前假设的第而条指令的位置
我们就像之前一样F8往下跟踪,此时指令为0xc
在这里面先做了一堆复制(一时没看懂在干嘛先放过去)
此时ebx所指向的事4485c7这个地址,在当前EIP网上的两条指令,取了一个地址然后与当前虚拟操作指令的
第一个操作数做差然后存在了EBX+0X81的位置
然后从我们虚拟的寄存器ESP的值复制给真正的esp
再讲虚拟堆栈栈底的值取出复制给esi(相当于虚拟的EBP)
此时edi为当前虚拟堆栈栈顶的值(应该相当于虚拟的ESP)
然后继续跟踪,他在4480d6比较esi与edi的大小
esi小于edi,就将esi所指向的虚拟堆栈中的数据压入真实的堆栈,然后esi+4再与edi做比较直到esi==edi(这个操作等于将虚拟堆栈中的值都拿到我们真实的堆栈中)
然后在此处将虚拟寄存器中的值全部赋值给真实的寄存器(这里我猜测即将执行的代码需要与我们实际的代码对接,比如调用我们实际的函数或者API或者跳转一类的操作)
然后在448647这一行果然调用了一个进入其他位置的函数
在这里有一个细节注意,注意当前这个CALL的地址是448647
在我们之前刚刚判断当前指令为0c的时候我们做了一大堆复制
其中在复制后面还做了一堆加减的操作,当时我看到这个操作的时候第一反应就是这个和我们硬编码
计算段跳转和短调用后面的操作数的操作非常像,计算偏移值
我们回去继续分析那段代码还是看之前执行到这个位置的图如下图
当时的ebx的值为0x4485c7
而第4485f4这一行就是将eax的值赋值给ebx+0x81也就是448648,这个地址刚好就是我们刚才
看的那个CALL要跳转的地址的操作数,可见在这个位置他对自己的这个函数做了一个简单的
HOOK(我也不知道这样算不算HOOK)而eax的来源我们来看一下,来自esi+4,而esi+4就是我们
当前指令后面的操作数,后面的减法操作就是类似于硬编码中短跳转指令计算偏移的操作,
以下是D70000的内存窗口
那么第二条指令实际上相当于CALL 或者 JMP 00419AE6 因为之前分析的时候他在跳转之前压栈了返回地址所以当前指令是CALL
CALL 00419ae6
同样在CALL完之后再将真实的寄存器值再写回我们的虚拟寄存器
后面一堆恢复的操作以后又将虚拟EIP指向下一个指令的位置(+0xc)可以看到在他这个体系中所有指令和操作数都是0xc字节的
后面我就不一条一条看了,因为目前我手上没有这个加壳软件的工具,不然可以直接调试加壳软件,找到他做转换的那个地方
然后直接把他的代码抠出来就不需要这样一点一点的分析了(心累)然后直接编写脚本把这块代码修复
不过好在这里的这些虚拟代码只有四种:
00 00 00 02 xx xx xx xx 00 00 00 00 Push xxxxxxxx | | | | | | | 00 00 00 03 00 00 00 01 00 00 00 00 PUSH EAX | | | | | | | 00 00 00 0c xx xx xx xx 00 00 00 00 CALL xxxxxxxx | | | | | | | 00 00 00 0b xx xx xx xx 00 00 00 01 MOV DWORD[xx xx xx xx],eax | | | | | | |
那么这段代码还原出来就是
PUSH 0 | CALL 00419AE6 | MOV DWORD [0041F3AE],EAX | CALL 004013D3 | PUSH 0 | PUSH 00401D07 | PUSH 0 | PUSH 0041D021 | PUSH DWORD[0041F3AE] | CALL 419A32 | PUSH 0041D018 | CALL 00419AB6 | PUSH EAX | CALL 00419ABC | 然后我们把这部分恢复后的OEP写回去就完事了
我这里通过脚本写入
var padder
mov padder,00401000
mov ecx,00000040
mov ebx,padder
mov [ebx],#6A00E8DF8A0100A3AEF34100E8C20300006A0068071D40006A006821D04100FF35AEF34100E888A01006818D04100E8828A010050E8828A01009060E8938A01#
修复后的效果如下,此处才是真正的OEP
接下来我们基本上就可以直接脱壳了,但是这里还是检查下IAT表是否需要修复
我在这里跟入了第一个函数,这个函数在这里经过了一个跳转表,我第一次跟入的时候翻的比较快,最终进到了KERLAN32.DLL的领空也就是说这里调用的API
可见这个地方应该是一个FF25间接CALL的跳转表(那个nop应该就是加壳软件修改这里时整出来的多余字节)
F8往下跟踪 可以发现这里下面的这些代码的结构都非常类似,最终都通过了一个[44bd84]的位置走掉
继续跟踪
到了这个位置,暂时看不出什么继续往下跟踪直到有跳转
然后就到了这儿,这个地方算是出现了一个间接跳转的JMP,我们查看下0xD804E6里面都有啥
于是到了这步,这个地方已经是KERL32.DLL的领空,可以看到这个里面最终调用了GetMoudleHandleW
这块应该是GetMoudleHandleA不过不是正正当当的入口位置,不过观察我们当前EIP前面没执行的代码,
可以发现在我们跳进来之前已经执行了类似的代码
那可以确定这块就是IAT了并且他修改了IAT,还把调用的API偷出来几个字节,这样我们调试的时候
直接在API最开始的地方下断点可能就会发现断不下来
那我们现在要做的就是,在最开始jmp列表的那个位置
然后将JMP的地址指向我们修正后的真正的API函数的位置
脚本的大致思路就是定位函数被调用的一路上所有的跳转指令,然后获取他们跳转的目标
用脚本一路跟踪到达进入相应API的前一层函数,然后计算被偷取的代码的数量,然后修正API的地址到最前面的JMP表
脚本代码:
var temp //临时变量
var iat //需要修改的跳转地址
var adder //临时地址
var adder1 //临时地址
var sesp
var seip
//保存现场
mov sesp,esp
mov seip,eip
mov iat,00401000
loop:
mov esp,sesp
//定位JMP列表
find iat,#e9????????90#
cmp $RESULT,0
je exit
mov iat,$RESULT
gci iat,DESTINATION
mov adder,$RESULT
//定位间接跳转
find adder,#ff25????????#
mov adder,$RESULT
gci adder,DESTINATION
mov adder,$RESULT
//定位第三个跳转
find adder,#61e9????????60#
mov adder,$RESULT
//此处下硬件执行断点
inc adder
bphws adder,"x"
mov eip,iat
esto
gci adder,DESTINATION
mov adder,$RESULT
//计算被偷指令字节数
find adder,#ff25????????#
mov adder1,$RESULT
mov temp,adder1
sub temp,adder
//定位真实函数地址
gci adder1,DESTINATION
mov adder,$RESULT
sub adder,temp
//修复JMP表指令
eval "jmp {adder}"
asm iat,$RESULT
add iat,5
jmp loop
exit:
mov esp,sesp
mov eip,seip
脚本修复效果如下
然后再通过通用输入表修复工具把这里的E9直接跳转修改为FF25间接跳转完成IAT表的修复
接下来就直接DUMP然后修复IAT就OK了 |
免费评分
-
查看全部评分
|
发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。 |
|
|
|
|