笔者常常从家门口乘坐某公交到某地去玩耍,然而那一趟公交发车周期长且时间不定,每次出行前都须要打开实时公交的软件搜索班次和信息不只浪费流量,并且很是不便。所以笔者便打算基于实时公交接口开发一个-1屏的Today Widget来显示这趟公交的实时信息。html
首先笔者抓取了App的网络数据包,并对请求数据进行了简要分析,请求数据的内容以下,处于信息安全考虑,敏感信息均以打码。前端
请求接口中包含了签名和时间戳字段,时间戳字段有效的防止了请求的连续重放,签名字段则保护了接口数据完整性,以黑盒角度分析,在签名算法未知的状况下仅能经过重放当前请求的形式来获取数据,一旦时间戳与服务器时间相差超过阈值,服务端就会抛出系统时间不许确的错误,以防止这种形式的攻击。因为签名的存在,接下来仅剩下两条路能够走,一个是在原来App的基础上进行二次开发,经过动态库注入的形式来添加本身想要的能力,另外一个是经过反编译获取签名算法。算法
对iOS应用进行二次开发的原理是将应用的可执行文件进行修改,在Mach-O文件的Load Commands字段中加入动态库加载指令,使其可以加载新添加的动态库,而后破解者在动态库中以切面的形式对原来的App逻辑进行修改,最后对应用和动态库进行重签名,这种方式有以下几个缺点:json
考虑到此次的应用主要是为-1屏添加Today Widget,所以二次开发方式彷佛行不通。数组
反编译方式彷佛是万能的,它的缺点主要是须要人工去定位相关逻辑,并对反汇编获得的汇编代码和反编译得出的C伪代码进行分析,成本较高,可是在iOS平台上实施反编译的成本是相对较低的,缘由有以下三点:安全
综合上述分析,笔者最后采用了反编译方式,来梳理接口签名的逻辑。服务器
笔者首先经过越狱市场获取了砸壳后的二进制文件,随后将其拖入IDA进行了反汇编。在反汇编完成以后,须要作的第一步是找到接口签名的相关逻辑。网络
经过网络数据包可知这是一个GET请求,而且包含了签名信息,所以咱们能够在IDA得到的符号中搜索get
、request
、signature
等关键词来查找相关符号,在这个过程当中可能须要排除大量信息,经过搜索,笔者找到了两处可疑的符号。app
经过名称判断,第二个方法传递的是一个URL数组,应该是因为批处理的请求,第一个方法则是普通的GET请求,所以咱们打开第一个方法的汇编代码,经过IDA插件将其反编译成C伪代码进行分析,伪代码内容以下。框架
id __cdecl -[DTAFNRequestGet GetDictionaryByURL:Op:KeyAndValue:successBlocker:failureBlockers:](DTAFNRequestGet *self, SEL a2, id a3, id a4, id a5, id a6, id a7)
{
DTAFNRequestGet *v7; // r5
id v8; // r6
int v9; // r8
int v10; // r1
int v11; // r6
int v12; // r1
int v13; // r4
int v14; // r1
int v15; // ST0C_4
int v16; // r1
int v17; // ST10_4
struct objc_object *v18; // r10
int v19; // r0
int v20; // r4
struct objc_object *v21; // r0
int v22; // r11
struct objc_object *v23; // r4
int v24; // r8
struct objc_object *v25; // r0
void *v26; // r0
void *v27; // r5
void *v28; // r0
void *v29; // r6
struct objc_object *v30; // r0
int v31; // r4
struct objc_object *v32; // r0
int v33; // r4
void *v34; // r0
void *v35; // r4
void *v36; // r0
void *v37; // r6
void *v38; // r0
int v39; // r4
int v40; // r10
int v41; // r6
void *v42; // r0
DTAFNRequestGet *v43; // r4
SEL v44; // r1
id v45; // r2
id v46; // r3
void *v48; // [sp+14h] [bp-48h]
int v49; // [sp+18h] [bp-44h]
int v50; // [sp+1Ch] [bp-40h]
int (*v51)(); // [sp+20h] [bp-3Ch]
void *v52; // [sp+24h] [bp-38h]
int v53; // [sp+28h] [bp-34h]
void *v54; // [sp+2Ch] [bp-30h]
int v55; // [sp+30h] [bp-2Ch]
int v56; // [sp+34h] [bp-28h]
int (*v57)(); // [sp+38h] [bp-24h]
void *v58; // [sp+3Ch] [bp-20h]
int v59; // [sp+40h] [bp-1Ch]
struct objc_object *v60; // [sp+70h] [bp+14h]
struct objc_object *v61; // [sp+74h] [bp+18h]
v7 = self;
v8 = a4;
v9 = objc_retain(a3, a2);
v11 = objc_retain(v8, v10);
v13 = objc_retain(a5, v12);
v15 = objc_retain(a6, v14);
v17 = objc_retain(a7, v16);
v18 = -[DTAFNetRequest AddNecessaryParamDic:](v7, "AddNecessaryParamDic:", v13);
objc_release(v13);
v19 = objc_retainAutoreleasedReturnValue(v18);
v20 = v19;
v21 = +[SingalMethod signByHMac:AndHttpMethod:](
&OBJC_CLASS___SingalMethod,
"signByHMac:AndHttpMethod:",
v19,
CFSTR("GET"));
v22 = objc_retainAutoreleasedReturnValue(v21);
objc_release(v20);
v23 = -[DTAFNetRequest UrlJointByUrl:Op:KeyAndValue:PostKeyAndValue:](
v7,
"UrlJointByUrl:Op:KeyAndValue:PostKeyAndValue:",
v9,
v11,
v22,
0);
objc_release(v11);
objc_release(v9);
v24 = objc_retainAutoreleasedReturnValue(v23);
v25 = +[AFHTTPRequestOperationManager manager](&OBJC_CLASS___AFHTTPRequestOperationManager, "manager");
v26 = (void *)objc_retainAutoreleasedReturnValue(v25);
v27 = v26;
v28 = objc_msgSend(v26, "securityPolicy");
v29 = (void *)objc_retainAutoreleasedReturnValue(v28);
objc_msgSend(v29, "setAllowInvalidCertificates:", 1);
objc_release(v29);
v30 = +[AFHTTPResponseSerializer serializer](&OBJC_CLASS___AFHTTPResponseSerializer, "serializer");
v31 = objc_retainAutoreleasedReturnValue(v30);
objc_msgSend(v27, "setResponseSerializer:", v31);
objc_release(v31);
v32 = +[AFHTTPRequestSerializer serializer](&OBJC_CLASS___AFHTTPRequestSerializer, "serializer");
v33 = objc_retainAutoreleasedReturnValue(v32);
objc_msgSend(v27, "setRequestSerializer:", v33);
objc_release(v33);
v34 = objc_msgSend(v27, "requestSerializer");
v35 = (void *)objc_retainAutoreleasedReturnValue(v34);
objc_msgSend(v35, "setTimeoutInterval:", 20.0);
objc_release(v35);
v36 = objc_msgSend(v27, "responseSerializer");
v37 = (void *)objc_retainAutoreleasedReturnValue(v36);
v38 = objc_msgSend(
&OBJC_CLASS___NSSet,
"setWithObjects:",
CFSTR("text/html"),
CFSTR("text/plain"),
CFSTR("application/json"),
CFSTR("charset=UTF-8"),
0);
v39 = objc_retainAutoreleasedReturnValue(v38);
objc_msgSend(v37, "setAcceptableContentTypes:", v39);
objc_release(v39);
objc_release(v37);
v54 = &_NSConcreteStackBlock;
v55 = -1040187392;
v56 = 0;
v57 = sub_CA608;
v58 = &unk_6A014C;
v59 = v15;
v48 = &_NSConcreteStackBlock;
v49 = -1040187392;
v50 = 0;
v51 = sub_CA7C0;
v52 = &unk_6A0164;
v40 = objc_retain(v15, sub_CA7C0);
v53 = v17;
v41 = objc_retain(v17, &selRef_GET_parameters_success_failure_);
v42 = objc_msgSend(v27, "GET:parameters:success:failure:", v24, 0, &v54, &v48);
v43 = (DTAFNRequestGet *)objc_retainAutoreleasedReturnValue(v42);
objc_release(v53);
objc_release(v59);
objc_release(v41);
objc_release(v40);
objc_release(v27);
objc_release(v24);
objc_release(v22);
return j__objc_autoreleaseReturnValue(v43, v44, v45, v46, a5, a6, a7, v60, v61);
}
复制代码
其中包含了变量定义和ARC代码,将其去掉后,余下的主要逻辑以下。
id __cdecl -[DTAFNRequestGet GetDictionaryByURL:Op:KeyAndValue:successBlocker:failureBlockers:](DTAFNRequestGet *self, SEL a2, id a3, id a4, id a5, id a6, id a7)
{
v18 = -[DTAFNetRequest AddNecessaryParamDic:](v7, "AddNecessaryParamDic:", v13);
v21 = +[SingalMethod signByHMac:AndHttpMethod:](
&OBJC_CLASS___SingalMethod,
"signByHMac:AndHttpMethod:",
v19,
CFSTR("GET"));
v23 = -[DTAFNetRequest UrlJointByUrl:Op:KeyAndValue:PostKeyAndValue:](
v7,
"UrlJointByUrl:Op:KeyAndValue:PostKeyAndValue:",
v9,
v11,
v22,
0);
v25 = +[AFHTTPRequestOperationManager manager](&OBJC_CLASS___AFHTTPRequestOperationManager, "manager");
v26 = (void *)objc_retainAutoreleasedReturnValue(v25);
v27 = v26;
v28 = objc_msgSend(v26, "securityPolicy");
v29 = (void *)objc_retainAutoreleasedReturnValue(v28);
objc_msgSend(v29, "setAllowInvalidCertificates:", 1);
objc_release(v29);
v30 = +[AFHTTPResponseSerializer serializer](&OBJC_CLASS___AFHTTPResponseSerializer, "serializer");
v31 = objc_retainAutoreleasedReturnValue(v30);
objc_msgSend(v27, "setResponseSerializer:", v31);
objc_release(v31);
v32 = +[AFHTTPRequestSerializer serializer](&OBJC_CLASS___AFHTTPRequestSerializer, "serializer");
v33 = objc_retainAutoreleasedReturnValue(v32);
objc_msgSend(v27, "setRequestSerializer:", v33);
objc_release(v33);
v34 = objc_msgSend(v27, "requestSerializer");
v35 = (void *)objc_retainAutoreleasedReturnValue(v34);
objc_msgSend(v35, "setTimeoutInterval:", 20.0);
objc_release(v35);
v36 = objc_msgSend(v27, "responseSerializer");
v37 = (void *)objc_retainAutoreleasedReturnValue(v36);
v38 = objc_msgSend(
&OBJC_CLASS___NSSet,
"setWithObjects:",
CFSTR("text/html"),
CFSTR("text/plain"),
CFSTR("application/json"),
CFSTR("charset=UTF-8"),
0);
v39 = objc_retainAutoreleasedReturnValue(v38);
objc_msgSend(v37, "setAcceptableContentTypes:", v39);
objc_release(v39);
objc_release(v37);
v54 = &_NSConcreteStackBlock;
v55 = -1040187392;
v56 = 0;
v57 = sub_CA608;
v58 = &unk_6A014C;
v59 = v15;
v48 = &_NSConcreteStackBlock;
v49 = -1040187392;
v50 = 0;
v51 = sub_CA7C0;
v52 = &unk_6A0164;
v40 = objc_retain(v15, sub_CA7C0);
v53 = v17;
v41 = objc_retain(v17, &selRef_GET_parameters_success_failure_);
v42 = objc_msgSend(v27, "GET:parameters:success:failure:", v24, 0, &v54, &v48);
v43 = (DTAFNRequestGet *)objc_retainAutoreleasedReturnValue(v42);
return j__objc_autoreleaseReturnValue(v43, v44, v45, v46, a5, a6, a7, v60, v61);
}
复制代码
能够看到,在上面的代码中,后半部分是对AFNetworking的调用和回调处理,这一部分对于分析接口签名没有帮助,也能够略去,最后剩下的逻辑以下。
id __cdecl -[DTAFNRequestGet GetDictionaryByURL:Op:KeyAndValue:successBlocker:failureBlockers:](DTAFNRequestGet *self, SEL a2, id a3, id a4, id a5, id a6, id a7)
{
v18 = -[DTAFNetRequest AddNecessaryParamDic:](v7, "AddNecessaryParamDic:", v13);
v21 = +[SingalMethod signByHMac:AndHttpMethod:](
&OBJC_CLASS___SingalMethod,
"signByHMac:AndHttpMethod:",
v19,
CFSTR("GET"));
v23 = -[DTAFNetRequest UrlJointByUrl:Op:KeyAndValue:PostKeyAndValue:](
v7,
"UrlJointByUrl:Op:KeyAndValue:PostKeyAndValue:",
v9,
v11,
v22,
0);
复制代码
先不考虑变量传递,能够看到在接口调用前,进行了三个操做,分别是AddNecessaryParamDic:
、signByHMac::
和UrlJointByUrl::::
,从字面意思分析可知分别是添加必要参数、使用HMac算法签名和构造url,在这里咱们已经找到了接口签名的方法,它就是SingalMethod
的类方法signByHMac:AndHttpMethod:
,下面咱们着手分析这个方法的实现。
签名方法signByHMac:AndHttpMethod:
的反编译结果以下。
id __cdecl +[SingalMethod signByHMac:AndHttpMethod:](SingalMethod_meta *self, SEL a2, id a3, id a4)
{
id v4; // r6
void *v5; // r8
void *v6; // r0
int v7; // r0
int v8; // r5
void *v9; // r0
void *v10; // r4
const __CFString *v11; // r6
int v12; // r1
int v13; // r1
unsigned int v14; // r10
int v15; // r4
void *v16; // r0
void *v17; // r0
int v18; // r1
int v19; // r6
void *v20; // r0
const __CFString *v21; // r11
void *v22; // r0
int v23; // r8
void *v24; // r0
int v25; // r4
void *v26; // r0
int v27; // r10
void *v28; // r0
int v29; // r0
int v30; // r11
void *v31; // r0
int v32; // r5
void *v33; // r0
void *v34; // r0
int v35; // r4
void *v36; // r0
char *v37; // r6
void *v38; // r4
int v39; // r1
void *v40; // r0
void *v41; // r0
unsigned int v42; // r5
int v43; // r6
void *v44; // r0
int v45; // r10
void *v46; // r0
int v47; // r4
void *v48; // r0
const __CFString *v49; // r6
void *v50; // r0
int v51; // r4
id v52; // r2
id v53; // r3
int v55; // [sp+Ch] [bp-124h]
id v56; // [sp+10h] [bp-120h]
unsigned __int64 v57; // [sp+10h] [bp-120h]
int v58; // [sp+14h] [bp-11Ch]
void *v59; // [sp+1Ch] [bp-114h]
int v60; // [sp+20h] [bp-110h]
int v61; // [sp+20h] [bp-110h]
void *v62; // [sp+24h] [bp-10Ch]
void *v63; // [sp+28h] [bp-108h]
char *v64; // [sp+2Ch] [bp-104h]
char *v65; // [sp+30h] [bp-100h]
void *v66; // [sp+34h] [bp-FCh]
DTAFNRequestGet *v67; // [sp+34h] [bp-FCh]
int v68; // [sp+38h] [bp-F8h]
char *v69; // [sp+3Ch] [bp-F4h]
void *v70; // [sp+40h] [bp-F0h]
SingalMethod_meta *v71; // [sp+44h] [bp-ECh]
__int64 v72; // [sp+48h] [bp-E8h]
__int64 v73; // [sp+50h] [bp-E0h]
__int64 v74; // [sp+58h] [bp-D8h]
__int64 v75; // [sp+60h] [bp-D0h]
const __CFString *v76; // [sp+68h] [bp-C8h]
int v77; // [sp+6Ch] [bp-C4h]
__int64 v78; // [sp+70h] [bp-C0h]
__int64 v79; // [sp+78h] [bp-B8h]
__int64 v80; // [sp+80h] [bp-B0h]
__int64 v81; // [sp+88h] [bp-A8h]
char v82; // [sp+94h] [bp-9Ch]
char v83; // [sp+D4h] [bp-5Ch]
struct objc_object *v84; // [sp+138h] [bp+8h]
struct objc_object *v85; // [sp+13Ch] [bp+Ch]
struct objc_object *v86; // [sp+140h] [bp+10h]
struct objc_object *v87; // [sp+144h] [bp+14h]
struct objc_object *v88; // [sp+148h] [bp+18h]
v71 = self;
v4 = a4;
v5 = (void *)objc_retain(a3, a2);
v56 = v4;
v58 = objc_retain(v4, &selRef_allKeys);
v6 = objc_msgSend(v5, "allKeys");
v7 = objc_retainAutoreleasedReturnValue(v6);
v8 = v7;
v9 = objc_msgSend(&OBJC_CLASS___NSMutableArray, "arrayWithArray:", v7);
v10 = (void *)objc_retainAutoreleasedReturnValue(v9);
objc_release(v8);
objc_msgSend(v10, "sortUsingComparator:", &off_69F730);
v11 = &stru_6B8088;
objc_retain(&stru_6B8088, v12);
v78 = 0LL;
v79 = 0LL;
v80 = 0LL;
v81 = 0LL;
v59 = (void *)objc_retain(v10, v13);
v66 = objc_msgSend(v59, "countByEnumeratingWithState:objects:count:", &v78, &v83, 16);
v62 = v5;
if ( v66 )
{
v11 = &stru_6B8088;
v60 = *(_DWORD *)v79;
do
{
v14 = 0;
do
{
v69 = (char *)v11;
if ( *(_DWORD *)v79 != v60 )
objc_enumerationMutation(v59);
v15 = *(_DWORD *)(HIDWORD(v78) + 4 * v14);
v16 = objc_msgSend(v71, "enCodeURL:", *(_DWORD *)(HIDWORD(v78) + 4 * v14));
v68 = objc_retainAutoreleasedReturnValue(v16);
v17 = objc_msgSend(v5, "objectForKeyedSubscript:", v15);
v19 = objc_retainAutoreleasedReturnValue(v17);
if ( v19 )
{
v20 = objc_msgSend(v5, "objectForKeyedSubscript:", v15);
v21 = (const __CFString *)objc_retainAutoreleasedReturnValue(v20);
}
else
{
objc_retain(&stru_6B8088, v18);
v21 = &stru_6B8088;
}
objc_release(v19);
v22 = objc_msgSend(v71, "enCodeURL:", v21);
v23 = objc_retainAutoreleasedReturnValue(v22);
if ( objc_msgSend(v69, "isEqualToString:", &stru_6B8088) )
v24 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@%@=%@"), v69, v68, v23);
else
v24 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@&%@=%@"), v69, v68, v23);
v25 = objc_retainAutoreleasedReturnValue(v24);
objc_release(v69);
v11 = (const __CFString *)v25;
objc_release(v23);
objc_release(v21);
objc_release(v68);
++v14;
v5 = v62;
}
while ( v14 < (unsigned int)v66 );
v66 = objc_msgSend(v59, "countByEnumeratingWithState:objects:count:", &v78, &v83, 16);
}
while ( v66 );
}
objc_release(v59);
v26 = objc_msgSend(v71, "enCodeURL:", v11);
v27 = objc_retainAutoreleasedReturnValue(v26);
objc_release(v11);
v28 = objc_msgSend(v71, "enCodeURL:", CFSTR("/"));
v29 = objc_retainAutoreleasedReturnValue(v28);
v30 = v29;
v31 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@&%@&%@"), v58, v29, v27);
v32 = objc_retainAutoreleasedReturnValue(v31);
objc_release(v27);
v33 = objc_msgSend(v71, "hmac:withKey:", v32, CFSTR("23c2f22fadf46f3b28b6adddd242959e&"));
v61 = objc_retainAutoreleasedReturnValue(v33);
v76 = CFSTR("signature");
v77 = v61;
v34 = objc_msgSend(&OBJC_CLASS___NSDictionary, "dictionaryWithObjects:forKeys:count:", &v77, &v76, 1);
v35 = objc_retainAutoreleasedReturnValue(v34);
v36 = objc_msgSend(&OBJC_CLASS___NSMutableDictionary, "dictionaryWithDictionary:", v5);
v37 = (char *)objc_retainAutoreleasedReturnValue(v36);
v55 = v35;
objc_msgSend(v37, "addEntriesFromDictionary:", v35);
v38 = objc_msgSend(v56, "isEqualToString:", CFSTR("GET"));
objc_release(v58);
if ( v38 )
{
v57 = __PAIR__(v30, v32);
v40 = objc_msgSend(&OBJC_CLASS___NSMutableDictionary, "dictionary");
v67 = (DTAFNRequestGet *)objc_retainAutoreleasedReturnValue(v40);
v72 = 0LL;
v73 = 0LL;
v74 = 0LL;
v75 = 0LL;
v65 = v37;
v41 = objc_msgSend(v37, "allKeys");
v63 = (void *)objc_retainAutoreleasedReturnValue(v41);
v70 = objc_msgSend(v63, "countByEnumeratingWithState:objects:count:", &v72, &v82, 16);
if ( v70 )
{
v64 = *(char **)v73;
do
{
v42 = 0;
do
{
if ( *(char **)v73 != v64 )
objc_enumerationMutation(v63);
v43 = *(_DWORD *)(HIDWORD(v72) + 4 * v42);
v44 = objc_msgSend(v71, "encodeParameter:", *(_DWORD *)(HIDWORD(v72) + 4 * v42));
v45 = objc_retainAutoreleasedReturnValue(v44);
v46 = objc_msgSend(v65, "objectForKey:", v43);
v47 = objc_retainAutoreleasedReturnValue(v46);
if ( v47 )
{
v48 = objc_msgSend(v65, "objectForKey:", v43);
v49 = (const __CFString *)objc_retainAutoreleasedReturnValue(v48);
}
else
{
v49 = &stru_6B8088;
objc_retain(&stru_6B8088, "objectForKey:");
}
objc_release(v47);
v50 = objc_msgSend(v71, "encodeParameter:", v49);
v51 = objc_retainAutoreleasedReturnValue(v50);
objc_msgSend(v67, "setValue:forKey:", v51, v45);
objc_release(v51);
objc_release(v49);
objc_release(v45);
++v42;
}
while ( v42 < (unsigned int)v70 );
v70 = objc_msgSend(v63, "countByEnumeratingWithState:objects:count:", &v72, &v82, 16);
}
while ( v70 );
}
objc_release(v63);
v5 = v62;
v30 = HIDWORD(v57);
v32 = v57;
v37 = v65;
}
else
{
v67 = (DTAFNRequestGet *)objc_retain(v37, v39);
}
objc_release(v37);
objc_release(v55);
objc_release(v61);
objc_release(v30);
objc_release(v32);
objc_release(v59);
objc_release(v5);
return j__objc_autoreleaseReturnValue(v67, __stack_chk_guard, v52, v53, v84, v85, v86, v87, v88);
}
复制代码
这部分代码很长,笔者在分析时,先把目光放在了涉及的方法调用上,经过搜寻代码中的objc_msgSend
,在第一个if语句前包含了以下的方法调用。
v6 = objc_msgSend(v5, "allKeys");
v9 = objc_msgSend(&OBJC_CLASS___NSMutableArray, "arrayWithArray:", v7);
objc_msgSend(v10, "sortUsingComparator:", &off_69F730);
v66 = objc_msgSend(v59, "countByEnumeratingWithState:objects:count:", &v78, &v83, 16);
复制代码
可见这里的allKeys是对字典的操做,咱们向上搜索继续分析v5的传递路径,因为ARC代码的存在,变量在传递时涉及屡次赋值操做,分析出的相关代码以下。
id __cdecl +[SingalMethod signByHMac:AndHttpMethod:](SingalMethod_meta *self, SEL a2, id a3, id a4)
{
// 略去无关代码
v5 = (void *)objc_retain(a3, a2);
v6 = objc_msgSend(v5, "allKeys");
}
复制代码
可见v5是经过对a3进行强引用获得的,a3是方法signByHMac:AndHttpMethod:
的第一个参数,所以a3是一个字典,咱们能够初步推测它应该是请求的参数字典,为了验证这个说法,咱们回到上一级调用者,分析传递到签名方法的参数,关键代码以下。
id __cdecl -[DTAFNRequestGet GetDictionaryByURL:Op:KeyAndValue:successBlocker:failureBlockers:](DTAFNRequestGet *self, SEL a2, id a3, id a4, id a5, id a6, id a7)
{
// 略去无关代码
v7 = self;
v8 = a4;
v9 = objc_retain(a3, a2);
v11 = objc_retain(v8, v10);
v13 = objc_retain(a5, v12);
v15 = objc_retain(a6, v14);
v17 = objc_retain(a7, v16);
v18 = -[DTAFNetRequest AddNecessaryParamDic:](v7, "AddNecessaryParamDic:", v13);
objc_release(v13);
v19 = objc_retainAutoreleasedReturnValue(v18);
v20 = v19;
v21 = +[SingalMethod signByHMac:AndHttpMethod:](
&OBJC_CLASS___SingalMethod,
"signByHMac:AndHttpMethod:",
v19,
CFSTR("GET"));
}
复制代码
可见传递到签名方法的Hmac为v19,而v19最初是来自a5参数,经过分析方法头可知a5对应的是KeyAndValue
,正是参数字典,a5经过添加必要参数后传递到签名方法,从这里也能够分析出签名方法的输入只有参数列表和请求方式,对于GET请求请求方式为字符串GET
,所以咱们只要实现出签名方法的逻辑,便可构造参数列表来构建合法请求。
接下来咱们继续分析签名方法,继续上面的内容,咱们看第一个if语句内的一小段内容。
v66 = objc_msgSend(v59, "countByEnumeratingWithState:objects:count:", &v78, &v83, 16);
v62 = v5;
if ( v66 )
{
v11 = &stru_6B8088;
v60 = *(_DWORD *)v79;
do
{
v14 = 0;
do
{
v69 = (char *)v11;
if ( *(_DWORD *)v79 != v60 )
objc_enumerationMutation(v59);
v15 = *(_DWORD *)(HIDWORD(v78) + 4 * v14);
v16 = objc_msgSend(v71, "enCodeURL:", *(_DWORD *)(HIDWORD(v78) + 4 * v14));
v68 = objc_retainAutoreleasedReturnValue(v16);
v17 = objc_msgSend(v5, "objectForKeyedSubscript:", v15);
v19 = objc_retainAutoreleasedReturnValue(v17);
if ( v19 )
{
v20 = objc_msgSend(v5, "objectForKeyedSubscript:", v15);
v21 = (const __CFString *)objc_retainAutoreleasedReturnValue(v20);
}
else
{
objc_retain(&stru_6B8088, v18);
v21 = &stru_6B8088;
}
objc_release(v19);
v22 = objc_msgSend(v71, "enCodeURL:", v21);
v23 = objc_retainAutoreleasedReturnValue(v22);
if ( objc_msgSend(v69, "isEqualToString:", &stru_6B8088) )
v24 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@%@=%@"), v69, v68, v23);
else
v24 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@&%@=%@"), v69, v68, v23);
v25 = objc_retainAutoreleasedReturnValue(v24);
objc_release(v69);
v11 = (const __CFString *)v25;
objc_release(v23);
objc_release(v21);
objc_release(v68);
++v14;
v5 = v62;
}
while ( v14 < (unsigned int)v66 );
v66 = objc_msgSend(v59, "countByEnumeratingWithState:objects:count:", &v78, &v83, 16);
}
while ( v66 );
}
复制代码
注意这里出现了以下的组合。
v66 = objc_msgSend(v59, "countByEnumeratingWithState:objects:count:", &v78, &v83, 16);
if ( v66 )
{
// ...
do
{
// ...
do
{
if ( *(_DWORD *)v79 != v60 )
objc_enumerationMutation(v59);
else
{
objc_retain(&stru_6B8088, v18);
v21 = &stru_6B8088;
}
++v14;
v5 = v62;
}
while ( v14 < (unsigned int)v66 );
v66 = objc_msgSend(v59, "countByEnumeratingWithState:objects:count:", &v78, &v83, 16);
}
while ( v66 );
}
复制代码
这里包含了countByEnumeratingWithState:::
方法以及两层while语句,和while内部的objc_enumerationMutation
调用,须要注意的是这一段是Objective-C的for-in语句实现的源码,咱们在分析时能够忽略这部分框架内容,关心内层while的逻辑,下面咱们着手分析第一个for-in语句的内容,因为内容较多,此次将讲解放在了代码的注释上,请读者阅读内层while循环体中的代码注释。
v66 = objc_msgSend(v59, "countByEnumeratingWithState:objects:count:", &v78, &v83, 16);
// v5为传入的参数字典
v62 = v5;
if ( v66 )
{
v11 = &stru_6B8088;
v60 = *(_DWORD *)v79;
do
{
v14 = 0;
do
{
v69 = (char *)v11;
if ( *(_DWORD *)v79 != v60 )
objc_enumerationMutation(v59);
// 经过计算偏移量获得数组中的一个元素,v14为迭代器,能够理解为for中的i,v15为当前取出的元素
v15 = *(_DWORD *)(HIDWORD(v78) + 4 * v14);
// 这里enCodeURL:的参数和v15是等效的,所以能够理解为将v15进行URL编码
v16 = objc_msgSend(v71, "enCodeURL:", *(_DWORD *)(HIDWORD(v78) + 4 * v14));
v68 = objc_retainAutoreleasedReturnValue(v16);
// 以v15为key从字典v5中取出值,下面的if是对值的判空操做
v17 = objc_msgSend(v5, "objectForKeyedSubscript:", v15);
v19 = objc_retainAutoreleasedReturnValue(v17);
if ( v19 )
{
v20 = objc_msgSend(v5, "objectForKeyedSubscript:", v15);
v21 = (const __CFString *)objc_retainAutoreleasedReturnValue(v20);
}
else
{
objc_retain(&stru_6B8088, v18);
v21 = &stru_6B8088;
}
objc_release(v19);
// 分析可知v21就是key v15对应的参数值,这里对参数值也进行了编码
v22 = objc_msgSend(v71, "enCodeURL:", v21);
v23 = objc_retainAutoreleasedReturnValue(v22);
// 这里将参数转化为字符串,规则为"key1=value1&key2=value2&...",可见是url编码的形式
if ( objc_msgSend(v69, "isEqualToString:", &stru_6B8088) )
v24 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@%@=%@"), v69, v68, v23);
else
v24 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@&%@=%@"), v69, v68, v23);
v25 = objc_retainAutoreleasedReturnValue(v24);
objc_release(v69);
v11 = (const __CFString *)v25;
objc_release(v23);
objc_release(v21);
objc_release(v68);
++v14;
v5 = v62;
}
while ( v14 < (unsigned int)v66 );
v66 = objc_msgSend(v59, "countByEnumeratingWithState:objects:count:", &v78, &v83, 16);
}
while ( v66 );
}
复制代码
根据上面的分析可知,第一个for-in经过遍历字典,生成了请求参数字典对应的URL参数字符串,并对其进行了编码,最终获得的参数字符串存储在v11变量中,下面的逻辑就十分简单了。
v26 = objc_msgSend(v71, "enCodeURL:", v11);
v27 = objc_retainAutoreleasedReturnValue(v26);
objc_release(v11);
v28 = objc_msgSend(v71, "enCodeURL:", CFSTR("/"));
v29 = objc_retainAutoreleasedReturnValue(v28);
v30 = v29;
v31 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@&%@&%@"), v58, v29, v27);
v32 = objc_retainAutoreleasedReturnValue(v31);
objc_release(v27);
v33 = objc_msgSend(v71, "hmac:withKey:", v32, CFSTR("******2fadf46f3b28b6adddd24******"));
v61 = objc_retainAutoreleasedReturnValue(v33);
v76 = CFSTR("signature");
v77 = v61;
v34 = objc_msgSend(&OBJC_CLASS___NSDictionary, "dictionaryWithObjects:forKeys:count:", &v77, &v76, 1);
v35 = objc_retainAutoreleasedReturnValue(v34);
v36 = objc_msgSend(&OBJC_CLASS___NSMutableDictionary, "dictionaryWithDictionary:", v5);
v37 = (char *)objc_retainAutoreleasedReturnValue(v36);
复制代码
可见这里对参数字符串又进行了一次URL编码,而后又构建了一个三部分用&链接的字符串:第一部分v58经过向上分析能够获得是签名方法的第二个参数,即请求方式,这里传入的是GET
;第二部分v29为/
,第三部分v27即编码后的参数字符串,所以这里构建了一个形如GET&/&k1=v1&ke=v2...
的字符串,随后调用了hmac:withKey:
方法进行签名,这里的第一个参数hmac即为咱们构建的字符串,第二个参数为密钥,为了信息安全这里对密钥进行了部分隐藏。
经过hmac加密后,获得的结果v33即为最终的签名结果被添加到请求参数中,分析到这里咱们已经知道,只须要根据网络请求数据构建参数列表,再计算出签名添加到列表中便可构建出合法请求。目前咱们离成功只有一步之遥,那就是分析hmac:withKey:
方法,幸运的是,这个方法很是简单,调用了系统的HMAC函数加密后又进行了base64编码而后获得签名,这个方法的内部实现以下,这部分逻辑很是简单,这里再也不赘述,读者能够自行阅读。
id __cdecl +[SingalMethod hmac:withKey:](SingalMethod_meta *self, SEL a2, id a3, id a4)
{
id v4; // r4
void *v5; // r10
int v6; // r1
void *v7; // r5
void *v8; // r0
int v9; // r1
void *v10; // r0
void *v11; // ST14_4
int v12; // r5
void *v13; // r0
int v14; // r4
void *v15; // r0
int v16; // ST0C_4
void *v17; // r11
void *v18; // ST10_4
void *v19; // ST08_4
void *v20; // r4
void *v21; // r8
void *v22; // r6
void *v23; // r0
void *v24; // r5
void *v25; // r0
void *v26; // r0
DTAFNRequestGet *v27; // r6
void *v28; // r0
SEL v29; // r1
id v30; // r2
id v31; // r3
struct objc_object *v33; // [sp+38h] [bp+8h]
struct objc_object *v34; // [sp+3Ch] [bp+Ch]
struct objc_object *v35; // [sp+40h] [bp+10h]
struct objc_object *v36; // [sp+44h] [bp+14h]
struct objc_object *v37; // [sp+48h] [bp+18h]
v4 = a4;
v5 = (void *)objc_retain(a3, a2);
v7 = (void *)objc_retain(v4, v6);
v8 = objc_msgSend(&OBJC_CLASS___NSString, "class");
if ( objc_msgSend(v7, "isKindOfClass:", v8) )
{
v10 = objc_msgSend(v7, "dataUsingEncoding:", 4);
v11 = v7;
v12 = objc_retainAutoreleasedReturnValue(v10);
v13 = objc_msgSend(v5, "dataUsingEncoding:", 4);
v14 = objc_retainAutoreleasedReturnValue(v13);
v15 = objc_msgSend(&OBJC_CLASS___NSMutableData, "dataWithLength:", 32);
v16 = objc_retainAutoreleasedReturnValue(v15);
v17 = (void *)objc_retainAutorelease(v12);
v18 = objc_msgSend(v17, "bytes");
v19 = objc_msgSend(v17, "length");
v20 = (void *)objc_retainAutorelease(v14);
v21 = objc_msgSend(v20, "bytes");
v22 = objc_msgSend(v20, "length");
v23 = (void *)objc_retainAutorelease(v16);
v24 = v23;
v25 = objc_msgSend(v23, "mutableBytes");
CCHmac(2, v18, v19, v21, v22, v25);
v26 = objc_msgSend(v24, "base64EncodedStringWithOptions:", 0);
v27 = (DTAFNRequestGet *)objc_retainAutoreleasedReturnValue(v26);
v28 = v24;
v7 = v11;
objc_release(v28);
objc_release(v20);
objc_release(v17);
}
else
{
v27 = (DTAFNRequestGet *)objc_retain(v7, v9);
}
objc_release(v7);
objc_release(v5);
return j__objc_autoreleaseReturnValue(v27, v29, v30, v31, v33, v34, v35, v36, v37);
}
复制代码
如今咱们已经完整的分析出了签名过程,其完整流程以下图所示。
到目前为止,咱们只剩下URL编码的方法enCodeURL:
没有分析,这部分逻辑也十分简单,只是简单地字符替换和添加,具体代码以下。
id __cdecl +[SingalMethod enCodeURL:](SingalMethod_meta *self, SEL a2, id a3)
{
int v3; // r8
SingalMethod_meta *v4; // r5
void *v5; // r8
void *v6; // r0
int v7; // r1
void *v8; // r0
void *v9; // r0
void *v10; // r6
void *v11; // r0
void *v12; // r4
void *v13; // r0
void *v14; // r6
void *v15; // r0
DTAFNRequestGet *v16; // r5
SEL v17; // r1
id v18; // r2
id v19; // r3
int v21; // [sp+0h] [bp-10h]
struct objc_object *v22; // [sp+18h] [bp+8h]
struct objc_object *v23; // [sp+1Ch] [bp+Ch]
struct objc_object *v24; // [sp+20h] [bp+10h]
struct objc_object *v25; // [sp+24h] [bp+14h]
struct objc_object *v26; // [sp+28h] [bp+18h]
v21 = v3;
v4 = self;
v5 = (void *)objc_retain(a3, a2);
v6 = objc_msgSend(&OBJC_CLASS___NSString, "class");
if ( objc_msgSend(v5, "isKindOfClass:", v6) )
{
v8 = objc_msgSend(v4, "encodeParameter:", v5);
v9 = (void *)objc_retainAutoreleasedReturnValue(v8);
v10 = v9;
v11 = objc_msgSend(v9, "stringByReplacingOccurrencesOfString:withString:", CFSTR("+"), CFSTR("%20"), v21);
v12 = (void *)objc_retainAutoreleasedReturnValue(v11);
objc_release(v10);
v13 = objc_msgSend(v12, "stringByReplacingOccurrencesOfString:withString:", CFSTR("*"), CFSTR("%2A"));
v14 = (void *)objc_retainAutoreleasedReturnValue(v13);
objc_release(v12);
v15 = objc_msgSend(v14, "stringByReplacingOccurrencesOfString:withString:", CFSTR("%7E"), CFSTR("~"));
v16 = (DTAFNRequestGet *)objc_retainAutoreleasedReturnValue(v15);
objc_release(v14);
}
else
{
v16 = (DTAFNRequestGet *)objc_retain(v5, v7);
}
objc_release(v5);
return j__objc_autoreleaseReturnValue(v16, v17, v18, v19, v22, v23, v24, v25, v26);
}
复制代码
其中调用了encodeParameter:
方法,该方法调用了系统方法来实现URL编码,而encodeURL:
则是额外作了一些替换,encodeParameter:
的实现以下。
id __cdecl +[SingalMethod encodeParameter:](SingalMethod_meta *self, SEL a2, id a3)
{
void *v3; // r4
void *v4; // r0
int v5; // r1
int v6; // r0
int v7; // r1
DTAFNRequestGet *v8; // r5
SEL v9; // r1
id v10; // r2
id v11; // r3
struct objc_object *v13; // [sp+14h] [bp+8h]
struct objc_object *v14; // [sp+18h] [bp+Ch]
struct objc_object *v15; // [sp+1Ch] [bp+10h]
struct objc_object *v16; // [sp+20h] [bp+14h]
struct objc_object *v17; // [sp+24h] [bp+18h]
v3 = (void *)objc_retain(a3, a2);
v4 = objc_msgSend(&OBJC_CLASS___NSString, "class");
if ( objc_msgSend(v3, "isKindOfClass:", v4) )
{
v6 = CFURLCreateStringByAddingPercentEscapes(0, v3, 0, CFSTR("!*'();:@&=+$,/?%#[]"), 134217984);
v8 = (DTAFNRequestGet *)objc_retain(v6, v7);
CFRelease();
}
else
{
v8 = (DTAFNRequestGet *)objc_retain(v3, v5);
}
objc_release(v3);
return j__objc_autoreleaseReturnValue(v8, v9, v10, v11, v13, v14, v15, v16, v17);
}
复制代码
这两个方法都比较简单,这里不过多的分析,而是给出其转化出的Objective-C函数的代码,供读者参考。
NSString * encodeParameter(NSString *input) {
CFStringRef output = CFURLCreateStringByAddingPercentEscapes(0, (__bridge_retained CFStringRef)input, 0, CFSTR("!*'();:@&=+$,/?%#[]"), 134217984);
return CFBridgingRelease(output);
}
NSString * encodeURL(NSString *input) {
input = encodeParameter(input);
input = [input stringByReplacingOccurrencesOfString:@"+" withString:@"%20"];
input = [input stringByReplacingOccurrencesOfString:@"*" withString:@"%2A"];
input = [input stringByReplacingOccurrencesOfString:@"%7E" withString:@"~"];
return input;
}
复制代码
下面咱们就能够动手写代码来实现这一套逻辑了,首先咱们能够根据抓包分析的结果构建参数列表,其中敏感信息进行了隐去。
NSDictionary * addParams(NSDictionary *dict) {
NSMutableDictionary *mut = dict.mutableCopy;
mut[@"uuid"] = @"0617444F-1F6A-48D2-92B1-*******";
mut[@"stopId"] = @"*******";
mut[@"access_id"] = @"****";
NSDate *date = [NSDate date];
long long ts = [date timeIntervalSince1970];
// 根据数据包可知须要的为毫秒时间戳
mut[@"timestamp"] = [NSString stringWithFormat:@"%lld", ts * 1000];
mut[@"platform"] = @"iOS";
mut[@"deviceId"] = @"7033b6eee4efd3c3b949952dc49cb52f7798c37b014b61a10bd******";
mut[@"appSource"] = @"******";
mut[@"token"] = @"";
return mut.copy;
}
复制代码
接下来咱们就能够实现一个完整的GET请求方法了。
+ (void)GET:(NSString *)baseURL params:(NSDictionary *)params success:(RequestGetSucceedBlock)success failure:(RequestGetFailureBlock)failure {
// 从入参基础上添加必要参数,对应于逆向中的AddNecessaryParamDic:方法
params = addParams(params);
NSMutableArray *allKeys = params.allKeys.mutableCopy;
// key升序排列
[allKeys sortUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
NSString *key1 = obj1;
NSString *key2 = obj2;
return [key1 compare:key2];
}];
// 构造url的参数字符串
NSString *output = @"";
NSString *url = @"";
for (NSString *key in allKeys) {
NSString *value = params[key];
if ([output isEqualToString:@""]) {
output = [NSString stringWithFormat:@"%@", value];
url = [NSString stringWithFormat:@"%@=%@", key, value];
} else {
output = [NSString stringWithFormat:@"%@,%@", output, value];
url = [NSString stringWithFormat:@"%@&%@=%@", url, key, value];
}
}
// 构造hmac算法的输入字符串
NSString *v58 = @"GET";
NSString *v29 = encodeURL(@"/");
NSString *v27 = encodeURL(url);
NSString *hmac = [NSString stringWithFormat:@"%@&%@&%@", v58, v29, v27];
NSString *key = @"******2fadf46f3b28b6adddd24******"; // r5
// 将字符串转为data,调用系统的Hmac函数CCHmac
// r4-r0-r8
NSData *hmacData = [hmac dataUsingEncoding:NSUTF8StringEncoding];
// r5=r11
NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding];
unsigned char macOutBytes[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, [keyData bytes], [keyData length], [hmacData bytes], [hmacData length], macOutBytes);
NSData *macOutData = [NSData dataWithBytes:macOutBytes length:CC_SHA256_DIGEST_LENGTH];
// 对加密结果进行base64处理,使其变为可打印字符
NSString *res = [macOutData base64EncodedStringWithOptions:0];
res = encodeURL(res);
// 构造最终的URL
url = [url stringByAppendingFormat:@"&signature=%@", res];
url = [NSString stringWithFormat:@"%@?%@", baseURL, url];
// 发起请求
[[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 响应处理
}] resume];
}
复制代码
反编译出的代码经常是十分复杂的,若是单纯从代码角度去分析将会异常困难,在分析时,能够结合情景和工程经验,例如在本文的分析中,利用先前的经验咱们知道,通常的请求方法都会包含URL和参数列表,而签名的对象通常就是参数列表的内容,签名函数主要是MD五、Hmac和SHA等算法,所以在分析时就会有目的的对号入座,因此积累工程经验对于逆向工程十分必要。
这一次Hacking过程以及本文的撰写均是处于学习和研究目的,绝无恶意,且关键信息均以隐去,不会对厂商形成威胁,且笔者未提供具体的二进制内容,但愿广大读者仅仅抱着学习和研究的心态去阅读本文,但愿此次过程记录可以帮助到走在逆向工程路上的大家。