GoSSIP_SJTU 2015/04/01 9:25

Author: GoSSIP, Software Security Team, Cryptography and Computer Security Laboratory, Shanghai Jiao Tong University

The first question


0 x1 analysis

Subject to download

The first topic of this competition is an APK file, after installation, users need to input a specific password, input correct will show successful cracking. The APK files for this topic do not have much protection and can be decompiled directly to the Java code using various analysis tools (such as JEB, etc.).

1. Read a mapping table from the offset 89473 of logo.png and encode 768 bytes into UTF-8, i.e. 256 Chinese tables 2. An 18-byte utF-8 (that is, six Chinese characters) read from offset 91265 is the final password for comparison. The input character is then converted to the ASCII character encoding to compare the final password.

0x2 Clever solution

We’ve provided a very pleasant solution here that doesn’t require complicated tools and analysis, and you can watch the video

After opening the app, we used ADB Logcat and added the unique lil tag of the app to filter the log output, and found table, PW and enPassword in the app output log. Enter a random string, such as 123456789, and the corresponding Chinese output is found in enPassword. Based on the output feedback, you can know the corresponding relationship as follows

  • 1 -?
  • 2 –
  • 3 – wu
  • 4 – door
  • 5 – righteousness
  • The 6 –
  • 7 – resin
  • 8 – bow
  • 9 – f

By observing Logcat output, it can be seen that the final target PW should be Yigongmeomanyuki. According to the corresponding relationship in the table above, we can get the final password as follows:

581026
Copy the code

The second question


0 x1 analysis

Subject to download

The second topic of this competition is still an independent APK file, after installation, the user needs to input a specific password, input correct will show success. APK has no key logic in Java layer code. It passes user input directly to the native method securityCheck. It is up to Native Code to decide whether the return is correct or not.

Open libcrackme.so with IDA and take a look at the general flow of the program. You can see that both the init_array segment and the JNI_Onload function do something before the securityCheck call. There is a judgment at the end of the securityCheck method that compares user input to Wojiushidaan. I tried wojiushidaan and found the password was wrong, so I can assume that the whole preceding logic will change the final string. The idea is that you just need to know the transformed value of the Wojiushidaan address at the end of the day. I tried to debug using IDA and found that once attached, the whole application will exit. There must be undebugged code in the previous code.

0x2 Clever solution

As with the previous problem, we offer a very clever solution:

Notice that before the final comparison, the program uses the android_log_print function. When we run the program directly, we find that the output is fixed here

I/yaotong ( XXX): SecurityCheck Started...
Copy the code

At this point, we wonder if we can patch the libcrackme.so directly, modify the printed content, and directly use this function to output the values that we really need to compare.

The way we choose patch is to directly move the log function down, because there is exactly the data address we need to print at address 0x12A4 assigned to R2 register (originally for later comparison), so we rewrite the code segments from 0x1284 to 0x129C with NOP. Call log at 0x12AC and change R1 at 0x12A0 to R3 so as not to affect the value of R1.

Below is the comparison before and after patch:

See the video for a complete solution:

According to the Logcat output, the final password is:

aiyou,bucuoo
Copy the code

The third question


Subject to download

Before introducing the third topic of this competition, we will first introduce InDroid, a peg analysis framework based on Dalvik VM developed by GoSSIP team. Its design idea is to directly modify the Dalvik VM interpreter on AOSP by inserting monitoring code when the interpreter interprets and performs Dalvik bytecode. In this way, dynamic information of all programs running on Dalvik can be obtained, such as instructions executed, method information called, parameter return value, data of various Java objects and so on. InDroid only needs to modify part of the dalvik VM code of AOSP. After compiling, it can directly flush the new libdvv. so to any real machine supported by AOSP (currently we mainly use Nexus models, especially Nexus4 and Galaxy Nexus). In the analysis process of the third and fourth questions in this competition, we used this tool to analyze, which greatly improved the analysis efficiency. For details, please refer to our published CIT 2014 paper DIAS: Automated Online Analysis for Android Applications

Back to the topic, decompilation of the APK in question 3 found that the code was protected by shell. The most convenient way to deal with this kind of shell APK is to use InDroid for dynamic monitoring, because the statically encrypted DEX will be decrypted and executed in Dalvik. This allows you to monitor instructions released during interpretation execution directly within the InDroid framework. In our own tools, we developed an interface to dynamically read the entire DEX information, read the structure of DexFile during execution, and then parse it (directly reuse the code of Android’s Dexdump). In this way, after running the program, our piling tool can get the dex information of the program directly, which is basically consistent with the results obtained after using Dexdump without protection. Although the information we got is Dalvik bytecode, which is not as friendly as decompilating directly into Java code, the program is not large and there is not much key logic, so it does not have a big impact on our analysis efficiency.

Demo video of unhulling using InDroid:

After we have the dexdump results of the shell, we can statically analyze the code. We find that the user’s input is passed to Class B, which inherits Class TimerTask, and is processed by Class B’s Run method. In the run method, if sendEmptyMessage is called with an argument of 0, the what value of the message obtained in Class C’s handleMessage method is 0, causing 103 to divide by 0 to jump into exception handling and trigger a success message.

Continuing with the logic of the run method, we can see that the user’s input is passed to the A method of Class E, which performs a Similar Morse decoding (which is not quite the same as standard Morse code), and then goes through the following series of numerous confusingly useless processing and impossible equality comparisons. The decoded string is fed into the critical judgment. The criteria for success are complicated: For the first two bytes of the decoded string, the result of the hashCode method is 3618, and the two bytes add up to 168 before the comparison can proceed. Let’s search for strings that match this type of input:

#! java for ( size_t i = 33; i < 127; ++i ) { for ( size_t j = 33; j < 127; ++j ) { String x =String.valueOf((char)j)+String.valueOf((char)i); if (x.hashCode()==3618 && (i+j) == 168) { System.out.println(x); System.out.println(j+i); }}}Copy the code

The output is:

s5
168
Copy the code

That is, only S5 satisfies the condition that hashCode is 3618 and that the sum is 168.

After identifying the first two characters, there are four more characters that need to be compared to the Annotation values of Class E and Class A. Since we used Dexdump code for unshell, Dexdump does not handle Annotations well even in the latest version.

// TODO: Annotations.
Copy the code

It doesn’t matter, however, that we have a dynamic analysis tool, because the ultimate goal is to get the return value of the getAnnotation method. We can still use InDroid to monitor the return value of the getAnnotation method when Dalvik executes to get the specific value of the Annotation. Here’s a video using InDroid to get specific information:

Finally, the string that meets the program’s requirements is

s57e1p
Copy the code

Use the corresponding table inside the program, invert it, can let the program input success prompt input should be:

... _____ _____.... ___. __.Copy the code

The fourth question


Subject to download

The fourth problem of this mobile security Challenge is an APK file containing the shell dex, which is the same as the third problem. We use the same method to solve the previous problem, and use InDroid to get the dexdump result of the original dex file:

Demo video of unhulling using InDroid:

The overall processing process of Dex is similar to the previous problem. HandleMessage is used to process the final judgment input success or not. Only after sendEmptyMessage(0) is triggered, the exception divided by 0 can be successfully triggered. But the topic after user input into byte, to a native method: ali $a $j M method, the other parameters include a constant 48 and Handler. It seems that the reverse native library is inevitable. Libmobisecy. so is a zip file that contains classes.dex. When you disassemble the file, the names of the classes and methods are all in the same file

throw new RuntimeException();
Copy the code

Libmobisecz. so is just a bunch of binary data, presumably decrypted at runtime and somehow mapped to actual code execution. So our target is the ELF file libmobisec.so.

Open libmobisec.so directly with IDA and IDA crashes. Readelf finds that the normal section header data is corrupted, so the so itself should also be shell, many data will only be unwrapped when running dynamically, so directly use dynamic method, first run the program, then dump the so directly in memory.

Firstly, you need to input some data randomly in the input box and click OK to ensure that the user input data is executed in the native method before dump. The method we used was to look at maps and dump the entire so using the dd command.

Enter the command:

[email protected]:/ # ps | grep crackme.a4
u0_a73    1935  126   512204 48276 ffffffff 400dc408 S crackme.a4
[email protected]:/ # cat /proc/1935/maps
5e0f2000-5e283000 r-xp 00000000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
5e283000-5e466000 r-xp 00000000 00:00 0 
5e466000-5e467000 rwxp 00000000 00:00 0 
5e467000-5e479000 rw-p 00000000 00:00 0
5e479000-5e490000 r-xp 00191000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
5e490000-5e491000 rwxp 001a8000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
5e491000-5e492000 rw-p 001a9000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
5e492000-5e493000 rwxp 001aa000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
5e493000-5e4c1000 rw-p 001ab000 103:04 741132    /data/app-lib/crackme.a4-1/libmobisec.so
Copy the code

Dump libmobisec.so memory using dd command

[email protected]:/ # dd if=/proc/1935/mem of=/sdcard/alimsc4 skip=1578049536 ibs=1 count=3993600
Copy the code

The dd command uses decimal numbers. Skip is the starting address of libmobisec.so and count is the total length. In order for IDA to recognize the libc function in libmobisec, we also need to load the liBC into IDA, so we can drag the liBC directly from system/lib.

adb pull /system/lib/libc.so ./
Copy the code

Libmobisec. So in the load Additional binary, load the libmobisec. So in the load Additional binary, load the libmobisec. The next task is to find the address of the function M\$j.

Start by looking directly for the M\$j function name in the DD generated ELF file, similar names will be processed into

Java_ali_00024a_M_00024j
Copy the code

Similar to the picture below:

In JNI_Onload, the RegisterNatives function will be used to map a JNI function to a Native function again. In JNI_Onload, I will use the RegisterNatives function.

At my wits’ end, I thought of the InDroid system again. In Dalvik, each Method is the structure of a Method. When the Method is native, the insNS pointer of the Method will point to the starting address of the native Method. Therefore, we modified InDroid to make Dalvik print the insNS pointer of M\$j before executing M\$j. Then we get a value pointing to another memory area, not in libdVM or libmobisec, and this memory page is mapped to RWX, which is probably code, so we dd out the memory, open it with IDA, disassemble it with ARM, and find that there is only one instruction. LOAD the PC to another address that happens to be in libmobisec. This is the M$j function. In IDA, right click on create Function and let IDA recognize the assembly instruction as a function. You can view the decompiled C code in F5.

The function itself does some control flow obfuscation, as well as many string encryption and decryption functions. Some simple operations, such as xor, are expanded into longer and more complex expression forms, such as the combination of and and or. We also see some distorted RC4, and so on. But since we have already dumped executed data, the necessary data has already been decrypted. The diagram below:

By looking at the decomcompiled C code, I found that method A of the Bh class in Java was called directly through a JNI method (also seen in Figure 2 constants). Go back to the DEX layer again to check method A, which continuously transmits input to different functions for processing. First, a method of CD, A method of cC, A method of P, A method of X, M$D method of Ali $A (native), A method of aS, A method of X, M&z method of Ali $A (native), CD method A, cC method A, each method is some simple mathematical operation, coding, cryptographic processing and other reversible operations, combined with reverse and Indroid monitoring of input and output, can easily determine the function of each Java function, the specific process is shown in the following code:

invoke-static {}, LbKn; .a:()Z //[email protected]move-result v3 invoke-static {v3}, LbKn; .b:(I)V //[email protected]add-int/lit8 v0, v5, #int 1 // #01 invoke-static {v4, v5}, Lcd; .a:([BI)[B //[email protected]move-result-object v1 add-int/lit8 v2, v0, #int 1 // #01 invoke-static {v1, v0}, LcC; .a:([BI)[B //[email protected]move-result-object v0 add-int/lit8 v1, v2, #int -1 // #ff invoke-static {v0, v2}, Lp; .a:([BI)[B //[email protected]move-result-object v0 invoke-static {v0, v1}, Lx; .a:([BI)[B //[email protected]move-result-object v0 add-int/lit8 v2, v1, #int -1 // #ff invoke-static {v0, v1}, Lali$a; .M$d:([BI)[B //[email protected]move-result-object v0 add-int/lit8 v1, v2, #int 1 // #01 invoke-static {v0, v2}, LaS; .a:([BI)[B //[email protected]move-result-object v0 invoke-static {v0, v1}, Lx; .a:([BI)[B //[email protected]move-result-object v0 add-int/lit8 v2, v1, #int 1 // #01 invoke-static {v0, v1}, Lali$a; .M$z:([BI)[B //[email protected]move-result-object v0 add-int/lit8 v1, v2, #int 1 // #01 invoke-static {v0, v2}, Lcd; .a:([BI)[B //[email protected]move-result-object v0 add-int/lit8 v2, v1, #int 1 // #01 invoke-static {v0, v1}, LcC; .a:([BI)[B //[email protected]
move-result-object v0
return-object v0
Copy the code

It is worth noting that there are two native methods, because InDroid can also monitor the parameters and return values of calling native methods. We find that none of these native methods do complex processing to the input, except for M\$d, which subtracted 8 from the fourth byte of the input.

We didn’t actually find the final comparison processing after doing these contravariations, but in the decrypted data (Figure 2), not only did we find the various methods and classes that needed to be called earlier, but we also found a very suspicious Base64 string.

aJTCZnf6NyBPYJfbrBuLu0wOhRFbPtvqpYjiby5J81M=
Copy the code

In addition, in the disassembly code of native M\$z method, we can see the length comparison of the Base64 string. Since we did not find the real comparison function, after obtaining the string, we can directly start from M\$z and reverse the previous transformation to get the answer.

The decryption code is as follows:

#! python #! /usr/bin/env python # encoding: utf-8 from Crypto.Cipher import AES def Lcda(s): return ''.join(map(lambda x: chr((ord(x) + 3) & 0xff), s)) def de_Lcda(s): return ''.join(map(lambda x: chr((ord(x) - 3) & 0xff), s)) def LcCa(s, a): return ''.join([chr(((ord(s[i]) ^ a) + i) & 0xff) for i in xrange(len(s))]) def de_LcCa(s, a): return ''.join([chr(((ord(s[i]) - i) & 0xff) ^ a) for i in xrange(len(s))]) def Lpa(s): return s[1:] + s[0] def de_Lpa(s): return s[-1] + s[:-1] def Lxa(s): return s.encode("base64")[:-1] def de_Lxa(s): return s.decode("base64") def LaliaMd(s): return s[:3] + chr((ord(s[3]) - 8) & 0xff) + s[4:] def de_LaliaMd(s): return s[:3] + chr((ord(s[3]) + 8) & 0xff) + s[4:] def LaSa(s): BS = 16 pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) cc = AES.new("qqVJwt11yyLm7hVK1iI2aw==".decode("base64"), AES.MODE_ECB) cipher = cc.encrypt(pad(s)) return cipher def de_LaSa(s): cc = AES.new("qqVJwt11yyLm7hVK1iI2aw==".decode("base64"), AES.MODE_ECB) cipher = cc.decrypt(s) return cipher res = "aJTCZnf6NyBPYJfbrBuLu0wOhRFbPtvqpYjiby5J81M=" flag = de_Lcda(de_LcCa(de_Lpa(de_Lxa(de_LaliaMd(de_LaSa(de_Lxa(res))))), 49)) print flagCopy the code

The result is:

alilaba2345ba
Copy the code

It is also worth mentioning how to find the addresses of M\$d and M\$z functions in the so library, but this method is a summary of some experience since the section structure of the entire Native ELF file has been modified. The two methods are different from M\$j. Since you can find the function name of M\$z in the dump libmobisec, verify that there is no RegiterNatives transformation for this method. Therefore, we can use the symbol table to find the offset between this function and the file header. This is done by looking for M\$z and the offset of the string table, such as 0x03FE, and then searching the entire file:

Because the symbol table should include string table offset as an item, the structure of this area is not a standard symbol table, but we can still see the contents of the structure, including the index, string table offset, and ELF special token number, so we guess 0x57BE4 offset is M\$z function. The address also happens to be a pushdown instruction, proving our conjecture.

The fifth problem


Subject to download

The last question of the 2015 Mobile Security Challenge was solved by only one contestant, WByang from GoSSIP, within the allotted time. Today we will reveal the mystery of this most difficult question.

Take a look at the file named Alicrackme_5.apk in JEB:

The dex file is unshell and unobfuscated and looks like a very simple program, with the Java code part using the function Register(“Bomb_Atlantis”, input) to judge the input. So all the logic to analyze should be in the Register function in libcrackme.so.

Next we opened the libcrackme.so with IDA, and as expected IDA couldn’t handle it at all, presumably with a strong obfuscation and shell processing:

Using the same techniques we used to solve the previous problem, we continue to use DD’s approach to remove some of the obfuscation and packing. After running the program once, find the location of libcrackme.so in memory from /proc/self/maps, use dd command to extract libcrackme.so in memory from /proc/self/mem, and then use the same technique used in problem 4. Load libcrackme.so and libc.so into IDA.

After opening the dump code with IDA, we found that most of the code still could not be identified by IDA. We need to manually locate the code to be analyzed and manually define (IDA shortcut KEY C) the code. Meanwhile, since the code will switch between THUMB instruction set and ARM instruction set, Sometimes you need to use the shortcut key ALT+G to set the T register to a different value, so that the code can be translated correctly. The first problem we encounter here is that we cannot locate the Register function. Using the same technique as in question 4, we can monitor the real address of the Register function with InDroid and start analysis on this address.

Some obfuscations used in the libcrackme.so dynamic library are no longer a problem for us after dealing with the previous ones (^_^). By analyzing the code, we have located several functions whose offsets should be different on different devices. The whole logic is not complicated, first there is a fixed string “Bomb_Atlantis” and a fixed salt to perform an MD5 operation. The salt is dynamically generated, but since the dynamic values are already generated when the memory is dumped, So you can find the salt directly. (For copyright reasons, we can’t publish some internal details of this topic, so please analyze the salt value for yourself.)

After that, the program will perform some xOR and calculation operations on the MD5 value and our input. After a few simple and reversible transformations, it will enter a complex function. After processing by this function, it will directly compare with a value in memory and return the comparison result.

Dynamic hook dynamic hook dynamic hook Since libcrackme.so doesn’t validate the upper-layer application that calls itself, we can write a program to load the so and call its methods. So we can hook any function in libcrackme.so and know the parameters and return values of any function. This is very helpful for understanding the program. The hook framework we use here is adBI, a binary injection framework on the Android platform developed by Collin Mulliner, a famous Android security researcher. Of course, this problem can’t inject our so through injection method, because the source program disables system calls like ptrace. We modified ADBI slightly to make it a dynamic hook framework that can be manually loaded. And since there is no symbol table to locate the address of the function, all hook addresses need to be hard-coded and strictly correspond to the memory map of the Android device running the program.

It needs to be pointed out that there is a small bug in ADBI. Line 118 of the file hook

h->jumpt[0]=0x60
Copy the code

Instead of 0x30, the corresponding Thumb assembly should be

push{r5,r6}
Copy the code

Rather than

Push {r4, r5}.Copy the code

This little bug is going to cause some problems in the solution. [addbi] [addBI] [addBI] [addBI] [addBI] [addBI] [addBI] [addBI] [addBI] [addBI] [addBI] [addBI] [addBI] [addBI] [addBI] [addBI]

We have been able to analyze libcrackme.so dynamically with the hook method. First we verified the results of our analysis of the previous steps. Then it is time to analyze the last complex processing function, through static analysis + dynamic debugging, we found that this is a cryptographic function similar to white box cryptography. After we enter into the function, the first after preprocessing steps like DES, after several rounds of look-up table, by querying a huge table will be some kind of encryption, we input to generate a ciphertext, again after a few simple processing and final in memory after a period of constant (for copyright reasons we inconvenience released subject to some internal details, So this constant please analyze) for comparison.

Through dynamic debugging, we can calculate the final output value of the encryption algorithm, but since the encryption algorithm’s key is integrated into the entire permutation table, it is obviously impossible to find an inverse permutation table. We simply filtered the other libcrackme.so functions and found no decryption functions, so it’s not practical to decrypt ciphertext properly. However, according to the analysis of the encryption algorithm, we found that these rounds of displacement are independent of each other, and the complexity of each round is not high, which means that we can blow up the algorithm in an acceptable time. We started with a code reuse reuse, but found it was so slow that we had to hook the table out of memory, rewrite the algorithm in C code, and search the result half an hour before the end of the match. According to the inverse method, the correct input is:

3EFoAdTxepVcVtGgdVDB6AA=   
Copy the code

Well, that concludes our series review of the 2015 Mobile Security Challenge! I hope you can talk with us more, and you are welcome to follow our weibo “gossip P_sjtu”, which is filled with great content almost every day.