ESP定律和动态脱UPX壳

关于ESP定律

简单来说,给栈顶ESP下硬件断点,ESP被拿走的时候就会停住,而在ESP上面push值或者pop值都不会停住

1
2
S:Stack栈
P:Pointer指针

注意栈底部大上面小,SP是栈顶指针,一有羽毛球进去就会减少4个大小

2FnutA.png

先放了一个球进栈,ESP值由开始的0012FFC4减去4变成了0012FFC0

2Fnnkd.png

再放一个ESP又减去4

2FneTH.png

所有的步骤执行完,ESP变成了0012FFA4

2FnVmD.png

现在正式开始ESP定律

0012FFA4下一个硬件访问断点

1
2
3
端点:就是运行到这个地方暂停
访问断电:再次访问0012FFA4这个地址就停下来
硬件访问端点:可以设置在任何位置上(普通软件断点可以用于内存,堆栈上。要记住)

下列语句执行完后

2FnKfI.png

上面语句执行完后,结果如下

1
访问断点:0012FFA4 EDI这个羽毛球不在桶里的时候,一改变就停止

2FnZ0e.png

现在给0012FFAC处下了硬件访问断点,研究一下怎么才会停下

这两种都不会停下

在栈顶0012FFAC上面加了两个新的羽毛球

2FnY7Q.png

又把刚才加的两个拿走了,也没有影响

2Fn3X8.png

这个才会停下,一定要把0012FFAC位置上的给拿走了(pop),才会停住

2FnUts.png

堆栈平衡

1
2
简单来说:
程序1(解压代码)执行过程中往栈里先pushpop->执行结束后栈为空,真正代码开始执行,找到OEP

upx加壳后的程序运行时会先加载upx加入的代码(我们叫它程序1),把压缩后的代码还原出来。程序1是一个完整的程序,作用是解码后面的代码

2FnQpt.png

完整的程序什么时候结束?

当栈第一次回到初始状态的时候

2Fn16f.png

没开始执行时栈是空的,程序开始执行后程序1入栈(想象push了很多值进栈)

现在栈还是空的,

2FnGnS.png

程序1先入栈(push一些值进栈)

2FnJ0g.png

程序1.1入栈(又push了一些值进栈)

2FnNkj.png

接着程序1.1和程序1陆续出栈,栈空了,解压代码执行结束。

程序1也就是解压代码执行完后,栈返回初始空状态,这里就是程序真正开始的地方,也就是我们要找的OEP

2Fnahn.png

ESP定律的变形用法

初始状态的ESP地址为0012FFC4,在它的上一个地址0012FFC0下一个硬件访问断点

2Fnwpq.png

总结

1
初始ESP指向0012FFC4,upx加壳程序运行时先执行解压代码,解压代码会在栈里先pushadpopad,在解压代码执行完后栈空了,只要事先在0012FFC4的上面0012FFC0下一个硬件访问断点,这样popad拿到初始ESP的时候就停住了,

动态脱upx壳

起点

2Fn010.png

接下来按f8单步执行pushad指令,再设置硬件读取断点

2FnsnU.png

设置完按f9运行程序,再次中断在一个不同的地址。这里并不是真实的程序代码,而是一个将栈空间向上清零0x80长度的循环。后面跟着一个向前的远距离的跳转(0043208C跳到00404DDC),这个就是跳到源代码的跳转(壳程序一般与程序原来的代码在不同的区段,故相隔较远)。

2Fnh1x.png

接下来删除硬件断点,菜单栏选择调试->硬件断点

2FnBcV.png

把光标移动到这个jmp,按f4使得程序执行到光标处,再按f8执行跳转

2FnDXT.png

跳转后出现了正常的函数开头和结尾,判断这个代码片段属于原程序

开头

2Fn674.png

结尾

2FnyBF.png

对程序进行Dump,在插件中选择脱壳程序

2FngAJ.png

单击获取EIP作为OEP,再单击脱壳

2Fn2N9.png

保存脱壳后的文件,然后用ida打开,可以看到被完全还原

2Fnf91.png

运行正常

2FnRhR.png


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!