準備
通過Charles抓包發現,請求中有一個驗籤引數signature,每次請求網路都會變化。
請求地址:
http://xxxx.xxxxxx.com/api/article/v2/get_category
引數:
{
"content":{"muid" :"f7e2cb93-5cf3-4b9f-a035-30555c13a167"},
"signature":"6c171c8f2bb05caca19047e3c4a04a7adff9eb3b3973ff3064fa4ab1ba17de64",
"sig_kv":"503_1",
"cten":"p"
}
複製程式碼
本次除錯的目的就是找到signature的生成演算法。
使用frida除錯
- frida的安裝
越獄手機安裝Frida:在Cydia中新增源(build.frida.re/),接著在源中找到Frida並安裝。
Mac安裝frida:需要先有Python環境,使用“pip install frida”安裝frida (Frida的詳細使用請參考官網:www.frida.re)
- 使用frida監控+[NSURL URLWithString:]的引數和呼叫堆疊
新建一個資料夾test,終端進入test目錄
列印iphone執行的app資訊,終端輸入命令:
frida-ps -Ua
複製程式碼
輸出如下:
PID Name Identifier
----- ---------- -----------------------------
17521 testApp com.testApp.zodiac
2048 支付寶 com.alipay.iphoneclient
4296 日曆 com.apple.mobilecal
3551 相機 com.apple.camera
複製程式碼
testApp的PID是17521
監控testApp中的"+[NSURL URLWithString:]"方法,終端命令:
frida-trace -U 17521 -m "+[NSURL URLWithString:]"
複製程式碼
終端輸出:
Instrumenting functions...
+[NSURL URLWithString:]: Loaded handler at "/Users/king/Documents/test/__handlers__/__NSURL_URLWithString__.js"
Started tracing 1 function. Press Ctrl+C to stop.
複製程式碼
在終端介面,按"control+c"退出frida的監控狀態。 在test資料夾中的__handlers__資料夾中找到__NSURL_URLWithString__.js檔案,主要內容如下:
{
onEnter: function (log, args, state) {
log("+[NSURL URLWithString:" + args[2] + "]");
},
onLeave: function (log, retval, state) {
}
}
複製程式碼
編輯檔案內容,結果如下:
{
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) + ")");
}
}
複製程式碼
ObjC.Object(args[2]) 列印引數的值
log('\tBacktrace:\n\t' + Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n\t')); 列印呼叫堆疊
log("+[NSURL URLWithString:]--return=(" + ObjC.Object(retval) + ")"); 列印返回值
這樣修改,frida監控NSURL時能列印出引數和堆疊,讓我們能很快找到網路請求的位置。
終端再次開啟frida監控:
frida-trace -U 17521 -m "+[NSURL URLWithString:]"
複製程式碼
當請求網路時,會看到終端的列印資訊:
4913 ms +[NSURL URLWithString:http:/testApp.ohippo.com/api/article/v2/get_list]
4913 ms Backtrace:
0x100bdfecc testApp!0xb87ecc
0x100be0294 testApp!0xb88294
0x1001dcee4 testApp!0x184ee4
0x1001dd6d4 testApp!0x1856d4
0x100087d18 testApp!0x2fd18
0x100086ef4 testApp!0x2eef4
0x193ce8ec0 UIKit!-[UIViewController loadViewIfRequired]
0x193ce8a9c UIKit!-[UIViewController view]
0x100176df0 testApp!0x11edf0
0x10014dbcc testApp!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)
複製程式碼
列印的資訊很多,這裡只擷取了一部分有用的列印資訊。
使用lldb+debugserver附加當前程式,列印模組偏移地址如下:
[ 0] 0x0000000000058000 /var/containers/Bundle/Application/FA17E6F7-4386-40B1-8B87-0A138169E67F/testApp.app/testApp(0x0000000100058000)
[ 1] 0x0000000101634000 /Users/king/Library/Developer/Xcode/iOS DeviceSupport/10.3.2 (14F89)/Symbols/usr/lib/dyld
...
...
複製程式碼
計算本次 +[NSURL URLWithString:]方法呼叫在ida中的地址: 0x100bdfecc - 0x0000000000058000 = 0x100B87ECC
在ida中找到0x100B87ECC位置,可以定位到這個方法:
+[HSServerAPIRequest requestWithURL:dataBody:method:enableEncryption:hashKey:sigKey:]
複製程式碼
在ida中檢視+[HSServerAPIRequest requestWithURL:dataBody:method:enableEncryption:hashKey:sigKey:]的虛擬碼,可以看到一個+[HSServerAPIRequest parametersWithDataBody:enableEncryption:hashKey:sigKey:]方法
ida中的虛擬碼:
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;
}
複製程式碼
從虛擬碼中可以看到"encryptedParametersWithDataBodyString:hashKey:sigKey:"和"plainParametersWithDataBodyString:hashKey:sigKey:"方法,可以跟進去看它們的虛擬碼具體內容。這個方法的虛擬碼中看到使用了AES256加密演算法,後來發現signature的生成與該方法無關,因此這裡不再詳述。
再看下面的虛擬碼:
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);
}
複製程式碼
從上面的虛擬碼中,看到一個"signedParametersWithContent:hashKey:sigKey:"方法,我們繼續跟進。
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;
}
複製程式碼
可以看到 CCHmac(2LL, v46, v49, v48, v50, v61) ,這個是加密演算法。
我通過動態除錯,確定網路請求,要執行到這個CCHmac處做加密,不妨列印上面的這幾個方法的引數和返回值,就能更直觀的看到結果。 下面是我還原的部分方法:
+[HSServerAPIRequest requestWithURL:dataBody:method:enableEncryption:hashKey:sigKey:](HSServerAPIRequest_meta *self, SEL, id, id, signed __int64, bool, id, id)
{
//引數:
NSDictionary * pDict = {
"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;
}
// 呼叫這個方法
+[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()
{
//執行如下的方法
[HSServerAPIRequest plainParametersWithDataBodyString:arg1=pStr hashKey: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];// = 5
if(count!=0)
{
[mutDict setObject: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
{
}
}
}
}
}
複製程式碼
上面的虛擬碼中,能看到加密的引數是 hashKey_3 和 content_3,v61用於儲存加密後的結果,最終得到v52就是最終的signature的值。
分析:CCHmac是一種常見的加密演算法,各種程式語言都有具體的實現,因此很容易用還原這個加密演算法,更好的方式是直接用。在python中可以直接呼叫這個加密演算法,我驗證過,是完全OK的。
總結
本文重點在用Frida監控方法呼叫,找到關鍵函式,並在ida中通過靜態分析,檢視虛擬碼找到加密演算法的蛛絲馬跡,並結合動態除錯,列印出演算法的引數和返回值,最終還原出清晰的邏輯。
補充
感謝您 幫忙在右上角 點個“⭐️”,非常感謝。
github地址:github.com/luoyanbei/r…
可關注公眾號:逆向APP,獲取本次逆向app的素材檔案,方便練習。