/home/jakluk When 140 chars are not enough

About me

Hack The Vote write-up

I wanted to practice my computer security and reverse engineering skills for quite some time, so I was happy to see that Hack The Vote CTF event was about to take place soon. The contemporary US presidental election theme sounded like fun, which was proven to be true. I participated under the nick pwny so that I wouldn’t be publicly ashamed if something went wrong, but 121st place out of 1000 (or 500 if you count only those who completed at least one challenge) doesn’t sound that bad. I really enjoyed the competition and I’m looking forward to participate in similar events.


Let’s skip the Sanity check. Yes, you could’ve got two points subtracted.

Sanders Fan Club

It seemed pretty easy. I started off by looking at the incomming traffic.

Some weird things are going on. Why is the flag2.jpg being sent as a stylesheet? A quick examination of the file reveals this.

So the comment is probably a hint. I was messing with the image in many different ways (diffing with Wikimedia source etc.) which lead me nowhere. I only realized the second day that I should do something with the Accept HTTP header since the image-sent-as-a-stylesheet event was caused by this header as well.

Bingo. (I’m glad I’m using Firefox by default.)

Spoiler: flag


$ ./consul
Poor Bernie.

Nothing interesting at the first glance. Let’s have a look at the assembly. I was using radare2 for the first time so it took me a while to get this view.

There are some functions, hidden unused right above the main one. We can run them from gdb by setting a breakpoint and jumping to the desired address, but only the real_help() function worked “out of the box”, the other ones were segfaulting.

(gdb) break main
Breakpoint 1 at 0x400b41
(gdb) run
Starting program: /home/jakluk/ctf/hack_the_vote/consul/consul 

Breakpoint 1, 0x0000000000400b41 in main ()
(gdb) set $pc = 0x400ad9
(gdb) cont
Leonardo De Pisa? Who's that–The next president?
[Inferior 1 (process 1715) exited normally]

When I jumped to address 0x400aeb right before subroutine calls in the other functions, I could get different outputs.

dont_call_me()  hi.
fake_help()     The end is forever. But after that, you're good to go.
help()          We didn't deserve Bernie.

My observation was that it just adds number stored in ESI register to ASCII codes of all chars in the string. So now the only task was to find the flag itself. It was hidden at address 0x601280 and it could’ve been decoded by increasing the chars by 64.

Spoiler: flag


Warp Speed

We can see some text, but it’s pretty hard to read. Let’s have a look inside.

Hmmm, another hidden message. Okay, let’s try to make it a square. The original image has a size of 1000×250 px, so the square image should be 500 px wide and high. I resized it by changing the corresponding bytes when I finally found usable JPEG documentation. Here’s the result, rotated for easier reading.

Spoiler: flag

While rewriting the flag, I lost many “accuracy points” b3c4u53 1 c4n’7 1337 5p34k.


Do we even know, who is this 4 CHAN???

Pretty funny quote to remember. Anyway, the input file looked like this:


…and so on. The TOP and KEK are evenly alternating, but the number of exclamation marks changes. I assumed that it’s some representation of binary, by trial and error I found that the exclamation marks in even words were zeroes and those in odd words were ones. All we have to do now is to convert this binary to string.

Spoiler: flag


with open('kek.txt') as f:
    kek = f.read()

kek = kek.replace('TOP', '').replace('KEK', '')
kek = kek.split(' ')

flag_bin = ''
for i in range(len(kek)):
    if i % 2 == 0:
        flag_bin += '0' * len(kek[i])
        flag_bin += '1' * len(kek[i])

flag_str = ''
for i in range(0, len(flag_bin), 8):
    flag_str += chr(int(flag_bin[i:i+8], 2))


Trump Trump

Can we get a signature from you Mr. Trump?

Since we are in the crypto category, this pun refers to cryptographic signature, which can be created with possession of a private key. We have only the public key, but we can communicate with someone who can help us (yes, I’m looking at you Mr. T). Of course he never signs pictures of himself.

Duneld Trump: Well, My horoscope said I should, so okay.
WHADDYA TRYIN TO PULL HERE? I don't sign pictures of myself!

The server accepts numbers and it’s using plain RSA without padding (something you should never do) which can be tested by sending number 1 to sign. We can make use of some math under the RSA principles. Here’s how we would usually get signature of a message (picture) :

If we can’t get this message signed directly, let’s pick a constant , such as that , multiply the message by it and send both and to the unsuspecting recipient. Then the signature of our message can be recovered like this:

How do you like your signature Mr. T?

Duneld Trump: Well, My horoscope said I should, so okay.
Trump looks shocked, appalled by the fact that he'd sign a picture of
himself for such a not-billionaire.
Trump sprints away at a blinding 2mph, dropping what he was carrying.
It's a stack of photos, you pick one up and look at it.

I recognized the ff d8 JPEG header thanks to the Warp Speed challenge.

Spoiler: flag

import gmpy2
import string
import binascii
import telnetlib

def get_signature(msg):
    tel = telnetlib.Telnet('trumptrump.pwn.republican', 3609)
    while True:
        line = tel.read_until(b'\n').decode()
        if line.startswith('(This is your big chance)'):
            tel.write(str(msg).encode() + b'\r\n')
        elif line.startswith('Trump: Here ya go, kid:'):
            return int(line[24:])
        elif line.startswith('ffd8'):
            return line[:-1]

with open('trumpkey') as f:
    key = f.readlines()

e = int(key[0][2:-1])
n = int(key[1][2:-1])

with open('trump.jpg', 'rb') as f:
    m = f.read()

m = gmpy2.mpz(int.from_bytes(m, byteorder='big'))
k = 3
km = k * m

s_k = get_signature(k)
s_km = get_signature(km)
s_m = int((s_k * gmpy2.invert(s_km, n)) % n)

img = get_signature(s_m)

with open('trump2.jpg', 'wb') as f:

Vermatrix Supreme

We’re given a random seed and matrix. We also have a source code of what’s happening on the server. The matrix is generated such as:

where are the initialisation vector (IV) digits and seed characters – seed is always padded so that is a multiple of 9. If we can guess the IV, server gives us the flag. Since XOR is reversible, we can extract the IV by running this procedure backwards. Here’s the script that takes care of everything.

import telnetlib

def pad(s):
    if len(s) % 9 == 0:
        return s
    for i in range(9 - (len(s) % 9)):
    return s

def genblockmatrix(s):
    outm = [[[7 for x in range(3)] for x in range(3)]
            for x in range(len(s)//9)]
    for matnum in range(len(s) // 9):
        for y in range(3):
            for x in range(3):
                outm[matnum][y][x] = s[(matnum*9) + x + (y*3)]
    return outm

tel = telnetlib.Telnet('vermatrix.pwn.democrat', 4201)

seed = tel.read_until(b'\n').decode()[6:-1]
seed = genblockmatrix(pad([ord(c) for c in seed]))

matrix = genblockmatrix([''] * ((len(seed) + 1)*9))
matrix[-1] = []

for i in range(3):
    matrix[-1].append([int(x) for x in tel.read_until(b'\n')
            .decode()[:-1].split(' ')])

for i in reversed(range(len(seed))):
    for y in range(3):
        for x in range(3):
            matrix[i][x][y] = ((seed[i][y][x] | matrix[i+1][y][x])
                    &~ (seed[i][y][x] & matrix[i+1][y][x]))

iv = ','.join([str(x) for y in matrix[0] for x in y]) + '\n'