HackTheBox: Joker Walkthrough [Mobile]

Mahmoud Shaaban
6 min readNov 10, 2024

--

Initial Analysis

Upon opening the application, it appeared to be a basic Tic-Tac-Toe (X and O) game with no additional visible features.

  • Application UI:

I began by examining the AndroidManifest.xml file, which revealed two primary components: MainActivity and a ContentProvider.

  • AndroidManifest.xml:

The MainActivity code only supported the Tic-Tac-Toe functionality and provided no interesting leads.

Investigating ContentProvider

Moving on to the ContentProvider, I examined its onCreate method. It launched a thread that executed a class implementing Runnable. I noted that the logic in the if statement within this thread was unimportant for now.

  • Content Provider Initialization:

Uncovering the run() Method

The run() method called a function b(context), located within the same class.

  • run() Method:

Understanding the b() Method

The b() function made a GET request to a URL derived from c.a.o().

  • b() Method and Associated Functions:

If the response’s status code was 200, it executed the a(context, str) function, using values derived from c.a.o().

  • a() Method:

Further Exploration with c.a.o() Method

Both a() and b() utilized c.a.o() to fetch critical information. The function, found in another class within a different package, took two arguments and returned a string. I decided to use Frida to call this function and observe its output.

  • o() Method:

Frida Script

Using the Frida script below, I extracted the results returned by c.a.o():

Java.perform(function(){
var aCls = Java.use("c.a");
const StringBuffer = Java.use('java.lang.StringBuffer');
console.log("In b() function");
const req1 = StringBuffer.$new("0,,(+bww(49!v?77?4=v;75w+,7*=w9((+w<=,914+g1<e5==,v,0=v273=*");
const req2 = StringBuffer.$new("X");
console.log("[+] Make request to: " + aCls.o(req1,req2));

console.log("In a() function");
const method1 = StringBuffer.$new("FAUeRWDPR");
const method2 = StringBuffer.$new("!$");
console.log("[+] method >> " + aCls.o(method1,method2));

const Res1 = StringBuffer.$new("TVGaV@\\FAPV@");
const Res2 = StringBuffer.$new("3");
console.log("[+] Resource >> " + aCls.o(Res1,Res2));

const str1_1 = StringBuffer.$new("Z3qSpRpRxWs");
const str1_2 = StringBuffer.$new("3\\^>_>_>W");
console.log("[+] list: " + aCls.o(str1_1,str1_2));

const endwith1 = StringBuffer.$new("spqn484");
const endwith2 = StringBuffer.$new("@");
console.log("[+] end with: " + aCls.o(endwith1,endwith2));

});

The output showed:

  • Output of o() Function:

Key Flow Analysis

  1. The application makes a GET request to https://play.google.com/store/apps/details?id=meet.the.joker.
  2. If the response status is 200, it invokes a().
  3. a() references a directory path, io/m/l/l/d/, then lists it.
  4. The function searches for a file that ends with 301.txt.

Exploring Assets Directory

Upon on getAssets and Resources, I found a file named eibephonenumberse301.txt in the assets directory, but it contained only junk data.

  • Assets Directory:

Continuing in a(), I noticed that a specific value was stored in f1860a and passed to c.a.v() as str2 along with the file path.

Inspecting c.a.v() Method

The c.a.v() method served as an encryption function, invoking a native function named goto2 in the JokerNat class. Despite searching, I could not locate the library associated with System.loadLibrary.

  • Encryption Function:

Decryption Process

To decrypt the file, I identified that:

  1. The string str2 required SHA-1 hashing.
  2. The first 16 bytes of the hash would serve as the AES key and IV.

Here’s a Python script to decrypt the file:

from Crypto.Cipher import AES
from Crypto.Hash import SHA1
from Crypto.Util.Padding import unpad
def decrypt_file(input_file, output_file, key_string):
sha1 = SHA1.new()
sha1.update(key_string.encode())
key_iv = sha1.digest()[:16]
cipher = AES.new(key_iv, AES.MODE_CBC, iv=key_iv)
with open(input_file, 'rb') as f_in, open(output_file, 'wb') as f_out:
encrypted_data = f_in.read()
decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
f_out.write(decrypted_data)
print("Decryption complete. Decrypted file saved as:", output_file)
str2 = "ma17FEC2_lYuoNQ$_ToT99u_e0kINhw_Bzy"
input_file = "eibephonenumberse301.txt"
output_file = "decrypted_file"
decrypt_file(input_file, output_file, str2)

After decrypting the file, I used a simple cat command to display its contents, hoping it contained the flag. Although it had readable text saying, "The flag is:", most of the data appeared as random, unintelligible characters. Assuming this might be the flag, I submitted it but was unsuccessful.

  • Decrypted File Content:

After identifying it as a library, I analyzed it using ghidra:

Reverse Engineering the Native a() Function

In my deeper examination of the app’s functions, I noticed similarities between a function named a() and a native function called goto2(). Both functions accepted the same parameter, but in a()with the first likely used to convert a Java AssetManager object to a C/C++ pointer, The second parameter might be reserved for future functionality or compatibility. Admittedly, I’m still developing my skills in reverse engineering, but I’m making steady progress. This indicated that the two functions were performing similar operations, likely manipulating asset files in some way.

  • Native Function Code Snippet:

After analyzing a(), I found an XOR operation at line 28. At this point, I wasn’t certain what values were being XORed, so I used ChatGPT to assist in identifying possible values and deciphering the operation logic.

Using ChatGPT for Assistance

With ChatGPT’s help, I identified that the function was XORing a string stored in fn with a specific byte located at address k.

  • Values for XOR Operation:

Here, I found that:

  • k = 5E
  • fn had the value: 1q3q2q2q:q;7<;.610;0+3<;,-;mnnp*&*

Decoding with CyberChef

To conduct the XOR operation, I used CyberChef, an online tool for encoding and decoding data. By XORing fn with k, I uncovered another used path within the assets directory: o/m/l/l/d/eibephonenumberse300.txt.

  • CyberChef Decoding Results:

Examining the d() Function and Its Operations

In the code, I saw that the d() function was called with eibephonenumberse300.txt and the size of the file. In line 33, I noted another XOR operation, similar to the one in the a() function, which XORed *pbVar10 with *pbVar1. This operation used a kdf value initialized as "The flag is:", storing it in pbVar1 for use in the XOR operation.

  • KDF Value and XOR Target File:

Decoding the File with XOR and CyberChef

Using CyberChef again, I XORed the contents of eibephonenumberse300.txt with the kdf value. The output was a readable Dalvik Executable (DEX) file, indicating this was likely the final piece needed to uncover the flag.

  • CyberChef Output — DEX File:

Analyzing the DEX File in JADX

After downloading and opening the newly decoded DEX file in JADX, I was able to analyze its contents thoroughly. Inside, I discovered a section that contained the actual flag in clear text.

Resources for Android Penetration Testing

--

--