2015 mobile security challenge (Alibaba & amp; xixue) full review
Question 1
0x1 Analysis
Download question
The first question of this competition is an APK file. After installation, you need to enter a specific password. If entered correctly, the attack is successful. There is not much protection for the APK file of this question. You can use various analysis tools (such as jeb) to decompile the Java code.
The Code logic for obtaining the correct registration code is: 1. read a ing table from the offset 89473 of the image logo.png, 768 bytes encoded into a UTF-8, that is, 256 Chinese tables 2. read 18 byte encoded UTF-8 (6 Chinese characters) from offset 91265 as the final comparison password. Then, by converting the entered characters, the conversion rule is ASCII character encoding to compare whether it is equal to the final password.
0x2 clever solution
We provide a very pleasant solution here, without complicated tools and analysis. For more information, see the video.
After opening the app, we use adb logcat and add the lil tag unique to this app to filter log output. We found that the app output logs contain table, pw, and enPassword. If you enter a string such as 123456789 at will, you will find that there is a corresponding Chinese output in the enPassword. Based on the output feedback, you can see the following correspondence.
1-do 2-wide 3-dead 4-door 5-righteousness 6-7-corpse 8-Bow 9-ji
By observing the Logcat output, we can see that the final target pw should be yangu ouwan. Based on the corresponding relationship in the above table, we can get the final password:
581026
Question 2 0x1 Analysis
Download question
The second question of this competition is still an independent APK file. After installation, you need to enter a specific password, and the entered password will be displayed correctly. The second question is: APK does not have the key logic in the Java-layer code, and user input is directly transmitted to the securityCheck native method (securityCheck method in libcrackme. so), the native code determines whether the returned result is correct or not.
Use the IDA tool to open libcrackme. so, first, let's look at the general process of the program. We can see that the program has done some processing in the init_array and JNI_Onload functions before the securityCheck method is called, in the final judgment of the securityCheck methodWojiushidaan. EnterWojiushidaanIf a Password error is found, you can guess that the function of the previous logic is to change the final string. In this case, you only need to know the final judgment time.WojiushidaanThe transformed value on the address is enough. Try IDA debugging and find that once attach goes up, the entire program will exit. It must be a code that has been reversed in the previous code.
0x2 clever solution
Like the previous question, we provide a very clever solution:
Note 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...
At this time, we wonder whether we can directly patch this libcrackme. so, modify the printed content, and directly use this function to help us output the values that really need to be compared at this time.
We chose patch to directly move this log function down, because there is a printed data address at the 0x12A4 address that we need to assign a value to the R2 register (originally for later use ), therefore, rewrite the code segment from 0x1284 to 0x129C with NOP, call the log function in 0x12AC, and change R1 at 0x12A0 to R3 without affecting the R1 value.
The following is a comparison between the patch and the patch:
Refer to the video to provide a complete solution process:
By observing the Logcat output, we can see that the final password is:
aiyou,bucuoo
Question 3
Download question
Before introducing the third question of this competition, we should first introduce an InDroid plug-in analysis framework based on Dalvik VM developed by our GoSSIP team, the design idea is to directly modify the Dalvik VM interpreter on AOSP. When the interpreter interprets and executes the Dalvik bytecode, it inserts the monitoring code, in this way, you can obtain the dynamic information of all the programs running on Dalvik, such as the executed commands, called method information, parameter return values, and data of various Java objects. InDroid only needs to modify some of AOSP's dalvik vm code. After compilation, you can directly compile the new libdvm. so is flushed into any AOSP-supported real-machine devices (currently we mainly use Nexus series models, especially Nexus4 and Galaxy Nexus ). In the analysis of the third and fourth questions of this competition, we use this tool for analysis, greatly improving the analysis efficiency.
Back to the question, decompile the APK of the third question and find that the Code uses shelling protection. To deal with this type of shelling APK, the most convenient way is to use InDroid for dynamic monitoring, because the static encrypted DEX will be decrypted and executed on the Dalvik during execution, we can directly monitor the commands released during the interpretation execution in the InDroid framework. In our own tools, we developed an interface for dynamically reading the entire dex information and read the DexFile structure during execution, then parse it (the Android dexdump code is reused during parsing ). In this way, after running the program, our plug-in tool can directly obtain the dex information of the program, which is basically consistent with the results obtained after using dexdump without protection. Although the information we get is the dalvik bytecode, it is not as friendly as directly decompiling the Java code, but because the program is not large and the key logic is not much, it does not have a great impact on our analysis efficiency.
After obtaining the dexdump result after shelling, we can perform static analysis on the code. We found that user input will be passed to Class B inherited from Class timertask, which is processed by Class B's run method. In the run method, if the value of the sendEmptyMessage parameter is 0 when the sendEmptyMessage method is called, the value of the message obtained in the handleMessage method of Class c is 0, in this case, 103, except 0, is jumped into Exception Handling and a success prompt is triggered.
Continue to analyze the logic of this run method. you can know that your input will be passed to the method of Class e, make a process similar to the Morse decoding process (its decoding is not the same as the standard Morse Code). Then, after the following series of useless obfuscation processing and comparison of impossible, send the decoded string to the key judgment. The conditions for successful judgment are complex: for the first two bytes of the decoded string, the result of using the hashcode method must be 3618 and the sum of the two bytes is 168, to enter the subsequent comparison. Search for strings that match these types of input:
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); } }}
Output:
s5168
That is to say, onlyS5The hashcode is 3618, and the sum is equal to 168.
After determining the first two characters, the last four characters must be compared with the Annotation value of Class e and Class. Because we directly use the dexdump code when shelling, and dexdump cannot handle Annotations well even in the latest version:
// TODO: Annotations.
But it doesn't matter. We also have a dynamic analysis tool, because the ultimate goal is to get the returned value of the getAnnotation method, you can still monitor the returned value when the InDroid is executed by Dalvik to the getAnnotation method, you can get the specific Annotation value. The video that uses InDroid to obtain specific information is as follows:
Finally, we can see that the string that meets the program requirements is
s57e1p
Use the corresponding table in the program to perform inverse transformation. The input prompt that the program input is successful should be:
… _____ ____. . ..___ .__.
Question 4
Download question
The fourth question of this mobile security challenge is the same as the third question. It is an APK file containing the shell dex. We use the same method as solving the previous question, use InDroid to get the dexdump result of the original dex file:
Demo Video using InDroid For shelling:
The overall processing process of Dex is similar to that of the previous question. handleMessage is used to process the final result of determining whether the input is successful. Only when the sendEmptyMessage (0) is triggered and the exception divided by 0 is successful. However, after converting the user input to byte, it is passed to a native method: The M $ j method of ali $ a. The parameter also includes a constant of 48 and Handler. It seems that the reverse native library is imperative. The files in the lib folder of this question are the same as those in the previous question. There are three files, libmobisecy. so is actually a zip file. After decompression, It is a classes. dex, after direct disassembly, the class and method names are all in, but the code inside is
throw new RuntimeException();
Libmobisecz. so is a pile of binary data directly. It is suggested that the data will be decrypted at runtime and mapped to real code execution in some way. Therefore, our goal is the ELF File libmobisec. so.
Open libmobisec. so directly with IDA and find IDA will crash. We can use readelf to find that the normal section header data is damaged. Therefore, this so is also shelled. Many data will be unlocked only during dynamic operation, therefore, we directly use the dynamic method. After running this program, we directly dumped this so dump in the memory.
First, you need to enter some data in the input box and click OK to ensure that the user input data is executed in the native method and then dump. We can use the dd command to dump the entire so after viewing maps.
Enter the following command:
root@maguro:/ # ps | grep crackme.a4u0_a73 1935 126 512204 48276 ffffffff 400dc408 S crackme.a4root@maguro:/ # cat /proc/1935/maps5e0f2000-5e283000 r-xp 00000000 103:04 741132 /data/app-lib/crackme.a4-1/libmobisec.so5e283000-5e466000 r-xp 00000000 00:00 0 5e466000-5e467000 rwxp 00000000 00:00 0 5e467000-5e479000 rw-p 00000000 00:00 05e479000-5e490000 r-xp 00191000 103:04 741132 /data/app-lib/crackme.a4-1/libmobisec.so5e490000-5e491000 rwxp 001a8000 103:04 741132 /data/app-lib/crackme.a4-1/libmobisec.so5e491000-5e492000 rw-p 001a9000 103:04 741132 /data/app-lib/crackme.a4-1/libmobisec.so5e492000-5e493000 rwxp 001aa000 103:04 741132 /data/app-lib/crackme.a4-1/libmobisec.so5e493000-5e4c1000 rw-p 001ab000 103:04 741132 /data/app-lib/crackme.a4-1/libmobisec.so
Run the dd command to dump libmobisec. so memory.
root@maguro:/ # dd if=/proc/1935/mem of=/sdcard/alimsc4 skip=1578049536 ibs=1 count=3993600
The dd command uses decimal digits. skip is the starting address of libmobisec. so, and count is the total length. To enable IDA to recognize the libc functions in libmobisec, we also need to load libc into IDA. libc will be dragged out of system/lib directly.
adb pull /system/lib/libc.so ./
Open libc with IDA, adjust the offset in the memory, that is, rebase program, load libmobisec. so in the load additional binary file, and load it through the offset in maps. The next task is to find the address of the M \ $ j function.
At the beginning, I tried to find the M \ $ j function name in the ELF File of dd. A similar name will be processed
Java_ali_00024a_M_00024j
For example:
However, I did not find the M \ $ j name. Anyone who has passed the JNI library knows that if this function name cannot be found in the symbol table, it indicates that during JNI_Onload, use the RegisterNatives function to map a JNI function to the Native function again.
Just as I couldn't do anything, I thought of the InDroid system again. In Dalvik, each Method is a structure of the Method. When the Method is native, The insns pointer of the Method points to the starting address of the native Method. Therefore, we modified the InDroid so that Dalvik can print the insns pointer of the M \ $ j method before executing the M \ $ j method. In this case, we get a value pointing to another memory region, neither in libdvm nor libmobisec, and this memory page is mapped to rwx, from this we can infer that the Code is also very likely. We then dd out the memory, open it with IDA, use the ARM platform disassembly, and find that there is a command, LOAD the PC to another address, this address is exactly in libmobisec. So we jumped to this address directly in IDA and found that it was just a pressure stack command, which confirmed our idea. Here is the M $ j function, so in IDA, this address command, right-click create function and ask IDA to identify this assembly instruction as a function instruction. Then, you can use F5 to view the decompiled C code.
This function has implemented some control flow obfuscation, as well as many character string encryption and decryption functions, some simple operations such as XOR, it is also expanded into a longer and more complex expression form, such as a combination of and or. We also see some deformed RC4, and so on. However, because we have already dumped the executed data, all necessary data has been decrypted. For example:
By viewing the decompiled C code, I found that Java's bh class method a is directly called through the JNI method in the Program (as can be seen in the constant in figure 2 ). Return to the dex layer again to view the method. This method continuously transmits the input to different functions for processing. First, the method of cd, the method of cC, and the method of p, a Method of x, M $ d Method of ali $ a (native), aS a method, a method of x, M & z method of ali $ a (native ), cd method a, cC method a, each of which is a simple mathematical operation, encoding, cryptography processing, and other reversible operations, combined with reverse and Indroid monitoring of input and output, you can easily determine the role of each Java function. The specific process is shown in the following code:
invoke-static {}, LbKn;.a:()Z // method@08a1move-result v3invoke-static {v3}, LbKn;.b:(I)V // method@08a2add-int/lit8 v0, v5, #int 1 // #01invoke-static {v4, v5}, Lcd;.a:([BI)[B // method@0b23move-result-object v1add-int/lit8 v2, v0, #int 1 // #01invoke-static {v1, v0}, LcC;.a:([BI)[B // method@0a30move-result-object v0add-int/lit8 v1, v2, #int -1 // #ffinvoke-static {v0, v2}, Lp;.a:([BI)[B // method@0e8dmove-result-object v0invoke-static {v0, v1}, Lx;.a:([BI)[B // method@0edemove-result-object v0add-int/lit8 v2, v1, #int -1 // #ffinvoke-static {v0, v1}, Lali$a;.M$d:([BI)[B // method@03d3move-result-object v0add-int/lit8 v1, v2, #int 1 // #01invoke-static {v0, v2}, LaS;.a:([BI)[B // method@022emove-result-object v0invoke-static {v0, v1}, Lx;.a:([BI)[B // method@0edemove-result-object v0add-int/lit8 v2, v1, #int 1 // #01invoke-static {v0, v1}, Lali$a;.M$z:([BI)[B // method@0440move-result-object v0add-int/lit8 v1, v2, #int 1 // #01invoke-static {v0, v2}, Lcd;.a:([BI)[B // method@0b23move-result-object v0add-int/lit8 v2, v1, #int 1 // #01invoke-static {v0, v1}, LcC;.a:([BI)[B // method@0a30move-result-object v0return-object v0
It is worth noting that there are two native methods, because InDroid can also monitor the parameters and return values that call the native method, we find that these native do not perform complicated processing on the input, only M \ $ d minus 8 for the fourth byte of the input.
After performing these inverse transformations, we did not find the final comparison processing. However, in the decrypted data (Figure 2), there are not only various methods and classes that need to be called before, we can also find a very suspicious Base64 string.
aJTCZnf6NyBPYJfbrBuLu0wOhRFbPtvqpYjiby5J81M=
In the disassembly code of the native M \ $ z method, we can see that the length of this Base64 string is compared, because we have not found a real comparison function, therefore, after we get this string, we get the answer by directly starting the transformation from M \ $ z to the previous one.
The specific decryption code is as follows:
#!/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 cipherdef 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 flag
Result:
alilaba2345ba
Here we also need to mention how to find the address of the M \ $ d and M \ $ z functions in the so library, but this method is a summary of some experience, the reason is that the partition structure of the entire native ELF file has been modified. These two methods are not the same as M \ $ j, because the function name of M \ $ z can be found in libmobisec generated by dump. It is proved that this method does not use RegiterNatives for transformation, therefore, we can use the symbol table to find the offset between this function and the file header. The method is to find the offset between M \ $ z and the string table, such as 0x03FE, and then search the entire file:
Because the symbol table should take the offset of the string table as an item, the structure of this area, we can find against the ELF structure that is not a standard symbol table, but we can still see the content of the structure, including index, string table offset, and the number of special ELF signs. Therefore, the 0x57BE4 offset is assumed to be the M \ $ z function. This address is also a command for pushing stacks, proving our conjecture.
Question 5
Download question
In the last question of the 2015 mobile security challenge, only one wbyang player from our GoSSIP team solved the problem within the specified competition time, today, we will unveil the secrets of this most difficult topic.
First, drop the file named alicrackme_5.apk into JEB and take a look:
The dex file is not shelled or obfuscated. It looks like a very simple program. The Java code section uses the Register ("bomb_atlanta", input) function to judge the input. Therefore, the logic to be analyzed should all be in the Register function in libcrackme. so.
Next, we opened libcrackme. so with IDA. As expected, IDA could not handle it at all. It should have been a strong obfuscation and shelling process:
Using the same skills as solving the previous questions, we continue to use the dd method to eliminate some obfuscation and shelling. After a program is run, find libcrackme from/proc/self/maps. so in the memory, use the dd command to extract libcrackme from/proc/self/mem. so, and then use the skills you have used to solve the fourth question. so and libc. so is loaded to IDA together.
After opening the dump code with IDA, we found that most of the Code still cannot be recognized by IDA. You need to manually locate the code to be analyzed and then manually define it (IDA shortcut keyC) Code, because the code will switch between the THUMB Instruction Set and the ARM instruction set, sometimes the shortcut key is required.ALT + GTo set the T register to a different value. Only after the correct value is set can the code be correctly translated. The first problem we encountered here is that we could not locate the Register function. We also used the technique in the fourth question to use InDroid to monitor the real address of the Register function. Then we can start the analysis on this address.
Some obfuscation methods used in the libcrackme. so dynamic library are no longer a problem for us after some similar obfuscation is handled (pai_^ ). Through code analysis, we have located several functions. The offsets of these functions should be different on different devices. The overall logic is not complex. First, there will be a fixed string "bomb_atlanta" and a fixed salt for an md5 operation. salt is dynamically generated, however, because these dynamic values have been generated during dump memory, we can directly find this salt (for some copyright reasons, we cannot publish some internal details of this question, therefore, please analyze the salt value by yourself)
After that, the program will perform some operations on the md5 value and our input, or calculate the md5 value. After several simple and reversible transformations, the program will enter a complex function, after this function is processed, it is directly compared with a value in memory and the comparison result is returned.
Here is an analysis method we used in the fifth question-dynamic hook. Because libcrackme. so does not verify the upper-layer applications of the call, we can write a program to load the so and call the method. This also causes us to load libcrackme. after so, you can load another so for hook, so we can hook libcrackme. any function in so to know the parameters and return values of any function, which is very helpful for us to understand the program. The hook framework we use here is a binary injection framework adbi developed by Collin Mulliner, a famous Android security researcher. Of course, this question cannot be injected into our so through the injection method, because the source program bans system calls such as ptrace. We made a slight modification to adbi to make it a dynamic hook framework that can be manually loaded. At the same time, because we cannot locate the function address through the symbol table, all hook addresses need to be hard-coded and must strictly match the memory ing of the Android device running the program.
It should be noted that there is a small bug in adbi, And the 118 rows of the hook. c file should be
h->jumpt[0]=0x60
Instead of 0x30, the corresponding thumb assembly should be
push{r5,r6}
Instead
push{r4,r5},
This small bug will have some impact in solving the problem. Note that the function that uses adbi to hook this question also needs to be noted. The code for this question contains the THUMB instruction set used by some functions. When you hook these functions, do not forget the manual hook address + 1.
Through the hook method, we can dynamically analyze libcrackme. so. First, we verify the analysis results of the previous step transformation. The last complex processing function is analyzed.Static analysis + dynamic debuggingWe found that this is an encryption function similar to white box cryptography. After entering the function, our input goes through a few steps similar to DES preprocessing, and then several rounds of table queries are performed to encrypt our input by querying a huge table, generate a piece of ciphertext. After several simple operations and a constant in the last memory (for some copyright reasons, we cannot publish some internal details of this question, so please analyze this constant).
Through dynamic debugging, we can calculate the final output value of the encryption algorithm. However, it is obviously impossible to find an inverse replacement table because the key of this encryption algorithm is integrated into the entire replacement table. We simply filtered out other libcrackme. so functions and did not find them for decryption. It is unrealistic to decrypt the ciphertext normally. However, based on the analysis of the encryption algorithm, we find that the replacement of these rounds is independent of each other, and the complexity of each round is not high, this means that we can crack the algorithm in an acceptable period of time. The initial idea was code reuse, which was directly cracked on the Android device. However, it was found that the speed was too slow. In the end, we had to use a stupid method to swap the table from the memory by dumping the hook, I used the C code to rewrite this algorithm and found the results half an hour before the end of the competition. The correct input based on the reverse push algorithm is:
3EFoAdTxepVcVtGgdVDB6AA=