Playing Hacks and Stuffs!
Hi, I participated in this CTF with a friend of mine and it was a fun one :P
In this writeup I’ll give to the solution to the challenges I solved
After downloading the attached file on checking it’s content gave this
If you notice it you’ll see that there are only just two words having multiple occurrence
So what I did was to replace beep
to 0
and boop
to 1
then convert to string
Here’s the script I wrote to do that:
#!/usr/bin/python3
fp = open('BeepBoop').read().split()
cnt = ''
for i in range(len(fp)):
if fp[i] == 'beep':
fp[i] = '0'
else:
fp[i] = '1'
for i in range(0, len(fp), 8):
cnt += chr(int(''.join(fp[i:i+8]), 2))
print(f"Decoded: {cnt[1::]}")
But after running it the result wasn’t the flag?
Ok at least it has the flag format sun{^.*}
so I assumed this to be some sort of cipher and on using cyberchef I got it to be rot13
So here’s the final script to get the flag: solve
#!/usr/bin/python3
import codecs
fp = open('BeepBoop').read().split()
cnt = ''
for i in range(len(fp)):
if fp[i] == 'beep':
fp[i] = '0'
else:
fp[i] = '1'
for i in range(0, len(fp), 8):
cnt += chr(int(''.join(fp[i:i+8]), 2))
flag = codecs.decode(cnt[1::], 'rot13')
print(f"Flag: {flag}")
Running it gives the flag
Flag: sun{exterminate-exterminate-exterminate}
After downloading the attached file on checking the file type shows this
So that’s a python compiled binary whose version is 3.8
We can decompile it using uncompyle6
Doing that I got this decompiled python code
class Dill:
prefix = 'sun{'
suffix = '}'
o = [5, 1, 3, 4, 7, 2, 6, 0]
def __init__(self) -> None:
self.encrypted = 'bGVnbGxpaGVwaWNrdD8Ka2V0ZXRpZGls'
def validate(self, value: str) -> bool:
return value.startswith(Dill.prefix) and value.endswith(Dill.suffix) or False
value = value[len(Dill.prefix):-len(Dill.suffix)]
if len(value) != 32:
return False
c = [value[i:i + 4] for i in range(0, len(value), 4)]
value = ''.join([c[i] for i in Dill.o])
if value != self.encrypted:
return False
return True
Looking at this we can see that it defines a class object called Dill
and some variables such as prefix, suffix, o
are created, the encrypted flag is also given
The validate function of this program does this:
sun{
and ends with }
it will return False
sun{}
from our provided valueFalse
c
of our extracted valueo
and the result is stored in value
False
else it returns True
Here’s my solve script which just basically maps the encrypted value to it’s right index position: solve
#!/usr/bin/python3
encrypted = 'bGVnbGxpaGVwaWNrdD8Ka2V0ZXRpZGls'
mapping = [5, 1, 3, 4, 7, 2, 6, 0]
prefix = "sun{"
suffix = "}"
enc = [encrypted[i:i+4] for i in range(0, len(encrypted), 4)]
r = [0]*8
for idx, value in enumerate(mapping):
r[value] = enc[idx]
r = ''.join(r)
flag = prefix + r + suffix
print(f"Flag: {flag}")
Running it I got the flag
To confirm it’s the right flag we can pass it into the Dill.validate()
function
Running it return True
which means that’s the right value
Flag: sun{ZGlsbGxpa2V0aGVwaWNrbGVnZXRpdD8K}
We are given a remote instance to connect to
After connecting to the remote instance via netcat it showed this
Welcome to DIGITAL DANCE ROBOTS!
-- INSTRUCTIONS --
Use the WASD keys to input the
arrow that shows up on screen.
If you beat the high score of
255, you win a FLAG!
-- Press ENTER To Start --
After pressing the ENTER
key it showed this
From reading it you can just get what we’re to do
The goal is that we need to send the received arrow corresponding character key which are W,A,S,D
. We’re to repeat this process 255
times
First when I tried it I spent some time before I got it to work because the way it was doing I/O
was weird to me but I eventually got it work
So my solution is simple and it involves basically grabbing the arrows, then iterate through every arrow in the arrows and map it to a hashtable (dictionary) containing it’s corresponding key value
Then I send the concatenated result to the server for evaluation
Here’s the result from running it:
I ran it with pwntools debug mode because I was too lazy to fix the code then :(
So I had to fix it well and on running the updated one you should get this
Here’s the solve script: solve
Flag: sun{d0_r0b0t5_kn0w_h0w_t0_d4nc3}
Going over to the web url shows this
Viewing page source shows this
After reading the javascript included I saw this
So basically going over to /posts
should return the list of posts in json
But from the challenge description there’s a secret draft and we need to find it
I wasn’t the one who solved this but @Theory
Here’s his solve script: solve
#!/bin/bash
url_base="https://beepboop.web.2023.sunshinectf.games/post/"
i=0
while true; do
url="$url_base$i"
response=$(curl -skS -L "$url")
hidden_value=$(echo "$response" | jq -r '.hidden')
if [[ $hidden_value == "true" ]]; then
echo "Found a response with hidden: true at $url"
echo "$response"
break
else
echo "No luck at $url"
i=$((i + 1))
fi
done
After running it we’ll get the hidden post which holds the flag
So frustrating bash so slow well it’s using curl so I guess that’s the reason :)
Flag: sun{wh00ps_4ll_IDOR}
Going over to the url shows this
We have a login page, on checking /robots.txt
reveals this
The first two directories are invalid but the third one downloaded a file
The file type is a sqlite database
The number of tables there are 4
The credential table looks interesting and on dumping it I got credential
Username: hotdogstand
Password: slicedpicklesandonions
We can use this cred to login on the webapp
This was also solved by @Theory
Cool that’s all for the web pretty easy
Flag: sun{5l1c3d_p1cKl35_4nd_0N10N2}
After downloading the binary checking the file type shows this
So we’re dealing with a x64 binary which is dynamically linked and not stripped
The protections enabled are Canary & NX
I ran the binary to get an overview of what it does
Ok it asks for a fruit we want to eat then asks what we want to replace it with
This looks already like a write what where (arbitrary write) sort of challenge
To identify the vulnerability I decompiled it in Ghidra
Here’s the decompilation of the main function
void main(void)
{
printf_sym = sym_lookup("printf");
scanf_sym = sym_lookup("scanf");
logo();
do {
basket();
} while( true );
}
sym_lookup
to look up the got value of printf & scanf
and the resulting value is stored in the global variable *_sym
logo()
function which just prints out that fruitbasket()
functionHere’s the decompiled code
void basket(void)
{
long in_FS_OFFSET;
int fruit;
undefined *local_30;
undefined *local_28;
long local_20;
undefined8 local_18;
long canary;
canary = *(long *)(in_FS_OFFSET + 0x28);
printf("\nWhich fruit would you like to eat [0-3] >>> ");
__isoc99_scanf("%i",&fruit);
printf("Replace it with a new fruit.\n",(&fruits)[fruit]);
printf("Type of new fruit >>>");
__isoc99_scanf("%24s",&fruits + fruit);
local_30 = &printf;
local_28 = &scanf;
local_20 = _printf;
local_18 = _scanf;
if ((printf_sym == _printf) && (printf_sym == _printf)) {
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
/* WARNING: Subroutine does not return */
exit(-1);
}
idx
fruit[idx]
print_sym
to the value stored in _printf
which is the got of printf
the logic is done again (pretty weird I’ll say why this happens later)The global variable fruits
contains 4 values
This is how it looks like in IDA, pretty neat right?
There’s also a win function which would give us the flag
So from this I was able to identify this:
idx
is within the length of the fruit arraysBut now what can we overwrite or what should we do?
Remember that after running the binary initially it exited instead of calling itself again due to the while loop why is that so?
To know the reason, we know that it can only happen if the got check returns false so let’s set a breakpoint there
From the result we can see that the resolved value from the lookup_symbols
returned got of printf
i.e printf@got
which is in the rdx
register while what it’s being compared with is the value stored in the address 0x404020
which is 0x6
So that’s the reason our it exited because the comparison is false
During the ctf I didn’t notice there was a more easier way to solve this but anyways here’s what I did
First I needed a way to control the value of printf_sym
so I can overwrite it with 0x6
so that the comparison returns True and we hit the while loop
To calculate that I calculated the offset from the global variable fruits
to printf_sym
I used gdb
Cool we can see our replaced value which is 8 A’s in the 0th index of the fruits array.
Then at the offset 10 is the value of printf_sym
We can also just calculate that if we know the addresses of the two values
Now that we have a relative offset to the printf_sym
address we can overwrite it using the write what where primitive
Here’s the helper function for that
At this point we know that the binary will keep looping
So what next?
The idea is that we want to call the win function right?
But that is only possible if any of the function to be called is replaced to the win function
So if we overwrite an address that’s supposed to be calling another address and that address is used in the basket
function then we can overwrite it
At this point I realised I didn’t need to overwrite printf_sym
because exit@plt
will be called
And because exit@got ---> exit@plt
that means we can overwrite exit@got
to the win
function
But we’ve already gotten our self in a while loop so I just decided to overwrite printf@got
to the win
function
Here’s my solve script: solve
Running it works
If we run it in a debugger attached to it’s current process we will see that printf@got
is going to call win
We can run it remotely to get the flag
Flag: sun{a_ray_of_sunshine_bouncing_around}