background

The author often takes a bus to play somewhere from his house. However, the time of the bus is long and uncertain. Before each trip, I have to open the real-time bus software to search for the frequency and information, which is not only a waste of traffic but also very inconvenient. Therefore, the author intends to develop a -1 screen Today Widget based on the real-time bus interface to display the real-time information of the bus.

Analysis of the

Network packet analysis

Firstly, the author captures the network data packet of App and makes a brief analysis of the request data. The content of the request data is as follows, and sensitive information is coded for information security consideration.

Request interface contains a signature and a timestamp field, effectively prevent the timestamp field in the request of continuous playback, the signature field is to protect the interface data integrity, in the perspective of the black box in the case of unknown signature algorithm can only through the playback of the current request form to obtain data, once the timestamp and the server time difference more than the threshold, The server throws a system time error to prevent this form of attack. Due to the existence of the signature, there are only two ways to go, one is to carry out secondary development on the basis of the original App and add the desired capabilities through the form of dynamic library injection, and the other is to obtain the signature algorithm through decompile.

Analysis of secondary development mode

The principle of secondary development for iOS applications is to modify the executable file of the application. Add dynamic library loading instructions in the Load Commands field of the Mach-O file to enable it to Load the newly added dynamic library, and then the jailer will modify the original App logic in the form of sections in the dynamic library. Finally, the application and dynamic library are re-signed. This method has the following disadvantages:

  1. Binary files of iOS apps are encrypted when they are released. The binary files of iOS apps need to be cracked when they are developed. The only way to get cracked apps is through jailbreaking or jailbreaking markets.
  2. The App cannot be published to the App Store due to the use of dynamic libraries.
  3. Since secondary development is based on dynamic libraries, there is no way to add targets to the application to implement some extended functionality.

Considering that this application is mostly about adding Today widgets to the -1 screen, a secondary development approach seems unlikely.

Decompiler mode analysis

Decompilation seems to be omnipotent. Its main disadvantage is that it requires manual positioning of relevant logic and analysis of the assembly code and C pseudo-code obtained from disassembly, which costs a lot. However, the cost of decompilation on iOS platform is relatively low for the following three reasons:

  1. The ARM instruction set used by iOS is fixed-length instruction, so the flower instruction that caused the disassembler to parse abnormal by inserting garbage data cannot be applied.
  2. IOS apps require binary static scanning during review, so there is a limit to code obfuscation, so it is difficult to complete obfuscation like the front-end;
  3. Objective-c is a dynamic language based on C, so the generated intermediate code contains a lot of Runtime calls that are useful for locating and analyzing logic.

Based on the above analysis, the author finally uses decompilation method to sort out the logic of interface signature.

In actual combat

I first obtained the cracked binaries from the jailbreak market and then dragged them into IDA for disassembly. After disassembly is complete, the first step you need to do is to find the logic for the interface signature.

The logic orientation

It can be seen from the network data packet that this is a GET request and contains signature information, so we can search the symbols obtained by IDA for keywords such as GET, Request and signature to find relevant symbols. In this process, a lot of information may need to be excluded. Through searching, the author found two suspicious symbols.

Judging from the name, the second method passes an array of URLS, which should be due to the batch request, while the first method is an ordinary GET request. Therefore, we open the assembly code of the first method and decompile it into C pseudocode through IDA plug-in for analysis. The pseudocode content is as follows.

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);
}
Copy the code

It contains variable definitions and ARC code, and after removing it, the main logic remains as follows.

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);
}
Copy the code

As you can see, the last part of the code above is the call and callback processing of AFNetworking, which is not helpful for analyzing the interface signature and can be omitted. The final logic is as follows.

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);
Copy the code

Ignoring variable transfer, it can be seen that three operations are performed before the interface is invoked, including AddNecessaryParamDic:, signByHMac:: and UrlJointByUrl::::. From the literal analysis, it can be seen that adding necessary parameters, using HMac algorithm signature and constructing URL, respectively. Here we have found the interface signature method, it is the class methods SingalMethod signByHMac: AndHttpMethod: and here we set out to analyze the realization of this method.

Signature method analysis

Signature method signByHMac: AndHttpMethod: decompiled results are as follows.

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);
}
Copy the code

This part of the code is very long, and when I analyze it, I focus on the method call involved first. By searching for objc_msgSend in the code, the first if statement contains the following method call.

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);
Copy the code

It can be seen that allKeys here is an operation on the dictionary. We searched up and continued to analyze the v5 transfer path. Due to the existence of ARC code, variable transfer involves multiple assignment operations, and relevant codes analyzed are as follows.

id __cdecl +[SingalMethod signByHMac:AndHttpMethod:](SingalMethod_meta *self, SEL a2, id a3, id a4)
{
    // omit extraneous code
    v5 = (void *)objc_retain(a3, a2);
    v6 = objc_msgSend(v5, "allKeys");
}
Copy the code

V5 is visible through the study of the strong reference of a3, a3 is method signByHMac: AndHttpMethod: the first parameter, therefore a3 is a dictionary, we can preliminary speculated that it should be ask the parameters of the dictionary, in order to verify this statement, we went back to the caller at the next higher level, the parameters of the analysis to the signature, The key code is as follows.

id __cdecl -[DTAFNRequestGet GetDictionaryByURL:Op:KeyAndValue:successBlocker:failureBlockers:](DTAFNRequestGet *self, SEL a2, id a3, id a4, id a5, id a6, id a7)
{
    // omit extraneous code
    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"));
}
Copy the code

It can be seen that the Hmac passed to the signature method is V19, and v19 is originally from parameter A5. By analyzing the method header, it can be seen that A5 corresponds to KeyAndValue, which is exactly the parameter dictionary. A5 is passed to the signature method after adding necessary parameters. For GET requests, the request mode is the string GET, so as long as we implement the logic of the signature method, we can construct the parameter list to build a legitimate request.

Continuing with our analysis of signature methods, let’s look at a short paragraph inside the first if statement.

  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 );
  }
Copy the code

Notice the following combination.

  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 );
  }
Copy the code

Here includes countByEnumeratingWithState: : : method and two layers while statement, and while the internal objc_enumerationMutation calls, Note that this paragraph is the source code for the implementation of objective-C for-IN statements. We can ignore this part of the framework and focus on the logic of the inner while. Next, we will analyze the content of the first for-in statement. Please read the code comments in the inner body of the while loop.

  v66 = objc_msgSend(v59, "countByEnumeratingWithState:objects:count:", &v78, &v83, 16);
  // v5 is the parameter dictionary passed in
  v62 = v5;
  if ( v66 )
  {
    v11 = &stru_6B8088;
    v60 = *(_DWORD *)v79;
    do
    {
      v14 = 0;
      do
      {
        v69 = (char *)v11;
        if( *(_DWORD *)v79 ! = v60 ) objc_enumerationMutation(v59);// Get an element in the array by calculating the offset. V14 is the iterator, which can be read as I in for, and v15 is the current fetched element
        v15 = *(_DWORD *)(HIDWORD(v78) + 4 * v14);
        // enCodeURL: is the same as v15, so it can be interpreted as urL-encoding v15
        v16 = objc_msgSend(v71, "enCodeURL:", *(_DWORD *)(HIDWORD(v78) + 4 * v14));
        v68 = objc_retainAutoreleasedReturnValue(v16);
        // Fetch a value from the dictionary V5 with v15 as key. The following if is nullated
        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);
        Key v15 = key v15 = key v15
        v22 = objc_msgSend(v71, "enCodeURL:", v21);
        v23 = objc_retainAutoreleasedReturnValue(v22);
        Key1 =value1&key2=value2&... , visible is the form of URL encoding
        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 );
  }
Copy the code

According to the above analysis, the first for-in generates the URL parameter string corresponding to the request parameter dictionary by traversing the dictionary, and encodes it. The resulting parameter string is stored in the v11 variable. The following logic is very simple.

  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);
Copy the code

The first part of the string, v58, is the second parameter of the signature method, which is the request method, passed in GET. The second part, v29, is /, and the third part, v27, is the encoded parameter string, so here we construct a GET&/&k1=v1&ke=v2… And then the method hMAC :withKey: was called for signature. Here, the first parameter hMAC is the string we constructed, and the second parameter is the key, which is partially hidden for information security.

After hMAC encryption, the result V33, which is the final signature result, is added to the request parameters. As we know from the analysis, we only need to build a parameter list according to the network request data, and then calculate the signature and add it to the list to build a legitimate request. At present, we are only one step away from success, that is to analyze hMAC :withKey: method, fortunately, this method is very simple, call the system hMAC function encryption and base64 encoding and get the signature, the internal implementation of this method is as follows, this part of the logic is very simple, I will not repeat here, Readers can read for themselves.

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);
}
Copy the code

Now we have fully analyzed the signature process, as shown in the figure below.

So far, we only have the URL encoding method enCodeURL: No analysis, this part of the logic is very simple, just a simple character substitution and addition, the specific code is as follows.

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);
}
Copy the code

The encodeParameter: method is called, which calls the system method to implement THE URL encoding, while encodeURL: is an additional substitution, encodeParameter: is implemented as follows.

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);
}
Copy the code

These two methods are relatively simple, so here is not much analysis, but the code of the Objective-C function transformed by them for your reference.

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;
}
Copy the code

Now we can start to write code to implement this logic. First, we can build a parameter list according to the result of packet capture analysis, in which sensitive information is hidden.

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];
    // According to the packet, the required timestamp is millisecond
    mut[@"timestamp"] = [NSString stringWithFormat:@"%lld", ts * 1000];
    mut[@"platform"] = @"iOS";
    mut[@"deviceId"] = @"7033b6eee4efd3c3b949952dc49cb52f7798c37b014b61a10bd******";
    mut[@"appSource"] = @ "* * * * * *";
    mut[@"token"] = @ "";
    return mut.copy;
}
Copy the code

Now we can implement a complete GET request method.

+ (void)GET:(NSString *)baseURL params:(NSDictionary *)params success:(RequestGetSucceedBlock)success failure:(RequestGetFailureBlock)failure {
    // AddNecessaryParamDic, corresponding to the AddNecessaryParamDic: method in reverse
    params = addParams(params);
    NSMutableArray *allKeys = params.allKeys.mutableCopy;
    // The key is in ascending order
    [allKeys sortUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        NSString *key1 = obj1;
        NSString *key2 = obj2;
        return [key1 compare:key2];
    }];
    // Construct an argument string for the 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]; }}// Construct the input string for the HMAC algorithm
    NSString *v58 = @"GET";
    NSString *v29 = encodeURL(@ "/");
    NSString *v27 = encodeURL(url);
    NSString *hmac = [NSString stringWithFormat:@ "% @ & % @ & % @", v58, v29, v27];
    NSString *key = @"******2fadf46f3b28b6adddd24******"; // r5
    // Convert the string to data and call the Hmac function 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 is applied to the encrypted result to make it printable
    NSString *res = [macOutData base64EncodedStringWithOptions:0];
    res = encodeURL(res);
    // Construct the final URL
    url = [url stringByAppendingFormat:@"&signature=%@", res];
    url = [NSString stringWithFormat:@ % @ "? % @", baseURL, url];
    // Initiate a request
    [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        // Response processing
    }] resume];
}
Copy the code

conclusion

Decompiling the code often is very complicated, if only from the perspective of the code will be extremely difficult to analysis, based on the analysis, can be combined with the situation and the engineering experience, such as the analysis in this paper, using previous experience as we know, the general request method contains the URL and argument list, and signature of the object is usually the content of the argument list, Signature functions are mainly ALGORITHMS such as MD5, Hmac and SHA. Therefore, the analysis will be targeted. Therefore, it is necessary to accumulate engineering experience for reverse engineering.

The statement

This time Hacking process and the writing of this article is in the study and research purposes, not malicious, and key information to hidden, not a threat to vendors, and the author did not provide a specific binary content, just holding the hope readers to study and research state of mind to read this article, hope the process records can help you to walk in the reverse engineering road.