IceCTF 2016 – Geocities (web 100)

August 26, 2016

TLDR: shellshock, metasploit, pivot, sqlmap

A cool web challenge, here the description:

I recently stumbled onto this old site, it’s a miracle that it’s still up! It must be running some ancient technology and probably hasn’t been updated in years, it’s our lucky day boys!


I first solved it like probably everyone else (index.cgi -> shellshock -> list files -> (search for flag on the server…) -> see perl script that connects to a DB on the internal network -> creates a modified version of the perl script in /tmp and executes it to get the DB content as there was no mysql on the vulnerable server).

But then this challenge was really cool, it’s not every day that you have a multiple machines environment (you can practice it in a Windows and Active Directory environment here :), so time to get the big guns, metasploit and sqlmap 🙂

I used the apache_mod_cgi_bash_env_exec Shellshock exploit to get a meterpreter shell.


From there, list the files and display the perl script to get the DB connection details (host, port, user, password, database name).



To get the IP address of the DB server, look into /etc/hosts


Then use the port forwarding command to forward all connections made to a port of the local machine to the DB server in order to be able to use sqlmap on the remote DB


And finally starts sqlmap using a direct connection to the local machine and the port defined above to dump the DB



PS: from the hosts file, an attentive reader would find another interesting sounding host, but its exploitation is left as an exercise for the reader


Google CTF 2016 – Ill intentions (mobile) – Take Two

May 3, 2016

After publishing my write-up and my “problem” with Inspeckage (I was able to see the intent but not their content), a really nice guy called mastho (from the khack40 CTF team) told me it was actually possible to do everything from Inspeckage. So time to have a deeper look!

After installation, you have to select which app you want to analyse.


Then you start the web UI and can see all the activities (exported and non exported).


It is possible to start them from here. So no need to use adb to start a broadcast intent.


After the activity starts, you then click on the button and another intent with a message is sent. Under the IPC tab, you can see that a broadcast intent was sent but not its content. That’s where I stopped during the CTF and went back to writing my own Xposed module.

Actually there is a tab called “+Hooks” that allows to create hooks on the fly. How good is that! In this case, just create a hook for the “putExtra” method of the “Intent” class…


… and tada! it’s in the logs!



Thanks mastho, no need to write code at all!


Google CTF 2016 – Ill intentions (mobile)

May 2, 2016

And another challenge solved using the Xposed  framework.

So we have an Android application, let’s start it in the emulator (Genymotion).


It’s quite ugly and doesn’t seem to be finished. It just tells us to use “Send_to_Activity”…
So let’s decompile it using jadx .

The MainActivity registers the Send_to_Activity as a BroadcastReceiver using the filter “com.ctf.INCOMING_INTENT”.

IntentFilter filter = new IntentFilter();
registerReceiver(new Send_to_Activity(), filter, permission._MSG, null);

Depending on the intent “msg” content, it launches another activity

public void onReceive(Context context, Intent intent) {
String msgText = intent.getStringExtra("msg");
if (msgText.equalsIgnoreCase("ThisIsTheRealOne")) {
  context.startActivity(new Intent(context, ThisIsTheRealOne.class));

In those activities, a button is displayed and clicking on it send another intent with a “msg”. This “msg” is the result of a complex calculation involving native code and probably contains the flag…

public void onClick(View v) {
  Intent intent = new Intent();
  String a = DefinitelyNotThisOne.this.getResources().getString(R.string.str1);
  intent.putExtra("msg", DefinitelyNotThisOne.this.definitelyNotThis(Utilities.doBoth(DefinitelyNotThisOne.this.getResources().getString(R.string.test)), Utilities.doBoth("Test")));
    DefinitelyNotThisOne.this.sendBroadcast(intent, permission._MSG);

So three possibilities: spend a lot of time reversing the native library doing the calculation, modify the app to directly write the msg content in the logs or do some dynamic analysis and write an Xposed module to intercept the intents and logs the message. I picked number 3 🙂

Actually I first tried to use Inspeckage – a tool developed to offer dynamic analysis of Android applications. By applying hooks to functions of the Android API, Inspeckage will help you understand what an Android application is doing at runtime.. I could see the intents and that they have some content but not the content itself… :/


So now back to solution 3 and the Xposed module to intercept the intents and log them

findAndHookMethod(ContextWrapper.class, "sendBroadcast", Intent.class, String.class, new XC_MethodHook() {

protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  Intent intent = (Intent) param.args[0];
  XposedBridge.log("sendBroadcast: " + intent.getStringExtra("msg"));



We just have to send the broadcast intents using adb:

mooh$ adb shell am broadcast -a com.ctf.INCOMING_INTENT --es msg ThisIsTheRealOne
Broadcasting: Intent { act=com.ctf.INCOMING_INTENT (has extras) }
Broadcast completed: result=0
mooh$ adb shell am broadcast -a com.ctf.INCOMING_INTENT --es msg DefinitelyNotThisOne
Broadcasting: Intent { act=com.ctf.INCOMING_INTENT (has extras) }
Broadcast completed: result=0
mooh$ adb shell am broadcast -a com.ctf.INCOMING_INTENT --es msg IsThisTheRealOne
Broadcasting: Intent { act=com.ctf.INCOMING_INTENT (has extras) }
Broadcast completed: result=0

click on the buttons



and finally read the logs

04-29 17:01:04.694 1569-1569/com.example.hellojni I/Xposed: sendBroadcast: KeepTryingThisIsNotTheActivityYouAreLookingForButHereHaveSomeInternetPoints!
04-29 17:01:37.475 1569-1569/com.example.hellojni I/Xposed: sendBroadcast: Told you so!
04-29 17:02:05.279 1569-1569/com.example.hellojni I/Xposed: sendBroadcast: Congratulation!YouFoundTheRightActivityHereYouGo-CTF{IDontHaveABadjokeSorry}




Squareroots CTF‑Workshop – März 2016

March 4, 2016

This page is intentionally left german.

Auch dieses Semester veranstalten wir, die squareroots, am Wochenende von Freitag den 18. bis Sonntag den 20. März 2016 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!



32C3 CTF – Android Reverse-Engineering libdroid

January 9, 2016

I unfortunately didn’t have time to participate to the CCC CTF this year, but I wanted to look at the android reverse challenge and see if I could solve it using the Xposed Framework. So here we go, same toolkit as last time, Jadx, Genymotion and Android Studio (see here)

Firing up an emulator with API 23 and starting the app shows a keypad with cute little smileys. After clicking on “bear”, “bear”, “ghost”, “monkey”,”heart”, “burger”, a message appears, “no rootkit for you”. Same happens after pressing the “get flag” button.


So let’s open the app in jadx. There we can see an obfuscated class with native calls

    public static native String generateConfusion();

    public static native String getFlag();

    public static native String getOperatingSystem();

    public static native String getPhoneNumber();

    public static native String installRootkit();

    public static native String installiOS();

    public static native String obtainWorldDomination();

    private static native void phoneHome(byte[] bArr, byte[] bArr2);

    static {
        a = a(getOperatingSystem(), 1);
        b = a(getPhoneNumber(), 1);
        c = a(installRootkit(), 1);
        d = a(generateConfusion(), 1);
        f = a(obtainWorldDomination(), 1);
        g = a(installiOS(), 1);
        flag = a(getFlag(), 1);

    protected void onCreate(Bundle savedInstanceState) {
        setContentView((int) R.layout.activity_a);
        try {
        } catch (Exception e) {
        this.e = BuildConfig.FLAVOR;

    void a(String a) throws Exception {
        InputStream b = getAssets().open(a);
        ByteArrayOutputStream b2 = new ByteArrayOutputStream();
        byte[] data = new byte[AccessibilityNodeInfoCompat.ACTION_COPY];
        while (true) {
            int nRead =, 0, data.length);
            if (nRead == -1) {
            b2.write(data, 0, nRead);
        BufferedReader b4 = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(a(b2.toByteArray(), b))));
        while (true) {
            String c = b4.readLine();
            if (c != null) {
                if (c.startsWith(g)) {
                    g = c.substring(g.length());
                if (c.startsWith((String) f)) {
                    f = Base64.decode(c.substring(((String) f).length()), 0);
            } else {

So when the app is starting, some static values are initialized and then modified again in the a(a) method.
The interesting part is the click event handler a(View v).

public void a(View v) {
        if (v.getId() == {
            this.e += d.charAt(1);
        if (v.getId() == {
            this.e += d.charAt(2);
        if (v.getId() == {
            this.e += d.charAt(3);
        if (v.getId() == {
            this.e += d.charAt(4);
        if (v.getId() == {
            this.e += d.charAt(5);
        if (v.getId() == {
            this.e += d.charAt(6);
        if (v.getId() == {
            this.e += d.charAt(7);
        if (v.getId() == {
            this.e += d.charAt(8);
        if (v.getId() == {
            this.e += d.charAt(9);
        if (v.getId() == {
            this.e += d.charAt(0);
        if (this.e.length() == 6 || v.getId() == {
            CharSequence flag = flag;
            try {
                InputStream b = getAssets().open(g);
                ByteArrayOutputStream b2 = new ByteArrayOutputStream();
                byte[] data = new byte[AccessibilityNodeInfoCompat.ACTION_COPY];
                while (true) {
                    int nRead =, 0, data.length);
                    if (nRead == -1) {
                    b2.write(data, 0, nRead);
                byte[] j = b2.toByteArray();
                byte[] f_ = new byte[16];
                System.arraycopy((byte[]) f, 0, f_, 0, ((byte[]) f).length);
                System.arraycopy(this.e.getBytes(), 0, f_, 10, this.e.getBytes().length);
                phoneHome(j, f_);
                if (new String(j).startsWith(c)) {
                    flag = new String(j);
            } catch (Exception e1) {
            Snackbar.make(v, flag, 0).setAction((CharSequence) "Action", null).show();
            this.e = BuildConfig.FLAVOR;

So everytime a key is pressed, a new character will be appended to the attribute “e”. If its length is 6 or the “get flag” button is pressed, some magic is done and a result is displayed in a message using a Snackbar.

Let’s bruteforce it! There are only 999999 possibilities.
The idea is then to set the variable “e” and simulate a click on the “get flag” button.

We first need to find the charset for “e”, ie we need the value of “d” (this.e += d.charAt(x);). We have seen above that “d” is set in the onCreate method, so let’s hook it

findAndHookMethod("ctf.stratumauhhur.libdroid.a", lpparam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() {

	protected void afterHookedMethod(MethodHookParam param) throws Throwable {
	    String d = (String) XposedHelpers.getObjectField(param.thisObject,"d");
	    XposedBridge.log("d= " + d);

Running the app returns

I/Xposed: d=  1234567890

So the charset is only digits, isn’t it? Wrong, wrong, wrong! (it did take me some time to realize my mistake…) It’s not “1234567890” but ” 123456789″…

Now we are nearly ready to start the brute force. We need somehow to log the popup message to get the flag. So let’s hook the make() method of the snackbar and log everything that doesn’t start with the default error message:

findAndHookMethod("", lpparam.classLoader, "make", View.class, CharSequence.class, int.class, new XC_MethodHook() {

    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        if (!((String) param.args[1]).startsWith("Sorry")) {
            XposedBridge.log("message= " + param.args[1]);

Here the pseudo-code for the brute force

String formatted = String.format("%06d", cnt.getAndIncrement());
if (formatted.endsWith("00000")) {
    XposedBridge.log("Count= " + formatted);
formatted = formatted.replaceAll("0"," ");
XposedHelpers.setObjectField(object, "e", formatted);
XposedHelpers.callMethod(object, "a", p.args[0]);

And then we start the app and press the “get flag” button to start the brute force. In less than 2 minutes, we get the flag


Thanks again 0x3cute for showing me this framework!


SECCON 2015 Online CTF – Reverse-Engineering Android APK 1

December 7, 2015

After reading a write up of the Trend Micro CTF about someone discovering the Xposed Framework and wanting to use it to solve CTF challenges, I decided to do the same.
In short, the Xposed framework allows to hook methods from an android application without having to modify the app.

What I used:
Genymotion (site) (android emulator faster than the stock one)
Xposed Framework (site)
jadx (site) dex to java decompiler
Android Studio (to write the Xposed module)

After opening the apk in jadx, the relevant parts are found in the MainActivity

Screen Shot 2015-12-07 at 12.56.16

In particularly:

if (1000 == MainActivity.this.cnt) {
    tv3.setText("SECCON{" + String.valueOf((MainActivity.this.cnt + MainActivity.this.calc()) * 107) + "}");

So after winning 1000 times in a row, the flag is displayed. It is calculated based on the counter and the result of the calc() method. Unfortunately here, the calc method is a native method.Instead of starting Hopper and reversing the native lib or patching the apk to display directly the flag, let’s try to write a Xposed module for it.

Idea is then to hook up the onClick method and set the attributes to the correct values (ie set count to 999 and the attribute m and n in order to make it a wining move)

public class RPS implements IXposedHookLoadPackage {
    public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {

        if (!lpparam.packageName.equals("com.example.seccon2015.rock_paper_scissors"))
        findAndHookMethod("com.example.seccon2015.rock_paper_scissors.MainActivity", lpparam.classLoader, "onClick", View.class, new XC_MethodHook() {
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                // set the modified values
                XposedHelpers.setIntField(param.thisObject, "cnt", 999);
                XposedHelpers.setIntField(param.thisObject, "m", 0);
                XposedHelpers.setIntField(param.thisObject, "n", 1);

Finally we start the application in the emulator, click on any button and the flag is displayed!

Screen Shot 2015-12-07 at 12.55.38


Squareroots CTF‑Workshop – November 2015

October 15, 2015

This page is intentionally left german.

Auch dieses Semester veranstalten wir, die squareroots, am Wochenende von Freitag den 6. bis Sonntag den 8. November 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!



ASIS CTF Finals 2015 – Giloph (crypto 300)

October 13, 2015

In this challenge we were given a normal network capture file of some “TCP” traffic.


After carefully looking at the capture you can guess that it is not actually just TCP traffic but rather TLS traffic. Fix that in wireshark by right-clicking on a packet and choose to “decode as… > ssl”
(That step actually took way more time for me than it should have, I searched for the string “http/1.1 spdy/3.1h2-14h2uP” in packet #4 and found some other captures online that were about TLS traffic so I figured that must be it, but there are many ways to see or guess the traffic type…)

Now we can see this challenge is about TLSv1.2. We have to think about ways to decrypt the traffic. First I looked at the certificate but nothing seemed to be “attackable” there (like a weak modulus, this one was 1024bits). Also the CipherSuite of the conversation was “TLS_DHE_RSA_WITH_AES_128_CBC_SHA” which means a private key wouldn’t actually be very helpful since the “DHE” part provides forward secrecy.
At least in theory. So how can we break the encryption? The RSA part is not attackable – Let’s take a look at the DHE (Diffie-Hellman) part:

Observe packet #6, the ServerKeyExchange part of the packet


We can actually see the parameters of the Diffie-Hellman Key-Exchange, which is normal. The abnormal part of the exchange are the values of the parameters. A quick look at wikipedia reveals:
“The protocol is considered secure against eavesdroppers if G and g are chosen properly.”

I’m not a math person and to be honest I had no idea what “chosen properly” means but went ahead and looked at the attack mentioned in the next sentences, since I assumed those parameters were not “proper”.

After studying that, it seems that the key exchange is attackable if (p-1) can be factored into “small numbers”. I was not sure what exactly that means, but I tried to factor (p-1) with my favourite algorithm I copied from stackexchange and it turns out it has quite a lot of small factors!

[2, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 17, 17, 37, 37, 37, 37, 37, 37, 43, 43, 43, 43, 43, 43, 43, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 61, 67, 67, 67, 67, 67, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 71, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 83, 83, 83, 83, 103, 103, 107, 107, 107, 107, 107, 109, 109, 109, 109, 109, 109, 127, 127, 127, 127, 127, 127, 127, 163, 167, 167, 167, 167, 167, 179, 179, 179, 179, 179, 197, 197, 197, 197, 197, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199L]

Okay, now we know there is a Diffie-Hellman Key-Exchange with some bad numbers and there exists some algorithm to break it. We could try to implement it, or just search for someone else who did it. I found this website:

And indeed: It takes less than a second for it to return the (secret!) exponents. If you want to understand what happened there, go through my code, I tried to explain it in the comments there a bit. If you know how Diffie-Hellman works and get what this attack does already, you can skip it.

Thanks Java for this BigInteger number, it’s our secret!


Feels good to break an algorithm that is used millions of times everyday… but now what. We have a big number but we still can’t see what’s in those packets. It’s time to dig into the RFCs about TLS:

Take a look at “8. Cryptographic Computations” and the points below it. That seems relevant! We find out that:
“The negotiated key (Z) is used as the pre_master_secret, and is converted into the master_secret, as specified above.”

Okay, now we know what that big number is and roughly what to do with it. It’s our “Pre-Master-Secret” and we need to somehow derive the “Master-Secret” (which is used to derive even more stuff). A teammate luckily found this tutorial, which shows you how to precicely do what we need:

After filling the script with the right numbers (client/server-secret from the pcap) we arrive at the following:


which gives us… The “MASTER-SECRET”:


Another number, WOW!

At this point I tried feeding the values we have to wireshark, for some reason it didn’t decode the packets though. This is supposed to work:
if you give it the CLIENT_RANDOM (used in computation above) and the Master-Secret.

So we tried a solution without wireshark: we followed the tutorial above further and wrote this script (or rather copied and filled in the right numbers) to derive the symmetric keys:

 clientkey 872f0e68167f5201bbc661417f148421
 clientiv  4eaaf3d612cb2629767aa11e1601a7ef
 serverkey 73eb5d0236c76e957381ff269081fba4
 serveriv  1dac9c3db71249572009791f82b5e87b

Now we have symetrically encrypted ciphertexts (ApllicationData in the pcap) and symmetric keys/ivs! Let’s try to decrypt it:

Just put the ciphertext (hex) into it, choose AES-CBC and give it the client/server “write key” and iv from the script (depending on if you want to decrypt the data coming from or going to the server/client).


(source .dat files in zip. link at the top)

We can see a GET-request and some answer, almost done!
Last step is to uncompress the gzip from the GET-response. I just used “binwalk -e” (overkill but it worked) on the .dat file and got the HTML-response:

<html><head><title>Top Secret</title></head>
<body><h1>Flag is ASIS{f702d759801533096be29291fd6e82c3}</h1></body></html>

Now that’s quite a topsecret website, and a flag too!
This was one of the most interesting challenges I solved in quite a while! It was very interesting to find out about so many of the internals of TLS and actually calculating all relevant keys “by hand”. Think about it: all of those calculations (and more) are done every time (close enough) you open a website that uses HTTPS!

Thanks ASIS!


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 🙂

Get Adobe Flash player