汇编技术之函数式汇编popad实现解析

易语言 2020-02-23 13:00:44

汇编技术之函数式汇编popad实现解析

要做到一个函数式popad(也就是用一个函数来实现popad)不是很容易的事
比如这个函数命名为 popad()

但call你这个 popad() 时,栈压入了返回地址,
你不可能直接在函数里直接popad 因为你得让eip指向调用处的下一句代码
你也不可能直接跳转到返回地址,因为你用了popad后所有寄存器会被改变包括esp,ebp,你不能找到原本的返回地址在哪个位置
所以我们得不改变任何寄存器,却能正常的使用popad 并且找到返回地址 并跳转到返回地址处

在逻辑原理上 我们可以在写代码时预留一段空指令 比如5字节 置入代码{144,144,144,144,144} 可以通过写预留代码 成 跳转到返回地址 来实现
但是这是不可行的,因为代码段内存受保护,是不可写的
总的来说就是我们需要动态写一块内存,把返回调用处的处理代码写进去,并让它执行,因为我们一开始并不知道调用处的返回地址是什么,也不能改变使用popad后的任何寄存器的状态。

既然代码段内存无法写入,那我们是不是得向系统申请一段内存?
其实不需要,那样更麻烦
而且,还有一块闲置的内存,也是非常重要的内存是已经分配好的:栈

先看看偏移地址和绝对地址之间的转换:

偏移地址=绝对地址-call指令所在地址-5
绝对地址=call指令所在地址+5+偏移地址
上面两条计算规则是针对与4字节指针跳转,如 call:0xE8 和 jmp:0xE9

置入定位代码用od跟踪看看

来到我们两个 call的地方

先清0 等下再看看 pushad()的效果 esp:0019FCE8

压入成功,esp:0019FCC8

在f8 看看 popad()效果

恢复成功,很完美,下面一步一步分析其原理。

看看动态执行效果

来到函数内部,返回地址在栈顶

这是我们自己的代码

mov esp,ebp
pop ebp
mov eax,dword ptr ss:[esp]
sub esp,0x190
mov ecx,eax
sub ecx,esp
sub ecx,0xC
mov dword ptr ss:[esp],0x194C481
mov dword ptr ss:[esp+0x4],0xE9610000
mov dword ptr ss:[esp+0x8],ecx
jmp esp

***********************************
mov esp,ebp
pop ebp

用于平掉编译器开的栈帧,也可以用leave

mov eax,dword ptr ss:[esp]
将返回地址放到eax,因为popad将从栈弹到寄存器,所以这时候是可以不保存随意改变寄存器也不影响

sub esp,0x190
将栈指针esp 弄成我们的内存指针,不一定要0x190,写入代码后不要覆盖到栈顶就好 , 只是考虑到我一般喜欢写栈顶往上的几个字节,预留多一点好,不然覆盖了自己的数据就不好了。

mov ecx,eax //用ecx计算偏移地址

sub ecx,esp //ecx=eax=[esp],也就是说ecx是返回地址,我们要跳到这,偏移地址=绝对地址-call指令所在地址-5。我们这里减去的是栈代码的起始地址,还没有计算jmp指令的位置

sub ecx,0xC //这是什么意思?不是减去5? 因为刚刚减去的是代码的起始地址,还得减去 起始地址与jmp指令所在位置的距离。 那jmp指令在哪?
**********************

mov dword ptr ss:[esp],0x194C481
mov dword ptr ss:[esp+0x4],0xE9610000
mov dword ptr ss:[esp+0x8],ecx


//往[esp],写入0x194C481,再往[esp+0x4]写入0xE9610000,最后往[esp+0x8]写入ecx,ecx也就是计算后的偏移地址

0x194C481 是什么。栈有个特殊性,字节顺序是颠倒的,所以写入的是颠倒后的,再颠倒过来就是0x81C49401 字节集就是:{129,196,148,1} 汇编代码就是:ADD esp,0x194
刚刚不是sub 0x190 吗 ? 为什么 现在要add 0x194? 因为当时栈顶是返回地址,而返回地址不需要的,我们得add 0x194把栈顶的返回地址干掉,
也就是说执行完这步的时候,栈上就是原本pushad存进去的了。 那么我们怎么跳回去?
关键是后边写入的,0xE9610000 和 ecx,ecx就是刚刚计算出来的偏移地址
0xE9610000反转就是0x000061E9, 这是什么意思? 其实,前面4个0 是补充上面ADD esp,0x194 的2个字节0,也就是说ADD esp,0x194这句代码是6字节
真正意义上,这里写入的代码是0x61E9,也就是 popad , jmp

这时候刚刚计算偏移地址为什么减去0xc就知道了 add esp,0x194 popad 这里是7字节 jmp指令所在位置就是 代码起始位置(esp)+7, 所以减去7再减去5, 就等于 减去0xc (12)

再写入ecx 最后就是 (popad jmp ecx里边的值) 的意思

我们的栈代码就是 add esp,0x194
popad
jmp ecx的值(计算后的偏移地址)

****************
jmp esp //跳到栈code执行。

最后完美popad并完美返回到调用处。

总结:栈code的代码得是反转后再写入,代码跟着的数值(前面用的ecx)不需要反转,因为本身就是反转的。