Official Write Up for DDCTF2019 – Android

Official Write Up Timeline

Official Write Up for DDCTF2019 - Android

For the paths not explored by the participants, the question setters will come forward to provide an official interpretation.

Android Author:bin233

Tangshan Normal University/Senior Year/Android Score 610 TOP2

Question 1: Breaking LEM

First, drag the apk into JEB for decompilation, go to the entry class and find the click event function.

Official Write Up for DDCTF2019 - Android

It was observed that the Java layer is only responsible for passing input content to the native layer, so we can directly analyze the so file’s Java_com_didictf_guesskey2019lorenz_MainActivity_stringFromJNI function.

Official Write Up for DDCTF2019 - Android

This function first compares the input content with the string “ddctf-android-lorenz-“. If the input length is insufficient, it fails directly; otherwise, it performs a truncation operation (e.g., input ddctf-android-lorenz-XXX will be truncated to XXX).

Official Write Up for DDCTF2019 - Android

Then it performs character-by-character verification on “XXX”; characters must belong to the string “ABCDEFGHIJKLMNOPQRSTUVWXYZ123456”. Next is the Lorenz encryption, and I found the implementation of this algorithm on GitHub, discovering that the encryption and decryption functions are the same. Therefore, we only need to obtain the ciphertext and let the apk run once to get the plaintext. Unsurprisingly, the Lorenz encryption only encrypts “XXX” (let’s denote the encrypted result as “YYY”). Then, it performs a sha256 operation on “YYY”.

Official Write Up for DDCTF2019 - Android

Analysis reveals that it is encrypted using a five-layer sha256 algorithm, which is then compared with shaCorrect. By searching for cross-references, one can find the real string of shaCorrect (initialized in init_array).

Official Write Up for DDCTF2019 - Android

The next task is to brute-force crack the sha256. That night of the competition, I completed all strings of 7 characters or fewer. In the end, I got a hint that it was an 8-character string and was told the first two characters. Therefore, by completing it to an 8-character string, we can ensure that the first two characters of the ciphertext after Lorenz encryption remain unchanged; we only need to brute-force the last six characters. Eventually, I was incredibly lucky and cracked it in a minute.

Official Write Up for DDCTF2019 - Android

After cracking, I concatenated the result with ddctf-android-lorenz- and let the apk automatically decrypt the plaintext for us.

Question 2: Have Fun

First, drag it into JEB and find that the identifiers have been obfuscated into invisible characters. Since the file is small, I manually renamed it to deobfuscate.

Official Write Up for DDCTF2019 - Android

It was easy to trace to the function that encrypts the input content for the first time; the o() and p() functions will release the dex file from Assets to a hidden folder, while also secretly modifying the bytecode.

Official Write Up for DDCTF2019 - Android

The apk uses first-generation protection technology, utilizing DEXClassLoader to hot-load the dex file, so I continued to follow up in the dexLoader function.

Official Write Up for DDCTF2019 - Android

To obtain the dex file faster and more accurately, I used IDA dynamic debugging on the dex, which allowed me to directly obtain the dex file path and the dex file about to be loaded (the algorithm for obtaining the dex file directly from assets was incorrect).

The correct algorithm implementation is as follows:

Official Write Up for DDCTF2019 - Android

Next, the program will delete that dex file and finally call the so layer function. The so file underwent section encryption, but static analysis was enough. From JNI_Onload, I obtained the dynamically registered triplet and found the specific function location.

Official Write Up for DDCTF2019 - Android
Official Write Up for DDCTF2019 - Android

The program will convert the input content into hexadecimal and compare it with fixed data in memory, as shown in the following image.

Official Write Up for DDCTF2019 - Android

The decryption script is as follows:

Official Write Up for DDCTF2019 - Android

Question 3: A Different Service

Official Write Up for DDCTF2019 - Android

The question setter hopes that participants can find multiple control points of the distracting branches, distinguishing the correct branch from the logic of the control conditions. This time, there were fewer distracting branches, so the brute force method used by participants was also feasible; if there were more distracting branches, it would be time-consuming.

————————————————————-

This question uses control flow flattening, and the visuals are stunning, forcing obfuscation debugging. First, the JAVA layer will start a service to participate in the verification of input content, which has no critical logic, focusing on the so layer. From JNI_OnLoad, I found the dynamically registered functions as follows:

Official Write Up for DDCTF2019 - Android

It is easy to find the function for anti-debug detection, which we will not concern ourselves with for now.

Official Write Up for DDCTF2019 - Android
Official Write Up for DDCTF2019 - Android

Next, pay attention to the handling function of Parcel, create a structure for easier analysis, and focus on the call to readString during dynamic debugging.

Official Write Up for DDCTF2019 - Android

Step-by-step tracking reveals that the following function will use the readString function (offset 0x1DB50).

Official Write Up for DDCTF2019 - Android
Official Write Up for DDCTF2019 - Android

Following the position shown in the image above (offset 0x10458), I finally obtained the input content from the java layer, and then entered the sendInput1 function (offset 0x1B0D4).

Official Write Up for DDCTF2019 - Android

Here, the program uses a socket to send the input content, then enters the recvResult function (offset 0x1470C).

Official Write Up for DDCTF2019 - Android

It was found that the received data was actually different from the sent data, and after multiple debugging attempts, it was discovered that the content received each time was also different; I temporarily set this issue aside. Next, the received data was analyzed, and the program would compare this content with fixed memory data (referred to as enFlag) (offset 0x8540).

Official Write Up for DDCTF2019 - Android
Official Write Up for DDCTF2019 - Android

Later, I thought of another service process and started debugging that service process. I traced to the validate function and found that if the input length was 32, it would return the dd string (and there was also a verification of whether the result of recv was ddd in the main process, otherwise that strange content would not be received).

Official Write Up for DDCTF2019 - Android

First encryption operation: Step-by-step tracking reveals that this is implemented in Python as follows:

Official Write Up for DDCTF2019 - Android

Second encryption operation: It will first save the first two elements, and then every two subsequent elements will be XORed; after processing, the previously saved elements are placed at the end. The pseudocode is as follows:

Official Write Up for DDCTF2019 - Android
Official Write Up for DDCTF2019 - Android
Official Write Up for DDCTF2019 - Android

The third encryption operation: It will XOR with a certain memory data (referred to as key) bit by bit, and finally send it out. This is the reason why the data received in the main thread differs from the data sent (the main process and service process communicate via socket, hence IDA could only control the main process space).

XORing the key with enFlag completes one decryption, but the last two elements are clearly not in the ASCII table, so it is inferred that I obtained an incorrect key (which confirms the previous phenomenon of receiving multiple different results).

Therefore, I first input a random 32-character string, self-implement the first and second encryption operations, and then XOR it with the data received from the main process; this way, I obtained multiple keys, one of which must be real.

Continuing to XOR these keys with enFlag, one of the key XOR results is shown in the image below.

Official Write Up for DDCTF2019 - Android

68 corresponds to the character ‘D’, and 69 is exactly due to the addition of index 1 during the first encryption, hence it is also ‘D’ (isn’t that just like DDCTF? It can be inferred that I have obtained the correct data). The next issue is to crack the “second encryption”; directly brute-forcing is unrealistic, so here are two decryption methods:

1. Reverse Guessing Method:

It can be inferred that the last element data is “}”, so after the “first encryption”, it is “} + 31 = 156”. Therefore, we only need to guess the second last element and reverse XOR it. The specific script is as follows:

def myPrint(res):
    ret=[]
    for i in range(32):
        ret+=chr(res[i]-i)
    print "".join(ret)

for j in range(160):
      ispass=0
        flag=[ 1 , 18 , 15 , 215 , 22 , 254 , 12 , 9 , 42 , 21 , 20 , 50 , 232 , 22 , 242 , 204 , 1 , 248 , 2 , 246 , 244 , 248 , 4 , 251 , 221 , 202 , 22 , 3 , 27 , 210 , 68 , 69 ]
        flag[30]=j
        flag[31]=156
        for i in range(1,31):
            flag[30-i] = flag[32-i]^flag[30-i]
            if(flag[30-i]<33 or flag[30-i]>160):
                ispass=1
                break
        if(ispass==0):
            flag[0]=68
            flag[1]=69
            myPrint(flag)
Official Write Up for DDCTF2019 - Android

2. Forward XOR Method:

Since we have already seen the “DD” string, the following must be “CTF”; XORing it again directly gives the specific script as follows:

flag=[ 1 , 18 , 15 , 215 , 22 , 254 , 12 , 9 , 42 , 21 , 20 , 50 , 232 , 22 , 242 , 204 , 1 , 248 , 2 , 246 , 244 , 248 , 4 , 251 , 221 , 202 , 22 , 3 , 27 , 210 , 68 , 69 ]
tmp=[]
tmp.append(flag.pop(30))
tmp.append(flag.pop(30))
tmp+=flag

tmp[2]=ord('C')+2
tmp[3]=ord('T')+3
for i in range(0,30):
    tmp[i+2]= tmp[i] ^ flag[i]
for i in range(32):
    tmp[i]-=i
print "".join(map(lambda x:chr(x),tmp))
Official Write Up for DDCTF2019 - Android

————— End —————

Further Reading

The official questions are still open for access; click “Read the original text” to go.

About Vulnerabilities

Please submit any vulnerabilities related to Didi Chuxing to

http://sec.didichuxing.com/

Official Write Up for DDCTF2019 - Android

Leave a Comment

Your email address will not be published. Required fields are marked *