JustCTF 2019 - Shellcode Executor PRO Writeup
Your favorite shellcode testing service, now in the cloud!nc 46.101.173.184 1446
The challenge only provides us with a non-stripped 64-bit binary. Running it gives us a menu with a couple of options as shown:
Checking the binary’s security flags:
Reversing
Firing up IDA, we find out a couple of functions:
Now, lets have a quick brief about what each function does:
main
: it callscreateShellcode
then prompt us with the menu and executes functions based on our choice.
|
|
createShellcode
: it takes a pointer, name of the shellcode and the actual shellcode, allocates memory on the heap for both the name and the shellcode and stores the addresses in the pointer.
|
|
downloadShellcode
: it allocates memory on the heap then takes its value from stdin usingfgets
then callsverifyUrl
on the input.
|
|
verifyUrl
: it loops over our input checking for invalid bytes<= 0x9
or that is what it looks like at least. Later on, we’ll find out that it does more than that.
|
|
deleteShellcode
: it onlyfree
the chunks we have stored in the pointer which was set earlier by thecreateShellcode
function.
|
|
executeShellcode
: it retrieves the shellcode from the heap and executes it after runningrestrictAccess
.
|
|
restrictAccess
it restrictssyscalls
to onlysigreturn
,exit
,exit_group
,read
,write
,mmap
,munmap
usingseccomp
.
|
|
Analysis
Clearly our goal is to run our shellcode but with the rules applied by seccomp
we have limited access to syscalls
, it only make sense for the flag to be loaded in the memory.
Simply looking at the binary’s strings we find out where our flag will be located:
Lets have a look at the demo shellcode, we know it’s located on the heap so we can get it from there easily instead of looking up for its global variable address:
So, this actually does what we need to do but instead of printing this demo text we want the flag so lets find where it is located. Interesting enough, we found it also on the heap in the same chunk of the shellcode:
That was because when the createFunction
allocated the chunk, it allocated 1024 Byte and copied 1024 from the start of the demo_shellcode
global variable and the flag was close by enough to get included.
We now have hint about what we need to do but we still need to work on the how. Looking back on downloadShellcode
and deleteShellcode
we notice a couple of things:
downloadShellcode
allocates a chunk equal in size to that of the shellcode increateShellcode
.deleteShellcode
frees the chunk the pointer is pointing to but the pointer is not nulled so it still points to the same chunk.
What happens if we deleted the shellcode then used the download function?
We took control of the shellcode that’ll be executed:
Bypassing the Verification
Now, we know that we need to write a shellcode to write the flag to stdout and we know how will we execute it. But we still have the function verifyUrl
which exits when it gets invalid bytes <= 0x9
.
We copied the shellcode from the demo and modified it with the flag offset and wrote a simple script to show us the opcodes to see if we’ll face troubles with verifyUrl
.
|
|
Running the script, our answer was yes:
We have many invalid bytes. My first attempt to solve this was to reconstruct the shellcode with different instructions which I knew was the intended solution after contacting the admin after the CTF but now the way I solved it after all.
I constructed part of the shellcode with much less amount of invalid bytes:
But for some reason all bytes >= 0x80
also caused the binary to exit and I couldn’t understand why. After the CTF I asked the admin about the function it turns out the decompilation was inaccurate as the original function was:
|
|
I was stuck so I looked at the verifyUrl
function again to notice this interesting line:
|
|
The condition of the for loop was the character itself so what if we send a null byte at the beginning of our shellcode before any occurrence of any invalid bytes.
Attempting to send '\x00' + shellcode
to the binary, it worked and it didn’t check the rest of the shellcode but of course our shellcode now is messed up and won’t run.
Exploitation
The solution to send a valid shellcode with a null byte in the beginning was to have a useless instruction at the beginning of our shellcode that’ll have null byte in its opcodes before the occurrence of invalid bytes.
After a couple of trials I found that xor al, 0x0
satisfies my needs:
And the final exploit was:
|
|
Finally, running the exploit we get the flag: justCTF{f0r_4_b3tt3r_fl4g_purch4s3_th3_full_v3rsi0n_0f_0ur_pr0duct}