Affichage des articles dont le libellé est android. Afficher tous les articles
Affichage des articles dont le libellé est android. 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

dimanche 25 avril 2010

Unpacking the boot.img

Hi!

Today, I will speaking a bit about Android boot.img.

I've been wondering what it is that makes the Android phones go root.
In facts, my theory was the following : we only need su to get root.

To check that, I coded a boot.img unpacker. The link to download it is below.

The boot.img format is defined in this kernel file : android/system/core/mkbootimg/bootimg.h.

/*
** +-----------------+
** | boot header     | 1 page
** +-----------------+
** | kernel          | n pages 
** +-----------------+
** | ramdisk         | m pages 
** +-----------------+
** | second stage    | o pages
** +-----------------+
**
** n = (kernel_size + page_size - 1) / page_size
** m = (ramdisk_size + page_size - 1) / page_size
** o = (second_size + page_size - 1) / page_size
**
** 0. all entities are page_size aligned in flash
** 1. kernel and ramdisk are required (size != 0)
** 2. second is optional (second_size == 0 -> no second)
** 3. load each element (kernel, ramdisk, second) at
**    the specified physical address (kernel_addr, etc)
** 4. prepare tags at tag_addr.  kernel_args[] is
**    appended to the kernel commandline in the tags.
** 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
** 6. if second_size != 0: jump to second_addr
**    else: jump to kernel_addr
*/


With this, we can code a decent unpacker.
The packer would be about using mkbootimg but i don't need it for now.

What I basically did to get to see what make a Nexus One get rooted it to compared the original boot.img to the corresponding SuperBoot boot.img.
What I saw is the following :

diff -ru original/default.prop rooted/default.prop
--- original/default.prop 2010-04-25 11:58:52.143574246 +0200
+++ rooted/default.prop 2010-04-25 11:59:12.373574922 +0200
@@ -1,7 +1,7 @@
#
# ADDITIONAL_DEFAULT_PROPERTIES
#
-ro.secure=1
+ro.secure=0
ro.allow.mock.location=0
-ro.debuggable=0
-persist.service.adb.enable=0
+ro.debuggable=1
+persist.service.adb.enable=1
diff -ru original/init.rc rooted/init.rc
--- original/init.rc 2010-04-25 11:58:52.163574413 +0200
+++ rooted/init.rc 2010-04-25 11:59:12.393586264 +0200
@@ -230,6 +230,11 @@

## Daemon processes to be run by init.
##
+service superboot /system/bin/sh /superboot/superboot.sh
+    user root
+    group root
+    oneshot
+
service console /system/bin/sh
console

Only in rooted/: superboot


Well, we can clearly see that some property are there to unlock a security and enable permanent USB Debugging mode.
More over, only the job is done in the superboot directory which contains :
- su
- superboot.sh
- Superboot.apk

Hell yeah, seems like my theory hold ;) .

- HOWTO: Unpack, Edit, and Re-Pack Boot Images
- Superboot - rooting the Nexus One
- Original Nexus One images
- m_101 GIT repository

samedi 24 avril 2010

HTC Desire

Hi!

I finally have a smartphone! Got my HTC desire yesterday, of course I couldn't wait to play with it a bit. It's pretty dazzling to think that it has a 1Ghz CPU and 576MB of RAM, crazy specs for a phone.


It's pretty straightforward to use, but there still is a lot of apps to have on it I guess : nmap, aircrack-ng, metasploit, maltego like, scapy, etc.
I was happy as hell to have it but got disappointed very fast to see that most of these tools weren't there or couldn't be launched because of some missing dependencies (ruby, python etc).
For Ruby, there was the Ruboto IRB shell, which seems to be a JRuby port (yeah java isn't that multiplateform afterall), but it doesn't seem to manage folders.
So I searched for another one and stumbled upon ASE - Android Scripting Environment, haven't tried it yet, gotta have to, more on this later.


With smartphone, mean communicating device, and the problem with the phone rates is that having all ports unlocked is expensive as hell, it's 10€ more compared to only having web (80, 443) and mail port (pop or imap? or maybe both dunno). It means that we would just need to have some kind of tunneling server to bypass the restriction and go anywhere we want to go.
Lucky i have WiFi where i am.


Since i'm kind of interested in security and like having a hackable device, I was asking myself if we could play with network routes, modules, etc ... we just can't ... or maybe we can ;) .
I found frustrating to not really own the phone, yeah we aren't root, just normal users.
So I looked a bit at the different steps needed to completely own the phone. There are like tons of tutorials as how to do it ... but all the same programs, we don't know that much about the root hack image itself (Superboot thingy).


Anyway, I found some cool work about it, the steps looks something like this :
- unlock bootloader
- put the rooted boot.img on sd card
- launch in bootloader mode
- choose fastboot
- rooted

I didn't have to unlock my phone since it already was. The only manipulation to do to unlock are these :
- Connect your phone in USB Debugging mode
- user@computer$ fastboot oem unlock
That's it, you unlocked the bootloader.

There was an elevation privilege exploit some months ago but it got fixed but now it seems. The root hack is now about flashing the bootloader.

The recovery image hack isn't necessary but useful if you wanna make a backup of your rom and play a bit more with custom roms.
The last thing to note is that the HTC Desire is pretty similar to the Nexus One so it shouldn't be too different for rooting it either.

For now it's only my theory I extrapolated from the doc' i read. So the goal would be to make a rooted boot.img and we'll have the key to the kingdom.

Anyway, I was thinking of building some kernel and stuffs from source but I didn't managed it yet. Got to look if it isn't some sort of cross compilation problem.

More over, when the phone is rooted, my guess would that it might be easier to hook stuffs in the system to better analyze it.


For now, I'm searching for an original HTC Desire firmware before I work more on a root hack for it.

That's all for today.


Sources :
- Why root?
- How to unlock the bootloader on your Nexus One
- Superboot
- Android Scripting Environment
- Obsolete android elevation privilege exploit
- Build CyanogenMod from source
- HOWTO: Unpack, Edit, and Re-Pack Boot Images
- Install_Custom_ROM
- Amon_RA Recovery image
- How to gain root access on your HTC Hero