首页
社区
课程
招聘
[原创]某营业厅算法分析
发表于: 2022-9-5 09:10 18760

[原创]某营业厅算法分析

2022-9-5 09:10
18760

阅读此文档的过程中遇到任何问题,请关注公众号【移动端Android和iOS开发技术分享】或加QQ群【812546729

1.目标

使用frida stalker分析某营业厅的签名算法。

2.操作环境

  • mac系统

  • frida-ios-dump:砸壳

  • Charles:抓包

  • 已越狱iOS设备:脱壳及frida调试

  • IDA Pro:静态分析

3.流程

寻找切入点

在账号密码登录页,点击登录,通过Charles抓包获取到关键词为loginAuthCipherAsymmertric,这也就是我们的切入点:

 

image-20220902221846037

分析过程

使用frida-ios-dump的砸壳命令dump.py com.wemomo.momoappdemo1 砸壳获取到ipa文件,再使用IDA Pro编译ipa文件,然后搜索搜索字符串loginAuthCipherAsymmertric失败,继续搜索userLoginNormal,失败。该应用对字符串都进行了混淆。搜索无果后,只能换个思路,尝试hook NSMutableURLRequest类。

 

使用frida-trace的frida-trace -UF -m "-[NSMutableURLRequest setHTTPBody:]"命令跟踪该函数,js代码如下:

1
2
3
4
5
6
7
8
{
  onEnter(log, args, state) {
      var arg2 = new ObjC.Object(args[2])
      log(`-[NSMutableURLRequest setHTTPBody:${arg2.bytes().readUtf8String()}]`);
  },
  onLeave(log, retval, state) {
  }
}

点击登录后,获取到的日志如下:

1
2
3
-[NSMutableURLRequest setHTTPBody:ndh=1&t=00%2F00%2F0000%2000%3A00%3A00%200%20-480&c.&v3=baoguang_event&deviceId=7B063D78-C5D8-45A6-8E3D-37B787DEB2CD&hitDate=2022-09-02%2023%3A01%3A52&currentPage=Denglujh-kuandaidenglushouji&v63=101-106&appVersion=4&c25=%E6%B6%88%E6%81%AF%2BSyjh-sytop-message-1_1%2B%28null%29%2B%28null%29&es_timestamp=1662130874519&v60=lo158b3b81&codeVersion=22033101&v65=WIFI&v61=disable&a.&action=trkAppBgAction&OSVersion=iOS%2012.5.5&DeviceName=iPhone7%2C2&RunMode=Application&AppID=CTPocket%209.4.0%20%284%29&CarrierName=%28null%29&Resolution=750x1334&TimeSinceLaunch=2536&.a&lunchtype=aut_lunch&.c&pev2=AMACTION%3AtrkAppBgAction&pageName=CTPocket%2F4&ce=UTF-8&aid=540B88F2280D4599-146C532D0B076319&pe=lnk_o&cp=foreground]
-[NSMutableURLRequest setHTTPBody:{"content":{"fieldData":{"isChinatelecom":"0","phoneNum":"13245678901","authentication":"222222","accountType":"","deviceUid":"ddecc53e1fce414e9e89b6ea47e567e3","systemVersion":"12.5.5","loginAuthCipherAsymmertric":"o4Af5TvC5iV25FhTE9NIZJEiqHLWg+JkcCF4AGp727uhvFydBWvkCz8HauqTDpIoRhpfqLUMLN6Hk1ucBZOPYhCHwm4N\/4PuPsMWZTEbips+uL74ufgeLMci0nIZRmFsCsBrgvUVkebKcRo2yO0DZQ2jtnKe+cG78v6aOHl5ssk=","loginType":"4"},"attach":"iPhone"},"headerInfos":{"broadAccount":"","source":"120002","shopId":"20004","userLoginName":"13245678901","broadToken":"","code":"userLoginNormal","clientType":"#9.4.0#channel50#iPhone 6#","token":"","timestamp":"20220902230115","sourcePassword":"TiqmIZ"}}]
-[NSMutableURLRequest setHTTPBody:ndh=1&t=00%2F00%2F0000%2000%3A00%3A00%200%20-480&c.&v3=hit_event&deviceId=7B063D78-C5D8-45A6-8E3D-37B787DEB2CD&hitDate=2022-09-02%2023%3A01%3A05&c20=%E5%8F%B3%E6%BB%91%E7%99%BB%E5%BD%95&appVersion=4&lastAction=Denglujh-denglu-mima-2_8%5E%E5%8F%B3%E6%BB%91%E7%99%BB%E5%BD%95&method=trkAppButtonClick%20-action%20...&v63=101-106&currentPage=Denglujh-kuandaidenglushouji&es_timestamp=1662130875056&c21=Denglujh-denglu-mima-2_8&v60=lo158b3b81&codeVersion=22033101&v65=WIFI&v61=disable&a.&action=trkAppButtonClick&OSVersion=iOS%2012.5.5&CarrierName=%28null%29&DeviceName=iPhone7%2C2&AppID=CTPocket%209.4.0%20%284%29&RunMode=Application&Resolution=750x1334&TimeSinceLaunch=2537&.a&lunchtype=aut_lunch&prePageAction=hit_event&.c&pev2=AMACTION%3AtrkAppButtonClick&pageName=CTPocket%2F4&ce=UTF-8&aid=540B88F2280D4599-146C532D0B076319&pe=lnk_o&cp=foreground]

搜索登录的账号13245678901后,发现日志里有登录的body信息,在setHTTPBody的js代码里打印堆栈:

1
2
3
4
5
6
7
8
9
10
11
{
  onEnter(log, args, state) {
      var arg2 = new ObjC.Object(args[2])
      log(`-[NSMutableURLRequest setHTTPBody:${arg2.bytes().readUtf8String()}]`);
      log('NSMutableURLRequest setHTTPBody called from:\n' +
              Thread.backtrace(this.context, Backtracer.ACCURATE)
              .map(DebugSymbol.fromAddress).join('\n') + '\n');
  },
  onLeave(log, retval, state) {
  }
}

获取到的堆栈信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0x10397f4d8 CTPocket!-[AFJSONRequestSerializer requestBySerializingRequest:withParameters:error:]
0x10397ae50 CTPocket!-[AFHTTPRequestSerializer requestWithMethod:URLString:parameters:error:]
0x10394edac CTPocket!-[AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:headers:uploadProgress:downloadProgress:success:failure:]
0x10394e378 CTPocket!-[AFHTTPSessionManager POST:parameters:headers:progress:success:failure:]
0x101b8fae8 CTPocket!-[ESHttpSessionManager postWithHost:urlString:parameters:success:failure:]
0x1015f91c8 CTPocket!-[ESNetworkingManager postWithURLString:parameters:modifiParamsBlock:success:failure:]
0x100c14da4 CTPocket!-[ESLoginService loginWithPhoneNbr:type:code:isChinatelecom:slidingTime:percentage:success:failure:]
0x101f85f58 CTPocket!-[ESBindLoginViewController phoneLoginWithPhoneNbr:type:code:slidingTime:percentage:isChinatelecom:isBind:failureBlock:]
0x101f6a060 CTPocket!-[ESBindLoginViewController phoneLoginViewController:inputView:didSliderWithSlidingTime:Percentage:phoneNbr:passwd:loginType:isChinatelecom:isBind:failureBlock:]
0x102ccb964 CTPocket!-[ESLoginViewController phoneLoginView:inputView:didSliderWithSlidingTime:Percentage:phoneNbr:passwd:loginType:isChinatelecom:isBind:]
0x101261728 CTPocket!-[ESPhoneLoginView passwordInputView:sliderWithSlidingTime:Percentage:phoneNbr:passwd:]
0x101e73d10 CTPocket!-[PasswordInputView commitBtnAction:]
0x1e7091300 UIKitCore!-[UIApplication sendAction:to:from:forEvent:]
0x10400a288 CTPocket!-[UIApplication(AutoTrack) sa_sendAction:to:from:forEvent:]
0x1e6b3a424 UIKitCore!-[UIControl sendAction:to:forEvent:]
0x1e6b3a744 UIKitCore!-[UIControl _sendActionsForEvents:withEvent:]

接下来我们使用frida-trace工具,对以上调用栈进行逐个跟踪并打印入参,最终确定loginAuthCipherAsymmertric参数在[ESLoginService loginWithPhoneNbr:type:code:isChinatelecom:slidingTime:percentage:success:failure:]方法里生成的,打开IDA Pro并找到该方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
id __cdecl -[ESLoginService loginWithPhoneNbr:type:code:isChinatelecom:slidingTime:percentage:success:failure:](ESLoginService *self, SEL a2, id a3, signed __int64 a4, id a5, id a6, id a7, id a8, id a9, id a10)
{
  id v10; // x19
  id v11; // x20
  id v12; // x25
  id v13; // x21
  ESLoginService *v14; // x24
  __int64 v15; // x1
  __int64 v16; // x1
  __int64 v17; // x1
  __int64 v18; // x1
  __int64 v19; // x1
  __int64 v20; // x1
  void *v21; // x0
  __int64 v22; // x25
  void *v23; // x0
  unsigned int v24; // off
  signed int v25; // w8
 
  v10 = a8;
  v11 = a7;
  v12 = a6;
  v13 = a5;
  v14 = self;
  objc_retain(a3, a2);
  objc_retain(a9, v15);
  objc_retain(a10, v16);
  objc_retain(v10, v17);
  objc_retain(v11, v18);
  objc_retain(v12, v19);
  objc_retain(v13, v20);
  v21 = objc_msgSend(&OBJC_CLASS___NSDate, "date");
  v22 = objc_retainAutoreleasedReturnValue(v21);
  -[ESLoginService setRequestDate:](v14, "setRequestDate:", v22);
  objc_release(v22);
  v23 = (void *)((__int64 (__fastcall *)(void *))((char *)off_105153068 + 92708870))(&OBJC_CLASS___NSDateFormatter);
  objc_msgSend(v23, "init");
  v24 = __ldar((unsigned int *)&dword_10577A424);
  if ( (unsigned int)&dword_10577A424 )
    v25 = 7;
  else
    v25 = 34;
  JUMPOUT(__CS__, (char *)*(&off_105153070 + v25) - dword_1051531F0[v25]);
}

傻眼了了吧。 JUMPOUT,也就是经常逆向会遇到的跳表,需要手动恢复。在这,我们使用frida stalker来跟踪该函数。ts代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
var addr = 0x0000000029FD08 // loginWithPhoneNbr函数的起始地址
var mainModule = Process.enumerateModules()[0];
console.log(JSON.stringify(mainModule));
var mainName: string = mainModule.name;
var baseAddr = Module.findBaseAddress(mainName)!;
Interceptor.attach(baseAddr.add(addr), {
    onEnter: function(args) {
        console.log(addr.toString(16), "= loginWithPhoneNbr onEnter =");
        var tid = Process.getCurrentThreadId();
        Stalker.follow(tid, {
            events: {
                call: true, // CALL instructions: yes please           
                ret: false, // RET instructions
                exec: false, // all instructions: not recommended as it's
                block: false, // block executed: coarse execution trace
                compile: false // block compiled: useful for coverage
            },
            transform: (iterator: StalkerArm64Iterator) => {
                let instruction = iterator.next();
                const startAddress = instruction!.address;
                var isAppCode = startAddress.compare(baseAddr.add(addr)) >= 0 && startAddress.compare(baseAddr.add(addr).add(10000)) === -1;
                do {
                    if (isAppCode) {
                        if (instruction!.mnemonic === "bl") {
                            iterator.putCallout((ctx) => {
                                var arm64Context = ctx as Arm64CpuContext;
                                console.log("bl x0 = " + new ObjC.Object(arm64Context.x0))
                                console.log("bl x1 = " + arm64Context.x1.readCString())
                            });                       
                        }
                    }
                    iterator.keep();
                } while ((instruction = iterator.next()) !== null);
             }
        })
    }, onLeave: function(retval) {
        console.log("retval:", new ObjC.Object(retval))
        console.log(addr.toString(16), "= loginWithPhoneNbr onLeave =");
    }
});

获取到的关键日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
29fd08 = loginWithPhoneNbr onEnter =
bl x0 = Utils
bl x1 = createRSAStringWithPhone:authentication:timestamp:slidingTime:percentage:
bl x0 = s21NZBfNx8ndPADDLDJCxCUOdWo45EL8jNr6TnHcwKwfnahCFpLGjXkZkhbGj2CZODR3LVjabCRw6Li3SLcvLtA6g0meKbUTgQzANtn9y2ttg9Svj9flxj9k18Ju5EBjloSbdd4ee4o0rHgn9W0iSSDs4zwKsqm+rOxYDZFHgWI=
bl x1 = autorelease
bl x0 = 0.000000
bl x1 = null
bl x0 = 0
bl x1 = null
bl x0 = NSMutableDictionary
bl x1 = dictionary
bl x0 = {
}
bl x1 = autorelease
bl x0 = {
}
bl x1 = setObject:forKey:
bl x0 = {
    accountType = "";
}
bl x1 = setObjectOrNil:forKey:
bl x0 = 222222
bl x1 = copyWithZone:
bl x0 = ESGlobalFactory
bl x1 = sharedInstance
bl x0 = <ESGlobalFactory: 0x282e568b0>
bl x1 = sharedInstance
bl x0 = <ESGlobalFactory: 0x282e568b0>
bl x1 = deviceInfo
bl x0 = <ESDeviceInfo: 0x28225e120>
bl x1 = deviceInfo
bl x0 = <ESDeviceInfo: 0x28225e120>
bl x1 = uuidForDevice
bl x0 = ddecc53e1fce414e9e89b6ea47e567e3
bl x1 = autorelease
bl x0 = {
    accountType = "";
    authentication = 222222;
}
bl x1 = setObjectOrNil:forKey:
bl x0 = ddecc53e1fce414e9e89b6ea47e567e3
bl x1 = copyWithZone:
bl x0 = <ESDeviceInfo: 0x28225e120>
bl x1 = release
bl x0 = <ESGlobalFactory: 0x282e568b0>
bl x1 = release
bl x0 = {
    accountType = "";
    authentication = 222222;
    deviceUid = ddecc53e1fce414e9e89b6ea47e567e3;
}
bl x1 = setObjectOrNil:forKey:
bl x0 = 0
bl x1 = copyWithZone:
bl x0 = NSString
bl x1 = stringWithFormat:
bl x0 = 4
bl x1 = autorelease
bl x0 = {
    accountType = "";
    authentication = 222222;
    deviceUid = ddecc53e1fce414e9e89b6ea47e567e3;
    isChinatelecom = 0;
}
bl x1 = setObjectOrNil:forKey:
bl x0 = 4
bl x1 = copyWithZone:
bl x0 = {
    accountType = "";
    authentication = 222222;
    deviceUid = ddecc53e1fce414e9e89b6ea47e567e3;
    isChinatelecom = 0;
    loginType = 4;
}
bl x1 = setObjectOrNil:forKey:
bl x0 = UIDevice
bl x1 = currentDevice
bl x0 = <UIDevice: 0x2820598e0>
bl x1 = currentDevice
bl x0 = <UIDevice: 0x2820598e0>
bl x1 = systemVersion
bl x0 = 12.5.5
bl x1 = X7Lyw9oGPgMDQ
bl x0 = {
    accountType = "";
    authentication = 222222;
    deviceUid = ddecc53e1fce414e9e89b6ea47e567e3;
    isChinatelecom = 0;
    loginType = 4;
    phoneNum = 13245678901;
}
bl x1 = setObjectOrNil:forKey:
bl x0 = 12.5.5
bl x1 = 0��ו�
bl x0 = <UIDevice: 0x2820598e0>
bl x1 = 0��ו�
bl x0 = {
    accountType = "";
    authentication = 222222;
    deviceUid = ddecc53e1fce414e9e89b6ea47e567e3;
    isChinatelecom = 0;
    loginType = 4;
    phoneNum = 13245678901;
    systemVersion = "12.5.5";
}
bl x1 = setObjectOrNil:forKey:
bl x0 = ESDataAccessFactory
bl x1 = sharedInstance
bl x0 = <ESDataAccessFactory: 0x280472460>
bl x1 = sharedInstance
bl x0 = <ESDataAccessFactory: 0x280472460>
bl x1 = highFrequencyNetworkingManager
bl x0 = <ESNetworkingManager: 0x282e9cc90>
bl x1 = highFrequencyNetworkingManager
bl x0 = <__NSStackBlock__: 0x16f4f0c28>
bl x1 = highFrequencyNetworkingManager
bl x0 = <__NSStackBlock__: 0x16f4f0c88>
bl x1 = retain
bl x0 = 20220905003215
bl x1 = retain
bl x0 = 13245678901
bl x1 = null
bl x0 = <ESNetworkingManager: 0x282e9cc90>
bl x1 = postWithURLString:parameters:modifiParamsBlock:success:failure:
bl x0 = {
    content =     {
        attach = iPhone;
        fieldData =         {
            accountType = "";
            authentication = 222222;
            deviceUid = ddecc53e1fce414e9e89b6ea47e567e3;
            isChinatelecom = 0;
            loginAuthCipherAsymmertric = "s21NZBfNx8ndPADDLDJCxCUOdWo45EL8jNr6TnHcwKwfnahCFpLGjXkZkhbGj2CZODR3LVjabCRw6Li3SLcvLtA6g0meKbUTgQzANtn9y2ttg9Svj9flxj9k18Ju5EBjloSbdd4ee4o0rHgn9W0iSSDs4zwKsqm+rOxYDZFHgWI=";
            loginType = 4;
            phoneNum = 13245678901;
            systemVersion = "12.5.5";
        };
    };
    headerInfos =     {
        broadAccount = "";
        broadToken = "";
        clientType = "#9.4.0#channel50#iPhone 6#";
        code = userLoginNormal;
        shopId = 20004;
        source = 120002;
        sourcePassword = TiqmIZ;
        timestamp = 20220905003218;
        token = "";
        userLoginName = "";
    };
}
bl x1 = a���

结果

通过日志,我们可以发现loginAuthCipherAsymmertric参数是使用Utils类的createRSAStringWithPhone:authentication:timestamp:slidingTime:percentage:方法生成的,伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
id __cdecl +[Utils createRSAStringWithPhone:authentication:timestamp:slidingTime:percentage:](Utils_meta *self, SEL a2, id a3, id a4, id a5, id a6, id a7)
{
  v7 = a7;
  v8 = a5;
  v9 = a4;
  v10 = a3;
  v11 = self;
  v12 = objc_retain(a6, a2);
  v14 = objc_retain(v7, v13);
  v16 = (void *)objc_retain(v8, v15);
  v18 = objc_retain(v9, v17);
  v20 = objc_retain(v10, v19);
  v21 = +[ESGlobalFactory sharedInstance](&OBJC_CLASS___ESGlobalFactory, "sharedInstance");
  v22 = (void *)objc_retainAutoreleasedReturnValue(v21);
  v23 = v22;
  v24 = objc_msgSend(v22, "deviceInfo");
  v25 = (void *)objc_retainAutoreleasedReturnValue(v24);
  v26 = v25;
  v27 = objc_msgSend(v25, "uuidForDevice");
  v28 = objc_retainAutoreleasedReturnValue(v27);
  v29 = +[Utils substring:ToIndex:](&OBJC_CLASS___Utils, "substring:ToIndex:", v28, 12LL);
  v30 = objc_retainAutoreleasedReturnValue(v29);
  v31 = +[Utils deviceName](&OBJC_CLASS___Utils, "deviceName");
  v32 = objc_retainAutoreleasedReturnValue(v31);
  v33 = v32;
  v34 = +[Utils substring:ToIndex:](&OBJC_CLASS___Utils, "substring:ToIndex:", v32, 10LL);
  v35 = objc_retainAutoreleasedReturnValue(v34);
  v36 = objc_msgSend(&OBJC_CLASS___UIDevice, "currentDevice");
  v37 = (void *)objc_retainAutoreleasedReturnValue(v36);
  v38 = v37;
  v39 = objc_msgSend(v37, "systemVersion");
  v40 = objc_retainAutoreleasedReturnValue(v39);
  objc_release(v38);
  v41 = +[Utils substring:ToIndex:](&OBJC_CLASS___Utils, "substring:ToIndex:", v40, 5LL);
  v42 = objc_retainAutoreleasedReturnValue(v41);
  objc_release(v40);
  v43 = (void *)objc_alloc(&OBJC_CLASS___NSDateFormatter);
  v44 = objc_msgSend(v43, "init");
  v45 = v44;
  v46 = v44;
  objc_msgSend(v44, "setDateFormat:", CFSTR("yyyyMMddHHmmss"));
  v47 = (void *)objc_alloc(&OBJC_CLASS___NSLocale);
  v48 = objc_msgSend(v47, "initWithLocaleIdentifier:", CFSTR("en_US"));
  objc_msgSend(v45, "setLocale:", v48);
  v49 = objc_msgSend(v16, "stringByReplacingOccurrencesOfString:withString:", CFSTR(":"), &stru_10480C358);
  v50 = objc_retainAutoreleasedReturnValue(v49);
  objc_release(v16);
  v51 = +[Utils substring:ToIndex:](&OBJC_CLASS___Utils, "substring:ToIndex:", v50, 14LL);
  v52 = objc_retainAutoreleasedReturnValue(v51);
  v53 = v52;
  v54 = v52;
  v55 = objc_msgSend(v11, "substring:ToIndex:", v12, 4LL);
  v56 = objc_retainAutoreleasedReturnValue(v55);
  v57 = objc_msgSend(v11, "substring:ToIndex:", v14, 2LL);
  v58 = objc_retainAutoreleasedReturnValue(v57);
  v59 = objc_msgSend(v11, "substring:ToIndex:", v18, 6LL);
  v60 = objc_retainAutoreleasedReturnValue(v59);
  v61 = objc_msgSend(v11, "substring:ToIndex:", v20, 11LL);
  v62 = objc_retainAutoreleasedReturnValue(v61);
  v63 = objc_msgSend(
          &OBJC_CLASS___NSString,
          "stringWithFormat:",
          CFSTR("%@%@%@%@%@%@%@%@"),
          v35,
          v42,
          v30,
          v62,
          v53,
          v60,
          v56,
          v58);
  v64 = objc_retainAutoreleasedReturnValue(v63);
  v65 = +[Utils loginRsaKey2](&OBJC_CLASS___Utils, "loginRsaKey2");
  v66 = objc_retainAutoreleasedReturnValue(v65);
  v67 = v66;
  v68 = +[RSAEncryptor encryptString:publicKey:](&OBJC_CLASS___RSAEncryptor, "encryptString:publicKey:", v64, v66);
  v69 = objc_retainAutoreleasedReturnValue(v68);
  return (id)objc_autoreleaseReturnValue(v69);
}

这就是生成loginAuthCipherAsymmertric的最终函数。

End

阅读此文档的过程中遇到任何问题,请关注公众号【移动端Android和iOS开发技术分享】或加QQ群【812546729

IMG_4048


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//