Affichage des articles dont le libellé est reversing. Afficher tous les articles
Affichage des articles dont le libellé est reversing. Afficher tous les articles

lundi 4 avril 2011

Prequals NDH2011: RCE200 (Android)

On va commencer par l'épreuve de reversing Android.
C'était la première fois que je jouais avec de l'Android, seems fun :).

Introduction

On avait à disposition une simple application dans laquelle il nous fallait parler.

Au lancement de l'application, nous somme accueillis par le screen suivant (sans le petit texte que j'ai ajouté :)):

 Il fallait prononcer un mot correctement pour obtenir le flag:
Non ce n'est pas le flag :).
J'ai pas réussi à l'obtenir par ce biais.

Pour ce challenge, plusieurs tools étaient disponibles, tel que Dex2Jar ou APKTool par exemple.
Ils ont plusieurs avantages et inconvénients, Dex2Jar nous sort du bytecode Java qu'on peut décompiler avec JD mais nous n'avons pas la possibilitée de facilement modifier l'APK.
APKTool nous permet de modifier l'APK et de le reconstruire correctement, par contre pas forcément évident à trouver la routine qui nous intéresse (à coup de grep ça peut se faire :)).

J'ai fais usage de la premire méthode pour analyser le programme:
dex2jar.sh RCE200.apk
jd-gui RCE200.apk.dex2jar.jar

Et le deuxième tool pour ajouter mon texte et patcher une condition afin de montrer un hash tous le temps (normalement il doit y avoir un affichage que lorsque le bon mot est prononcé).
Je me suis donc fais 2 helpers pour manipuler facilement le dump smali obtenu:

depack.sh
#!/bin/sh

cd apktool
java -jar apktool.jar d ../$1
mv `echo $1 | cut -d '.' -f 1` ../

pack.sh
#!/bin/sh

# if android key does not exist, we create one
if [ ! -f ~/keystore-android ]
then
    keytool -genkeypair -v -keystore ~/keystore-android -alias rce200 -keyalg RSA -keysize 2048
fi

#
if [ -f $1.aligned.apk ]
then
    rm $1.aligned.apk
fi

cd apktool
export PATH=./:$PATH
java -jar apktool.jar b ../$1
cd ../
jarsigner -verbose -keystore ~/keystore-android $1/dist/$1.apk rce200
cp $1/dist/$1.apk ./$1.rebuilt.apk
./zipalign -v 4 $1.rebuilt.apk $1.aligned.apk
rm $1.rebuilt.apk


Let's reverse it!

Après avoir transformer notre .apk and .jar, nous ouvrons celui-ci avec jd-gui et on se retrouve avec 4 sources .java: ReverseMe.java, a.java, b.java et c.java.

Le premier fichier (ReverseMe.java):

package ndh.prequals.rce;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;

public class ReverseMe extends Activity
{
  private a a = null;
  private TextView b = null;

  protected void onActivityResult(int paramInt1, int paramInt2, Intent paramIntent)
  {
    if ((paramInt1 == 1234) && (paramInt2 == -1))
    {
      ArrayList localArrayList = paramIntent.getStringArrayListExtra("android.speech.extra.RESULTS");
      if ((!localArrayList.isEmpty()) && (a.b((String)localArrayList.get(0))))
      {
        TextView localTextView = this.b;
        String str = a.a((String)localArrayList.get(0));
        localTextView.setText(str);
      }
    }
    super.onActivityResult(paramInt1, paramInt2, paramIntent);
  }

  public void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130903040);
    Button localButton = (Button)findViewById(2131034114);
    TextView localTextView = (TextView)findViewById(2131034113);
    this.b = localTextView;
    PackageManager localPackageManager = getPackageManager();
    String str1 = c.d();
    Intent localIntent = new Intent(str1);
    if (localPackageManager.queryIntentActivities(localIntent, 0).size() != 0)
    {
      String str2 = c.b();
      String str3 = Build.PRODUCT;
      if (!str2.equals(str3))
      {
        b localb = new b(this);
        localButton.setOnClickListener(localb);
      }
    }
    a locala = new a();
    this.a = locala;
  }
}

onCreate() va mettre en place les différents éléments de l'application à sa création comme l'image, le texte et le button.
onActivityResult() est le handler qui va afficher notre flag si nous prononçons le bon mot. (Patchez la condition pour toujours avoir un hash d'affiché :)).

Le deuxième fichier (a.java):

package ndh.prequals.rce;

import android.os.Build;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public final class a
{
  public static String a(String paramString)
  {
    try
    {
      MessageDigest localMessageDigest = MessageDigest.getInstance(c.e());
      byte[] arrayOfByte = paramString.getBytes();
      localMessageDigest.update(arrayOfByte);
      localObject1 = localMessageDigest.digest();
      StringBuffer localStringBuffer1 = new StringBuffer();
      int i = 0;
      int j = localObject1.length;
      if (i >= j)
      {
        localObject1 = localStringBuffer1.toString();
        return localObject1;
      }
      String str;
      for (Object localObject2 = Integer.toHexString(localObject1[i] & 0xFF); ; localObject2 = str)
      {
        if (((String)localObject2).length() >= 2)
        {
          StringBuffer localStringBuffer2 = localStringBuffer1.append((String)localObject2);
          i += 1;
          break;
        }
        str = "0" + (String)localObject2;
      }
    }
    catch (NoSuchAlgorithmException localNoSuchAlgorithmException)
    {
      while (true)
        Object localObject1 = null;
    }
  }

  public static boolean b(String paramString)
  {
    try
    {
      MessageDigest localMessageDigest = MessageDigest.getInstance(c.c());
      byte[] arrayOfByte1 = paramString.getBytes();
      localMessageDigest.update(arrayOfByte1);
      byte[] arrayOfByte2 = localMessageDigest.digest();
      StringBuffer localStringBuffer1 = new StringBuffer();
      String str1 = c.b();
      String str2 = Build.PRODUCT;
      if (str1.equals(str2))
        StringBuffer localStringBuffer2 = localStringBuffer1.append(65);
      int i = 0;
      int j = arrayOfByte2.length;
      if (i >= j)
      {
        String str3 = localStringBuffer1.toString();
        String str4 = c.a();
        bool = str3.equals(str4);
        return bool;
      }
      String str5;
      for (Object localObject = Integer.toHexString(bool[i] & 0xFF); ; localObject = str5)
      {
        if (((String)localObject).length() >= 2)
        {
          StringBuffer localStringBuffer3 = localStringBuffer1.append((String)localObject);
          i += 1;
          break;
        }
        str5 = "0" + (String)localObject;
      }
    }
    catch (NoSuchAlgorithmException localNoSuchAlgorithmException)
    {
      while (true)
        boolean bool = false;
    }
  }
}

La méthode a() nous renvoi le hash sha1 de la chaine qu'on met en paramètre et b() nous renvoi un hash MD5.

Le troisième fichier (b.java):
package ndh.prequals.rce;

import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;

final class b
  implements View.OnClickListener
{
  b(ReverseMe paramReverseMe)
  {
  }

  public final void onClick(View paramView)
  {
    ReverseMe localReverseMe = this.a;
    if (paramView.getId() == 2131034114)
    {
      String str = c.d();
      Intent localIntent1 = new Intent(str);
      Intent localIntent2 = localIntent1.putExtra("android.speech.extra.LANGUAGE_MODEL", "free_form");
      Intent localIntent3 = localIntent1.putExtra("android.speech.extra.PROMPT", "Enter password");
      localReverseMe.startActivityForResult(localIntent1, 1234);
    }
  }
}

Cette classe a une unique méthode qui va lancer la boîte de dialogue de reconnaissance vocale.

Le dernier fichier (c.java):
package ndh.prequals.rce;

public final class c
{
  private static byte[] a = { 90, 5, 88, 88, 13, 13, 90, 90, 10, 4, 9, 11, 93, 90, 11, 15, 93, 95, 5, 93, 5, 8, 8, 88, 90, 95, 9, 14, 90, 8, 13, 94 };
  private static byte[] b = { 91, 83, 83, 91, 80, 89, 99, 79, 88, 87 };
  private static byte[] c = { 113, 120, 9 };
  private static byte[] d = { 93, 82, 88, 78, 83, 85, 88, 18, 79, 76, 89, 89, 95, 84, 18, 93, 95, 72, 85, 83, 82, 18, 110, 121, 127, 115, 123, 114, 117, 102, 121, 99, 111, 108, 121, 121, 127, 116 };
  private static byte[] e = { 111, 116, 125, 17, 13 };

  public static String a()
  {
    return a(a);
  }

  private static String a(byte[] paramArrayOfByte)
  {
    byte[] arrayOfByte = new byte[paramArrayOfByte.length];
    int i = 0;
    while (true)
    {
      int j = paramArrayOfByte.length;
      if (i >= j)
        return new String(arrayOfByte);
      int k = (byte)(paramArrayOfByte[i] ^ 0x3C);
      arrayOfByte[i] = k;
      i += 1;
    }
  }

  public static String b()
  {
    return a(b);
  }

  public static String c()
  {
    return a(c);
  }

  public static String d()
  {
    return a(d);
  }

  public static String e()
  {
    return a(e);
  }
}
Ici nous avons affaire à plusieurs chaînes de caractères obfusquée par un XORing avec une clé de 0x3C.

J'ai coder un rapide utilitaire pour me dé-obfusquer ces chaînes:
// author : m_101
// licence: beerware
// year   : 2011
// ctf    : ndh2011 prequals

#include <stdio.h>
#include <stdlib.h>

void decrypt(unsigned char *encrypted) {
    size_t idxEnc;
    int c;

    printf("Decrypted: '");
    for (idxEnc = 0; encrypted[idxEnc] != 0; idxEnc++) {
        c = encrypted[idxEnc] ^ 0x3c;
        printf("%c", c); 
    }
    printf("'\n");
}

int main (int argc, char *argv[]) {
    unsigned char a[] = {
        90, 5, 88, 88, 13, 13, 90, 90,
        10, 4, 9, 11, 93, 90, 11, 15,
        93, 95, 5, 93, 5, 8, 8, 88,
        90, 95, 9, 14, 90, 8, 13, 94,
        0
    };
    unsigned char b[] = { 91, 83, 83, 91, 80, 89, 99, 79, 88, 87 };
    unsigned char c[] = { 113, 120, 9 };
    unsigned char d[] = {
        93, 82, 88, 78, 83, 85, 88, 18,
        79, 76, 89, 89, 95, 84, 18, 93,
        95, 72, 85, 83, 82, 18, 110, 121,
        127, 115, 123, 114, 117, 102, 121, 99,
        111, 108, 121, 121, 127, 116,
        0
    };
    unsigned char e[] = { 111, 116, 125, 17, 13, 0 };

    decrypt(a);
    decrypt(b);
    decrypt(c);
    decrypt(d);
    decrypt(e);


    return 0;
}

Vous obtenez ceci:
$ ./decode
Decrypted: 'f9dd11ff6857af73ac9a944dfc52f41b'
Decrypted: 'google_sdk|'
Decrypted: 'MD5'
Decrypted: 'android.speech.action.RECOGNIZE_SPEECH'
Decrypted: 'SHA-1'

On trouve un hash MD5, tiens tiens ...
Une petite recherche google nous donne ceci:
md5(salope) = f9dd11ff6857af73ac9a944dfc52f41b

Donc au final, l'application va faire un hash sha1 du mot qu'on prononce et l'afficher si celui-ci est correct.
Je n'ai pas réussi à l'obtenir par ce biais, mais nous savons que c'est un sha1.
$ printf "salope" | openssl dgst -sha1
913beccad686975f8c686d9b3b1ee6bb97c22d6f

Et voilà, done :).

J'espère que ce rapide tour d'horizon du reversing Android vous a plut.
J'ai mis plus de doc en lien si vous voulez approfondir ;).

Je n'ai pas encore fini de reverser le RCE300 par contre, donc l'article de reversing NDS va attendre un peu.

Cheers,

m_101

- ReverseMe: RCE200
- Tool: dex2jar
- Tool: apktool
- Doc: DalvikVM
- Doc: dalvik opcodes
- Doc: Reversing Android par virtualabs
- Doc: Primer on Android OS Reversing by ARTeam

mercredi 2 février 2011

[Reversing] Ge0's KeygenMe fast

Bonjour,

Ce soir nous allons étudier rapidement un keygen me "basique", c'est-à-dire sans aucunes obfuscation, anti-debugging ou autre "protections" particulières.

C'est un challenge du site Zenk-Sécurité, pour cette raison, je ne vais pas dévoiler mon keygen mais juste donner des pistes (fortement détaillées).
Seuls ceux qui auront résolu le challenge pourront avoir accès à mon keygen.
Ca forcera les "gens" à lire entièrement ce write-up et à taper dans le challenge un minimum ^^.

Le page du challenge se trouve : ici.

On nous dit que la page http://venom630.free.fr/keygenmefast/keygenmefast.php va nous envoyer un keygenme sous forme d'un fichier ELF (exécutable Linux donc).
Le pseudo dont il faut générer le sérial se trouve dans les entêtes HTTP:
X-Pseudo-Keygenmefast: Overclok (par exemple)
Et pour finir, nous devons envoyer notre solution concaténée au pseudo sous la forme d'un hash SHA1.
http://venom630.free.fr/keygenmefast/verifkeygenmefast.php?solution=sha1.

De ces informations, nous pouvons tirer plusieurs hypothèses:
- un binaire de base est utilisé
- certaines données vont changer

Pour vérifier ces hypothèses, c'est somme toute assez simple, on va faire usage de diff et hexdump comme ci-dessous:
$ diff -u <(hexdump -C ./keygenmefast1) <(hexdump -C ./keygenmefast2)
--- /dev/fd/63 2011-02-02 16:50:47.833079643 +0100
+++ /dev/fd/62 2011-02-02 16:50:47.833079643 +0100
@@ -91,25 +91,25 @@
 000005a0  00 00 bb 42 a0 04 08 01  c3 8a 13 8b 7c 24 08 81  |...B........|$..|
 000005b0  c7 05 00 00 00 8a 37 38  f2 0f 85 ba fe ff ff 68  |......78.......h|
 000005c0  83 a0 04 08 e8 a7 fd ff  ff 31 c0 c9 c3 55 89 e5  |.........1...U..|
-000005d0  8b 45 08 25 ff 00 00 00  34 49 25 3f 00 00 00 c9  |.E.%....4I%?....|
+000005d0  8b 45 08 25 ff 00 00 00  34 13 25 3f 00 00 00 c9  |.E.%....4.%?....|
 000005e0  c2 04 00 55 89 e5 81 ec  08 00 00 00 8b 4d 0c 8b  |...U.........M..|
 000005f0  75 08 01 ce 31 c0 e9 0b  00 00 00 4e 8a 16 81 e2  |u...1......N....|
 00000600  ff 00 00 00 01 d0 49 81  f9 ff ff ff ff 75 ec 34  |......I......u.4|
-00000610  08 25 3f 00 00 00 c9 c2  08 00 55 89 e5 b8 01 00  |.%?.......U.....|
+00000610  84 25 3f 00 00 00 c9 c2  08 00 55 89 e5 b8 01 00  |.%?.......U.....|
 00000620  00 00 8b 75 08 8b 4d 0c  e9 0f 00 00 00 31 db 8a  |...u..M......1..|
 00000630  1e 80 e3 ff f7 e3 25 ff  00 00 00 46 49 81 f9 ff  |......%....FI...|
-00000640  ff ff ff 75 e8 34 7e 25  3f 00 00 00 c9 c2 08 00  |...u.4~%?.......|
+00000640  ff ff ff 75 e8 34 2f 25  3f 00 00 00 c9 c2 08 00  |...u.4/%?.......|
 00000650  55 89 e5 81 ec 04 00 00  00 8b 75 08 8a 06 8b 4d  |U.........u....M|
 00000660  0c e9 09 00 00 00 46 8a  1e 38 c3 76 02 88 d8 49  |......F..8.v...I|
-00000670  81 f9 ff ff ff ff 75 ee  34 a7 50 e8 b0 fc ff ff  |......u.4.P.....|
+00000670  81 f9 ff ff ff ff 75 ee  34 ed 50 e8 b0 fc ff ff  |......u.4.P.....|
 00000680  e8 fb fc ff ff 25 3f 00  00 00 c9 c2 08 00 55 89  |.....%?.......U.|
 00000690  e5 31 db 8b 75 08 8b 4d  0c 49 e9 1d 00 00 00 31  |.1..u..M.I.....1|
 000006a0  d2 8a 16 51 53 68 02 00  00 00 52 e8 58 00 00 00  |...QSh....R.X...|
 000006b0  5b 59 01 c3 81 e3 ff 00  00 00 46 49 81 f9 ff ff  |[Y........FI....|
-000006c0  ff ff 75 db 89 d8 34 46  25 3f 00 00 00 c9 c2 08  |..u...4F%?......|
+000006c0  ff ff 75 db 89 d8 34 77  25 3f 00 00 00 c9 c2 08  |..u...4w%?......|
 000006d0  00 55 89 e5 31 c0 8b 75  08 81 fe 00 00 00 00 74  |.U..1..u.......t|
 000006e0  1e e8 9a fc ff ff 8b 4d  08 e9 07 00 00 00 51 e8  |.......M......Q.|
-000006f0  8c fc ff ff 59 49 75 f6  25 ff 00 00 00 34 39 25  |....YIu.%....49%|
+000006f0  8c fc ff ff 59 49 75 f6  25 ff 00 00 00 34 57 25  |....YIu.%....4W%|
 00000700  3f 00 00 00 c9 c2 04 00  55 89 e5 8b 4d 0c b8 01  |?.......U...M...|
 00000710  00 00 00 81 f9 00 00 00  00 74 0f 8b 5d 08 f7 e3  |.........t..]...|
 00000720  e9 02 00 00 00 f7 e3 49  75 fb c9 c2 08 00 90 90  |.......Iu.......|
@@ -145,11 +145,11 @@
 00001010  76 83 04 08 86 83 04 08  00 00 00 00 00 00 00 00  |v...............|
 00001020  55 74 69 6c 69 73 61 74  69 6f 6e 20 3a 20 25 73  |Utilisation : %s|
 00001030  20 3c 70 73 65 75 64 6f  3e 20 3c 63 6c 65 66 3e  | &ltpseudo> &ltclef>|
-00001040  0a 00 52 76 6e 64 35 56  78 4b 4e 44 45 6b 70 5a  |..Rvnd5VxKNDEkpZ|
-00001050  2b 31 67 71 53 54 2d 75  62 73 72 68 6d 43 42 33  |+1gqST-ubsrhmCB3|
-00001060  63 61 30 38 6c 37 55 6f  46 32 66 69 50 79 77 4d  |ca08l7UoF2fiPywM|
-00001070  6a 7a 57 59 41 58 48 49  51 34 4c 47 65 74 36 39  |jzWYAXHIQ4LGet69|
-00001080  4a 4f 00 42 72 61 76 6f  20 21 21 0a 00 00 00 00  |JO.Bravo !!.....|
+00001040  0a 00 31 68 69 2b 59 42  65 56 61 4e 4c 33 44 4a  |..1hi+YBeVaNL3DJ|
+00001050  4d 45 58 7a 32 6c 74 50  5a 2d 78 6d 34 6f 47 53  |MEXz2ltPZ-xm4oGS|
+00001060  75 76 71 77 49 55 66 79  43 36 6a 70 6b 62 4b 54  |uvqwIUfyC6jpkbKT|
+00001070  35 6e 63 4f 57 73 46 39  72 67 38 64 52 48 37 30  |5ncOWsF9rg8dRH70|
+00001080  51 41 00 42 72 61 76 6f  20 21 21 0a 00 00 00 00  |QA.Bravo !!.....|
 00001090  47 43 43 3a 20 28 55 62  75 6e 74 75 2f 4c 69 6e  |GCC: (Ubuntu/Lin|
 000010a0  61 72 6f 20 34 2e 34 2e  34 2d 31 34 75 62 75 6e  |aro 4.4.4-14ubun|
 000010b0  74 75 35 29 20 34 2e 34  2e 35 00 00 2e 73 68 73  |tu5) 4.4.5...shs|

Ainsi on remarque qu'on a 6 octets qui changent et une chaîne de 64 caractères aussi qui est générée. Ca n'a pas l'air d'être de la base64 (ne finit pas par des =).

On récupère donc les différents offsets de ces diffs :
- offsets octets: 0x5d9, 0x610, 0x646, 0x679, 0x6c7, 0x6fe
- offset string: 0x1042
Et les valeurs correspondantes:
- octets: 0x13, 0x84, 0x2f, 0xed, 0x77, 0x57
- string:1hi+YBeVaNL3DJMEXz2ltPZ-xm4oGSuvqwIUfyC6jpkbKT5ncOWsF9rg8dRH70QAs
On verra à quoi ils servent par la suite.

Analysons la routine principale:

.text:08048450 ; =============== S U B R O U T I N E =======================================
.text:08048450
.text:08048450 ; Attributes: bp-based frame
.text:08048450
.text:08048450 main            proc near               ; DATA XREF: start+17 o
.text:08048450
.text:08048450 argc            = dword ptr  8
.text:08048450 argv            = dword ptr  0Ch
.text:08048450
.text:08048450                 push    ebp
.text:08048451                 mov     ebp, esp
.text:08048453                 mov     eax, [ebp+argc]
.text:08048456                 cmp     eax, 3
.text:0804845B                 jnb     short loc_8048485
.text:0804845D                 mov     esi, [ebp+argv]
.text:08048460                 mov     edi, [esi]
.text:08048462                 sub     esp, 8
.text:08048468                 mov     esi, offset format ; "Utilisation : %s  \n"
.text:0804846D                 mov     [esp], esi      ; s
.text:08048470                 mov     [esp+4], edi
.text:08048474                 call    _printf
.text:08048479
.text:08048479 main_exit:                              ; CODE XREF: main+6F j
.text:08048479                                         ; main+9B j ...
.text:08048479                 mov     eax, 1
.text:0804847E                 mov     ebx, 1          ; status
.text:08048483                 int     80h             ; LINUX - sys_exit
.text:08048485 ; ---------------------------------------------------------------------------
.text:08048485
.text:08048485 loc_8048485:                            ; CODE XREF: main+B j
.text:08048485                 sub     esp, 0Ch
.text:0804848B                 mov     ebx, [ebp+argv]
.text:0804848E                 mov     edx, ebx
.text:08048490                 add     edx, 4
.text:08048496                 mov     edx, [edx]
.text:08048498                 mov     [esp+4], edx    ; argv[1] <=> username
.text:0804849C                 mov     edx, ebx
.text:0804849E                 add     edx, 8
.text:080484A4                 mov     edx, [edx]
.text:080484A6                 mov     [esp+8], edx
.text:080484AA                 mov     esi, [esp+8]    ; argv[2] <=> usersserial
.text:080484AE                 push    esi             ; userserial
.text:080484AF                 call    _strlen
.text:080484B4                 add     esp, 4
.text:080484BA                 cmp     eax, 6
.text:080484BF                 jnz     short main_exit
.text:080484C1                 mov     edi, [esp+4]
.text:080484C5                 push    edi             ; username
.text:080484C6                 call    _strlen
.text:080484CB                 add     esp, 4
.text:080484D1                 mov     [esp], eax      ; szUsername
.text:080484D4                 push    eax
.text:080484D5                 call    get_offset0
.text:080484DA                 mov     ebx, offset hash ; "1hi+YBeVaNL3DJMEXz2ltPZ-xm4oGSuvqwIUfyC"...
.text:080484DF                 add     ebx, eax
.text:080484E1                 mov     dl, [ebx]
.text:080484E3                 mov     edi, [esp+8]    ; argv[2] <=> userserial
.text:080484E7                 mov     dh, [edi]
.text:080484E9                 cmp     dl, dh
.text:080484EB                 jnz     short main_exit
.text:080484ED                 mov     edi, [esp]      ; szUsername
.text:080484F0                 push    edi
.text:080484F1                 mov     edi, [esp+8]    ; argv[2] <=> userserial
.text:080484F5                 push    edi
.text:080484F6                 call    get_offset1
.text:080484FB                 mov     ebx, offset hash ; "1hi+YBeVaNL3DJMEXz2ltPZ-xm4oGSuvqwIUfyC"...
.text:08048500                 add     ebx, eax
.text:08048502                 mov     dl, [ebx]
.text:08048504                 mov     edi, [esp+8]
.text:08048508                 inc     edi
.text:08048509                 mov     dh, [edi]
.text:0804850B                 cmp     dl, dh
.text:0804850D                 jnz     main_exit
.text:08048513                 mov     edi, [esp]      ; szUsername
.text:08048516                 push    edi
.text:08048517                 mov     edi, [esp+8]    ; argv[2] <=> userserial
.text:0804851B                 push    edi
.text:0804851C                 call    get_offset2
.text:08048521                 mov     ebx, offset hash ; "1hi+YBeVaNL3DJMEXz2ltPZ-xm4oGSuvqwIUfyC"...
.text:08048526                 add     ebx, eax
.text:08048528                 mov     dl, [ebx]
.text:0804852A                 mov     edi, [esp+8]
.text:0804852E                 add     edi, 2
.text:08048534                 mov     dh, [edi]
.text:08048536                 cmp     dl, dh
.text:08048538                 jnz     main_exit
.text:0804853E                 mov     edi, [esp]      ; szUsername
.text:08048541                 push    edi
.text:08048542                 mov     edi, [esp+8]    ; argv[2] <=> userserial
.text:08048546                 push    edi
.text:08048547                 call    get_offset3
.text:0804854C                 mov     ebx, offset hash ; "1hi+YBeVaNL3DJMEXz2ltPZ-xm4oGSuvqwIUfyC"...
.text:08048551                 add     ebx, eax
.text:08048553                 mov     dl, [ebx]
.text:08048555                 mov     edi, [esp+8]
.text:08048559                 add     edi, 3
.text:0804855F                 mov     dh, [edi]
.text:08048561                 cmp     dl, dh
.text:08048563                 jnz     main_exit
.text:08048569                 mov     edi, [esp]      ; szUsername
.text:0804856C                 push    edi
.text:0804856D                 mov     edi, [esp+8]    ; argv[2] <=> userserial
.text:08048571                 push    edi
.text:08048572                 call    get_offset4
.text:08048577                 mov     ebx, offset hash ; "1hi+YBeVaNL3DJMEXz2ltPZ-xm4oGSuvqwIUfyC"...
.text:0804857C                 add     ebx, eax
.text:0804857E                 mov     dl, [ebx]
.text:08048580                 mov     edi, [esp+8]    ; argv[2] <=> userserial
.text:08048584                 add     edi, 4
.text:0804858A                 mov     dh, [edi]
.text:0804858C                 cmp     dl, dh
.text:0804858E                 jnz     main_exit
.text:08048594                 mov     edi, [esp+4]    ; argv[1] <=> username
.text:08048598                 xor     edx, edx
.text:0804859A                 mov     dl, [edi]
.text:0804859C                 push    edx
.text:0804859D                 call    get_offset5
.text:080485A2                 mov     ebx, offset hash ; "1hi+YBeVaNL3DJMEXz2ltPZ-xm4oGSuvqwIUfyC"...
.text:080485A7                 add     ebx, eax
.text:080485A9                 mov     dl, [ebx]
.text:080485AB                 mov     edi, [esp+8]
.text:080485AF                 add     edi, 5
.text:080485B5                 mov     dh, [edi]
.text:080485B7                 cmp     dl, dh
.text:080485B9                 jnz     main_exit
.text:080485BF                 push    offset aBravo   ; "Bravo !!\n"
.text:080485C4                 call    _printf
.text:080485C9                 xor     eax, eax
.text:080485CB                 leave
.text:080485CC                 retn
.text:080485CC main            endp ; sp-analysis failed

On remarque que des blocs ont le même pattern:
- appel d'une fonction qui calcule un dword
- ajout de ce dword à hash
- comparaison du caractère calculé et du caractère entré
    -> si incorrect on sort du keygen
    -> si correct on continue
- lorsqu'on arrive à la fin, on obtient un "Bravo !!\n" ^^

Maintenant, en analysant chaque fonction de calcul d'offset, on remarque les choses suivantes:
get_offset0:
.text:080485D8                 xor     al, 13h
.text:080485DA                 and     eax, 3Fh
.text:080485DF                 leave
.text:080485E0                 retn    4
.text:080485E0 get_offset0     endp
get_offset1:
.text:0804860F                 xor     al, 84h
.text:08048611                 and     eax, 3Fh
.text:08048616                 leave
.text:08048617                 retn    8
.text:08048617 get_offset1     endp
get_offset2:
.text:08048645                 xor     al, 2Fh
.text:08048647                 and     eax, 3Fh
.text:0804864C                 leave
.text:0804864D                 retn    8
.text:0804864D get_offset2     endp
get_offset3:
.text:08048678                 xor     al, 0EDh
.text:0804867A                 push    eax             ; seed
.text:0804867B                 call    _srand
.text:08048680                 call    _rand
.text:08048685                 and     eax, 3Fh
.text:0804868A                 leave
.text:0804868B                 retn    8
.text:0804868B get_offset3     endp
get_offset4:
.text:080486C6                 xor     al, 77h
.text:080486C8                 and     eax, 3Fh
.text:080486CD                 leave
.text:080486CE                 retn    8
.text:080486CE get_offset4     endp
get_offset5:
.text:080486FF                 and     eax, 3Fh
.text:08048704                 leave
.text:08048705                 retn    4
.text:08048705 get_offset5     endp
Vous vous souvenez de nos 6 octets "changeant"?
- octets: 0x13, 0x84, 0x2f, 0xed, 0x77, 0x57
Eh oui, ce sont les "clés" utilisées par les fonctions de calcul d'offset.

En fait, le keygenme va faire les choses suivantes:
- vérification de la longueur de l'username
- vérification de la longueur du serial
    -> bye bye si pas égal à 6
- calculs des offsets en utilisant les 6 bytes
- calcul du sérial
- comparaison entre le serial rentré et le sérial calculé
- bravo ou exit

On se rend compte que pour calculer les différents offsets, le serial est utilisé.
Je suis parti dans l'idée d'utiliser l'username pour générer le serial.

Et voilà, reste plus qu'à coder un beau keygen ;).

Have fun,

m_101

- Mon keygen: Keygen

dimanche 3 octobre 2010

[Wargame] Blackbox at smashthestack.org : level 1 (login)

Hello!

Enfin de retour sur Internet après quelques semaines d'absence :).
Je me suis remis à faire des challenges et je vais ici parler d'un wargame qui m'a
bien plu : http://blackbox.smashthestack.org:85/.
Il y a des failles classiques mais présentées d'une manière un peu plus intelligente.

Nous allons commencer par le level1 :
level1@blackbox:~$ ./login2 
Username: aa
Password: aa
Invalid username or password

A partir de là on peut déjà avoir une petite idée de ce que le programme fait,
il y a surement une utilisation d'un strcmp() ou strncmp() simple (nous somme
au level1 après tout :)).

Voyons le code ASM.

level1@blackbox:~$ gdb login2 
GNU gdb 6.4.90-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".

(gdb) disassemble main
Dump of assembler code for function main:
0x0804827a <main+0>: lea    0x4(%esp),%ecx
0x0804827e <main+4>: and    $0xfffffff0,%esp
0x08048281 <main+7>: pushl  0xfffffffc(%ecx)
0x08048284 <main+10>: push   %ebp
0x08048285 <main+11>: mov    %esp,%ebp
0x08048287 <main+13>: push   %ebx
0x08048288 <main+14>: push   %ecx
0x08048289 <main+15>: sub    $0x30,%esp
0x0804828c <main+18>: lea    0xfffffff4(%ebp),%eax
0x0804828f <main+21>: mov    %eax,(%esp)
0x08048292 <main+24>: call   0x8072ec0 <_ZNSsC1Ev>
0x08048297 <main+29>: lea    0xfffffff0(%ebp),%eax
0x0804829a <main+32>: mov    %eax,(%esp)
0x0804829d <main+35>: call   0x8072ec0 <_ZNSsC1Ev>
0x080482a2 <main+40>: movl   $0x80ffe48,0x4(%esp)
0x080482aa <main+48>: movl   $0x8130f60,(%esp)
0x080482b1 <main+55>: call   0x806d8f0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
0x080482b6 <main+60>: lea    0xfffffff4(%ebp),%eax
0x080482b9 <main+63>: mov    %eax,0x4(%esp)
0x080482bd <main+67>: movl   $0x8130ec0,(%esp)
0x080482c4 <main+74>: call   0x806b2e0 <_ZSt7getlineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RSbIS4_S5_T1_E>
0x080482c9 <main+79>: movl   $0x80ffe53,0x4(%esp)
0x080482d1 <main+87>: movl   $0x8130f60,(%esp)
0x080482d8 <main+94>: call   0x806d8f0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
0x080482dd <main+99>: lea    0xfffffff0(%ebp),%eax
0x080482e0 <main+102>: mov    %eax,0x4(%esp)
0x080482e4 <main+106>: movl   $0x8130ec0,(%esp)
0x080482eb <main+113>: call   0x806b2e0 <_ZSt7getlineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RSbIS4_S5_T1_E>
0x080482f0 <main+118>: movl   $0x80ffe5e,0x4(%esp)
0x080482f8 <main+126>: lea    0xfffffff4(%ebp),%eax
0x080482fb <main+129>: mov    %eax,(%esp)
0x080482fe <main+132>: call   0x80483ee <_ZSteqIcSt11char_traitsIcESaIcEEbRKSbIT_T0_T1_EPKS3_>
0x08048303 <main+137>: xor    $0x1,%al
0x08048305 <main+139>: test   %al,%al
0x08048307 <main+141>: jne    0x8048328 <main+174>
0x08048309 <main+143>: movl   $0x80ffe65,0x4(%esp)
0x08048311 <main+151>: lea    0xfffffff0(%ebp),%eax
0x08048314 <main+154>: mov    %eax,(%esp)
0x08048317 <main+157>: call   0x80483ee <_ZSteqIcSt11char_traitsIcESaIcEEbRKSbIT_T0_T1_EPKS3_>
0x0804831c <main+162>: xor    $0x1,%al
0x0804831e <main+164>: test   %al,%al
0x08048320 <main+166>: jne    0x8048328 <main+174>
0x08048322 <main+168>: movb   $0x1,0xffffffe7(%ebp)
0x08048326 <main+172>: jmp    0x804832c <main+178>
0x08048328 <main+174>: movb   $0x0,0xffffffe7(%ebp)
0x0804832c <main+178>: movzbl 0xffffffe7(%ebp),%eax
0x08048330 <main+182>: test   %al,%al
0x08048332 <main+184>: je     0x8048366 <main+236>
0x08048334 <main+186>: movl   $0x80ffe6e,0x4(%esp)
0x0804833c <main+194>: movl   $0x8130f60,(%esp)
0x08048343 <main+201>: call   0x806d8f0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
0x08048348 <main+206>: movl   $0x806e0c0,0x4(%esp)
0x08048350 <main+214>: mov    %eax,(%esp)
0x08048353 <main+217>: call   0x806bf10 <_ZNSolsEPFRSoS_E>
0x08048358 <main+222>: movl   $0x80ffe80,(%esp)
0x0804835f <main+229>: call   0x80b5ab0 <system>
0x08048364 <main+234>: jmp    0x804838a <main+272>
0x08048366 <main+236>: movl   $0x80ffe88,0x4(%esp)
0x0804836e <main+244>: movl   $0x8130f60,(%esp)
0x08048375 <main+251>: call   0x806d8f0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
0x0804837a <main+256>: movl   $0x806e0c0,0x4(%esp)
0x08048382 <main+264>: mov    %eax,(%esp)
0x08048385 <main+267>: call   0x806bf10 <_ZNSolsEPFRSoS_E>
0x0804838a <main+272>: lea    0xfffffff0(%ebp),%eax
0x0804838d <main+275>: mov    %eax,(%esp)
0x08048390 <main+278>: call   0x8074e40 <_ZNSsD1Ev>
0x08048395 <main+283>: jmp    0x80483ad <main+307>
0x08048397 <main+285>: mov    %eax,0xffffffdc(%ebp)
0x0804839a <main+288>: mov    0xffffffdc(%ebp),%ebx
0x0804839d <main+291>: lea    0xfffffff0(%ebp),%eax
0x080483a0 <main+294>: mov    %eax,(%esp)
0x080483a3 <main+297>: call   0x8074e40 <_ZNSsD1Ev>
0x080483a8 <main+302>: mov    %ebx,0xffffffdc(%ebp)
0x080483ab <main+305>: jmp    0x80483c5 <main+331>
0x080483ad <main+307>: lea    0xfffffff4(%ebp),%eax
0x080483b0 <main+310>: mov    %eax,(%esp)
0x080483b3 <main+313>: call   0x8074e40 <_ZNSsD1Ev>
0x080483b8 <main+318>: mov    $0x0,%eax
0x080483bd <main+323>: mov    %eax,0xffffffe0(%ebp)
0x080483c0 <main+326>: jmp    0x80483e1 <main+359>
0x080483c2 <main+328>: mov    %eax,0xffffffdc(%ebp)
0x080483c5 <main+331>: mov    0xffffffdc(%ebp),%ebx
0x080483c8 <main+334>: lea    0xfffffff4(%ebp),%eax
0x080483cb <main+337>: mov    %eax,(%esp)
0x080483ce <main+340>: call   0x8074e40 <_ZNSsD1Ev>
0x080483d3 <main+345>: mov    %ebx,0xffffffdc(%ebp)
0x080483d6 <main+348>: mov    0xffffffdc(%ebp),%eax
0x080483d9 <main+351>: mov    %eax,(%esp)
0x080483dc <main+354>: call   0x80a5180 <_Unwind_Resume>
0x080483e1 <main+359>: mov    0xffffffe0(%ebp),%eax
0x080483e4 <main+362>: add    $0x30,%esp
0x080483e7 <main+365>: pop    %ecx
0x080483e8 <main+366>: pop    %ebx
0x080483e9 <main+367>: pop    %ebp
0x080483ea <main+368>: lea    0xfffffffc(%ecx),%esp
0x080483ed <main+371>: ret    
End of assembler dump.

On peut remarquer qu'il y a des noms de fonctions étranges, non compréhensible.
Après un peu de recherche, on se rend vite compte que c'est du à un mécanisme qu'on
appelle le name mangling. Ca permet entre autre le polymorphisme. C'est donc un
bon indicateur que le language utilisé est du C++.
Vu que le programme a été compilé avec G++, on va voir comment démangleer ça.

Le tool nécessaire au demangling de noms G++ est c++filt qui est fourni dans la
suite GNU.

Par exemple :
level1@blackbox:~$ c++filt _ZNSsC1Ev
std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string()

La fonction que l'on cherche est l'implémentation de l'opérateur ==.

Après un peu de recherche :
level1@blackbox:~$ c++filt _ZSteqIcSt11char_traitsIcESaIcEEbRKSbIT_T0_T1_EPKS3_
bool std::operator==<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char const*)

On va voir que l'opérateur == est utilisé 2 fois : une fois pour le nom de l'user
et une fois pour le password.

La partie intéressante :
0x080482f0 <main+118>: movl   $0x80ffe5e,0x4(%esp)
0x080482f8 <main+126>: lea    0xfffffff4(%ebp),%eax
0x080482fb <main+129>: mov    %eax,(%esp)
0x080482fe <main+132>: call   0x80483ee <_ZSteqIcSt11char_traitsIcESaIcEEbRKSbIT_T0_T1_EPKS3_>
0x08048303 <main+137>: xor    $0x1,%al
0x08048305 <main+139>: test   %al,%al
0x08048307 <main+141>: jne    0x8048328 <main+174>
0x08048309 <main+143>: movl   $0x80ffe65,0x4(%esp)
0x08048311 <main+151>: lea    0xfffffff0(%ebp),%eax
0x08048314 <main+154>: mov    %eax,(%esp)
0x08048317 <main+157>: call   0x80483ee <_ZSteqIcSt11char_traitsIcESaIcEEbRKSbIT_T0_T1_EPKS3_>

On a deux emplacement statique dans le binaire : 0x80ffe5e et 0x80ffe65.

On les extraits et on a : level2 et PassFor2. Comme vous pouvez vous en douter,
on vient de trouver l'utilisateur et le mot de passe pour le niveau suivant ;).

Pour la reconstruction du code, on doit avoir quelque chose de pas très loin de ça :
#include <iostream>
#include <string>

using namespace std;

int main (int argc, char *argv[]) {
    string username, password;

    cout << "Username: ";
    cin >> username;
    cout << endl << "Password: ";
    cin >> password;
    
    if (username == "level2" && password == "PassFor2")
        system("/bin/sh");
    else
        cout << "Invalid username or password";
    
    return 0;
}

Et voilà :).
Nous passons directement au niveau 3 vu que le niveau 2 est un simple buffer overflow (utilisez pattern_create, pattern_offset et gdb comme expliqués dans les autres articles ;)).

m_101

dimanche 13 juin 2010

Fast analysis of Orange Hadopi Executable

I've read Bluetouff article here. From that point, I decided to take a rapid look at how is that possible?

Be careful, this must be considered as a MALWARE!

For starters, the installer is packed with UPX but it doesn't need any serial for the installation to take place. The serial is only there to activate the software.

After installing the software, you get a bunch of executables :
- cdtsvc.exe : service of download control
- cdtsvc64.exe : same as cdtsvc.exe but for 64 bits
- cdtupd.exe : probably the executable for checking updates

It seems that none of the executables are packed or whatsoever.

Just analyzing cdtsvc.exe, I got quite a bunch of informations on the executable itself.
Looking at C string references and unicode strings, you get to have those informations :

Path :
C:\\Documents and Settings\\jbroutin\\Mes documents\\Visual Studio 2008\\Projects\\ddp-hadopi\\hadopi-client-gui\\trunk\\Release\\cdtsvc.pdb

RSA Key :
BgIAAACkAABSU0ExAAQAAAEAAQCzLXpRE/3Y3n9F1lf2wlRsQGzgs4gfBRNj/PmPArxOtJ1Z1ra8bTktvDCyLsImEc12d6DvVeYsAIgjMZNxQqczzhEla7ZAXQXOOBo38ZUZot961pXx76GCRMAfAYz2S3O79bBGCtJ5wh3UcmvAUcEmdqLzFebBw7Ef+qxyfgamyw==

URL :
http://update-cdt.nordnet.fr/hadopi-server-technical-ws-1

The author of the software is named jbroutin, chances are that his last name is Broutin. It clearly show HADOPI projects and using Visual Studio 2008.
Looking at the code, we can confirm that he's using C++ or some other kind of oriented object langage (there are lots of get() and set()).

Nordnet contacted Bluetouff so the URL makes sense. It might be the URL for the Java Applet Bluetouff was talking about. I just can't imagine the damage it could be done injecting code in it.

For the RSA Key, I don't know what it's for for now. The RSA Key shown here is encoded using Base64 for those who were asking themselves.

Looking more, we see stuffs like these :
hadopi

Ok we now know that we really are dealing with some HADOPI crap.

going further :

HTTP/1.0

[...]

Content-Type: application/x-www-form-urlencoded

No wonder that Bluetouff get to have a lot of informations using Wireshark ...

We haven't even looked at the code yet that we have quite some infos ...


Cross-referencing the string with "HTTP/1.0" we land in a subroutine at address 0x405574.
This routine seems to construct some kind of URLs and make a request with it.

Looking at where it's called from, you get a bunch of function but at the top is 0x4027EB. This seems to be the main routine which set up the service and stuffs.

I look at one of the functions that called directly 0x405574 and there was 0x406991 : this function seems to check for some kind of updates and licence check too but I dunno, what would be cdtupd.exe then?

From 0x406991 I got this sequence of numbers :
0.2.1.1.3
0.2.1.3.2

33.1.1.1
33.2.1.3
33.2.2.1
33.2.1.2

I don't know about the first two (they might be OID, reverse DNS, or something else).
For the second groups, they look like IPs.

I tried to have a DNS query with all of thoses, but I only get an answer for 33.1.1.1 :
m_101@m_101:~$ nslookup 33.1.1.1
Server:  192.168.1.1
Address: 192.168.1.1#53

Non-authoritative answer:
*** Can't find 1.1.1.33.in-addr.arpa.: No answer

Authoritative answers can be found from:
33.in-addr.arpa
 origin = CON1R.NIPR.MIL
 mail addr = DANIEL\.KNOPPS.DISA.MIL
 serial = 2010061101
 refresh = 10800
 retry = 900
 expire = 1209600
 minimum = 10800

I also found some strange stuffs by cross referencing an API called CreateToolhelp32Snapshot() . I landed in routine 0x40C225, why the hell is the software going around all the processes?
I didnt dig further but it seems strange to me.

That's all I got for today, it took me around 20 minutes to get all that ... what about the people who spent hours on this? They might already have a clone or something else entirely.

m_101

- link : Orange HADOPI Software
- link : Installed files

mercredi 2 juin 2010

BuKoG KeyGenMe #1

Today, I ate some little keygenme, quite interesting in fact for how it was conceived.

First of all, since it's level 2 :
- no packer
- no obfuscator
- no protections but serial checking

It was pretty straightforward to find the serial checking routine. It's located in DialogFunc().

From there, we just need to identify username and serial fields using manual boron tagging.
After that, stumble upon an interesting routine :

.text:004010FB serial_check_wrapper proc near          ; CODE XREF: DialogFunc+93
.text:004010FB                 call    serial_check
.text:00401100                 retn
.text:00401100 serial_check_wrapper endp

As we can see, it's wrapping a call to some routine, which is in fact the serial checking routine.
Why is this wrapped? We'll see that later ;) .
Anyway, the serial checking routine is as followed :

.text:00401200 serial_check    proc near               ; CODE XREF: serial_check_wrapper
.text:00401200                 push    offset username
.text:00401205                 call    hash
.text:0040120A                 push    eax
.text:0040120B                 push    offset serial
.text:00401210                 call    hash
.text:00401215                 pop     ebx
.text:00401216                 sub     ebx, eax
.text:00401218                 mov     [ebp-8], bl     ; change return address
.text:0040121B                 mov     eax, 0
.text:00401220                 retn
.text:00401220 serial_check    endp

Having identified the username and serial buffers previously, it's get a lot easier.
We see that the return address depend on the last byte of the following operation : hash(username) - hash(serial) .
Since a byte contains 256 values, it means that we'll have 256 return values too.

We don't need to launch the crackme yet. Static analysis is a bit harder but way more challenging :) .
So we called serial_check_wrapper() located at address 0x004010FB.
We should return to 0x00401100 after the serial_check() routine but since we modify the last byte, it never happens.
The address we return should be of the form : 0x004011XX where XX is our modified byte.

Looking after the serial_check_wrapper() routine, we see a bunch of returns and an interesting line around 0x00401113.
So 255 out of 256 we get the "Wrong Serial" message.
Before making the keygen, we need to analyse the hash() function.

The hash function is as follow :
.text:00401247 hash            proc near               ; CODE XREF: serial_check+5
.text:00401247                                         ; serial_check+10
.text:00401247
.text:00401247 str             = dword ptr  8
.text:00401247
.text:00401247                 push    ebp
.text:00401248                 mov     ebp, esp
.text:0040124A                 mov     eax, 0FFFFFFFFh
.text:0040124F                 mov     esi, [ebp+str]
.text:00401252                 xor     edx, edx
.text:00401254
.text:00401254 loc_401254:                             ; CODE XREF: hash+2D
.text:00401254                 mov     ebx, eax
.text:00401256                 shr     ebx, 8
.text:00401259                 mov     ecx, eax
.text:0040125B                 and     ecx, 0FFh
.text:00401261                 mov     dl, [esi]
.text:00401263                 cmp     dl, 0
.text:00401266                 jz      short loc_401276
.text:00401268                 xor     edx, ecx
.text:0040126A                 xor     ebx, dword_4030CC[edx*4]
.text:00401271                 mov     eax, ebx
.text:00401273                 inc     esi
.text:00401274                 jmp     short loc_401254
.text:00401276 ; ---------------------------------------------------------------------------
.text:00401276
.text:00401276 loc_401276:                             ; CODE XREF: hash+1F
.text:00401276                 xor     eax, 0FFFFFFFFh
.text:00401279                 leave
.text:0040127A                 retn    4
.text:0040127A hash            endp

We can see here that it's using some array.
Where is it generated?
We can easily try to locate it using cross referencing, it happens that only hash() use it.
Let's see the data section. Just before the used array, there is an hInstance variable.
Cross referencing it, happens to land us in a CRC32 802.3 routine :

.text:00401221 crc32_ieee8023  proc near               ; CODE XREF: start
.text:00401221                 mov     ecx, 100h
.text:00401226                 mov     edx, 0EDB88320h
.text:0040122B
.text:0040122B loc_40122B:                             ; CODE XREF: crc32_ieee8023+23
.text:0040122B                 lea     eax, [ecx-1]
.text:0040122E                 push    ecx
.text:0040122F                 mov     ecx, 8
.text:00401234
.text:00401234 loc_401234:                             ; CODE XREF: crc32_ieee8023:loc_40123A
.text:00401234                 shr     eax, 1
.text:00401236                 jnb     short loc_40123A
.text:00401238                 xor     eax, edx
.text:0040123A
.text:0040123A loc_40123A:                             ; CODE XREF: crc32_ieee8023+15
.text:0040123A                 loop    loc_401234
.text:0040123C                 pop     ecx
.text:0040123D                 mov     hInstance[ecx*4], eax
.text:00401244                 loop    loc_40122B
.text:00401246                 retn
.text:00401246 crc32_ieee8023  endp

The idenfitication process of this function went through reversing and searching about that magic number : 0x0EDB88320.

So we have all we need for a keygen :
- hash() use crc32 802.3 table
- serial_check() returns to 0x004011XX
- XX must be equal to 0x13 if we want correct message
- (hash(username) - hash(serial)) & 0xFF must be equal to 0x13

Using all this, I used the bruteforce approach. I only bruteforce the first 1000 numbers but it's enough.
Here is the keygen :

// BuKoBG Keygenme #1

#include 
#include 

// crc32
unsigned int* crc32 (unsigned int polynom) {
    unsigned short int carry;
    int i, j;
    size_t n;
    unsigned int *crc32_table;
      
    n = 256;
    crc32_table = calloc (n, sizeof(*crc32_table));
    if (!crc32_table)
        return NULL;
    
    for (i = n - 1; i >= 0; i--) {
        crc32_table[i] = i;
        for (j = 7; j >= 0; j--) {
            carry = crc32_table[i] & 1;
            crc32_table[i] >>= 1;
            if (carry)
                crc32_table[i] ^= polynom;
        }
    }

    return crc32_table;
}

// hash
unsigned int hash (unsigned char *str, size_t len) {
    size_t i, index;
    unsigned int hashed = -1, *hashtable;
    
    // check pointers
    if (!str || !len)
        return -1;

    // generate crc32 IEEE 802.3 table
    hashtable = crc32(0xedb88320);

    // generate serial
    i = 0;
    index = 0;
    while (str[i] && i < len) {
        index = str[i] ^ (hashed & 0xff);
        hashed = (hashed >> 8) ^ hashtable[index];
        ++i;
    }

    hashed ^= -1;
    
    // clean up
    free(hashtable);

    return hashed;
}

// keygen
unsigned int keygen (unsigned char *username, size_t userlen) {
    unsigned int hash1, hash2, key = -1, licence, serial;
    unsigned char serial_str[4] = {0};
    size_t seriallen = 4;

    hash1 = hash (username, userlen);
    
    // bruteforce serial
    for (licence = 0, serial = 0; (licence & 0xff) != 0x13; serial++) {
        // convert integer to string
        snprintf(serial_str, seriallen, "%03u", serial);
        hash2 = hash (serial_str, seriallen);
        licence = hash1 - hash2;
    }

    return --serial;
}

int main (int argc, char *argv[]) {
    unsigned char username[256] = {0};
    unsigned int serial;
    size_t len = 256;

    printf("Username : ");
    gets(username);
    serial = keygen (username, len);
    // limit search to first 1000 numbers
    if (serial < 1000)
        printf("\nSerial   : %03u\n", serial);
    else
        printf("No serial found\n");

    return 0;
}

Hope you enjoyed this small snack :) .

m_101

- link : BuKoG KeyGenMe #1

vendredi 28 mai 2010

BratAlarm's Just a little crackme

It's been some time since I updated my blog but today I got bored and reversed a very little crackme.
It is the BratAlarm "Just a little crackme" that I'm going to talk about.
It was a bit interesting in the sense that you need to have basic mathematics knowledge.

Basic maths knowledge

Complex numbers are used throughout the whole keygenme to generate the serial.

Complex number basic operations used are the following :
multiplication : (a, b) * (c, d) = (a*c - b*d, b*c + a*d) = (a + i*b) * (c + i*d)
addition : (a, b) + (c, d) = (a + c, b + d) = (a + i*b) + (c + i*d)

Where is the serial checking and generation located?
First of all, this keygenme wasn't packed nor obfuscated in any sort so it was pretty simple to find out
where the interesting parts are :
- API analysis : DialogBoxParamA
- Strings

Using these twos clues, we can extract the fact that everything happens in DialogFunc() and that it is using complex numbers.

How is the serial generated then?

First we need to know what's the serial pattern.
From the following disassembly :
.text:00401129                 mov     serial_part1_real, eax
.text:0040112E                 add     edi, 9
.text:00401131                 mov     byte ptr [edi+8], 0
.text:00401135                 push    edi
.text:00401136                 call    str2num
.text:0040113B                 mov     serial_part1_imaginary, eax
.text:00401140                 add     edi, 9
.text:00401143                 mov     byte ptr [edi+8], 0
.text:00401147                 push    edi
.text:00401148                 call    str2num
.text:0040114D                 mov     serial_part2_real, eax
.text:00401152                 add     edi, 9
.text:00401155                 mov     byte ptr [edi+8], 0
.text:00401159                 push    edi
.text:0040115A                 call    str2num
.text:0040115F                 mov     serial_part2_imaginary, eax

We can safely say that the serial is of some form like that :
AAAAAAAA-BBBBBBBB-CCCCCCCC-DDDDDDDD

The serial is generated in many parts :
* Get username
* Generate serial
- Do a checksum of the string and use it to generate a real and imaginary part, you get Za
- Use some magic to generate a value for a second complex number, you get Zb
- Then we must resolve some equation to get the serial

These are the functions needed to create the serial :
- strsum() : a checksum using all the characters in a string
- str2num() : Convert hexdigit in ASCII to the number form
- gen_magic() : Generate some value using a magic number and the string

The checksum function :
.text:004010B5 strsum:                                 ; CODE XREF: DialogFunc+91
.text:004010B5                 mov     dl, [esi]
.text:004010B7                 add     eax, edx
.text:004010B9                 inc     esi
.text:004010BA                 test    edx, edx
.text:004010BC                 jnz     short strsum


The generation of first number C1 :
.text:004010BE                 mov     ComplexNumber1_real, eax ; Re(C1)
.text:004010C3                 dec     eax
.text:004010C4                 imul    eax, 3
.text:004010C7                 mov     ComplexNumber1_imaginary, eax ; Im(C1)

Magic happens!
.text:004010D8 gen_magic2:                             ; CODE XREF: DialogFunc+B7 j
.text:004010D8                 mov     dl, [esi]
.text:004010DA                 xor     eax, edx
.text:004010DC                 rol     eax, 5
.text:004010DF                 inc     esi
.text:004010E0                 test    edx, edx
.text:004010E2                 jnz     short gen_magic2

Generation of C2 :
.text:004010E4                 xor     edx, edx
.text:004010E6                 mov     ecx, 7A69h
.text:004010EB                 div     ecx
.text:004010ED                 mov     ComplexNumber2_real, edx ; Re(C2)
.text:004010F3                 and     eax, 0FFFh
.text:004010F8                 mov     ComplexNumber2_imaginary, eax ; Im(C2)

str2num()
.text:004012C5 str2num         proc near               ; CODE XREF: DialogFunc+F9 p
.text:004012C5                                         ; DialogFunc+10B p ...
.text:004012C5
.text:004012C5 arg_0           = dword ptr  8
.text:004012C5
.text:004012C5                 push    ebp
.text:004012C6                 mov     ebp, esp
.text:004012C8                 pusha
.text:004012C9                 xor     eax, eax
.text:004012CB                 xor     edx, edx
.text:004012CD                 mov     ecx, 8
.text:004012D2                 mov     esi, [ebp+arg_0]
.text:004012D5
.text:004012D5 loc_4012D5:                             ; CODE XREF: str2num+28 j
.text:004012D5                 mov     dl, [esi]
.text:004012D7                 test    dl, dl
.text:004012D9                 jz      short loc_4012EF
.text:004012DB                 sub     dl, 30h ; '0'
.text:004012DE                 cmp     dl, 0Ah
.text:004012E1                 jb      short loc_4012E6
.text:004012E3                 sub     dl, 7           ; hex digit part
.text:004012E6
.text:004012E6 loc_4012E6:                             ; CODE XREF: str2num+1C j
.text:004012E6                 shl     eax, 4
.text:004012E9                 or      eax, edx
.text:004012EB                 inc     esi
.text:004012EC                 dec     ecx
.text:004012ED                 jnz     short loc_4012D5
.text:004012EF
.text:004012EF loc_4012EF:                             ; CODE XREF: str2num+14 j
.text:004012EF                 mov     [ebp+arg_0], eax
.text:004012F2                 popa
.text:004012F3                 mov     eax, [ebp+arg_0]
.text:004012F6                 leave
.text:004012F7                 retn    4
.text:004012F7 str2num         endp

Now going to check the serial generation part.
It is done in two routines :

* First routine
.text:004011F1 gen_serial_part1 proc near              ; CODE XREF: DialogFunc+15C
.text:004011F1
.text:004011F1 ComplexResult2  = byte ptr -10h
.text:004011F1 ComplexResult1  = byte ptr -8
.text:004011F1 ComplexResult   = dword ptr  8
.text:004011F1 ComplexNumber   = dword ptr  0Ch
.text:004011F1
.text:004011F1                 push    ebp
.text:004011F2                 mov     ebp, esp
.text:004011F4                 add     esp, 0FFFFFFF0h
.text:004011F7                 pusha
.text:004011F8                 mov     edi, [ebp+ComplexResult]
.text:004011FB                 mov     esi, [ebp+ComplexNumber]
.text:004011FE                 lea     ebx, [ebp+ComplexResult1]
.text:00401201                 lea     ecx, [ebp+ComplexResult2]
.text:00401204                 push    esi
.text:00401205                 push    esi
.text:00401206                 push    ebx
.text:00401207                 call    complex_multiply
.text:0040120C                 push    offset serial_part1_real
.text:00401211                 push    esi
.text:00401212                 push    ecx
.text:00401213                 call    complex_multiply
.text:00401218                 push    ecx
.text:00401219                 push    ebx
.text:0040121A                 push    edi
.text:0040121B                 call    complex_add
.text:00401220                 push    offset serial_part2_real
.text:00401225                 push    edi
.text:00401226                 push    edi
.text:00401227                 call    complex_add
.text:0040122C                 popa
.text:0040122D                 leave
.text:0040122E                 retn    8
.text:0040122E gen_serial_part1 endp

So we have this :
Za = SP2 + C3 * SP1 + C3 * C3

* Second routine
.text:00401231 gen_serial_part2 proc near              ; CODE XREF: DialogFunc+16B
.text:00401231
.text:00401231 ComplexResult2  = byte ptr -10h
.text:00401231 ComplexResult1  = byte ptr -8
.text:00401231 ComplexResult   = dword ptr  8
.text:00401231 ComplexNumber   = dword ptr  0Ch
.text:00401231
.text:00401231                 push    ebp
.text:00401232                 mov     ebp, esp
.text:00401234                 add     esp, 0FFFFFFF0h
.text:00401237                 pusha
.text:00401238                 mov     edi, [ebp+ComplexResult]
.text:0040123B                 mov     esi, [ebp+ComplexNumber]
.text:0040123E                 lea     ebx, [ebp+ComplexResult1]
.text:00401241                 lea     ecx, [ebp+ComplexResult2]
.text:00401244                 push    offset ComplexNumber1_real
.text:00401249                 push    esi
.text:0040124A                 push    ebx
.text:0040124B                 call    complex_add
.text:00401250                 push    offset ComplexNumber2_real
.text:00401255                 push    esi
.text:00401256                 push    ecx
.text:00401257                 call    complex_add
.text:0040125C                 push    ecx
.text:0040125D                 push    ebx
.text:0040125E                 push    edi
.text:0040125F                 call    complex_multiply
.text:00401264                 popa
.text:00401265                 leave
.text:00401266                 retn    8
.text:00401266 gen_serial_part2 endp

We obtain this :
Zb = (C1 + C3)*(C2 + C3)

Next the serial is checked as followed :
Za = Zb
SP2 + C3 * SP1 + C3 * C3 = (C1 + C3)*(C2 + C3)
SP2 + C3 * SP1 + C3 * C3 = C1 * C2 + C1 * C3 + C3 * C2 + C3 * C3
SP2 + C3 * SP1 + C3 * C3 = C1 * C2 + C3 * (C1 + C2) + C3 * C3
SP2 + C3 * SP1 = C1 * C2 + C3 * (C1 + C2)

Using identification, we can see that :
SP1 = C1 + C2
SP2 = C1 * C2

Here is the C source for the keygen :
#include <stdlib.h>
#include <stdio.h>

struct complex_t {
    // real part
    int real;
    // imaginary part
    int i;
};

// complex number multiplication
struct complex_t* complex_multiply (struct complex_t **result,
                                    struct complex_t *complex1,
                                    struct complex_t *complex2)
{
    // check pointer validity
    if (!result || !complex1 || !complex2)
        return NULL;

    // check pointer validity
    if (!*result)
        *result = malloc(sizeof(**result));

    // we calculate the real part
    (*result)->real = complex1->real * complex2->real - complex1->i * complex2->i;
    // we calculate the imaginary part
    (*result)->i = complex1->i * complex2->real + complex1->real * complex2->i;

    // result = (a+bi)*(c+di) = (a*c - b*d) + (b*c + a*d)i

    return *result;
}

// complex number addition
struct complex_t* complex_add (struct complex_t **result,
                               struct complex_t *complex1,
                               struct complex_t *complex2)
{
    // check pointer validity
    if (!result || !complex1 || !complex2)
        return NULL;

    // check pointer validity
    if (!*result)
        *result = malloc(sizeof(**result));

    // we calculate the real part
    (*result)->real = complex1->real + complex2->real;
    // we calculate the imaginary part
    (*result)->i = complex1->i + complex2->i;

    // result = (a+bi)+(c+di) = (a+c) + (b+d)i

    return *result;
}

unsigned int strsum (unsigned char *str, size_t len) {
    size_t i;
    int sum;

    // check pointers
    if (!str || !len)
        return -1;

    // sum
    for (i = 0, sum = 0; *str && i < len; i++)
        sum += str[i];

    return sum;
}

unsigned int rol32 (unsigned int val, size_t shift) {
    shift = shift % 32;
    return (val << shift) | (val >> (32 - shift));
}

int gen_magic (unsigned char *str, size_t len) {
    unsigned int magic = 0x12345678;
    unsigned char byte;

    // check pointers
    if (!str || !len)
        return -1;

    // magic happen
    do {
        byte = *str;
        magic ^= byte;
        magic = rol32(magic, 5);
        ++str;
    } while (byte);

    return magic;
}

struct complex_t** keygen (unsigned char *name, size_t len) {
    unsigned int magic;
    struct complex_t c1, c2;
    struct complex_t *s1 = NULL, *s2 = NULL, **serial;

    // compute Za and Zb
    c1.real = strsum(name, len);
    c1.i = (c1.real - 1) * 3;
    magic = gen_magic(name, len);
    c2.real = magic % 0x7a69;
    c2.i = (magic / 0x7a69) & 0xfff;

    // compute serial part 1    
    s1 = complex_add (&s1, &c1, &c2);
    // compute serial part 2
    s2 = complex_multiply (&s2, &c1, &c2);
    
    // show complex numbers
    printf ("(a, b) : (%x, %x)\n", c1.real, c1.i);
    printf ("(c, d) : (%x, %x)\n", c2.real, c2.i);
    // show serial calculations
    printf ("Serial part 1 = (a+c, b+d)\n");
    printf ("Serial part 2 = (a*c - b*d, b*c + a*d)\n");
    // show serial
    printf ("serial : %08X-%08X-%08X-%08X\n", s1->real, s1->i, s2->real, s2->i);
    
    return serial;
}

int main (int argc, char *argv[]) {
    unsigned char *username;

    username = calloc (1, 128);

    // ask username
    printf ("Please input username\n");
    gets(username);
    // show serial
    keygen (username, 128);
    
    return 0;
}

We don't need to reconstruct source code from keygenme but here it is for those who didn't follow :

// keygenme
int keygenme (unsigned char *name, size_t len) {
    unsigned int magic;
    struct complex_t *ctmp1 = NULL, *ctmp2 = NULL;
    struct complex_t c1, c2, c3;
    struct complex_t userserial_part1, userserial_part2;
    struct complex_t *serial_part1 = NULL, *serial_part2 = NULL;

    // part 1
    //
    c1.real = strsum (name, len);
    c1.i = (c1.real - 1) * 3;
    //
    magic = gen_magic(name, len);
    c2.real = magic % 0x7a69;
    c2.i = (magic / 0x7a69) & 0xfff;
    //
    c3.real = 3;
    c3.i = 0x4e1f;
    //
    userserial_part1.real = str2num(name, 8);
    userserial_part1.i = str2num(name + 9, 8);
    userserial_part2.real = str2num(name + 18, 8);
    userserial_part2.i = str2num(name + 27, 8);
    // we generate part 1
    ctmp1 = complex_multiply (&ctmp1, &c3, &c3);
    ctmp2 = complex_multiply (&ctmp2, &c3, &userserial_part1);
    serial_part1 = complex_add (&serial_part1, ctmp1, ctmp2);
    serial_part1 = complex_add (&serial_part1, serial_part1, &userserial_part2);
    // we generate part 2
    complex_add (&ctmp1, &c3, &c1);
    complex_add (&ctmp2, &c3, &c2);
    serial_part2 = complex_multiply (&serial_part2, ctmp2, ctmp1);
    
    return 0;
}

Hope you enjoyed reading this small tutorial.

Happy reversing,

m_101

- link : Just a Little Crackme