To prepare

Charles packet capture shows that there is a validation parameter, signature, in the request, and the network changes every time the request is made.

Request address: http://xxxx.xxxxxx.com/api/article/v2/get_category parameters: {"content": {"muid" :"f7e2cb93-5cf3-4b9f-a035-30555c13a167"},
"signature":"6c171c8f2bb05caca19047e3c4a04a7adff9eb3b3973ff3064fa4ab1ba17de64"."sig_kv":"503 _1"."cten":"p"
}
Copy the code

The purpose of this debugging is to find the signature generation algorithm.

Debug using Frida

  1. The installation of frida

Install Frida: Add the source (build.frida.re/) to Cydia, then find Frida in the source and install it.

Install Frida on Mac: Python is required. Use “PIP install Frida” to install frida (for details, see www.frida.re).

  1. Monitor the arguments and call stack for +[NSURL URLWithString:] using Frida

Create a folder test and go to the test directory

Print the app information of the iPhone, and enter the command on the terminal:

frida-ps -Ua
Copy the code

The output is as follows:

  PID  Name        Identifier                   
-----  ----------  -----------------------------
17521  testApp com.testapp.zodiac 2048 Alipay com.alipay.iphoneclient 4296 calendar com.apple.mobilecal 3551 camera com.apple.cameraCopy the code

TestApp’s PID is 17521

Monitor testApp’s “+[NSURL URLWithString:]” method, terminal command:

frida-trace -U 17521 -m "+[NSURL URLWithString:]"

Copy the code

Terminal output:

Instrumenting functions...                                              
+[NSURL URLWithString:]: Loaded handler at "/Users/king/Documents/test/__handlers__/__NSURL_URLWithString__.js"
Started tracing 1 function. Press Ctrl+C to stop.  
Copy the code

On the terminal screen, press “Control + C” to exit the frida monitoring status. The __nsurl_urlWithString__.js file is found in the __handlers__ folder in the test folder.

{
    onEnter: function (log, args, state) {
        log("+[NSURL URLWithString:" + args[2] + "]");
    },

    onLeave: function (log, retval, state) {
    
    }
}
Copy the code

Edit the contents of the file. The result is as follows:

{
    onEnter: function (log, args, state) {
        log("+[NSURL URLWithString:" + ObjC.Object(args[2]) + "]");
        log('\tBacktrace:\n\t' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t'));
    },

    onLeave: function (log, retval, state) {
        log("+[NSURL URLWithString:]--return=(" + ObjC.Object(retval) + ")"); }}Copy the code

Objc. Object(args[2]) Prints the value of the parameter

log(‘\tBacktrace:\n\t’ + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join(‘\n\t’)); Print the call stack

log(“+[NSURL URLWithString:]–return=(” + ObjC.Object(retval) + “)”); Print return value

By doing so, Frida can print out parameters and stacks when monitoring NSURL, allowing us to quickly find the location of the network request.

Enable friDA monitoring on the terminal again:

frida-trace -U 17521 -m "+[NSURL URLWithString:]"
Copy the code

When you request the network, you will see the terminal print information:

4913 ms  +[NSURL URLWithString:http:/testApp.ohippo.com/api/article/v2/get_list]
  4913 ms  	Backtrace:
	0x100bdfecc testApp! 0xb87ecc 0x100be0294testApp! 0xb88294 0x1001dcee4testApp! 0x184ee4 0x1001dd6d4testApp! 0x1856d4 0x100087d18testApp! 0x2fd18 0x100086ef4testApp! 0x2eef4 0x193ce8ec0 UIKit! -[UIViewController loadViewIfRequired] 0x193ce8a9c UIKit! -[UIViewController view] 0x100176df0testApp! 0x11edf0 0x10014dbcctestApp! 0xf5bcc 0x193d1e010 UIKit! -[UIApplication sendAction:to:from:forEvent:] 0x193d1df90 UIKit! -[UIControl sendAction:to:forEvent:] 0x193d08504 UIKit! -[UIControl _sendActionsForEvents:withEvent:] 0x193d1d874 UIKit! -[UIControl touchesEnded:withEvent:] 0x193d1d390 UIKit! -[UIWindow _sendTouchesForEvent:] 0x193d18728 UIKit! -[UIWindow sendEvent:] 4916 ms +[NSURL URLWithString:]--return=(http:/testApp.ohippo.com/api/article/v2/get_list)
Copy the code

There is a lot of printed information, but here is only a part of the useful printed information.

Use LLDB + debugServer to attach the current process and print the module offset address as follows:

[  0] 0x0000000000058000 /var/containers/Bundle/Application/FA17E6F7-4386-40B1-8B87-0A138169E67F/testApp.app/testApp (0 x0000000100058000) [1] 0 x0000000101634000 / Users/king/Library/Developer/Xcode/iOS DeviceSupport / 10.3.2 (14F89)/Symbols/usr/lib/dyld ... .Copy the code

Calculate the address in IDA for this +[NSURL URLWithString:] method call: 0x100Bdfec-0x0000000000058000 = 0x100B87ECC

IDA 0x100B87ECC can be located to this method:

+[HSServerAPIRequest requestWithURL:dataBody:method:enableEncryption:hashKey:sigKey:]
Copy the code

Check in the IDA + [HSServerAPIRequest requestWithURL: dataBody: method: enableEncryption: hashKey: sigKey:] the pseudo code, You can see a + [HSServerAPIRequest parametersWithDataBody: enableEncryption: hashKey: sigKey:] method

Id __cdecl +[HSServerAPIRequest parametersWithDataBody:enableEncryption:hashKey:sigKey:](HSServerAPIRequest_meta *self, SEL a2, id a3, bool a4, id a5, id a6)
{
  v6 = a6;
  v7 = a5;
  v8 = a4;
  v9 = a3;
  v10 = self;
  v11 = objc_retain(a3, a2);
  v13 = objc_retain(v7, v12);
  v15 = objc_retain(v6, v14);
  v16 = ((id (__cdecl *)(HSUtils_meta *, SEL, id))objc_msgSend)(
          (HSUtils_meta *)&OBJC_CLASS___HSUtils,
          "jsonStringWithObject:",
          v9);
  v17 = objc_retainAutoreleasedReturnValue(v16);
  objc_release(v11);
  if ( v8 )
    v18 = objc_msgSend(v10, "encryptedParametersWithDataBodyString:hashKey:sigKey:", v17, v13, v15);
  else
    v18 = objc_msgSend(v10, "plainParametersWithDataBodyString:hashKey:sigKey:", v17, v13, v15);
  v19 = (struct objc_object *)objc_retainAutoreleasedReturnValue(v18);
  objc_autorelease(v19);
  return v19;
}
Copy the code

Can be seen in the pseudo code “encryptedParametersWithDataBodyString: hashKey: sigKey:” and “plainParametersWithDataBodyString: hashKey: sigKey:” method, You can follow them and see what their pseudocode is. The pseudo-code for this method shows the use of the AES256 encryption algorithm, but it turns out that signature generation is not related to this method, so I won’t go into details here.

Take a look at the pseudocode below:

id __cdecl +[HSServerAPIRequest plainParametersWithDataBodyString:hashKey:sigKey:](HSServerAPIRequest_meta *self, SEL a2, id a3, id a4, id a5)
{
  v5 = a5;
  v6 = a4;
  v7 = self;
  v8 = objc_retain(a3, a2);
  v10 = objc_retain(v6, v9);
  v12 = objc_retain(v5, v11);
  v13 = objc_msgSend(v7, "class");
  v14 = objc_msgSend(v13, "signedParametersWithContent:hashKey:sigKey:", v8, v10, v12);
  v15 = (void *)objc_retainAutoreleasedReturnValue(v14);
  objc_release(v12);
  objc_release(v10);
  objc_release(v8);
  objc_msgSend(v15, "setObject:forKey:", CFSTR("p"), CFSTR("cten"));
  return (id)objc_autoreleaseReturnValue(v15);
}
Copy the code

See from the pseudo code above, a “signedParametersWithContent: hashKey: sigKey:” method, we continue to follow up.

id __cdecl +[HSServerAPIRequest signedParametersWithContent:hashKey:sigKey:](HSServerAPIRequest_meta *self, SEL a2, id a3, id a4, id a5)
{
  
  v5 = a5;
  v6 = a4;
  v7 = objc_retain(a3, a2);
  v9 = (void *)objc_retain(v6, v8);
  v11 = (void *)objc_retain(v5, v10);
  v59 = CFSTR("content");
  v60 = v7;
  v12 = objc_msgSend(&OBJC_CLASS___NSDictionary, "dictionaryWithObjects:forKeys:count:", &v60, &v59, 1LL);
  v13 = objc_retainAutoreleasedReturnValue(v12);
  v14 = v13;
  v15 = objc_msgSend(&OBJC_CLASS___NSMutableDictionary, "dictionaryWithDictionary:", v13);
  v16 = (void *)objc_retainAutoreleasedReturnValue(v15);
  objc_release(v14);
  if ( objc_msgSend(v11, "length") )
  {
    v18 = (void *)objc_retain(v11, v17);
  }
  else
  {
    v19 = (HSConfig *)+[HSConfig sharedInstance](&OBJC_CLASS___HSConfig, "sharedInstance");
    v20 = (void *)objc_retainAutoreleasedReturnValue(v19);
    v21 = v20;
    v22 = objc_msgSend(v20, "data");
    v23 = (void *)objc_retainAutoreleasedReturnValue(v22);
    v24 = v23;
    v25 = objc_msgSend(v23, "valueForKeyPath:", CFSTR("libCommons.Connection.SigKey"));
    v18 = (void *)objc_retainAutoreleasedReturnValue(v25);
    objc_release(v24);
    objc_release(v21);
  }
  if ( objc_msgSend(v18, "length") )
    objc_msgSend(v16, "setObject:forKey:", v18, CFSTR("sig_kv"));
  if ( objc_msgSend(v9, "length") )
  {
    v27 = (void *)objc_retain(v9, v26);
    if ( objc_msgSend(v27, "length") != (void *)32 )
    {
      v28 = objc_msgSend(
              &OBJC_CLASS___NSException,
              "exceptionWithName:reason:userInfo:",
              CFSTR("wrong specified hash key"),
              CFSTR("the lengh of hash key is not correct"),
              0LL);
LABEL_16:
      v55 = (void *)objc_retainAutoreleasedReturnValue(v28);
      objc_msgSend(v55, "raise"); objc_release(v55); v54 = 0LL; goto LABEL_17; }}else
  {
    v29 = (HSConfig *)+[HSConfig sharedInstance](&OBJC_CLASS___HSConfig, "sharedInstance");
    v30 = (void *)objc_retainAutoreleasedReturnValue(v29);
    v31 = v30;
    v32 = objc_msgSend(v30, "data");
    v33 = (void *)objc_retainAutoreleasedReturnValue(v32);
    v34 = v33;
    v35 = objc_msgSend(v33, "valueForKeyPath:", CFSTR("libCommons.Connection.HashKey"));
    v27 = (void *)objc_retainAutoreleasedReturnValue(v35);
    objc_release(v34);
    objc_release(v31);
    if ( objc_msgSend(v27, "length") != (void *)32 )
    {
      v28 = objc_msgSend(&OBJC_CLASS___NSException, "exceptionWithName:reason:userInfo:");
      goto LABEL_16;
    }
  }
  v36 = sub_100B81304(v27);
  v37 = objc_retainAutoreleasedReturnValue(v36);
  v38 = objc_msgSend(v16, "objectForKeyedSubscript:", CFSTR("content"));
  v39 = objc_retainAutoreleasedReturnValue(v38);
  objc_release(v39);
  if ( v39 )
  {
    v57 = v11;
    v58 = v7;
    v41 = objc_msgSend(v16, "objectForKeyedSubscript:", CFSTR("content"));
    v42 = objc_retainAutoreleasedReturnValue(v41);
    v44 = objc_retain(v37, v43);
    v45 = (void *)objc_retainAutorelease(v44);
    v46 = (const char *)objc_msgSend(v45, "cStringUsingEncoding:", 4LL);
    objc_release(v45);
    v47 = (void *)objc_retainAutorelease(v42);
    v48 = (const char *)objc_msgSend(v47, "cStringUsingEncoding:", 4LL);
    v49 = strlen(v46);
    v50 = strlen(v48);
    CCHmac(2LL, v46, v49, v48, v50, v61);
    v51 = objc_msgSend(&OBJC_CLASS___NSMutableString, "stringWithCapacity:", 64LL);
    v52 = (void *)objc_retainAutoreleasedReturnValue(v51);
    v53 = 0LL;
    do
      objc_msgSend(v52, "appendFormat:", CFSTR("%02x"), (unsigned __int8)v61[v53++]);
    while ( v53 != 32 );
    objc_msgSend(v16, "setObject:forKey:", v52, CFSTR("signature"));

    v7 = v58;
    v11 = v57;
  }
  v54 = objc_retain(v16, v40);
LABEL_17:
  if ( __stack_chk_guard == v62 )
    result = (id)objc_autoreleaseReturnValue(v54);
  return result;
}
Copy the code

You can see CCHmac(2LL, V46, V49, V48, V50, V61), this is the encryption algorithm.

I use dynamic debugging to determine the network request, to execute to this CCHmac to do encryption, might as well print the above several methods of the parameters and return values, can more intuitive to see the results. Here’s part of how I restored it:

+[HSServerAPIRequest requestWithURL:dataBody:method:enableEncryption:hashKey:sigKey:](HSServerAPIRequest_meta *self, SEL, id, id, signed __int64, bool, id, id) {"category_id" = 2586351c525f3793b98fa2592111e70e;
    direction = old;
    muid = "f7e2cb93-5cf3-4b9f-a035-30555c13a167";
    "nearest_article_id" = "these-are-the-6-zodiac-signs-who-are-most-likely-to-ghost-you-a16139";
    "page_size"= 10; } // Call this method +[HSServerAPIRequest parametersWithDataBody:enableEncryption:hashKey:sigKey:]; { NSString * pStr = +[HSUtils jsonStringWithObject:pDict]; / / = {"nearest_article_id" : "these-are-the-6-zodiac-signs-who-are-most-likely-to-ghost-you-a16139"."page_size" : 10,
  				"muid" : "f7e2cb93-5cf3-4b9f-a035-30555c13a167"."category_id" : "2586351c525f3793b98fa2592111e70e"."direction" : "old"
			}



		if() {/ / the following method to execute [HSServerAPIRequest plainParametersWithDataBodyString: arg1 = pStrhashKey:arg2=nil sigKey:arg3=nil ];
			{

				NSDictionary * dict = {content = "{\n \"nearest_article_id\" : \"these-are-the-6-zodiac-signs-who-are-most-likely-to-ghost-you-a16139\",\n \"page_size\" : 10,\n \"muid\" : \"f7e2cb93-5cf3-4b9f-a035-30555c13a167\",\n \"category_id\" : \"2586351c525f3793b98fa2592111e70e\",\n \"direction\" : \"old\"\n}"; } NSMutableDictionary * mutDict = [NSMutableDictionary dictionaryWithDictionary:dict]; id data;if([arg3 length]==0)
				{
					data = [[HSConfig sharedInstance] data];
					NSString * sigKey = [data valueForKeyPath:@"libCommons.Connection.SigKey"]; / / = @"503 _1"} int count = [sigKey length]; / / = 5if(count! =0) { [mutDictsetObject:sigKey forKey:@"sig_kv"];
				}

				if([arg2 length]==0)
				{
					NSString * hashKey = [data valueForKeyPath:@"libCommons.Connection.HashKey"]; / / ="E56j-4$X=XzA7H#H4]p2e@)V1=Rg6qS="


					if([hashKey length] == 32)// = 32
					{
						NSString * hashKey_2 = sub_100B81304(hashKey); / / ="HJdq=ZT? l? yp1)V)ZbRYw#E/il; &d,Nl"

						// x22 = mutDict
						NSString * content = [mutDict objectForKeyedSubscript:@"content"];
						// x19 = {"nearest_article_id" : "precise-ways-to-put-yourself-out-there-to-meet-mr-right-based-on-zodiac-signs-a16206"."page_size" : 10,"muid" : "f7e2cb93-5cf3-4b9f-a035-30555c13a167"."category_id" : "2586351c525f3793b98fa2592111e70e"."direction" : "old"}

						if(content)
						{
							char * hashKey_3 =[hashKey_2 cStringUsingEncoding:4]; char * content_3 = [content cStringUsingEncoding:4]; // = x19 int length_hashKey_3 = strlen(hashKey_3); // = x27 = 32 int length_content_3 = strlen(content_3); // = x4 = 263 _CCHmac(2,hashKey_3,length_hashKey_3,content_3,length_content_3); v51 = [NSMutableString stringWithCapacity:64LL]; // = v52 v53 = 0LL;do
                                [v52 appendFormat:@"%02x", (unsigned __int8)v61[v53++]);
                            while ( v53 != 32 );
                            [v16 setObject:v52 forKey:@"signature"];

						}
						else{}}else
					{
						return; }}else{}}}}}Copy the code

In the pseudocode above, you can see that the encryption parameters are hashKey_3 and content_3, v61 is used to hold the encrypted result, and v52 is the final signature value.

Analysis: CCHmac is a common encryption algorithm, a variety of programming languages have specific implementation, so it is easy to restore this encryption algorithm, a better way is to directly use. In Python, you can call this encryption algorithm directly, and I verified that it works perfectly.

conclusion

This paper focuses on monitoring method calls with Frida to find the key functions, and through static analysis in IDA, check the pseudo code to find the clues of the encryption algorithm, and combined with dynamic debugging, print out the parameters and return values of the algorithm, and finally restore the clear logic.

supplement

Thank you for clicking “⭐️” in the upper right corner. Thank you very much.

Github: github.com/luoyanbei/r…

Can pay attention to the public number: reverse APP, access to the reverse APP material files, convenient practice.

Follow the public account: reverse APP