To search

w3pwnz

w3pwnz, therefore we are

Tag - engineering

Entries feed Comments feed

Thursday, October 20 2011 17:34

Unpack FSG2 - Reconstruction manuelle de l'IAT

La cible :


Je vous propose aujourd’hui mon premier tutoriel concernant l’unpack d’un programme packé avec FSG2. Et j’en profiterai pour vous montrer comment on peut reconstruire l’IAT manuellement.

Tout d’abord notre cible =>UnPackMe_FSG2.0.rar, on confirme qu'il est bien packé en l'ouvrant dans PEid qui nous détecte "FSG 2.0 -> bart/xt"

PEiD_FSG

Dump du programme :


La première étape est de retrouver l'OEP (Original Entry Point) du programme et de faire un dump du programme. Ouvrons donc le programme dans Olly, il nous averti que l'OEP est en dehors du code et que le programme est sûrement packé.

Olly_Detect_Packer
Pour trouver l'OEP, faire F9 dans Olly jusqu'à tomber sur la section de code 0040XXXX où l'on peux tomber sur le code suivant correspondant au début de l'unpack du code :

00400154 > 8725 D03E4100    XCHG DWORD PTR DS:[413ED0],ESP        // Déplace ESP à l'adresse 413ED0
0040015A   61               POPAD                                                               // Récupération des registres 
0040015B   94               XCHG EAX,ESP                                                    // Déplace ESP dans EAX
0040015C   55               PUSH EBP                                                           // Push EBP = 401000

On peut voir en regardant la mémoire à l'adresse 401000 (Ctrl+G dans la zone en bas à droite correspondant à la mémoire) et en débugguant pas à pas que le programme est en train de s'unpacker en mémoire à partir de cette adresse.

En parcourant la suite du code, on tombe sur une succession de sauts conditionnels suivi d'un jmp vers l'OEP, typique au FSG :

004001CD  ^78 F3            JS SHORT UnPackMe.004001C2
004001CF   75 03            JNZ SHORT UnPackMe.004001D4
004001D1   FF63 0C          JMP DWORD PTR DS:[EBX+C]

Il suffit de poser un BP sur le dernier JMP pour voir vers quelle adresse le programme va sauter et nous avons alors notre OEP => 401000. Maintenant que nous avons notre OEP en main, il va falloir faire un dump du programme. Pour cela nous allons utiliser un plugin Olly nommé Ollydump. Faire F8 pour basculer sur notre OEP et faire un click droit Analysis / Remove Analysis from module pour voir le code du programme.
Olly_Remove_Analysis
Pour faire un dump du programme, il suffit maintenant de faire un click droit - Dump Debugged Process Une nouvelle fenêtre va s'ouvrir où l'on va pouvoir spécifier notre nouvel EP et lancer notre dump :
OllyDump

Attention : Pensez à décocher Rebuild Import qui permet de reconstruire les imports depuis Ollydump car ce n'est pas sa spécialité et le but ici est de reconstruire l'IAT manuellement.

Reconstruction de l'IAT :


Avant de commencer, pour les plus novices vous vous demandez sûrement qu'est ce que l'IAT ( Import Address Table). Il faut savoir que la plupart des programmes font appel à de nombreuses APIs disponibles dans des fichiers dll. On peux prendre par exemple l'API MessageBoxA disponible dans la dll user32.dll permettant d'afficher une boîte de dialogue. Toutes ces APIs sont donc disponibles au travers d'appels aux fonctions contenus dans les dll mais les API selon l'architecture et le système d'exploitation ne sont pas au même emplacement mémoire. Chaque programme comporte donc une table permettant au programme de retrouver l'adresse des fonctions qu'il va utiliser. Cette table va se remplir automatiquement lors de son lancement avec les bonnes adresses. Si vous souhaitez des explications plus détaillées et plus techniques, je vous envoie vers un article de mysterie

Après le dump d'un programme, il faut savoir que l'IAT n'est pas correct. Il faut donc le reconstruire.

La méthode "classique" :


Après le dump du programme fait avec Ollydump, on conserve Ollydbg ouvert et on lance ImportRec. On sélectionne dans le champ du haut le process à attacher, dans notre cas unpackme_fsg2.0.exe.

ImpRec

1) On modifie l'OEP par celui que l'on a trouvé précédemment : 404000, on indique donc dans le champs OEP la valeur RVA : 4000

2) On clique sur IAT AutoSearch, il va alors chercher pour nous les imports de fonctions à effectuer, il va nous afficher à partir de quelle adresse (RVA) il récupère les fonctions et quelle taille il doit récupérer. On peux alors vérifier sous Olly qu'il n'en a pas oublié. Pour cela sélectionner la zone en bas à gauche d'Olly qui liste les octets en mémoire et faire un CTRL+G pour aller à l'adresse indiquée : 004011E8.

olly_mem

On voit donc bien les différents appels de fonctions que l'on peux repérer par leur adresses commençant par 75XXXXXX, mais en remontant un peu dans la zone mémoire, on se rend compte que d'autres adresses vers des fonctions sont stockées à partir de l'adresse 00401198. On va donc modifier dans ImportRec le champ RVA par l'adresse que l'on a trouvé RVA : 11998 et on va définir une taille suffisamment grande pour ne rien oublier Size : 100

3) On clique sur Get Imports, il va alors nous afficher les dlls qu'il a trouvé

ImpRecImportFonction

On voit que certains thunks sont invalides. On va donc cliquer sur Show Invalid pour voir ce qui cloche. On voit alors deux imports incorrects. Il suffit de cliquer sur chacun d'eux et faire Cut Thunks.

Nous n'avons plus qu'à ajouter ces imports au dump que l'on a effectué précédemment, pour cela :

4) On clique sur FixDump et on sélectionne notre dump que l'on a sauvegardé précédemment. On peux vérifier qu'il fonctionne bien en l’exécutant et on peux également l'ouvrir avec PEiD qui ne détecte plus de packer.

La méthode "manuelle" :


Et « just for fun » comme on dit, voici comment faire manuellement ce que fait ImportRec.

La première étape est de noter l'ensemble des API utilisées par le programme. Pour cela on retourne dans Olly juste après avoir fait le dump et on fait un click droit Search for / All intermodular call et il nous affiche alors l'ensemble des API utilisées.
olly_intermodular_call

On va maintenant ouvrir le dump que l'on a effectué tout à l'heure et aller rajouter manuellement l'ensemble des fonctions utilisées dans notre IAT. Pour cela on utilise LordPe, on clique sur PE Editor on sélectionne notre dump. Une nouvelle fenêtre va s'ouvrir avec l'ensemble des informations du header de notre EXE. On va cliquer sur Directories puis sur les 3 petits points à coté du champ Import Table

LordPe_IAT

LordPe va alors nous afficher l'IAT. Dans notre cas il nous affichera un peu n'importe quoi vu que l'IAT est corrompu. On va donc rajouter nous même l'ensemble des imports dont nous avons besoin, pour cela faire un click dans la partie haute de la fenêtre sur l'import en erreur et sélectionner Add Import

LordPe_AddImport

Une nouvelle fenêtre va alors s'afficher où l'on va pouvoir ajouter l'ensemble de nos appels de fonctions. Pour cela rien de plus facile, il suffit de renseigner le nom de la dll et les API dont le programme a besoin :

LordPe_adddll

Il faudra effectuer cette opération pour chacune des dlls que le programme utilise. Lorsque vous avez ajouté tout ce qu'il vous faut, vous pouvez supprimer l'import en error. Pour cela faire un click droit sur l'import et sélectionner KillImageImportDescriptor. Vous pouvez alors fermer cette fenêtre et cliquer sur save pour sauvegarder tout le travail que l'on vient de faire.

Nous avons donc maintenant un dump du programme non packé avec une table d'import correct, une dernière étape reste à faire : modifier les différents appels à ces fonctions dans le programme. On va donc ouvrir notre dump dans OllyDbg et faire un click droit Search For / All Intermodulars Calls où l'on va retrouver la liste des fonctions que l'on avait noté à la première étape et pour chacun des ces calls nous allons modifier l'adresse par celle qui est spécifiée dans notre table d'import.

olly_fixcall

Par exemple pour la fonction GetModuleHandleA il faudra modifier le CALL DWORD PTR DS:4011F4 par CALL DWORD PTR DS:4140C1. Pour cela sous Olly il suffit de double cliquer sur la fonction que l'on veux modifier, OllyDBG va nous placer sur l'endroit du code où l'appel est passé et il faudra de nouveau double-cliquer dessus pour modifier le code.

Après avoir effectué cette opération pour l'ensemble des CALL, il faudra sauvegarder les modifications. Pour cela sélectionner l'ensemble du code en faisant click droit Copy / Select All puis click droit Copy to executable / Selection et enfin dans la fenêtre qui s'affiche faire un click droit puis Save File.

Vous aurez alors un programme complètement unpacké avec sa table d'import reconstruite manuellement, enfin presque. Vous pouvez si vous le souhaitez aller encore plus loin et reconstruire l'IAT en modifiant manuellement le PE header du fichier sans passer par LordPE...

Pour terminer, comme vous avez pu le constater la méthode manuelle est tout de même très fastidieuse, surtout si votre programme fait appel à de nombreuses API mais je pense qu'il est toujours intéressant de faire à la main au moins une fois cette opération pour bien comprendre son fonctionnement.

Tuesday, April 5 2011 16:40

NDH2k11 Prequals - Rce200

Deuxième épreuve de Reverse qui avait pour support une application Android.

Fichier : "RCE200.apk"

Fort de la lecture des slides de Virtualabs concernant le reverse d'application Android, nous savions que les sources Java de l'application étaient disponibles grâce à 2 outils : Dex2Jar (Transformer le .apk en .jar contenant des .class (Bytecode Java)) et JD-GUI (Permet l'affichage des .class en .java).

Nous découvrons que l'application se compose de 4 fichiers :
- "ReverseMe.java"
- "a.java"
- "b.java"
- "c.java"

Tout d'abord, je tiens à préciser que j'ai pas utilisé de device Android pour résoudre cette épreuve ni utilisé d'émulateur, seule la lecture des sources m'a permis de choper le flag.
Allez, c'est parti pour de l'analyse !

Commençons par le 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);
  }
}

Les attributs a,b,c,d,e sont des strings, mis sous forme de tableau de Byte.
On voit très clairement que la fonction private static String a(byte paramArrayOfByte) {} effectue un XOR chaque caractère de la chaine d'entrée et la clé 0x3C.

Réflexe: passer les strings a,b,c,d,e dans la fonction a() histoire de voir quelles nouvelles chaines on obtient (chars imprimables ou non).

Résultat:

a="f9dd11ff6857af73ac9a944dfc52f41b"
b="google_sdk"
c="MD5"
d="android.speech.action.RECOGNIZE_SPEECH"
e="SHA-1"

Tiens tiens, "MD5", "SHA-1" ça sent le hash là dedans garçon !

Au passage, on remarque avec lucidité que la string a fait 32 caractères, on essaie donc de voir si ce n'est pas le hash MD5 d'une quelconque chaine.

Et là bingo, passcracking.ru nous indique que c'est le hash MD5 de "salope".

Élégant non ?

Comme je l'ai dit plus haut je n'avais pas de device Android durant cette épreuve. A ce moment de l'épreuve, je ne savais pas donc pas que la saisie de notre input se faisait via une méthode de Speech-to-Text. La chaine "android.speech.action.RECOGNIZE_SPEECH" m'a mis sur la piste.

Passons au 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()); //c.(e)="SHA-1"
      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());//c.(c)="MD5"
      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;
    }
  }
}

Une analyse rapide des méthodes a() et b() de cette classe nous indique que:
- a() nous renvoi le hash SHA-1 de la chaine passée en paramètre
- b() compare le md5 du mot prononcé avec c.a() ( c.a()="f9dd11ff6857af73ac9a944dfc52f41b"=md5("salope") )


Voici le contenu du 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 gère la partie graphique, met en place les Listeners et gère l'évenement onClick (qui correspond au click tactile de l'utilisateur, pour valider sa saisie)

Donc pas grand chose à tirer de cette classe.

Et enfin, voici la classe la plus intéressante "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;
  }
}

La méthode onCreate() va juste instancier les élements graphiques de l'application.

La méthode onActivityResult() gère les interactions entre les éléments de cette application, donc entre ce que l'on va prononcer et le fait de réussir ou non l'authentification.

Rapide commentaire des principales instructions de la méthode onActivityResult().

ArrayList localArrayList = paramIntent.getStringArrayListExtra("android.speech.extra.RESULTS");
/* Récupère ce que l'utilisateur a prononcé et le met dans localArrayList */
 
 
if ((!localArrayList.isEmpty()) && (a.b((String)localArrayList.get(0))))
/* Si localArrayList n'est pas vide et que l'utilisateur a prononcé "salope" .... */
 
String str = a.a((String)localArrayList.get(0));
localTextView.setText(str);
/* ...alors le hash SHA-1 de "salope" est affiché. */

Le flag de cette épreuve était donc "913beccad686975f8c686d9b3b1ee6bb97c22d6f"

Épreuve originale, accessible et fun !

Que demande le peuple ?

NDH2k11 Prequals - Rce300

L'épreuve qui nous a donné le plus de mal, est sans doute la RCE300. C'était la dernière épreuve qu'il nous restait, et toute la team s'y est attaqué afin de finir ces préquals :p

On nous fournissais un fichier crackme.nds, avec pour seule indication qu'il s'agissait d'une application pour nintendo DS !

L'intéressant ici était de se documenter sur le format, et d'arriver à se faire un environnement de débug + reverse :)

Au final, nous avons utilisé les outils suivants :

Avec ça on est paré, on peut donc maintenant lancer DeSmuME en mode dev avec la commande :

DesMume_dev.exe --arm9gdb=5555 crackme.nds

Pour y attacher ensuite un gdb, avec la commande :

target remote localhost:5555

rce300_gdb_desmusme.png

On utilise ensuite IDA, avec le loader permettant de charger le *.nds :

rce300_ida_nds.png

Au passage il est bon de noter que la DS utilise un processeur ARM, il va donc falloir se documenter un peu sur l'ARM, ici, ici, ici et ici.

En cherchant la chaîne password, on tombe sur une première routine intéressante : sub_20002B8. Dans laquelle on voit clairement l'affichage du prompt et la demande de serial (on devine que sub_2003888 est un printf() et que sub_2003ACC est un scanf()) : rce300_ida_printf_scanf.png

On voit ensuite que si on entre un serial, le chemin emprunté passe par 2 boucles qui ont la même structure :

  • Multiplication de R0 par une constante
  • Appel de sub_2003530 (qui prend en paramètre R0, et le caractère courant du serial entré dans R1, et qui renvoie R0 modifié)
  • Xor de R0 par une constante
  • En fin de boucle : comparaison de R0 à une checksum : granted si les 2 checksums matchent, denied sinon !

gdb_ida_checksums.png

On voit que :

  • La première checksum est 0x33E0D2F1
  • La deuxième checksum est 0xBCFA8D3F

Et enfin, on analyse la fonction sub_3530, qui était assez impréssionnante : rce300_ida_sub3530.png

Plusieurs hints avaient été données sur l'IRC #ndhprequals, notamment qu'il fallait faire un brute-force, et qu'il fallait se limiter au charset alphanumérique et à une longueur de 6 caractères. En refaisant l'alogrithme en C (le plugin hex-rays pour IDA peut grandement aider...) on est alors capable de faire un rapide bruteforce, qui au bout de quelques dizaine de minutes nous a sorti le serial valide "DsLrox" ! \o/

Voilà donc une épreuve vraiment sympathique, dont le plus intéressant pour moi a été de découvrir comment mettre en place un environnement de débug comme celui ci. Et ce fut également un bel exemple de travail en équipe ;)