com.jiaxiao.driveAPP
hsign
i guess it’s md5
defineHandler({
onEnter(log, args, state) {
// log('CC_MD5 called from:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
log('CC_MD5() onEnter: ')
log(hexdump(args[0], {length: args[1].toInt32()}));
this.args2 = args[2];
},
onLeave(log, retval, state) {
log('CC_MD5() onLeave: ')
log(hexdump(this.args2, {length: 16})); // md5 has 16 bytes
log("--------------------------------------------------------------------\n")
}
});
// frida-trace -U -N com.jiaxiao.driveAPP -i CC_MD5

there are a lot of md5 called. Here is the updated script that will only print the backtrace when the plaintext data being hashed starts with “17.4.0”.
defineHandler({
onEnter(log, args, state) {
// Get pointer to the input data and its length
const dataPtr = args[0];
const dataLength = args[1].toInt32();
// Read a small portion of the input to check the prefix without being wasteful
const plaintext = dataPtr.readUtf8String(Math.min(dataLength, 20));
// **Conditional Check**: Only proceed if the input starts with the target string
if (plaintext && plaintext.startsWith("17.4.0")) {
console.log("\n==================================================");
log("✅ Found MD5 input starting with '17.4.0'.");
log("Full plaintext: " + dataPtr.readUtf8String(dataLength));
// --- Start of IDA Address Calculation Logic ---
const backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE);
const driverModule = Process.getModuleByName('Driver');
const idaBase = new NativePointer('0x100000000');
if (!driverModule) {
log('Error: Could not find the "Driver" module. Aborting calculation.');
return;
}
const driverEndAddress = driverModule.base.add(driverModule.size);
const formattedBacktrace = backtrace.map(function(addr) {
const symbol = DebugSymbol.fromAddress(addr);
// Use the reliable memory range check to see if the address is part of your app
const isAppCode = addr.compare(driverModule.base) >= 0 && addr.compare(driverEndAddress) < 0;
if (isAppCode) {
// It's in the Driver app, so calculate the IDA address
const offset = addr.sub(driverModule.base);
const idaAddress = idaBase.add(offset);
return `${idaAddress} Driver!${symbol.name}`;
} else {
// It's a system library, so find its module and show the runtime address
const otherModule = Process.getModuleByAddress(addr);
const moduleName = otherModule ? otherModule.name : 'UnknownModule';
return `${addr} ${moduleName}!${symbol.name}`;
}
}).join('\n');
// Log the final, formatted backtrace
log('Backtrace (with calculated IDA addresses):\n' + formattedBacktrace + '\n');
console.log("==================================================");
}
// You can optionally keep general logging for all MD5 calls
// log('CC_MD5() onEnter: ');
// log(hexdump(dataPtr, { length: dataLength }));
// Store the output buffer pointer for the onLeave handler
this.args2 = args[2];
},
onLeave(log, retval, state) {
// This will still log the hash result for every call
log('CC_MD5() onLeave (MD5 Hash): ');
log(hexdump(this.args2, { length: 16 }));
log("--------------------------------------------------------------------\n");
}
});
// frida-trace -U -N com.jiaxiao.driveAPP -i CC_MD5
5454 ms ✅ Found MD5 input starting with '17.4.0'.
5454 ms Full plaintext: 17.4.0_f7d80f1550c82ea9bceacef128b70dc9f70f599e___ios_15.8.1_https://user.ksedt.com/api/login/v2_3_305163_1756667882029_hZLJ3qzMgFK25A2S
==================================================
5454 ms Backtrace (with calculated IDA addresses):
0x102a0efc4 Driver!-[NSString jx_toMD5]
0x10396e7e0 Driver!+[XYBaseRequestHeader headerKeyValuesWithRequestUrl:hsnssign:]
0x101fd36e8 Driver!+[DrLogin58NetworkManager request:withMethod:parameters:withTimeoutInterval:resultClassType:withSuccess:withFail:]
0x101fd47c8 Driver!+[DrLogin58RequestManager login:success:Failure:]
0x101feefec Driver!-[DrUserLoginViewController passwordLoginRequestByPhone:ThePassword:]
0x101ff0734 Driver!-[DrUserLoginViewController verifyLoginButtonClicked:]
0x1845d8748 UIKitCore!-[UIApplication sendAction:to:from:forEvent:]
0x1846f7870 UIKitCore!-[UIControl sendAction:to:forEvent:]
0x18448d9ec UIKitCore!-[UIControl _sendActionsForEvents:withEvent:]
0x18452498c UIKitCore!-[UIButton _sendActionsForEvents:withEvent:]
0x18479c4c0 UIKitCore!-[UIControl touchesEnded:withEvent:]
0x10168cedc Driver!-[UIButton touchesEnded:withEvent:]
0x1842a212c UIKitCore!-[UIWindow _sendTouchesForEvent:]
0x1842d1c4c UIKitCore!-[UIWindow sendEvent:]
0x184472a64 UIKitCore!-[UIApplication sendEvent:]
0x1842a6c2c UIKitCore!__dispatchPreprocessedEventFromEventQueue
0x10396e7e0 Driver!+[XYBaseRequestHeader headerKeyValuesWithRequestUrl:hsnssign:]
0x10396E42C
id __cdecl -[XYBaseRequestHeader hsignBeforeMD5Encryption](XYBaseRequestHeader *self, SEL a2)
{
void *v3; // x0
void *v4; // x0
void *v5; // x0
void *v6; // x0
void *v7; // x0
void *v8; // x0
void *v9; // x0
void *v10; // x0
void *v11; // x0
void *v12; // x0
id v13; // x22
void *v14; // x0
void *v15; // x0
void *v16; // x0
void *v17; // x0
id v18; // x23
void *v19; // x0
id v20; // x24
void *v21; // x0
id v22; // x25
void *v23; // x0
id v24; // x26
void *v25; // x0
id v26; // x28
void *v27; // x0
id v28; // x19
void *v29; // x0
id v30; // x0
id v31; // x21
id v33; // [xsp+50h] [xbp-D0h]
id v34; // [xsp+60h] [xbp-C0h]
id v35; // [xsp+68h] [xbp-B8h]
id v36; // [xsp+70h] [xbp-B0h]
id v37; // [xsp+80h] [xbp-A0h]
id v38; // [xsp+88h] [xbp-98h]
id v39; // [xsp+90h] [xbp-90h]
id v40; // [xsp+98h] [xbp-88h]
id v41; // [xsp+A0h] [xbp-80h]
id v42; // [xsp+A8h] [xbp-78h]
id v43; // [xsp+B0h] [xbp-70h]
id v44; // [xsp+B8h] [xbp-68h]
id v45; // [xsp+C0h] [xbp-60h]
v3 = sub_103CF5B60(self, a2); // _objc_msgSend(a1, "happver");
v43 = objc_retainAutoreleasedReturnValue(v3);
v4 = sub_103CEC340(self); // _objc_msgSend(a1, "getSignStr:");
v38 = objc_retainAutoreleasedReturnValue(v4);
v5 = sub_103CFA920(self); // _objc_msgSend(a1, "himei");
v42 = objc_retainAutoreleasedReturnValue(v5);
v6 = sub_103CEC340(self); // _objc_msgSend(a1, "getSignStr:");
v45 = objc_retainAutoreleasedReturnValue(v6);
v7 = sub_103CEC340(self); // _objc_msgSend(a1, "getSignStr:");
objc_retainAutoreleasedReturnValue(v7);
v8 = sub_103CEC340(self);
v44 = objc_retainAutoreleasedReturnValue(v8);
v9 = sub_103CFB620(self); // _objc_msgSend(a1, "hos");
v41 = objc_retainAutoreleasedReturnValue(v9);
v10 = sub_103CEC340(self);
v35 = objc_retainAutoreleasedReturnValue(v10);
v11 = sub_103CFB860(self); // _objc_msgSend(a1, "hosver");
v39 = objc_retainAutoreleasedReturnValue(v11);
v12 = sub_103CEC340(self);
v13 = objc_retainAutoreleasedReturnValue(v12);
v14 = sub_103CFBD80(self); // _objc_msgSend(a1, "hpath");
v37 = objc_retainAutoreleasedReturnValue(v14);
v15 = sub_103CEC340(self);
v34 = objc_retainAutoreleasedReturnValue(v15);
v16 = sub_103CFBDA0(self); // _objc_msgSend(a1, "hproductid");
v36 = objc_retainAutoreleasedReturnValue(v16);
v17 = sub_103CEC340(self);
v18 = objc_retainAutoreleasedReturnValue(v17);
v19 = sub_103CFBDC0(self); // _objc_msgSend(a1, "hr");
v20 = objc_retainAutoreleasedReturnValue(v19);
v21 = sub_103CEC340(self);
v22 = objc_retainAutoreleasedReturnValue(v21);
v23 = sub_103CFBE20(self); // _objc_msgSend(a1, "htime");
v24 = objc_retainAutoreleasedReturnValue(v23);
v25 = sub_103CEC340(self);
v26 = objc_retainAutoreleasedReturnValue(v25);
v27 = sub_103CFBE00(self); // _objc_msgSend(a1, "hsignSuffix");
v28 = objc_retainAutoreleasedReturnValue(v27);
v29 = sub_103CEC340(self);
v33 = objc_retainAutoreleasedReturnValue(v29);
v30 = sub_103E3E760(&OBJC_CLASS___NSString); // _objc_msgSend(a1, "stringWithFormat:");
v40 = objc_retainAutoreleasedReturnValue(v30);
objc_release(v33);
objc_release(v28);
objc_release(v26);
objc_release(v24);
objc_release(v22);
objc_release(v20);
objc_release(v18);
objc_release(v36);
objc_release(v34);
objc_release(v37);
objc_release(v13);
objc_release(v39);
objc_release(v35);
objc_release(v41);
objc_release(v44);
objc_release(v31);
objc_release(v45);
objc_release(v42);
objc_release(v38);
objc_release(v43);
return objc_autoreleaseReturnValue(v40);
}
hook
// -[XYBaseRequestHeader hsignBeforeMD5Encryption](XYBaseRequestHeader *self, SEL a2)
// Frida script to call getter methods on an object
// Get a handle for the target method
var targetMethod = ObjC.classes.XYBaseRequestHeader['- hsignBeforeMD5Encryption'];
Interceptor.attach(targetMethod.implementation, {
/**
* Called when the method is entered.
* We will call the object's own methods to get the component strings.
*/
onEnter: function (args) {
console.log("\n[+] Intercepted -[XYBaseRequestHeader hsignBeforeMD5Encryption]");
// Get a handle to the 'self' object instance
const self = ObjC.Object(args[0]);
// This object will store the individual string values we read
const signatureComponents = {};
// This is the list of getter methods we identified from your pseudocode
const getterMethods = [
"happver",
"himei",
"hos",
"hosver",
"hpath",
"hproductid",
"hr",
"htime",
"hsignSuffix"
];
console.log("Reading component strings from the 'self' object:");
// Loop through the list of method names and call each one on 'self'
getterMethods.forEach(function(methodName) {
// Check if the method exists before calling it to prevent errors
if (self[methodName]) {
const result = self[methodName](); // e.g., self.happver()
// Convert the result (likely an NSString) to a readable string
signatureComponents[methodName] = result ? result.toString() : "null";
} else {
signatureComponents[methodName] = "method not found";
}
});
// Print all the collected component strings in a clean JSON format
console.log(JSON.stringify(signatureComponents, null, 2));
},
/**
* Called when the method is about to return.
* 'retval' is the final, assembled string.
*/
onLeave: function (retval) {
if (!retval.isNull()) {
const finalString = ObjC.Object(retval).toString();
console.log("\n[✅] Final string returned by the method:");
console.log(finalString);
} else {
console.log("\n[-] Method returned nil.");
}
console.log("--------------------------------------------------");
}
});
[+] Intercepted -[XYBaseRequestHeader hsignBeforeMD5Encryption]
Reading component strings from the 'self' object:
{
"happver": "17.4.0",
"himei": "f7d80f1550c82ea9bceacef128b70dc9f70f599e",
"hos": "ios",
"hosver": "15.8.1",
"hpath": "https://user.ksedt.com/api/login/v2",
"hproductid": "3",
"hr": "335580",
"htime": "1756675077215",
"hsignSuffix": "hZLJ3qzMgFK25A2S"
}
[✅] Final string returned by the method:
17.4.0_f7d80f1550c82ea9bceacef128b70dc9f70f599e___ios_15.8.1_https://user.ksedt.com/api/login/v2_3_335580_1756675077215_hZLJ3qzMgFK25A2S
Based on the code you’re analyzing, himei is a custom-named method that retrieves a unique device identifier. The h is likely a developer-specific prefix, and imei stands for IMEI.
What is an IMEI?
The IMEI (International Mobile Equipment Identity) is a unique 15-digit number used to identify a specific mobile device on a cellular network. Think of it as a serial number or a fingerprint for your phone. It’s tied to the phone’s hardware, not your SIM card.1
This number allows network providers to track devices, approve their access to the network, and block a phone if it’s been reported stolen.
The Modern iOS Context (Privacy)
A crucial point for your analysis is that Apple no longer allows apps to access the real hardware IMEI on modern versions of iOS. This was restricted many years ago to protect user privacy.
Therefore, while the method in the app is named himei, it’s almost certainly not getting the actual IMEI. Instead, it is likely retrieving a privacy-safe alternative provided by Apple, such as the identifierForVendor (IDFV).
- Identifier for Vendor (IDFV): This is an alphanumeric string that uniquely identifies a device to the app’s developer. All apps from the same developer on a single device will share the same IDFV. However, if the user deletes all apps from that developer, the IDFV will be reset upon reinstallation.
Conclusion
In short, the himei method is the app’s internal way of getting a unique device identifier to include in its security signature. While it’s named after the old IMEI standard, on a modern iPhone, it’s actually fetching a more private, temporary identifier like the IDFV.
hext-union
shift + F12 to open string view and search hext-union


/**
* @brief Processes a dictionary to create a derived, encrypted/signed value under the key "hext-union".
* If the key already exists, it does nothing. Otherwise, it filters the dictionary based on a
* given set of keys, creates a JSON string from the filtered data, "encrypts" it, and adds it back.
*
* Probable Original Signature:
* + (NSDictionary *)processDictionary:(NSDictionary *)inputDict withKeysToProcess:(NSSet *)keys shouldRemoveProcessedKeys:(BOOL)removeFlag;
*
* @param a1 The input dictionary to process.
* @param a2 A set of keys that should be included in the encryption process.
* @param a3 A boolean flag. If false, the processed keys are removed from the final dictionary.
* @return A new dictionary that may contain the "hext-union" key-value pair.
*/
id __fastcall sub_10396DBE0(void *a1, void *a2, char a3)
{
id v4; // x20
void *v5; // x21
id v6; // x0
id v7; // x23
void *v8; // x21
id v9; // x22
void *v10; // x0
void *v11; // x0
id v12; // x24
id v13; // x25
id v14; // x0
id v15; // x26
__int64 v16; // x27
void *i; // x23
id v18; // x0
id v19; // x0
id v20; // x19
id v21; // x21
void *v22; // x0
id v23; // x19
void *v24; // x0
id v25; // x21
id v26; // x21
id v28; // [xsp+8h] [xbp-138h]
// Retain input arguments: a1 is the input dictionary, a2 is the set of keys to process.
objc_retain(a1);
v4 = objc_retain(a2);
// Get all keys from the input dictionary.
v6 = sub_103C70240(v5); // _objc_msgSend(a1, "allKeys");
v7 = objc_retainAutoreleasedReturnValue(v6);
// Check if the dictionary ALREADY contains the "hext-union" key.
// NOTE: The decompiler missed the string argument. The assembly at 0x10396DC40
// shows this is actually: [v7 containsObject:@"hext-union"]
if ( (sub_103CA5A80(v7) & 1) != 0 ) // _objc_msgSend(v7, "containsObject:");
{
// If the key already exists, do nothing further. Just return a retained copy of the original dictionary.
v9 = objc_retain(v8);
}
else
{
// --- If "hext-union" is NOT present, we must generate it. ---
// 1. Create a mutable copy of the input dictionary. This is what we'll modify and ultimately return.
v10 = sub_103CBDDC0(&OBJC_CLASS___NSMutableDictionary);// _objc_msgSend(NSMutableDictionary, "dictionaryWithDictionary:", a1);
v9 = objc_retainAutoreleasedReturnValue(v10);
// 2. Create a new, empty dictionary to hold the key-value pairs that will be encrypted.
v11 = sub_103CBDB00(&OBJC_CLASS___NSMutableDictionary);// _objc_msgSend(NSMutableDictionary, "dictionary");
v12 = objc_retainAutoreleasedReturnValue(v11);
// 3. --- Filtering Loop ---
// Iterate through all the keys from the original dictionary.
v13 = objc_retain(v7);
v14 = sub_103CA8B80(v13); // Start of a for-in loop: for (id key in v13)
if ( v14 )
{
// ... loop mechanics ...
do
{
for ( i = 0LL; i != v15; i = i + 1 )
{
// ...
if ( *(8LL * i) ) // For each key...
{
// Check if this key is in the provided set of keys to process (v4).
if ( sub_103CA5A80(v4) ) // _objc_msgSend(v4, "containsObject:", key)
{
// If it is, get its value and add it to our temporary 'filtered' dictionary (v12).
v18 = sub_103D4DAC0(v9); // _objc_msgSend(v9, "objectForKeyedSubscript:", key);
objc_retainAutoreleasedReturnValue(v18);
v19 = sub_103E3E760(&OBJC_CLASS___NSString);// _objc_msgSend(NSString, "stringWithFormat:", @"%@", value);
v20 = objc_retainAutoreleasedReturnValue(v19);
sub_103DED500(v12); // _objc_msgSend(v12, "setObject:forKeyedSubscript:", v20, key);
objc_release(v20);
objc_release(v21);
// If the boolean flag 'a3' is false, remove the processed key from the main dictionary.
if ( (a3 & 1) == 0 )
sub_103D7B3C0(v9); // _objc_msgSend(v9, "removeObjectForKey:", key);
}
}
}
v15 = sub_103CA8B80(v13); // Continue loop
}
while ( v15 );
}
objc_release(v13);
// 4. --- Encryption/Transformation ---
// Convert the dictionary of filtered parameters into a JSON string.
v22 = sub_103D296E0(v12); // _objc_msgSend(v12, "jx_toJsonString");
v23 = objc_retainAutoreleasedReturnValue(v22);
// THIS IS THE ACTUAL ENCRYPTION/TRANSFORMATION FUNCTION.
// It takes the JSON string (v23) and returns the final value.
v24 = sub_10396DACC(v23);
objc_retainAutoreleasedReturnValue(v24);
// 5. --- Set the Final Value ---
// Set the encrypted value back into the main dictionary (v9) using the key "hext-union".
// NOTE: The decompiler missed the key argument. The assembly at 0x10396DDCC shows
// this is actually: [v9 setObject:v25 forKeyedSubscript:@"hext-union"];
sub_103DED500(v9);
objc_release(v25);
objc_release(v23);
objc_release(v12);
v7 = v28;
}
// --- Cleanup and Return ---
objc_release(v7);
objc_release(v4);
objc_release(v26);
// Return the final dictionary (either the original or the modified one).
return objc_autoreleaseReturnValue(v9);
}
the “encryption” of the value for the hext-union key happens inside the function sub_10396DACC.
You don’t see the string hext-union in the pseudocode because of a common limitation in decompilers like IDA’s Hex-Rays. While incredibly powerful, they can sometimes fail to correctly identify and display constant string arguments, especially when they are passed through registers. The assembly code, however, shows the ground truth.
The “Encryption” Function (sub_10396DACC)
Let’s trace the logic to see how the value for hext-union is created:
- Filtering Data: The code iterates through a dictionary. It picks out specific key-value pairs and puts them into a new dictionary called
v12. - JSON Conversion: It then converts this new dictionary (
v12) into a JSON string by callingsub_103D296E0(which corresponds to[v12 jx_toJsonString]). - Transformation/Encryption: This JSON string is then passed as an argument to
sub_10396DACC. This function is the one that performs the actual transformation, hashing, or “encryption.” - Setting the Value: The result from
sub_10396DACCis then set back into the main dictionary usinghext-unionas the key.
So, the function you’re looking for is sub_10396DACC. Its input is a JSON string of filtered parameters, and its output is the final value for the hext-union key.
Why “hext-union” is Missing from the Pseudocode
This is a classic reverse engineering scenario where the assembly tells a clearer story than the decompiled C code.
The Assembly Code Provides the Proof:
Your assembly view clearly shows the string being used twice:
First Use (The Check):
Code snippet
__text:000000010396DC40 ADRL X2, cfstr_HextUnion ; "hext-union" __text:000000010396DC48 BL sub_103CA5A80Here, the address of the string
"hext-union"is loaded into theX2register right before callingsub_103CA5A80(containsObject:). In the ARM64 calling convention,X2is the first argument to the method. So this is equivalent to[v7 containsObject:@"hext-union"]. The decompiler missed this.Second Use (Setting the Value):
Code snippet
__text:000000010396DDCC ADRL X3, cfstr_HextUnion ; "hext-union" ... __text:000000010396DDDC BL sub_103DED500Here, the string
"hext-union"is loaded into theX3register. The call is tosub_103DED500(setObject:forKeyedSubscript:). For this method,X2holds the object (the value to set) andX3holds the key. This is equivalent to[dictionary setObject:encryptedValue forKey:@"hext-union"]. The decompiler also failed to represent this constant string.
Summary of the Function’s Purpose
Putting it all together, the sub_10396DBE0 function does the following:
- It receives a dictionary of parameters.
- It checks if this dictionary already contains the key
"hext-union". If it does, it returns the dictionary as is. - If not, it filters out a subset of the parameters, converts them to a JSON string, “encrypts” that string using
sub_10396DACC, and adds the result back to the dictionary under the key"hext-union".
This is a very common pattern for creating a derived parameter, like a signature or a checksum, from other request data before sending it in a network request. The cross-reference (XREF) in your assembly (-[XYBaseRequestHeader headerKeyValues]+240) strongly suggests this function is used to prepare HTTP header values.
// 1. Get the base address of the main application binary.
// This is crucial because of ASLR (Address Space Layout Randomization).
const baseAddr = Module.findBaseAddress('Driver'); // this name is found by command `frida-ps -Uai`
if (!baseAddr) {
console.error("Could not find base address for YourAppName. Is the app running?");
} else {
// 2. Calculate the function's real address in memory.
// The address from IDA (0x10396DACC) is the file offset.
// We subtract the default image base address (0x100000000) to get the true offset.
const functionOffset = 0x10396DACC - 0x100000000;
const targetAddr = baseAddr.add(functionOffset);
console.log(`[+] Target App Base Address: ${baseAddr}`);
console.log(`[+] Encryption Function Address: ${targetAddr}`);
// 3. Use Interceptor to hook the function at its address.
Interceptor.attach(targetAddr, {
// This function is called when sub_10396DACC is entered.
onEnter: function(args) {
console.log("\n[+] Intercepted call to encryption function!");
// The first argument (a1) is in args[0]. It's a pointer to an Objective-C object (id).
// We can wrap it with ObjC.Object to interact with it.
const inputString = new ObjC.Object(args[0]);
// Log the original input (the cleartext JSON string).
console.log(" [->] Input (Cleartext JSON):", inputString.toString());
// You could even save it for later use in onLeave
this.input = inputString.toString();
},
// This function is called when sub_10396DACC is about to return.
onLeave: function(retval) {
// The return value is in retval. It's also a pointer to an Objective-C object (id).
const outputString = new ObjC.Object(retval);
// Log the original output (the encrypted, Base64-encoded string).
console.log(" [<-] Output (Encrypted String):", outputString.toString());
}
});
}
[+] Target App Base Address: 0x102d04000
[+] Encryption Function Address: 0x106671acc
[Apple iPhone::com.jiaxiao.driveAPP ]->
[+] Intercepted call to encryption function!
[->] Input (Cleartext JSON): {"himei":"f7d80f1550c82ea9bceacef128b70dc9f70f599e","zxaid":"A01-ZPRKHXiMy6GFjN+mdZRC4PA654J6Pk9h","hcityid":"143","hosver":"15.8.1","zxid":"Z01-1758934824-fkbw7wDLUK6R093X-DE52"}
[<-] Output (Encrypted String): B5zCEgw3MF1yGl6wHAUHImZGyrDUTGR4u43K_utaU8BadPsWonMhefeE_GYcij0KG2UEDAqTIJNQh1ZSBgPICxcb-9L8it95Ys9OjiJR8V9z1uvUns8vVikFownxpEOjBPv0_EwImE42DIap8IOopbBKHMrc9v4aMxCS7XkvUzKHIj-H7tClmpm45GLrxFsWdGYdBlXNN_qfdnxWQ_sOpYAemBGRoKN3gN4mciLx9puPoMxX37UrcuWheL3y6gj7
so we can dive into the function sub_10396DACC
/**
* @brief This function encrypts a given string using AES-256 and then Base64-encodes the result.
* It's the core encryption routine used to generate the value for the "hext-union" parameter.
*
* Probable Original Signature:
* + (NSString *)encryptAndEncodeString:(NSString *)jsonString;
*
* @param a1 The input string (expected to be JSON) to be encrypted.
* @return An AES-256 encrypted, web-safe, Base64-encoded NSString.
*/
id __fastcall sub_10396DACC(void *a1)
{
id v1; // x20
void *v2; // x0
id v3; // x19
__int64 v4; // x21
void *v5; // x0
id v6; // x0
id v7; // x22
id v8; // x0
id v9; // x20
void *v10; // x21
id v11; // x0
id v12; // x23
id v13; // x0
id v14; // x24
void *v15; // x0
id v16; // x20
id v17; // x21
// Retain the input string (a1) for the duration of this function.
v1 = objc_retain(a1);
// --- Step 1: Fetch Encryption Key and IV ---
// Get the secret key by calling a class method on XYBaseRequestHeader.
v2 = sub_103CCBAC0(&OBJC_CLASS___XYBaseRequestHeader); // _objc_msgSend([XYBaseRequestHeader class], "encryptSecretKey");
v3 = objc_retainAutoreleasedReturnValue(v2); // v3 now holds the secretKey string.
// Get the secret Initialization Vector (IV) from another class method.
v5 = sub_103CCBAA0(*(v4 + 1688)); // _objc_msgSend([XYBaseRequestHeader class], "encryptSecretIv");
objc_retainAutoreleasedReturnValue(v5); // The result (secretIv string) is stored in v10/v17.
// --- Step 2: Convert All Inputs to Raw Data (NSData) ---
// Convert the input JSON string to an NSData object for encryption.
v6 = sub_103D29680(v1); // _objc_msgSend(v1, "jx_toData");
v7 = objc_retainAutoreleasedReturnValue(v6); // v7 now holds the jsonData.
objc_release(v1); // Original string is no longer needed.
// Convert the secret key string to NSData.
v8 = sub_103D29680(v3);
v9 = objc_retainAutoreleasedReturnValue(v8); // v9 now holds the keyData.
// Convert the secret IV string to NSData.
v11 = sub_103D29680(v10);
v12 = objc_retainAutoreleasedReturnValue(v11); // v12 now holds the ivData.
// --- Step 3: Perform AES-256 Encryption ---
// This is the core encryption call. It encrypts the jsonData (v7) using the keyData (v9) and ivData (v12).
v13 = sub_103D28160(v7); // _objc_msgSend(v7, "jx_aes256EncryptWithKey:iv:", v9, v12);
v14 = objc_retainAutoreleasedReturnValue(v13); // v14 now holds the raw encryptedData.
objc_release(v12); // ivData is no longer needed.
objc_release(v9); // keyData is no longer needed.
objc_release(v7); // jsonData is no longer needed.
// --- Step 4: Base64-Encode the Encrypted Data ---
// The raw encrypted data is encoded into a web-safe Base64 string for safe transport over HTTP.
v15 = sub_103E3E1C0(&OBJC_CLASS___GTMBase64); // _objc_msgSend([GTMBase64 class], "stringByWebSafeEncodingData:padded:", v14, NO);
v16 = objc_retainAutoreleasedReturnValue(v15); // v16 now holds the final Base64-encoded string.
objc_release(v14); // encryptedData is no longer needed.
// --- Step 5: Cleanup and Return ---
// Release the originally fetched key and IV strings.
objc_release(v17);
objc_release(v3);
// Return the final, encrypted, and Base64-encoded string.
return objc_autoreleaseReturnValue(v16);
}
[+] AES Encrypt Call Intercepted
args[0] (Data to encrypt): {length = 179, bytes = 0x7b226869 6d656922 3a226637 64383066 ... 582d4445 3532227d }
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
281575880 7b 22 68 69 6d 65 69 22 3a 22 66 37 64 38 30 66 {"himei":"f7d80f
281575890 31 35 35 30 63 38 32 65 61 39 62 63 65 61 63 65 1550c82ea9bceace
2815758a0 66 31 32 38 62 37 30 64 63 39 66 37 30 66 35 39 f128b70dc9f70f59
2815758b0 39 65 22 2c 22 7a 78 61 69 64 22 3a 22 41 30 31 9e","zxaid":"A01
2815758c0 2d 5a 50 52 4b 48 58 69 4d 79 36 47 46 6a 4e 2b -ZPRKHXiMy6GFjN+
2815758d0 6d 64 5a 52 43 34 50 41 36 35 34 4a 36 50 6b 39 mdZRC4PA654J6Pk9
2815758e0 68 22 2c 22 68 63 69 74 79 69 64 22 3a 22 31 34 h","hcityid":"14
2815758f0 33 22 2c 22 68 6f 73 76 65 72 22 3a 22 31 35 2e 3","hosver":"15.
281575900 38 2e 31 22 2c 22 7a 78 69 64 22 3a 22 5a 30 31 8.1","zxid":"Z01
281575910 2d 31 37 35 38 39 33 34 38 32 34 2d 66 6b 62 77 -1758934824-fkbw
281575920 37 77 44 4c 55 4b 36 52 30 39 33 58 2d 44 45 35 7wDLUK6R093X-DE5
281575930 32 22 7d 2"}
args[2] (Keyjx_aes256EncryptWithKey): {length = 16, bytes = 0x3338387a7a6837707734696167307039}
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
282dfd9a0 33 38 38 7a 7a 68 37 70 77 34 69 61 67 30 70 39 388zzh7pw4iag0p9
args[3] (IV): {length = 16, bytes = 0x336b796c77676c6d63376d7a66783265}
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
282dfc540 33 6b 79 6c 77 67 6c 6d 63 37 6d 7a 66 78 32 65 3kylwglmc7mzfx2e
retval (Encrypted Data): {length = 192, bytes = 0x079cc212 0c37305d 721a5eb0 1c050722 ... e5a178bd f2ea08fb }
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
281753750 07 9c c2 12 0c 37 30 5d 72 1a 5e b0 1c 05 07 22 .....70]r.^...."
281753760 66 46 ca b0 d4 4c 64 78 bb 8d ca fe eb 5a 53 c0 fF...Ldx.....ZS.
281753770 5a 74 fb 16 a2 73 21 79 f7 84 fc 66 1c 8a 3d 0a Zt...s!y...f..=.
281753780 1b 65 04 0c 0a 93 20 93 50 87 56 52 06 03 c8 0b .e.... .P.VR....
281753790 17 1b fb d2 fc 8a df 79 62 cf 4e 8e 22 51 f1 5f .......yb.N."Q._
2817537a0 73 d6 eb d4 9e cf 2f 56 29 05 a3 09 f1 a4 43 a3 s...../V).....C.
2817537b0 04 fb f4 fc 4c 08 98 4e 36 0c 86 a9 f0 83 a8 a5 ....L..N6.......
2817537c0 b0 4a 1c ca dc f6 fe 1a 33 10 92 ed 79 2f 53 32 .J......3...y/S2
2817537d0 87 22 3f 87 ee d0 a5 9a 99 b8 e4 62 eb c4 5b 16 ."?........b..[.
2817537e0 74 66 1d 06 55 cd 37 fa 9f 76 7c 56 43 fb 0e a5 tf..U.7..v|VC...
2817537f0 80 1e 98 11 91 a0 a3 77 80 de 26 72 22 f1 f6 9b .......w..&r"...
281753800 8f a0 cc 57 df b5 2b 72 e5 a1 78 bd f2 ea 08 fb ...W..+r..x.....
but something seems wrong. the result is different with the one in Charles

![]()
so i decided to hook base64

0x1029FEE60
id __cdecl +[GTMBase64 stringByWebSafeEncodingData:padded:](id a1, SEL a2, id a3, bool a4)
{
__int64 v5; // x21
id result; // x0
id v7; // x0
void *v8; // x0
sub_103C8A6E0(a3); // _objc_msgSend(a1, "bytes");
sub_103D30280(a3); // _objc_msgSend(a1, "length");
result = sub_103C7CB40(v5); // _objc_msgSend(a1, "baseEncode:length:charset:padded:");
if ( result )
{
v7 = objc_alloc(&OBJC_CLASS___NSString);
v8 = sub_103D06D40(v7); // _objc_msgSend(a1, "initWithData:encoding:");
result = objc_autorelease(v8);
}
return result;
}
if (ObjC.available) {
console.log("[*] Objective-C runtime available. Attaching hook...");
try {
// 1. Get a handle to the target Objective-C method.
const targetMethod = ObjC.classes.GTMBase64['+ stringByWebSafeEncodingData:padded:'];
// 2. Use Interceptor to attach to the method's implementation pointer.
Interceptor.attach(targetMethod.implementation, {
// This function is called when the method is entered.
onEnter: function (args) {
console.log("\n[+] Intercepted +[GTMBase64 stringByWebSafeEncodingData:padded:]");
// --- Accessing Arguments ---
// args[0] is 'self': the GTMBase64 class object.
// args[1] is '_cmd': the selector "stringByWebSafeEncodingData:padded:".
// The first REAL argument (the NSData) is at index 2.
const nsData = new ObjC.Object(args[2]);
const dataLength = nsData.length();
const dataBytes = nsData.bytes();
// The second REAL argument (the boolean 'padded') is at index 3.
const isPadded = args[3].toInt32() !== 0;
console.log(` [->] Input (Plaintext NSData, ${dataLength} bytes, padded: ${isPadded}): 📄`);
// Print a hexdump of the input data.
if (dataLength > 0 && dataBytes) {
console.log(hexdump(dataBytes, {
length: Math.min(dataLength, 256)
}));
}
},
// This function is called when the method is about to return.
onLeave: function (retval) {
// 'retval' holds the return value, which is a pointer to an NSString.
const retvalString = new ObjC.Object(retval);
console.log(" [<-] Return Value (Base64 NSString): 📜", retvalString.toString());
}
});
console.log("[*] Hook is active. Waiting for the app to call the method...");
} catch (error) {
console.error("[!] An error occurred while setting up the hook:", error.message);
}
} else {
console.error("[-] Objective-C runtime not available.");
}
[Apple iPhone::com.jiaxiao.driveAPP ]-> [*] Objective-C runtime available. Attaching hook...
[*] Hook is active. Waiting for the app to call the method...
[*] Objective-C runtime available. Attaching hook...
[*] Hook is active. Waiting for the app to call the method...
[+] Intercepted +[GTMBase64 stringByWebSafeEncodingData:padded:]
[->] Input (Plaintext NSData, 192 bytes, padded: false): 📄
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
280efab20 07 9c c2 12 0c 37 30 5d 72 1a 5e b0 1c 05 07 22 .....70]r.^...."
280efab30 66 46 ca b0 d4 4c 64 78 bb 8d ca fe eb 5a 53 c0 fF...Ldx.....ZS.
280efab40 5a 74 fb 16 a2 73 21 79 f7 84 fc 66 1c 8a 3d 0a Zt...s!y...f..=.
280efab50 1b 65 04 0c 0a 93 20 93 50 87 56 52 06 03 c8 0b .e.... .P.VR....
280efab60 17 1b fb d2 fc 8a df 79 62 cf 4e 8e 22 51 f1 5f .......yb.N."Q._
280efab70 73 d6 eb d4 9e cf 2f 56 29 05 a3 09 f1 a4 43 a3 s...../V).....C.
280efab80 04 fb f4 fc 4c 08 98 4e 36 0c 86 a9 f0 83 a8 a5 ....L..N6.......
280efab90 b0 4a 1c ca dc f6 fe 1a 33 10 92 ed 79 2f 53 32 .J......3...y/S2
280efaba0 87 22 3f 87 ee d0 a5 9a 99 b8 e4 62 eb c4 5b 16 ."?........b..[.
280efabb0 74 66 1d 06 55 cd 37 fa 9f 76 7c 56 43 fb 0e a5 tf..U.7..v|VC...
280efabc0 80 1e 98 11 91 a0 a3 77 80 de 26 72 22 f1 f6 9b .......w..&r"...
280efabd0 8f a0 cc 57 df b5 2b 72 e5 a1 78 bd f2 ea 08 fb ...W..+r..x.....
[<-] Return Value (Base64 NSString): 📜 B5zCEgw3MF1yGl6wHAUHImZGyrDUTGR4u43K_utaU8BadPsWonMhefeE_GYcij0KG2UEDAqTIJNQh1ZSBgPICxcb-9L8it95Ys9OjiJR8V9z1uvUns8vVikFownxpEOjBPv0_EwImE42DIap8IOopbBKHMrc9v4aMxCS7XkvUzKHIj-H7tClmpm45GLrxFsWdGYdBlXNN_qfdnxWQ_sOpYAemBGRoKN3gN4mciLx9puPoMxX37UrcuWheL3y6gj7
I finally remembered to use hexdump, not from hex lol. never mind XD this shit waste me about three hours :angry::punch:

