To search

w3pwnz

w3pwnz, therefore we are

Tag - analysis

Entries feed Comments feed

Thursday, October 18 2012 16:03

HackYou CTF - Crypto100, Crypto200, Crypto300 Writeups

Crypto100 - Schneier's Algorithm



cry100.jpg

Download file cry100.jpg
This is a code that Bruce Schneier likes to insert as an autograph.
Read from the last column to the first to discover the flag.

Flag: hackyouisthebestestctfftw

Crypto200 - XOROWbIu WbI(|)P



Download file cry200.txt.enc
The title suggests it's XOR, so let's launch a XOR analysis, here with Cryptool:
cryptool_xor.png
We are not done yet, but that's quite a good start!
We can assume the first word is "Congratulations", and deduce the final XOR key from it and the corresponding bytes in the ciphertext:

awe@awe-laptop ~ % hexdump -C cry200.txt.enc|head -n  1
00000000  d5 cb 44 a4 e4 12 e2 d1  46 a2 e2 1a f9 ca 59 e2  |..D.....F.....Y.|
awe@awe-laptop ~ % echo "Congratulations"|hexdump -C|head -n 1
00000000  43 6f 6e 67 72 61 74 75  6c 61 74 69 6f 6e 73 0a  |Congratulations.|
>>> s1 = "d5 cb 44 a4 e4 12 e2 d1  46 a2 e2 1a f9 ca 59 e2".replace(' ','').decode("hex")
>>> s2 = "43 6f 6e 67 72 61 74 75  6c 61 74 69 6f 6e 73 0a".replace(' ','').decode("hex")
>>> ' '.join('%02x' % (ord(s1[i]) ^ ord(s2[i])) for i in range(len(s1)))
'96 a4 2a c3 96 73 96 a4 2a c3 96 73 96 a4 2a e8'

96 a4 2a c3 96 73 is repeated, that's our XOR key.

Now let's decrypt the whole file:

#!/usr/bin/env python2
 
txt = open("cry200.txt.enc", "rb").read()
key = "96 a4 2a c3 96 73".replace(" ", "").decode("hex")
 
def my_xor(cipher, key):
    keylen = len(key)
    res = ""
 
    for pos, c in enumerate(cipher):
        res += chr(ord(c) ^ ord(key[pos % keylen]))
 
    return res
 
print my_xor(txt, key)

Congratulations! While the quick brown fox jumps over the lazy dog, the plain xor cipher is still very unsecure when the key is much shorter than the message. Your flag: Foxie Dogzie Crypto Pwnd

Flag: Foxie Dogzie Crypto Pwnd

Crypto300 - Hardcore



Download file cry300.py.

Here is what the algorithm does:

  • - Init sbox (SALTED_BOX) with range(128).
  • - add_key(SALTED_SBOX, KEY)
  • - every time it receives something from a user, it splits user input into two equal size blocks (k and m), and calls encrypt(SALTED_BOX, k, m)
  • - return hex-encoded result of the encrypt call to the user

If you are too lazy to open the file, here is add_key function:

def add_key(sbox, k):
    for i, c in enumerate(k):
        sbox[i], sbox[ord(c)] = sbox[ord(c)], sbox[i]
        for i in xrange(len(sbox)):
            sbox[i] = (sbox[i] + 1) % 128
    return

Notice this is all reversible as long as we have an idea of the inital sbox, which is the case here, we know it was range(128).
Now we must submit an even sized string, let's study the encrypt() function:

def encrypt(sbox, k, m):
    sbox = sbox[::]
    add_key(sbox, k)
 
    c = ""
    for ch in m:
        c += chr(sbox[ord(ch)])
        sbox = combine(sbox, sbox)
    return c

The sbox is copied (sbox = sbox[::]) so that every time you will encrypt something, sbox will remain in the same state as above, salted with our challenge flag.
What about if we submit a string of 2 characters? 'k' will contain the first one, 'm' the last. So in the for loop, we will bypass the combine() call.
Simply perform 127 queries to extract the result of add_key(sbox, k). It is then trivial to reverse it and obtain sbox (SALTED_BOX).
It is indeed possible to optimize this process and to save queries by detecting a constant increment in dumped sbox characters, but I prefered the easy dirty way ;)
We only need to revert add_key(range(128), KEY), now that we have its output.
A quick bruteforce does it well, for each possible key length.
Once again, the optimization could save this but ... :D

Here is my code:

#!/usr/bin/env python2
 
import socket
import string
 
#HOST = 'localhost'
HOST = '93.191.13.142'
PORT = 7777
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect((HOST, PORT))
 
res = [-1]
 
for i in xrange(1, 128):
    txt = '1' + chr(i)
    s.sendall(txt)
    data = s.recv(1024)
    res += [ord(data.rstrip().decode("hex"))]
 
s.close()
 
for i in xrange(0, 128):
    if i not in res:
        res[0] = i
        break
 
res[0], res[ord('1')] = res[ord('1')], res[0]
 
res = map(lambda x: (x - 1) % 128, res)
 
print res
 
ref = res[:]
 
for bf in xrange(1, 128):
    pwd = ""
    dump = True
    res = ref[:]
 
    for pos in reversed(xrange(0, bf)):
        res = map(lambda x: (x - 1) % 128, res)
 
        for i, c in enumerate(res):
            if c == 2 * pos:
                break
 
        res[pos], res[i] = res[i], res[pos]
 
        if not chr(i) in string.printable:
            dump = False
            break
 
        pwd = chr(i) + pwd
 
    if dump:
        print "-" * 80
        print pwd

And its output:

% ./sploit_cry300.py
[86, 3, 13, 122, 14, 2, 75, 28, 29, 5, 77, 19, 34, 6, 74, 8, 83, 38, 127, 41, 40, 15, 1, 31,
89, 88, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
73, 46, 32, 76, 36, 78, 79, 80, 81, 82, 42, 84, 85, 26, 87, 51, 50, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
114, 115, 116, 117, 118, 119, 120, 121, 43, 123, 124, 125, 126, 44, 0, 48, 49, 27, 4,
35, 39, 7, 45, 9, 10, 11, 12, 33, 30, 47, 16, 17, 18, 37, 20, 21, 22, 23, 24, 25]
--------------------------------------------------------------------------------
jj	
--------------------------------------------------------------------------------
m	
--------------------------------------------------------------------------------
<is`th1s`k3y`l0n9`en0ugh?>
--------------------------------------------------------------------------------
1sek3yhl >9`en0ugh?> !"#$% !"#$%
--------------------------------------------------------------------------------
snk3y?l"!9`en0ugh?>!"#$%&'!"#$%&'
--------------------------------------------------------------------------------
0k3y>l$#9`en0ugh?>"#$%&'()"#$%&'()
--------------------------------------------------------------------------------
k3y#l&%9`en0ugh?>#$%&'()*+#$%&'()*+
--------------------------------------------------------------------------------
3y%l('9`en0ugh?>$%&'()*+,-$%&'()*+,-
--------------------------------------------------------------------------------
y'l*)9`en0ugh?>%&'()*+,-./%&'()*+,-./

Flag: <is`th1s`k3y`l0n9`en0ugh?>

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 ?