栈溢出基础

Posted by nop on 2019-10-03
Words 1.4k In Total

函数的进入与返回

将示例程序加载到IDA中,主逻辑很简单即只是调用函数hello,查看函数hello的伪代码:

Alt

hello函数获取输入后输出“hello,输入内容”后退出。
在IDA-view窗口中对call hello一行下断点,然后开启32位Docker远程调试。

Alt

需要注意的地方是栈窗口、EIP寄存器、EBP寄存器、ESP寄存器。
EIP寄存器的内容始终是下一条将要执行的指令的地址,也即是说,只要通过某种方式修改EIP中的值,就可以控制整个程序的执行,从而pwn掉程序(可以在寄存器窗口,EIP后的数字上点击鼠标右键选择Modify value..将数值改成其他值,F9运行后即可跳过call hello而转向执行修改后的地址对应的指令)
栈的最小单位是函数栈帧,函数栈帧结构图如下:

Alt
Alt

栈的生长方向是向低地址生长,也即是说表格中的方向和IDA中栈窗口的方向一致,即越往上地址越小,在IDA的栈窗口中,新入栈的内容会压在原来内容之上。
进入hello函数前先记录下EBP和ESP(受到地址空间随机化ASLR影响,每台电脑每次运行结果可能不一致)

Alt

执行完call指令之后,EIP发生了改变,即call指令是可以改变EIP的内容的(始终指向下一条指令地址的行为),call指令执行会将下一条指令的地址压栈即上表中的EIP(类似于指令push eip; mov eip,[hello])

Alt
Alt

继续执行后,发现通过依次执行三条指令(push ebp; mov ebp,esp; sub esp,18h)为hello函数开辟了新的栈帧同时将原来的栈帧(执行call hello函数的main函数的栈帧的栈底EBP保存在栈中。继续执行到read函数,输入标志性的内容(123),可以发现存储输入的局部变量buf就位于新开辟的栈中:

Alt

继续执行到leave一行时再次回到了刚执行完sub esp,18h的状态:

Alt

执行完leave后,栈帧被销毁,整体状态回到了call hello执行前的状态。即leave指令相当于add esp,xxh; mov esp,ebp; pop ebp

Alt

再继续执行时,EIP指向了call hello的下一条指令,同时栈中保存的EIP值被弹出,栈顶地址+4,即retn等同于pop eip
此时hello函数代码执行完毕,控制流程返回到了调用hello函数的main函数中。

栈溢出实战

通过调试,了解到函数栈的初始化和销毁过程,而且可以发现随着输入的内容变多,输入的内容离栈上保存的EIP越来越近,所以可以通过输入修改栈中保存的EIP地址,从而再retn执行之后去执行修改后的EIP指向的指令,进而达到“pwn”掉程序的目的。
Ctrl+F2结束当前调试,直接在call _read处下断,启动调试,程序中断后界面如下:

Alt

通过观察,read函数的参数以及栈中保存的EIP地址,可以计算出两者的偏移量为0x16(22),所以输入22字节的内容就会到达EIP,如果输入22+4=26字节的数据就会覆盖掉栈中保存的EIP的内容,尝试输入'A'*22+'B'*4AAAAAAAAAAAAAAAAAAAAAABBBB后执行完retn,EIP中的值应该是BBBB(ASCII码:42424242):

Alt

修改成功,但因为42424242并非合法的地址,所以程序会报错,但如果将BBBB改成程序中某个函数的地址,执行完retn之后,便会转向执行修改后指向的函数。

结合pwntools打造一个远程代码执行漏洞exp

通过调试观察发现了程序的一个漏洞,通过这个漏洞可以做到远程使程序崩溃。相似的,如果能够挖掘到安全软件或者系统的漏洞从而使其崩溃,就可以让某些保护失效,从而使之后的入侵更加轻松。对于本程序,可以制作一个RCE(远程代码执行),进而让程序执行自己想要他执行的内容。
在IDA的Functions window可以看到一个getShell的函数:

Alt

通过这个后门函数,就可以实现和系统本身的交互,所以可以通过之前发现的漏洞,将EIP中的内容修改为这个后门函数的地址,从而达到目的。
先把hello程序的IO转发到10001端口上:

Alt

然后获取Docker的IP:

Alt

启动python,导入pwntools,并且打开一个和Docker的10001端口的连接:

Alt

通过之前的分析可以知道构造payload的组成应该是任意22字节的字符加上4字节的地址,但4字节的地址需要将16进制数转化成字符串,可以选用pwntools提供的函数p32()(即pack32位地址,同样还有unpack32位地址的u32()以及不同位数的p16(),p64()等),所以构造payload:22*'A'+p32(0x0804846B)
由于读取输入的函数是read,所以在输入时不需要以回车作为结束符(printf,getc,gets等则需要),所以这里直接使用send函数发送payload:

Alt

其中,interactive()可以和系统进行交互,进入后即可得到想要的内容。


You are welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them.