Past Catalogue:

Class file format details

Smali: Hello World

Smali — Mathematical operations, conditional judgment, loops

Smali syntax parsing — classes

Android Reverse Note-Androidmanifest.xml file format parsing

Android reverse Note — DEX file format parsing

Inadvertently in the snow to see a simple CrackMe application, just with this example to summarize the reverse process of the basic common tools used, and some simple common routines. If you’re interested, you can try it out. It’s a very simple process. I have uploaded APK to Github for download.

First, install the app, which looks like this:

The requirement is to register. There are many methods of blasting, which can be roughly divided into three categories. The first is to directly modify the SMALI code to bypass the registration; the second is to clear the registration process and get the correct registration code. The third type is hook. Here are some of these blasting processes.

Modify smALI directly for blasting

To get the SMALI code, you first have to decomcompile the Apk, which can be done using ApkTool. To use ApkTool, run the following command:

Apktool d creackme. Apk I: Using apktool 2.3.4- Dirty on crackme. Apk I: Loading resource table... I: Decoding AndroidManifest.xml with resources... I: Loading resource table from file: /home/luyao/.local/share/apktool/framework/1.apk I: Regular manifest package... I: Decoding file-resources... I: Decoding values */* XMLs... I: Baksmaling classes.dex... I: Copying assets and libs... I: Copying unknown files... I: Copying original files...Copy the code

Crackme folder will be generated in the current directory as follows:

The SMALI folder contains all the SMALI code for the Apk. There are many tools to read and modify smALI code. I prefer to import the whole decomcompiled folder into IDEA or Android Studio for reading and modifying. Maybe I am an Android developer, using these two tools will be more convenient, and the global search function is very powerful.

After importing Android Studio, we see all the SMALI code, so where do we start? If a user fails to register, he or she will play a Toast, “Invalid user name or registration code”, which is the breakthrough. Search this string globally,

Unsuccessd, as defined in String.xml, is r.string.unsuccessd at the time of writing the code, which is an int value that is compiled as a number. Let’s search unsuccessD globally again:

You can see its ID in public.xml, which is used directly in the code. Do a global search of 0x7F05000b to see where the Toast pops up.

We can see that this id is used in line 433 of mainActivity. smali, and we locate this file:

    .line 117
    if-nez v0, :cond_0  If v0 does not equal 0, jump to cond_0

    .line 119
    const v0, 0x7f05000b

    .line 118
    invoke-static {p0, v0, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

    move-result-object v0

    .line 119
    invoke-virtual {v0}, Landroid/widget/Toast;->show()V
Copy the code

The logic is simple. Check whether the value of register V0 is 0. If it is not 0, invalid user name or registration code is displayed. If v0 = 0, the Toast will pop up. If -nez = if-ez, the Toast will pop up. After the modification, use ApkTool to repackage the file. The repackage command is as follows:

apktool b crackme -o crackme_new.apk
Copy the code

Crackme_new. apk file will be generated in the current directory. Note that this installation package is unsigned and cannot be installed directly. Use Either Jarsinger or Apksigner. After signing to install, enter username:

The registration is successful. It was a little low, but it worked. Below we do not modify the SMALI code, by reading the SMALI code to understand its registration code generation logic, through the formal way to register.

Obtain registration code blasting

We’ve already found the logic in mainActivity.smali. Find the onClick() event for this button and look at the logic:

.line 116
invoke-direct {p0, v0, v1}, Lcom/droider/crackme0201/MainActivity;->checkSN(Ljava/lang/String;Ljava/lang/String;)Z

move-result v0

.line 117
if-eqz v0, :cond_0

.line 119
const v0, 0x7f05000b

.line 118
invoke-static {p0, v0, v2}, Landroid/widget/Toast;->makeText(Landroid/content/Context;II)Landroid/widget/Toast;

move-result-object v0

.line 119
invoke-virtual {v0}, Landroid/widget/Toast;->show()V

goto :goto_0
Copy the code

This is just a snippet of the core code in onClick. The checkSN() method is called to obtain a Boolean value that determines whether the registration was successful. CheckSN () = checkSN(); checkSN() = checkSN();

.method private checkSN(Ljava/lang/String;Ljava/lang/String;)Z
    .locals 10  # use 10 registers
    .param p1, "userName"   # Ljava/lang/String; Parameter register p1 holds the userName userName
    .param p2, "sn"    # Ljava/lang/String; Parameter register P2 holds the registration code SN

    .prologue
    const/4 v7, 0x0 # store 0x0 in register V7

    .line 45
    if-eqz p1, :cond_0  If p1, userName = 0, go to cond_0

    :try_start_0
    invoke-virtual {p1}, Ljava/lang/String;->length()I # call userName. Length ()

    move-result v8  # store the result of userName. Length () in register v8

    if-nez v8, :cond_1 If v8 does not equal 0, jump to cond_1

    .line 69
    :cond_0
    :goto_0
    return v7

    .line 47
    :cond_1
    if-eqz p2, :cond_0  If p2, sn = 0, jump to cond_0

    invoke-virtual {p2}, Ljava/lang/String;->length()I  # execution sn. Length ()

    move-result v8  # Store the sn.length() result in register V8

    const/16 v9, 0x10 Store 0x10 in register V9

    if-ne v8, v9, :cond_0   # If sn.length! = 0x10, jump to cond_0

    .line 49
    const-string v8, "MD5"  # save the string "MD5" to register v8

    # call static methods MessageDigest. GetInstance (" MD5 ")
    invoke-static {v8}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;

    move-result-object v1   Assign the result of the previous method to register v1, in this case the MessageDigest object

    .line 50
    .local v1, "digest":Ljava/security/MessageDigest;
    invoke-virtual {v1}, Ljava/security/MessageDigest;->reset()V Call the digest.reset() method

    .line 51
    invoke-virtual {p1}, Ljava/lang/String;->getBytes()[B   # call userName. GetByte ()

    move-result-object v8   The byte array obtained in the previous step is stored in V8

    invoke-virtual {v1, v8}, Ljava/security/MessageDigest;->update([B)V # Call digest.update(byte[])

    .line 52
    invoke-virtual {v1}, Ljava/security/MessageDigest;->digest()[B  # Call digest.digest()

    move-result-object v0   The result of the previous step is stored in v0, which is a byte[] object

    .line 53
    .local v0, "bytes":[B
    const-string v8, "" # Save the string "" to V8

    Call the toHexString(byte[] b,String s) method in MainActivity
    invoke-static {v0, v8}, Lcom/droider/crackme0201/MainActivity;->toHexString([BLjava/lang/String;)Ljava/lang/String;

    move-result-object v3   The string returned by the previous method is stored in v3

    .line 54
    .local v3, "hexstr":Ljava/lang/String;
    new-instance v5, Ljava/lang/StringBuilder;  # Create a StringBuilder object

    invoke-direct {v5}, Ljava/lang/StringBuilder;-><init>()V    Execute the StringBuilder constructor

    .line 55
    .local v5, "sb":Ljava/lang/StringBuilder;   Declare the variable sb to point to the StringBuilder instance you just created
    const/4 v4, 0x0 # v4 = 0x0

    .local v4, "i":I    # i = 0x0
    :goto_1 # for loop begins
    invoke-virtual {v3}, Ljava/lang/String;->length()I  Get the length of the hexstr string

    move-result v8  # v8 = hexstr.length()

    if-lt v4, v8, :cond_2   If v4 is less than v8, i.e. I < hexstr.length(), jump to cond_2

    .line 58
    # This is out of the for loop
    invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v6   # v6 = sb.toString()

    .line 63
    .local v6, "userSN":Ljava/lang/String;  # userSN = sb.toString()

    # userSN.equalsIgnoreCase(sn)
    invoke-virtual {v6, p2}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z

    move-result v8  # v8 = userSN.equalsIgnoreCase(sn)

    if-eqz v8, :cond_0 If v8 = 0, go to cond_0, userSN! = sn

    .line 69
    const/4 v7, 0x1

    goto :goto_0    # jump to goto_0, end checkSN() and return v7

    .line 56
    .end local v6    # "userSN":Ljava/lang/String;
    :cond_2
    invoke-virtual {v3, v4}, Ljava/lang/String;->charAt(I)C # execution hexstr. CharAt (I)

    move-result v8  # v8 = hexstr.charAt(i)

    # call sb. Append (8)
    invoke-virtual {v5, v8}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;
    :try_end_0
    .catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_0

    .line 55
    add-int/lit8 v4, v4, 0x2    # v4 increment 0x2, i.e. I +=2

    goto :goto_1    # jump to goTO_1 to form a loop

    .line 65
    .end local v0    # "bytes":[B
    .end local v1    # "digest":Ljava/security/MessageDigest;
    .end local v3    # "hexstr":Ljava/lang/String;
    .end local v4    # "i":I
    .end local v5    # "sb":Ljava/lang/StringBuilder;
    :catch_0
    move-exception v2

    .line 66
    .local v2, "e":Ljava/security/NoSuchAlgorithmException;
    invoke-virtual {v2}, Ljava/security/NoSuchAlgorithmException;->printStackTrace()V

    goto :goto_0
.end method
Copy the code

The general logic is that the UserName UserName is converted into a hexadecimal string to obtain the Hash value by MD5 operation. So, how to get the registration code? There are generally three ways, hit the log, dynamic debugging SMALI, write their own registration machine. Let’s go through them one by one.

Play the log log

In fact, it is quite common to inject log code during the reverse process. Proper logging can help us understand the code execution process. In this example, the final registration code will be compared with the correct registration code, during the comparison we can log the correct registration code to print out, so that we can directly enter the registration code for registration.

The smALI code for logging is fixed, and the general format is as follows:

const-string vX, "TAG"
invoke-static {vX,vX}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
Copy the code

VX is all about registers. Add these two lines of code before the verification operation of the registration code:

.line 63
.local v6, "userSN":Ljava/lang/String;  # userSN = sb.toString()

const-string v8, "TAG"
invoke-static {v8,v6}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I

# userSN.equalsIgnoreCase(sn)
invoke-virtual {v6, p2}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z
Copy the code

Re-package to run, enter the user name and registration code, will have the following log:

This will get you the correct registration code.

Dynamically debug SMALI

Debugging smali on the fly is much more straightforward. Whether you’re writing your own program or working backwards, debug is always a great way to quickly clear up logic. Smali can also be debugged dynamically, depending on the Smalidea Plugin, which you can install in the Android Studio Plugin or download locally.

First step, we need to make sure that our application is in the debug version, add android:debuggable=”true” to the androidmanifest.xml, repackage and install on the phone.

The second step is to import the decompilated SMALI folder into Android Studio or IDEA and configure the remote debugging environment. Select Run -> Edit Configurations, click the + in the upper left corner and select Remote to bring up the configuration window, as shown below:

Note that the port number you entered is not fixed, as long as it is not occupied. Once the configuration is complete, make sure to place breakpoints where appropriate, which I did in this case in the checkSN() method.

Step 3: Start the process on the command line to debug the wait mode. First execute:

adb shell am start -D -n com.droider.crackme0201/.MainActivity
Copy the code

The application will enter wait debug mode, as shown below:

To set up port forwarding, run the following command:

adb forward tcp:8700 jdwp:pid
Copy the code

Replace it with your own application PID. Pid can be obtained by ps and grep:

adb shell ps | grep com.droider.crackme0201
u0_a364   30110 537   2166480 30204 futex_wait 0000000000 S com.droider.crackme0201
Copy the code

My PID here is 30010.

Finally, start debug in Android Studio or IDEA. Go to Run -> Debug and the application goes into Debug mode. After that, the operation is exactly the same as the Debug mode in our development. We can see the values in the registers at run time, and the running logic is clear. The breakpoint running to the registration check point is as follows:

UserName is the userName, sn is the registration code I entered, and userSN is the correct registration code.

Registered machine

In fact, the registration machine is to rewrite the registration code generation process, read the SMALI can write a program to generate the registration code. I won’t go into that.

Hook

The specific Hook operation is not demonstrated here for space reasons. Hook tools on the Java layer many, the most common is Xposed, direct Hook checkSN method return value, or print out the correct registration code. If you do not have Root device, there are a series of hook framework based on VirtualApp, such as support for Xposed application VirtualXposed and so on, of course, VirtualApp itself also support hook operation. In addition, there are frameworks such as Frida that can do similar things.

JADX

Finally, we will introduce a decompiler called JADX, which can decompilate Apk directly into Java code for viewing, after all, SMali code is not so user-friendly. I got an Apk, and basically the first thing I did was drop it into JADX to look at it, which supports both command-line operations and graphical interfaces. Let’s open the CrackMe app with JADX and take a look:

You can directly see the corresponding Java code, clear the logic and then read the SMali code to modify, twice the result with half the effort. There are many other tools that support decompilating Java code, such as Androgurad, which is based on the Python implementation.

conclusion

In terms of reverse difficulty, this CrackMe is very simple, but the main purpose of this article is to introduce some reverse related knowledge, the actual reverse process you face any Apk is certainly much more complex than this. Here are some things you should know:

  • Decompile and repackage using ApkTool
  • Basic reading ability of SMALI code
  • Log logging is injected into smali code
  • Dynamically debug smALI code
  • Common Hook framework
  • Jadx use

I have also written several articles on smali syntax, previous contents:

Class file format details

Smali: Hello World

Smali — Mathematical operations, conditional judgment, loops

Smali syntax parsing — classes

Android Reverse Note-Androidmanifest.xml file format parsing

Android reverse Note — DEX file format parsing

Next article will write Android Apk resources. Arsc file structure, will also support mind mapping and Java source code parsing.

Article first published wechat public account: Bingxin said, focus on Java, Android original knowledge sharing, LeetCode problem solving.

More JDK source code analysis, scan code to pay attention to me!