HackTheBox: Joker Walkthrough [Mobile]
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
- The application makes a GET request to
https://play.google.com/store/apps/details?id=meet.the.joker
. - If the response status is 200, it invokes
a()
. a()
references a directory path,io/m/l/l/d/
, then lists it.- 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:
- The string
str2
required SHA-1 hashing. - 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