├── README.md └── images ├── qrcode_gongzhonghao.jpg ├── wechatpay@2x.png └── zhifubao@2x.png /README.md: -------------------------------------------------------------------------------- 1 | # reserveSignatureOfOneApp 2 | 使用frida、ida和动态调试,逆向某app,找到网络请求中signature的生成算法 3 | 4 | 通过Charles抓包发现,请求中有一个验签参数signature,每次请求网络都会变化。 5 | 6 | ``` 7 | 请求地址: 8 | http://xxxx.xxxxxx.com/api/article/v2/get_category 9 | 10 | 参数: 11 | { 12 | "content":{"muid" :"f7e2cb93-5cf3-4b9f-a035-30555c13a167"}, 13 | "signature":"6c171c8f2bb05caca19047e3c4a04a7adff9eb3b3973ff3064fa4ab1ba17de64", 14 | "sig_kv":"503_1", 15 | "cten":"p" 16 | } 17 | ``` 18 | 本次调试的目的就是找到signature的生成算法。 19 | 20 | ### 使用frida调试 21 | 1. frida的安装 22 | 23 | 越狱手机安装Frida:在Cydia中添加源(https://build.frida.re/),接着在源中找到Frida并安装。 24 | 25 | Mac安装frida:需要先有Python环境,使用“pip install frida”安装frida 26 | (Frida的详细使用请参考官网:www.frida.re) 27 | 28 | 2. 使用frida监控+[NSURL URLWithString:]的参数和调用堆栈 29 | 30 | 新建一个文件夹test,终端进入test目录 31 | 32 | 打印iphone运行的app信息,终端输入命令: 33 | 34 | ``` 35 | frida-ps -Ua 36 | ``` 37 | 输出如下: 38 | 39 | ``` 40 | PID Name Identifier 41 | ----- ---------- ----------------------------- 42 | 17521 testApp com.testApp.zodiac 43 | 2048 支付宝 com.alipay.iphoneclient 44 | 4296 日历 com.apple.mobilecal 45 | 3551 相机 com.apple.camera 46 | ``` 47 | testApp的PID是17521 48 | 49 | 监控testApp中的"+[NSURL URLWithString:]"方法,终端命令: 50 | 51 | ``` 52 | frida-trace -U 17521 -m "+[NSURL URLWithString:]" 53 | 54 | ``` 55 | 终端输出: 56 | 57 | ``` 58 | Instrumenting functions... 59 | +[NSURL URLWithString:]: Loaded handler at "/Users/king/Documents/test/__handlers__/__NSURL_URLWithString__.js" 60 | Started tracing 1 function. Press Ctrl+C to stop. 61 | ``` 62 | 63 | 在终端界面,按"control+c"退出frida的监控状态。 64 | 在test文件夹中的__handlers__文件夹中找到__NSURL_URLWithString__.js文件,主要内容如下: 65 | 66 | ``` 67 | { 68 | onEnter: function (log, args, state) { 69 | log("+[NSURL URLWithString:" + args[2] + "]"); 70 | }, 71 | 72 | onLeave: function (log, retval, state) { 73 | 74 | } 75 | } 76 | ``` 77 | 编辑文件内容,结果如下: 78 | ``` 79 | { 80 | onEnter: function (log, args, state) { 81 | log("+[NSURL URLWithString:" + ObjC.Object(args[2]) + "]"); 82 | log('\tBacktrace:\n\t' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t')); 83 | }, 84 | 85 | onLeave: function (log, retval, state) { 86 | log("+[NSURL URLWithString:]--return=(" + ObjC.Object(retval) + ")"); 87 | } 88 | } 89 | ``` 90 | ObjC.Object(args[2]) 91 | 打印参数的值 92 | 93 | log('\tBacktrace:\n\t' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t')); 94 | 打印调用堆栈 95 | 96 | log("+[NSURL URLWithString:]--return=(" + ObjC.Object(retval) + ")"); 97 | 打印返回值 98 | 99 | 这样修改,frida监控NSURL时能打印出参数和堆栈,让我们能很快找到网络请求的位置。 100 | 101 | 终端再次开启frida监控: 102 | ``` 103 | frida-trace -U 17521 -m "+[NSURL URLWithString:]" 104 | ``` 105 | 当请求网络时,会看到终端的打印信息: 106 | 107 | ``` 108 | 4913 ms +[NSURL URLWithString:http:/testApp.ohippo.com/api/article/v2/get_list] 109 | 4913 ms Backtrace: 110 | 0x100bdfecc testApp!0xb87ecc 111 | 0x100be0294 testApp!0xb88294 112 | 0x1001dcee4 testApp!0x184ee4 113 | 0x1001dd6d4 testApp!0x1856d4 114 | 0x100087d18 testApp!0x2fd18 115 | 0x100086ef4 testApp!0x2eef4 116 | 0x193ce8ec0 UIKit!-[UIViewController loadViewIfRequired] 117 | 0x193ce8a9c UIKit!-[UIViewController view] 118 | 0x100176df0 testApp!0x11edf0 119 | 0x10014dbcc testApp!0xf5bcc 120 | 0x193d1e010 UIKit!-[UIApplication sendAction:to:from:forEvent:] 121 | 0x193d1df90 UIKit!-[UIControl sendAction:to:forEvent:] 122 | 0x193d08504 UIKit!-[UIControl _sendActionsForEvents:withEvent:] 123 | 0x193d1d874 UIKit!-[UIControl touchesEnded:withEvent:] 124 | 0x193d1d390 UIKit!-[UIWindow _sendTouchesForEvent:] 125 | 0x193d18728 UIKit!-[UIWindow sendEvent:] 126 | 4916 ms +[NSURL URLWithString:]--return=(http:/testApp.ohippo.com/api/article/v2/get_list) 127 | ``` 128 | 打印的信息很多,这里只截取了一部分有用的打印信息。 129 | 130 | 使用lldb+debugserver附加当前进程,打印模块偏移地址如下: 131 | 132 | ``` 133 | [ 0] 0x0000000000058000 /var/containers/Bundle/Application/FA17E6F7-4386-40B1-8B87-0A138169E67F/testApp.app/testApp(0x0000000100058000) 134 | [ 1] 0x0000000101634000 /Users/king/Library/Developer/Xcode/iOS DeviceSupport/10.3.2 (14F89)/Symbols/usr/lib/dyld 135 | ... 136 | ... 137 | ``` 138 | 计算本次 +[NSURL URLWithString:]方法调用在ida中的地址: 139 | 0x100bdfecc - 0x0000000000058000 = 0x100B87ECC 140 | 141 | 在ida中找到0x100B87ECC位置,可以定位到这个方法: 142 | 143 | ``` 144 | +[HSServerAPIRequest requestWithURL:dataBody:method:enableEncryption:hashKey:sigKey:] 145 | ``` 146 | 在ida中查看+[HSServerAPIRequest requestWithURL:dataBody:method:enableEncryption:hashKey:sigKey:]的伪代码,可以看到一个+[HSServerAPIRequest parametersWithDataBody:enableEncryption:hashKey:sigKey:]方法 147 | 148 | ``` 149 | ida中的伪代码: 150 | 151 | id __cdecl +[HSServerAPIRequest parametersWithDataBody:enableEncryption:hashKey:sigKey:](HSServerAPIRequest_meta *self, SEL a2, id a3, bool a4, id a5, id a6) 152 | { 153 | v6 = a6; 154 | v7 = a5; 155 | v8 = a4; 156 | v9 = a3; 157 | v10 = self; 158 | v11 = objc_retain(a3, a2); 159 | v13 = objc_retain(v7, v12); 160 | v15 = objc_retain(v6, v14); 161 | v16 = ((id (__cdecl *)(HSUtils_meta *, SEL, id))objc_msgSend)( 162 | (HSUtils_meta *)&OBJC_CLASS___HSUtils, 163 | "jsonStringWithObject:", 164 | v9); 165 | v17 = objc_retainAutoreleasedReturnValue(v16); 166 | objc_release(v11); 167 | if ( v8 ) 168 | v18 = objc_msgSend(v10, "encryptedParametersWithDataBodyString:hashKey:sigKey:", v17, v13, v15); 169 | else 170 | v18 = objc_msgSend(v10, "plainParametersWithDataBodyString:hashKey:sigKey:", v17, v13, v15); 171 | v19 = (struct objc_object *)objc_retainAutoreleasedReturnValue(v18); 172 | objc_autorelease(v19); 173 | return v19; 174 | } 175 | ``` 176 | 从伪代码中可以看到"encryptedParametersWithDataBodyString:hashKey:sigKey:"和"plainParametersWithDataBodyString:hashKey:sigKey:"方法,可以跟进去看它们的伪代码具体内容。 177 | 178 | ``` 179 | id __cdecl +[HSServerAPIRequest encryptedParametersWithDataBodyString:hashKey:sigKey:](HSServerAPIRequest_meta *self, SEL a2, id a3, id a4, id a5) 180 | { 181 | v5 = a5; 182 | v6 = a4; 183 | v7 = self; 184 | v8 = objc_retain(a3, a2); 185 | v10 = objc_retain(v6, v9); 186 | v12 = objc_retain(v5, v11); 187 | v13 = +[HSConfig sharedInstance](&OBJC_CLASS___HSConfig, "sharedInstance"); 188 | v14 = (void *)objc_retainAutoreleasedReturnValue(v13); 189 | v15 = v14; 190 | v16 = objc_msgSend(v14, "data"); 191 | v17 = (void *)objc_retainAutoreleasedReturnValue(v16); 192 | v18 = v17; 193 | v19 = objc_msgSend(v17, "valueForKeyPath:", CFSTR("libCommons.Connection.EncryptionKeyVersion")); 194 | v20 = (void *)objc_retainAutoreleasedReturnValue(v19); 195 | if ( !objc_msgSend(v20, "length") ) 196 | objc_msgSend( 197 | &OBJC_CLASS___NSException, 198 | "raise:format:", 199 | CFSTR("ConnectionConfigException"), 200 | CFSTR("EncryptionKeyVersion is empty")); 201 | v21 = objc_msgSend(v7, "class"); 202 | v22 = objc_msgSend(v21, "encryptKey"); 203 | v23 = objc_retainAutoreleasedReturnValue(v22); 204 | v24 = v23; 205 | v25 = +[HSAESUtils AES256EncryptString:withKey:](&OBJC_CLASS___HSAESUtils, "AES256EncryptString:withKey:", v8, v23); 206 | v26 = objc_retainAutoreleasedReturnValue(v25); 207 | v27 = objc_msgSend(v7, "class"); 208 | v28 = objc_msgSend(v27, "signedParametersWithContent:hashKey:sigKey:", v26, v10, v12); 209 | v29 = (void *)objc_retainAutoreleasedReturnValue(v28); 210 | objc_release(v12); 211 | objc_msgSend(v29, "setObject:forKey:", CFSTR("a"), CFSTR("cten")); 212 | objc_msgSend(v29, "setObject:forKey:", v20, CFSTR("cten_kv")); 213 | 214 | return (id)objc_autoreleaseReturnValue(v29); 215 | } 216 | ``` 217 | 这里可以看到使用了一个AES256加密算法。 218 | 219 | ``` 220 | id __cdecl +[HSServerAPIRequest plainParametersWithDataBodyString:hashKey:sigKey:](HSServerAPIRequest_meta *self, SEL a2, id a3, id a4, id a5) 221 | { 222 | v5 = a5; 223 | v6 = a4; 224 | v7 = self; 225 | v8 = objc_retain(a3, a2); 226 | v10 = objc_retain(v6, v9); 227 | v12 = objc_retain(v5, v11); 228 | v13 = objc_msgSend(v7, "class"); 229 | v14 = objc_msgSend(v13, "signedParametersWithContent:hashKey:sigKey:", v8, v10, v12); 230 | v15 = (void *)objc_retainAutoreleasedReturnValue(v14); 231 | objc_release(v12); 232 | objc_release(v10); 233 | objc_release(v8); 234 | objc_msgSend(v15, "setObject:forKey:", CFSTR("p"), CFSTR("cten")); 235 | return (id)objc_autoreleaseReturnValue(v15); 236 | } 237 | ``` 238 | 从上面的伪代码中,看到一个"signedParametersWithContent:hashKey:sigKey:"方法,我们继续跟进。 239 | 240 | ``` 241 | id __cdecl +[HSServerAPIRequest signedParametersWithContent:hashKey:sigKey:](HSServerAPIRequest_meta *self, SEL a2, id a3, id a4, id a5) 242 | { 243 | 244 | v5 = a5; 245 | v6 = a4; 246 | v7 = objc_retain(a3, a2); 247 | v9 = (void *)objc_retain(v6, v8); 248 | v11 = (void *)objc_retain(v5, v10); 249 | v59 = CFSTR("content"); 250 | v60 = v7; 251 | v12 = objc_msgSend(&OBJC_CLASS___NSDictionary, "dictionaryWithObjects:forKeys:count:", &v60, &v59, 1LL); 252 | v13 = objc_retainAutoreleasedReturnValue(v12); 253 | v14 = v13; 254 | v15 = objc_msgSend(&OBJC_CLASS___NSMutableDictionary, "dictionaryWithDictionary:", v13); 255 | v16 = (void *)objc_retainAutoreleasedReturnValue(v15); 256 | objc_release(v14); 257 | if ( objc_msgSend(v11, "length") ) 258 | { 259 | v18 = (void *)objc_retain(v11, v17); 260 | } 261 | else 262 | { 263 | v19 = (HSConfig *)+[HSConfig sharedInstance](&OBJC_CLASS___HSConfig, "sharedInstance"); 264 | v20 = (void *)objc_retainAutoreleasedReturnValue(v19); 265 | v21 = v20; 266 | v22 = objc_msgSend(v20, "data"); 267 | v23 = (void *)objc_retainAutoreleasedReturnValue(v22); 268 | v24 = v23; 269 | v25 = objc_msgSend(v23, "valueForKeyPath:", CFSTR("libCommons.Connection.SigKey")); 270 | v18 = (void *)objc_retainAutoreleasedReturnValue(v25); 271 | objc_release(v24); 272 | objc_release(v21); 273 | } 274 | if ( objc_msgSend(v18, "length") ) 275 | objc_msgSend(v16, "setObject:forKey:", v18, CFSTR("sig_kv")); 276 | if ( objc_msgSend(v9, "length") ) 277 | { 278 | v27 = (void *)objc_retain(v9, v26); 279 | if ( objc_msgSend(v27, "length") != (void *)32 ) 280 | { 281 | v28 = objc_msgSend( 282 | &OBJC_CLASS___NSException, 283 | "exceptionWithName:reason:userInfo:", 284 | CFSTR("wrong specified hash key"), 285 | CFSTR("the lengh of hash key is not correct"), 286 | 0LL); 287 | LABEL_16: 288 | v55 = (void *)objc_retainAutoreleasedReturnValue(v28); 289 | objc_msgSend(v55, "raise"); 290 | objc_release(v55); 291 | v54 = 0LL; 292 | goto LABEL_17; 293 | } 294 | } 295 | else 296 | { 297 | v29 = (HSConfig *)+[HSConfig sharedInstance](&OBJC_CLASS___HSConfig, "sharedInstance"); 298 | v30 = (void *)objc_retainAutoreleasedReturnValue(v29); 299 | v31 = v30; 300 | v32 = objc_msgSend(v30, "data"); 301 | v33 = (void *)objc_retainAutoreleasedReturnValue(v32); 302 | v34 = v33; 303 | v35 = objc_msgSend(v33, "valueForKeyPath:", CFSTR("libCommons.Connection.HashKey")); 304 | v27 = (void *)objc_retainAutoreleasedReturnValue(v35); 305 | objc_release(v34); 306 | objc_release(v31); 307 | if ( objc_msgSend(v27, "length") != (void *)32 ) 308 | { 309 | v28 = objc_msgSend(&OBJC_CLASS___NSException, "exceptionWithName:reason:userInfo:"); 310 | goto LABEL_16; 311 | } 312 | } 313 | v36 = sub_100B81304(v27); 314 | v37 = objc_retainAutoreleasedReturnValue(v36); 315 | v38 = objc_msgSend(v16, "objectForKeyedSubscript:", CFSTR("content")); 316 | v39 = objc_retainAutoreleasedReturnValue(v38); 317 | objc_release(v39); 318 | if ( v39 ) 319 | { 320 | v57 = v11; 321 | v58 = v7; 322 | v41 = objc_msgSend(v16, "objectForKeyedSubscript:", CFSTR("content")); 323 | v42 = objc_retainAutoreleasedReturnValue(v41); 324 | v44 = objc_retain(v37, v43); 325 | v45 = (void *)objc_retainAutorelease(v44); 326 | v46 = (const char *)objc_msgSend(v45, "cStringUsingEncoding:", 4LL); 327 | objc_release(v45); 328 | v47 = (void *)objc_retainAutorelease(v42); 329 | v48 = (const char *)objc_msgSend(v47, "cStringUsingEncoding:", 4LL); 330 | v49 = strlen(v46); 331 | v50 = strlen(v48); 332 | CCHmac(2LL, v46, v49, v48, v50, v61); 333 | v51 = objc_msgSend(&OBJC_CLASS___NSMutableString, "stringWithCapacity:", 64LL); 334 | v52 = (void *)objc_retainAutoreleasedReturnValue(v51); 335 | v53 = 0LL; 336 | do 337 | objc_msgSend(v52, "appendFormat:", CFSTR("%02x"), (unsigned __int8)v61[v53++]); 338 | while ( v53 != 32 ); 339 | objc_msgSend(v16, "setObject:forKey:", v52, CFSTR("signature")); 340 | 341 | v7 = v58; 342 | v11 = v57; 343 | } 344 | v54 = objc_retain(v16, v40); 345 | LABEL_17: 346 | if ( __stack_chk_guard == v62 ) 347 | result = (id)objc_autoreleaseReturnValue(v54); 348 | return result; 349 | } 350 | ``` 351 | 可以看到 CCHmac(2LL, v46, v49, v48, v50, v61) ,这个是加密算法。 352 | 353 | 我通过动态调试,确定网络请求,要执行到这个CCHmac处做加密,不妨打印上面的这几个方法的参数和返回值,就能更直观的看到结果。 354 | 下面是我还原的部分方法: 355 | 356 | ``` 357 | +[HSServerAPIRequest requestWithURL:dataBody:method:enableEncryption:hashKey:sigKey:](HSServerAPIRequest_meta *self, SEL, id, id, signed __int64, bool, id, id) 358 | { 359 | 360 | //参数: 361 | NSDictionary * pDict = { 362 | "category_id" = 2586351c525f3793b98fa2592111e70e; 363 | direction = old; 364 | muid = "f7e2cb93-5cf3-4b9f-a035-30555c13a167"; 365 | "nearest_article_id" = "these-are-the-6-zodiac-signs-who-are-most-likely-to-ghost-you-a16139"; 366 | "page_size" = 10; 367 | } 368 | 369 | 370 | // 调用这个方法 371 | +[HSServerAPIRequest parametersWithDataBody:enableEncryption:hashKey:sigKey:]; 372 | { 373 | 374 | NSString * pStr = +[HSUtils jsonStringWithObject:pDict]; 375 | // = { 376 | "nearest_article_id" : "these-are-the-6-zodiac-signs-who-are-most-likely-to-ghost-you-a16139", 377 | "page_size" : 10, 378 | "muid" : "f7e2cb93-5cf3-4b9f-a035-30555c13a167", 379 | "category_id" : "2586351c525f3793b98fa2592111e70e", 380 | "direction" : "old" 381 | } 382 | 383 | 384 | 385 | if() 386 | { 387 | //执行如下的方法 388 | [HSServerAPIRequest plainParametersWithDataBodyString:arg1=pStr hashKey:arg2=nil sigKey:arg3=nil ]; 389 | { 390 | 391 | 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}";} 392 | 393 | NSMutableDictionary * mutDict = [NSMutableDictionary dictionaryWithDictionary:dict]; 394 | 395 | 396 | id data; 397 | 398 | if([arg3 length]==0) 399 | { 400 | data = [[HSConfig sharedInstance] data]; 401 | NSString * sigKey = [data valueForKeyPath:@"libCommons.Connection.SigKey"]; 402 | // = @"503_1" 403 | 404 | } 405 | 406 | 407 | int count = [sigKey length];// = 5 408 | 409 | if(count!=0) 410 | { 411 | [mutDict setObject:sigKey forKey:@"sig_kv"]; 412 | } 413 | 414 | if([arg2 length]==0) 415 | { 416 | NSString * hashKey = [data valueForKeyPath:@"libCommons.Connection.HashKey"]; 417 | // = "E56j-4$X=XzA7H#H4]p2e@)V1=Rg6qS=" 418 | 419 | 420 | if([hashKey length] == 32)// = 32 421 | { 422 | NSString * hashKey_2 = sub_100B81304(hashKey);// = "HJdq=ZT?l?yp1)V)ZbRYw#E/il;&d,Nl" 423 | 424 | // x22 = mutDict 425 | NSString * content = [mutDict objectForKeyedSubscript:@"content"]; 426 | // 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"} 427 | 428 | if(content) 429 | { 430 | char * hashKey_3 =[hashKey_2 cStringUsingEncoding:4]; 431 | char * content_3 = [content cStringUsingEncoding:4];// = x19 432 | 433 | int length_hashKey_3 = strlen(hashKey_3);// = x27 = 32 434 | int length_content_3 = strlen(content_3);// = x4 = 263 435 | 436 | _CCHmac(2,hashKey_3,length_hashKey_3,content_3,length_content_3); 437 | 438 | v51 = [NSMutableString stringWithCapacity:64LL];// = v52 439 | v53 = 0LL; 440 | do 441 | [v52 appendFormat:@"%02x", (unsigned __int8)v61[v53++]); 442 | while ( v53 != 32 ); 443 | [v16 setObject:v52 forKey:@"signature"]; 444 | 445 | } 446 | else 447 | { 448 | 449 | } 450 | 451 | } 452 | else 453 | { 454 | return; 455 | } 456 | 457 | } 458 | else 459 | { 460 | 461 | } 462 | 463 | 464 | } 465 | 466 | 467 | } 468 | 469 | 470 | } 471 | 472 | 473 | } 474 | 475 | ``` 476 | 上面的伪代码中,能看到加密的参数是 hashKey_3 和 content_3,v61用于保存加密后的结果,最终得到v52就是最终的signature的值。 477 | 478 | 分析:CCHmac是一种常见的加算法,各种编程语言都有具体的实现,因此很容易用还原这个加密算法,更好的方式是直接用。在python中可以直接调用这个加密算法,我验证过,是完全OK的。 479 | 480 | ### 总结 481 | 482 | 本文重点在用Frida监控方法调用,找到关键函数,并在ida中通过静态分析,查看伪代码找到加密算法的蛛丝马迹,并结合动态调试,打印出算法的参数和返回值,最终还原出清晰的逻辑。 483 | 484 | 感谢您 帮忙在右上角 点个“⭐️”,非常感谢。 485 | 486 | 可关注公众号,获取本次逆向app的素材文件,方便练习。 487 | 488 | ## 多谢支持 ^_^ 489 |