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!