Solving a Random Mobile CrackMe Challenge with Lobotomy - Part 0x2

Overview

In
Solving a Random Mobile CrackMe Challenge with Lobotomy - Part 0x1
we used Lobotomy to identify and bypass s simple anti-emulation technique in our mobile CrackMe challenge. Now let's dive into the rest of the challenge and figure out how to solve it.

DES

If we run Lobotomy's class_tree module, we see the com.syclover.crackme001.DES class again.

(lobotomy) class_tree
...
..
.


    --> class : public Lcom/syclover/crackme001/DES;
        --> field : public static final Ljava/lang/String; ALGORITHM_DES
            --> method : public constructor <init> ()V
            --> method : public static byte2HexString ([B)Ljava/lang/String;
            --> method : public static encode (Ljava/lang/String; Ljava/lang/String;)Ljava/lang/String;
            --> method : private static encode (Ljava/lang/String; [B)Ljava/lang/String;

Let's run Lobotomy's surgical module, to identify if there is any actual crypto stuff going on.

(surgical) modules list


    --> [0] zip
    --> [1] intent
    --> [2] socket
    --> [3] system
    --> [4] crypto


(surgical) modules select
[2017-01-08 19:06:16.438994] Select module : 4
[2017-01-08 19:06:17.907613] crypto module selected (!)
(surgical) api list




    --> crypto : SecretKeyFactory : <init>
    --> crypto : SecretKeyFactory : generateSecret
    --> crypto : SecretKeyFactory : getAlgorithm
    --> crypto : SecretKeyFactory : getInstance
    --> crypto : SecretKeyFactory : getKeySpec
    --> crypto : SecretKeyFactory : getProvider


    --> crypto : DESKeySpec : <init>
    --> crypto : DESKeySpec : getKey


(surgical) api select
[2017-01-08 19:06:24.397366] Select class : DESKeySpec
[2017-01-08 19:06:28.531149] Select method : <init>
[2017-01-08 19:06:31.619057] Analyzing ...
[2017-01-08 19:06:31.638886] Results found (!)

Sure enough, we find API usage for Android's DESKeySpec constructor. We can validate that this usage was found in the CrackMe's DES class, and view the results.

(surgical) api analyzed list


    --> [0] Lcom/syclover/crackme001/DES; -> encode
    --> Class : Lcom/syclover/crackme001/DES;
        --> Method : encode
             --> XREFS ###########
                T: Lcom/syclover/crackme001/DES; encode (Ljava/lang/String; [B)Ljava/lang/String; 8
                F: Lcom/syclover/crackme001/MainActivity; OnMySelfClick (Landroid/view/View;)V 44


    public static String encode(String p1, String p2)
    {
        return com.syclover.crackme001.DES.encode(p1, p2.getBytes());
    }

Bug!

So as I was writing this post, I realized Lobotomy was not filtering out method results based on their signature. We can plainly see the public static String encode(String p1, String p2) signature of encode calls into a different signature, where the initialization of DESKeySpec is actually happening.

encode

Let's drop into Lobotomy's interact module to look at the target encode method.

(lobotomy) interact

In [1]: clz = self.find_class("DES")

In [2]: for m in clz.get_methods():  
   ...:     if m.name == "encode":
   ...:         m.pretty_show()
   ...:

The encode method takes a string and a byte-array as its arguments. The string is used as key for the DESKeySpec constructor, and the byte-array is the data being encrypted.

0  (00000000) new-instance         v2, Ljavax/crypto/spec/DESKeySpec;  
    1  (00000004) invoke-virtual       v9, Ljava/lang/String;->getBytes()[B
    2  (0000000a) move-result-object   v8
    3  (0000000c) invoke-direct        v2, v8, Ljavax/crypto/spec/DESKeySpec;-><init>([B)V
    4  (00000012) const-string         v8, 'DES'
    5  (00000016) invoke-static        v8, Ljavax/crypto/SecretKeyFactory;->getInstance(Ljava/lang/String;)Ljavax/crypto/SecretKeyFactory;
    6  (0000001c) move-result-object   v5
    7  (0000001e) invoke-virtual       v5, v2, Ljavax/crypto/SecretKeyFactory;->generateSecret(Ljava/security/spec/KeySpec;)Ljavax/crypto/SecretKey;
    8  (00000024) move-result-object   v7
    9  (00000026) const-string         v8, 'DES/CBC/PKCS5Padding'
    10 (0000002a) invoke-static        v8, Ljavax/crypto/Cipher;->getInstance(Ljava/lang/String;)Ljavax/crypto/Cipher;
    11 (00000030) move-result-object   v1
    12 (00000032) new-instance         v4, Ljavax/crypto/spec/IvParameterSpec;
    13 (00000036) const-string         v8, 'JoyChou '
    14 (0000003a) invoke-virtual       v8, Ljava/lang/String;->getBytes()[B
    15 (00000040) move-result-object   v8
    16 (00000042) invoke-direct        v4, v8, Ljavax/crypto/spec/IvParameterSpec;-><init>([B)V
    17 (00000048) move-object          v6, v4
    18 (0000004a) const/4              v8, 1
    19 (0000004c) invoke-virtual       v1, v8, v7, v6, Ljavax/crypto/Cipher;->init(I Ljava/security/Key; Ljava/security/spec/AlgorithmParameterSpec;)V
    20 (00000052) invoke-virtual       v1, v10, Ljavax/crypto/Cipher;->doFinal([B)[B
    21 (00000058) move-result-object   v0
    22 (0000005a) const/4              v8, 0
    23 (0000005c) invoke-static        v0, v8, Landroid/util/Base64;->encodeToString([B I)Ljava/lang/String;
    24 (00000062) move-result-object   v8
    25 (00000064) return-object        v8

We can also observe a static initialization vector being used as well.

Ljavax/crypto/spec/IvParameterSpec;  
    13 (00000036) const-string         v8, 'JoyChou '
    14 (0000003a) invoke-virtual       v8, 

The encrypted data is base64 encoded, and returned to the calling method.

    22 (0000005a) const/4              v8, 0
    23 (0000005c) invoke-static        v0, v8, Landroid/util/Base64;->encodeToString([B I)Ljava/lang/String;
    24 (00000062) move-result-object   v8
    25 (00000064) return-object        v8

OnMySelfClick

If we check out encode 's XREF(s), we can see that the method OnMySelfClick is its only caller.

########## XREF
F: Lcom/syclover/crackme001/MainActivity; OnMySelfClick (Landroid/view/View;)V 44  
T: Lcom/syclover/crackme001/DES; encode (Ljava/lang/String; [B)Ljava/lang/String; 8  

What's that? Get to the fucking solution? Yea, I feel you. So the encode method gets its arguments from the first text field in the CrackMe's UI.

    12 (00000034) invoke-virtual       v2, Landroid/widget/EditText;->getText()Landroid/text/Editable;
    13 (0000003a) move-result-object   v4
    14 (0000003c) invoke-interface     v4, Landroid/text/Editable;->toString()Ljava/lang/String;
    15 (00000042) move-result-object   v4
    16 (00000044) invoke-static        v3, v4, Lcom/syclover/crackme001/DES;->encode(Ljava/lang/String; Ljava/lang/String;)Ljava/lang/String;

The CrackMe essentially takes the second text field and compares it against the encrypted version of the first text field. If they match, you win.

Here is a simple Java program that will generate the solution.

public class Main {

    public static void main(String[] args) {
        String username = "rotlogix";
        String iv = "JoyChou ";
        try {
            Key key = SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec(username.getBytes()));
            Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
            cipher.init(1, key, new IvParameterSpec(iv.getBytes()));
            String encoded = Base64.encode(cipher.doFinal(username.getBytes()));
            System.out.println("[*] " + encoded);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
[*] Output : 8Mv5fWJmCM4=

Conclusion

I definitely have some bugs to work out with Lobotomy, but I think overall it did a good job. It would have been nice to actually emulate the the encode method with controlled inputs to dynamically generate the encoded output. I might take some queues from simplify and take a crack at writing a smali virtual machine.