首页
社区
课程
招聘
[原创]xx搞笑关注功能分析
发表于: 2019-6-2 22:01 15480

[原创]xx搞笑关注功能分析

2019-6-2 22:01
15480

     最近在学习ios逆向,就顺手拿个app练练手,顺便分享一下。

     本文使用皮皮搞笑 1.5.5 ios 版本为例,逆向分析关注功能。

     工具 

    打开App后,如下图,可以看到一个关注按钮,通过点击按钮,可以实现关注该用户动态。

 

    通过抓包分析,点击关注按钮时,会发送add?sign=xxxxxx数据包,想要分析数据来源,可以从界面上的关注按钮着手.


   为了更直观分析,这里使用Reveal神器来分析UI界面,在使用Reveal前要在手机设置 > Reveal->Enabled Applications 里面打开需要分析的App.

   配置好Reveal后,在手机上打开需要分析的app界面,再在笔记本上打开Reveal工具,Reveal支持USB和网络连接两种方式,一般选择USB.

   打开Reveal后,如下图,可以看到AppUI 应用结构层级

    通过分析AppUI 应用结构层级,可以明显看到红色关注按钮,直接点击按钮,可以看到的确是一个UIButton,内存地址0x143883f80,视图控制器Class         为 LJJUSerProfileVC,内存地址0x140abb400


    为了验证数据是否正确,使用Cycript(动态调式工具),来获取运行时信息

    使用ssh登录iphone,执行命令 crcript -p pid或进程名

     接下来对app进行脱壳.再使用IDA加载脱壳后的Mach-o文件,恢复App符号表可参考(http://blog.imjun.net/posts/restore-symbol-of-iOS-app/)

为了方便查看,使用F5后的伪代码来进行观察   

    LJJUSerProfileVC followClick 又调用了 LJJUSerProfileVC followClick]_block 函数


    因为函数调用较多,就不一一贴图了,经过分析,以下为主要函数调用流程:

ZYURLManager requestHeaderDictionaryWithParameters:主要功能就是h_model,u_id,h_ch,h_app,h_ts,token 等字段进行填充,其中h_ts为时间戳.

以下是ZYURLManager requestHeaderDictionaryWithParameters: 部分伪代码

从调用图可以看出sub_100200C40 又调用了sub_1002003C4 和 sub_100200BB0 函数

sub_100200C40 流程图

上图中,其中蓝小方块为调用sub_1002003C4 和 sub_100200BB0 函数,是整个函数必经之路

sub_1002003C4  流程图

我们知道md5算法中需要 0x67452301 0xefcdab89  0x98badcfe 0x10325476 数据做为初始值,以分组为单位进行处理,分别对每一块信息块进行MD5计算,每次计算4轮,每轮16次计算.

根据以上信息可以推断 sub_1002003C4  md5算法或其变形,不过还需要进一步分析才能确定

sub_100200BB0 伪代码

小结一下

ZYURLManager requestHeaderDictionaryWithParameters: 将需要提交的数据进行字段进行填充,大部分都是都是固定字段的如h_model,u_id,h_ch,h_app,h_ts,token等字段,这里就不做详细分析。

ZYCrypto signWithParameters 调用sub_100200C40 函数将数据进行签名.

iphone ssh 登录,开启 debugserver 。 

对关键的函数下断点,查看其参数和返回值

sub_1002003C4 函数头地址为0x100200c40 + 0x24000 = 0x100224c40

查看参数可以看到就是post字符串数据.接下来再分析sub_1002003c4参数和返回值

分别对sub_1002003c4 入口和返回后地址下断

    整个分析流程大概就这些了,通过界面分析到功能函数静态和动态分析,这也是在逆向中常用的思路。


IDA 7.0
Reveal 21(11690)
Cycript
lldb
Charles
    环境
Iphone6 IOS 9.1
Macos Mojave 10.14.5

三.界面分析

    通过分析app UI界面为突破口,来进行逆向分析。

    打开App后,如下图,可以看到一个关注按钮,通过点击按钮,可以实现关注该用户动态。

 

    通过抓包分析,点击关注按钮时,会发送add?sign=xxxxxx数据包,想要分析数据来源,可以从界面上的关注按钮着手.


   为了更直观分析,这里使用Reveal神器来分析UI界面,在使用Reveal前要在手机设置 > Reveal->Enabled Applications 里面打开需要分析的App.

   配置好Reveal后,在手机上打开需要分析的app界面,再在笔记本上打开Reveal工具,Reveal支持USB和网络连接两种方式,一般选择USB.

   打开Reveal后,如下图,可以看到AppUI 应用结构层级


    通过分析AppUI 应用结构层级,可以明显看到红色关注按钮,直接点击按钮,可以看到的确是一个UIButton,内存地址0x143883f80,视图控制器Class         为 LJJUSerProfileVC,内存地址0x140abb400


    为了验证数据是否正确,使用Cycript(动态调式工具),来获取运行时信息

    使用ssh登录iphone,执行命令 crcript -p pid或进程名

Davinde-iPhone:/var root# cycript -p PPSocial 
cy# #0x143883f80
#"<UIButton: 0x143883f80; frame = (270 29; 50 26); alpha = 0; opaque = NO; layer = <CALayer: 0x1438a4cc0>>"
    通过以上信息可以判断,0x143883f80的确是UIButton按钮的地址,如果不确定,可以通过调用hidden方法,并且观察app对应的按钮是否有变化.
cy# #0x143883f80.hidden=YES  //隐藏按钮
true
cy# #0x143883f80.hidden=NO //显示按钮
false
    光知道UiButton内存地址好像没有用,如何得到UiBtton的点击事件的所调用函数呢? 通过cycript 脚可以来遍历
@import mycript  //导入mycript 脚本
cy# MyBtnTouchUpEvent(#0x143883f80)
[@["followClick"]]
   下面是MyBtnTouchUpEvent代码,获取该Button对象所有的Target对象,并将事件相关所有action遍历输出,顺提一下,action可能有多个
// 获取按钮绑定的所有TouchUpInside事件的方法名
	MyBtnTouchUpEvent = function(btn) { 
		var events = [];
		var allTargets = btn.allTargets().allObjects()
		var count = allTargets.count;
    	for (var i = count - 1; i >= 0; i--) { 
    		if (btn != allTargets[i]) {
    			var e = [btn actionsForTarget:allTargets[i] forControlEvent:UIControlEventTouchUpInside];
    			events.push(e);
    		}
    	}
	   return events;
	};
     通过MyBtnTouchUpEvent得到followClick方法,需要测试下这个方法是否正确,通过调用LJJUSerProfileVC控制器的 followClick方法来测试一下
cy# [#0x140abb400 followClick] //#0x140abb400为LJJUSerProfileVC对象
    通过反复[#0x140abb400 followClick] 方法调用,可以实现手动点击关注效果,现在可以确定LJJUSerProfileVC followClick就是关注按钮的action方法.
四.静态分析

     接下来对app进行脱壳.再使用IDA加载脱壳后的Mach-o文件,恢复App符号表可参考(http://blog.imjun.net/posts/restore-symbol-of-iOS-app/)

为了方便查看,使用F5后的伪代码来进行观察   

    LJJUSerProfileVC followClick 又调用了 LJJUSerProfileVC followClick]_block 函数


    因为函数调用较多,就不一一贴图了,经过分析,以下为主要函数调用流程:

LJJUSerProfileVC followClick
    LJJUSerProfileVC attention:
        LJJAttentionManager userAttentionOther:isAttened:from:success:failure:
            ZYAPIClient postWithPath:parameters:userInfo:progress:success:retry:failure:
                ZYAPIClient postWithURL:parameters:userInfo:progress:success:retry:failure:
                    ZYURLManager requestHeaderDictionaryWithParameters:
                    ZYCrypto signWithParameters:
分析过程中发现比较重要的函数是ZYURLManager requestHeaderDictionaryWithParameters:和ZYCrypto signWithParameters:

ZYURLManager requestHeaderDictionaryWithParameters:主要功能就是h_model,u_id,h_ch,h_app,h_ts,token 等字段进行填充,其中h_ts为时间戳.

以下是ZYURLManager requestHeaderDictionaryWithParameters: 部分伪代码


ZYAPIClient signWithParameters: 从名字可以看出是对参数进行签名的,里面调用了-ZYCrypto signWithParameters:

进入-ZYCrypto signWithParameters:函数

从调用图可以看出sub_100200C40 又调用了sub_1002003C4 和 sub_100200BB0 函数

sub_100200C40 流程图


放大后更清楚

上图中,其中蓝小方块为调用sub_1002003C4 和 sub_100200BB0 函数,是整个函数必经之路

sub_1002003C4  流程图


sub_1002003C4 部分伪代码

我们知道md5算法中需要 0x67452301 0xefcdab89  0x98badcfe 0x10325476 数据做为初始值,以分组为单位进行处理,分别对每一块信息块进行MD5计算,每次计算4轮,每轮16次计算.

根据以上信息可以推断 sub_1002003C4  md5算法或其变形,不过还需要进一步分析才能确定

sub_100200BB0 伪代码



LJJUSerProfileVC followClick
 	LJJUSerProfileVC attention:
		LJJAttentionManager userAttentionOther:isAttened:from:success:failure:
			ZYAPIClient postWithPath:parameters:userInfo:progress:success:retry:failure:
				ZYAPIClient postWithURL:parameters:userInfo:progress:success:retry:failure:
					ZYURLManager requestHeaderDictionaryWithParameters:
					ZYCrypto signWithParameters:
						-ZYCrypto signWithParameters:
							sub_100200C40
								sub_1002003C4
								sub_100200BB0

小结一下

ZYURLManager requestHeaderDictionaryWithParameters: 将需要提交的数据进行字段进行填充,大部分都是都是固定字段的如h_model,u_id,h_ch,h_app,h_ts,token等字段,这里就不做详细分析。

ZYCrypto signWithParameters 调用sub_100200C40 函数将数据进行签名.

五.动态调试

   为了验证推测是否正确,需要对app进行动态调试。

iphone ssh 登录,开启 debugserver 。 

Davinde-iPhone:~ root# debugserver *:10011 -a PPSocial
debugserver-@(#)PROGRAM:debugserver  PROJECT:debugserver-340.3.51.1
 for arm64.
Attaching to process PPSocial...
Listening to port 10011 for a connection from *...
Waiting for debugger instructions for process 0.
   mac lldb 附加
process connect connect://192.168.31.199:10011
  获取ASLR偏移
(lldb) image list -o -f PPSocial
[  0] 0x0000000000024000 /var/mobile/Containers/Bundle/Application/C3170E24-F5A3-4FD8-AB42-532DA856722E/PPSocial.app/PPSocial(0x0000000100024000)
 函数的内存地址(VM Address ) = File Offset + ASLR Offset  IDA  中的地址是未使用ASLR的VM Address.

对关键的函数下断点,查看其参数和返回值

sub_1002003C4 函数头地址为0x100200c40 + 0x24000 = 0x100224c40

(lldb) br s -a 0x100224c40
Breakpoint 1: where = PPSocial`_mh_execute_header + 2079508, address = 0x0000000100224c40
成功下断后,运行app,点击关注。
  
Process 1457 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100224c40 PPSocial`_mh_execute_header + 2100288
PPSocial`_mh_execute_header:
->  0x100224c40 <+2100288>: sub    sp, sp, #0x90             ; =0x90
    0x100224c44 <+2100292>: stp    x22, x21, [sp, #0x60]
    0x100224c48 <+2100296>: stp    x20, x19, [sp, #0x70]
    0x100224c4c <+2100300>: stp    x29, x30, [sp, #0x80]
    
    (lldb) memory read $x0 $x0+400
0x12876f268: 7b 22 68 5f 6d 6f 64 65 6c 22 3a 22 69 50 68 6f  {"h_model":"iPho
0x12876f278: 6e 65 20 36 22 2c 22 68 5f 63 68 22 3a 22 61 70  ne 6","h_ch":"ap
0x12876f288: 70 73 74 6f 72 65 22 2c 22 75 69 64 22 3a 39 37  pstore","uid":97
0x12876f298: 35 31 37 32 39 30 2c 22 68 5f 61 70 70 22 3a 22  517290,"h_app":"
0x12876f2a8: 7a 75 69 79 6f 75 5f 6c 69 74 65 22 2c 22 68 5f  zuiyou_lite","h_
0x12876f2b8: 74 73 22 3a 31 35 35 38 39 36 36 35 30 31 35 36  ts":155896650156
0x12876f2c8: 34 2c 22 68 5f 61 76 22 3a 22 34 2e 35 2e 35 22  4,"h_av":"4.5.5"
0x12876f2d8: 2c 22 68 5f 6e 74 22 3a 31 2c 22 68 5f 64 69 64  ,"h_nt":1,"h_did
0x12876f2e8: 22 3a 22 64 38 30 36 62 65 35 35 34 61 61 31 34  ":"d806be554aa14
0x12876f2f8: 34 63 34 37 34 64 30 35 61 63 38 61 37 66 35 36  4c474d05ac8a7f56
0x12876f308: 64 63 31 22 2c 22 68 5f 6d 22 3a 31 36 37 31 33  dc1","h_m":16713
0x12876f318: 35 32 34 39 2c 22 68 5f 70 69 70 69 22 3a 22 31  5249,"h_pipi":"1
0x12876f328: 2e 35 2e 35 22 2c 22 64 69 64 22 3a 22 64 38 30  .5.5","did":"d80
0x12876f338: 36 62 65 35 35 34 61 61 31 34 34 63 34 37 34 64  6be554aa144c474d
0x12876f348: 30 35 61 63 38 61 37 66 35 36 64 63 31 22 2c 22  05ac8a7f56dc1","
0x12876f358: 68 5f 6f 73 22 3a 39 31 30 30 30 30 2c 22 74 6f  h_os":910000,"to
0x12876f368: 6b 65 6e 22 3a 22 54 30 4b 35 4e 6a 64 75 79 53  ken":"T0K5NjduyS
0x12876f378: 39 39 69 42 61 72 47 71 56 4c 52 62 52 51 74 68  99iBarGqVLRbRQth
0x12876f388: 78 37 63 4b 45 77 46 6e 4b 5f 45 6a 45 54 6a 7a  x7cKEwFnK_EjETjz
0x12876f398: 5f 58 62 53 65 54 71 58 73 61 6d 68 45 79 61 46  _XbSeTqXsamhEyaF
0x12876f3a8: 74 48 4e 77 4c 6b 75 36 42 55 39 22 2c 22 68 5f  tHNwLku6BU9","h_
0x12876f3b8: 64 74 22 3a 31 7d 00 00 00 00 00 00 00 00 00 e0  dt":1}.........�
0x12876f3c8: 00 00 00 00 00 00 00 e0 08 00 ff ff f5 01 00 00  .......�..���...
0x12876f3d8: f5 01 00 00 f5 01 00 00 f5 01 00 00 61 00 00 00  �...�...�...a...
0x12876f3e8: 00 00 00 00 62 00 00 00 70 b5 0d 28 01 00 00 00  ....b...p�.(....
(lldb)

查看参数可以看到就是post字符串数据.接下来再分析sub_1002003c4参数和返回值

分别对sub_1002003c4 入口和返回后地址下断

(lldb) br s -a 0x100224e30
Breakpoint 2: where = PPSocial`_mh_execute_header + 2080004, address = 0x0000000100224e30
(lldb) br s -a 0x100224e34
Breakpoint 3: where = PPSocial`_mh_execute_header + 2080008, address = 0x0000000100224e34
sub_1002003c4 有三个参数分别为 x0,x1,x2 
->  0x100224e30 <+2100784>: bl     0x1002243c4               ; PPSocial.__TEXT.__text + 2077336
    0x100224e34 <+2100788>: sub    x0, x29, #0x38            ; =0x38
    0x100224e38 <+2100792>: mov    x8, sp
    0x100224e3c <+2100796>: bl     0x100224bb0               ; PPSocial.__TEXT.__text + 2079364
Target 0: (PPSocial) stopped.
(lldb) register read
General Purpose Registers:
        x0 = 0x0000000128773ef0
        x1 = 0x0000000000000162
        x2 = 0x000000016fdd8ef8
        x3 = 0x0000000000000016
        x4 = 0x0000000000000158
        x5 = 0x0000000000000010
        x6 = 0x0000000000000022
        x7 = 0x000000016fdd8eb0
        x8 = 0x0000000000000080
x0为字符串数据,x1字符串长度,x2传出参数,字符串头尾进行处理.
lldb) memory read $x0 $x0+$x1
0x128773ef0: 59 30 4d 54 42 6c 4f 44 63 78 3a 22 69 50 68 6f  Y0MTBlODcx:"iPho
0x128773f00: 6e 65 20 36 22 2c 22 68 5f 63 68 22 3a 22 61 70  ne 6","h_ch":"ap
0x128773f10: 70 73 74 6f 72 65 22 2c 22 75 69 64 22 3a 39 37  pstore","uid":97
0x128773f20: 35 31 37 32 39 30 2c 22 68 5f 61 70 70 22 3a 22  517290,"h_app":"
0x128773f30: 7a 75 69 79 6f 75 5f 6c 69 74 65 22 2c 22 68 5f  zuiyou_lite","h_
0x128773f40: 74 73 22 3a 31 35 35 38 39 36 36 35 30 31 35 36  ts":155896650156
0x128773f50: 34 2c 22 68 5f 61 76 22 3a 22 34 2e 35 2e 35 22  4,"h_av":"4.5.5"
0x128773f60: 2c 22 68 5f 6e 74 22 3a 31 2c 22 68 5f 64 69 64  ,"h_nt":1,"h_did
0x128773f70: 22 3a 22 64 38 30 36 62 65 35 35 34 61 61 31 34  ":"d806be554aa14
0x128773f80: 34 63 34 37 34 64 30 35 61 63 38 61 37 66 35 36  4c474d05ac8a7f56
0x128773f90: 64 63 31 22 2c 22 68 5f 6d 22 3a 31 36 37 31 33  dc1","h_m":16713
0x128773fa0: 35 32 34 39 2c 22 68 5f 70 69 70 69 22 3a 22 31  5249,"h_pipi":"1
0x128773fb0: 2e 35 2e 35 22 2c 22 64 69 64 22 3a 22 64 38 30  .5.5","did":"d80
0x128773fc0: 36 62 65 35 35 34 61 61 31 34 34 63 34 37 34 64  6be554aa144c474d
0x128773fd0: 30 35 61 63 38 61 37 66 35 36 64 63 31 22 2c 22  05ac8a7f56dc1","
0x128773fe0: 68 5f 6f 73 22 3a 39 31 30 30 30 30 2c 22 74 6f  h_os":910000,"to
0x128773ff0: 6b 65 6e 22 3a 22 54 30 4b 35 4e 6a 64 75 79 53  ken":"T0K5NjduyS
0x128774000: 39 39 69 42 61 72 47 71 56 4c 52 62 52 51 74 68  99iBarGqVLRbRQth
0x128774010: 78 37 63 4b 45 77 46 6e 4b 5f 45 6a 45 54 6a 7a  x7cKEwFnK_EjETjz
0x128774020: 5f 58 62 53 65 54 71 58 73 61 6d 68 45 79 61 46  _XbSeTqXsamhEyaF
0x128774030: 74 48 4e 77 4c 6b 75 36 42 55 39 22 2c 22 68 5f  tHNwLku6BU9","h_
0x128774040: 64 74 22 3a 31 7d 5a 44 7b 22 68 5f 6d 6f 64 65  dt":1}ZD{"h_mode
0x128774050: 6c 22
按c 继续,查看 sub_1002003c4 返回值
(lldb) c
Process 1457 resuming
Process 1457 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
    frame #0: 0x0000000100224e34 PPSocial`_mh_execute_header + 2100788
PPSocial`_mh_execute_header:
->  0x100224e34 <+2100788>: sub    x0, x29, #0x38            ; =0x38
    0x100224e38 <+2100792>: mov    x8, sp
    0x100224e3c <+2100796>: bl     0x100224bb0               ; PPSocial.__TEXT.__text + 2079364
    0x100224e40 <+2100800>: ldrsb  w20, [sp, #0x17]
Target 0: (PPSocial) stopped.
(lldb) memory read 0x000000016fdd8ef8   
0x16fdd8ef8: 27 dd a1 ce 26 ed 16 f3 00 d5 51 93 5c 4c 10 a4  'ݡ�&�.�.�Q.\L.�
0x16fdd8f08: 76 8e 2b 21 e1 de 00 eb e8 90 dd 6f 01 00 00 00  v.+!��.��.�o....
经过抓包分析对比这个就是返回的签名数据

六.代码还原

    直接将伪代码稍微修改就可以使用,以下是关键代码
   
__int64 sub_1002003C4(__int64 a1, __int64 a2, __int64 a3)
{
    __int64 len; // x2
    __int64 buf; // x3
    signed int v5; // w5
    __int64 v6; // x20
    signed int h0; // w4
    signed int h1; // w22
    unsigned int h2; // w23
    unsigned int h3; // w21
    signed int new_len; // w26
    char *new_buf; // x1
    signed int a; // w21
    __int64 v14; // x25
    __int64 v15; // x20
    signed int v16; // w24
    __int64 i_1; // x8
    unsigned __int64 v18; // x12
    int *v19; // x13
    char *v20; // x14
    signed int a_1; // w8
    unsigned int b_1; // w9
    unsigned int c_1; // w10
    signed int d_1; // w11
    int v25; // w11
    unsigned __int64 v26; // t2
    int v27; // w8
    int v28; // w16
    int v29; // w17
    int v30; // w9
    int b; // w0
    int v32; // w16
    int v33; // w9
    int v34; // w10
    unsigned __int64 v35; // x12
    int *v36; // x13
    signed __int64 v37; // x14
    char *v38; // x15
    int v39; // w11
    unsigned __int64 v40; // t2
    int v41; // w8
    int v42; // w9
    int v43; // t1
    int v44; // w10
    unsigned __int64 v45; // x12
    int *v46; // x13
    signed __int64 v47; // x14
    char *v48; // x15
    int v49; // w11
    unsigned __int64 v50; // t2
    int v51; // w8
    int v52; // w9
    int v53; // w17
    int v54; // t1
    int v55; // t1
    int v56; // w10
    unsigned __int64 v57; // x12
    int *v58; // x13
    char *v59; // x14
    signed __int64 v60; // x15
    int v61; // w11
    unsigned __int64 v62; // t2
    int v63; // w8
    int v64; // t1
    int v65; // w9
    __int64 result; // x0
    unsigned int v67; // t1
    int v68; // w10
    __int64 i; // x8
    int v70; // w10
    __int64 v71; // x11
    unsigned __int64 v72; // x10
    __int64 v73; // [xsp+8h] [xbp-128h]
    unsigned int v74; // [xsp+28h] [xbp-108h]
    int v75; // [xsp+30h] [xbp-100h]
    unsigned int d; // [xsp+3Ch] [xbp-F4h]
    __int64 v77; // [xsp+40h] [xbp-F0h]
    char v78[64]; // [xsp+48h] [xbp-E8h]
    unsigned __int64 v79[64]; // [xsp+88h] [xbp-A8h]
    //unsigned __int64 v80; // [xsp+90h] [xbp-A0h]
    char msg[64]; // [xsp+98h] [xbp-98h]

    v73 = a3;
    len = a2;
    buf = a1;
    v5 = 0;
    v6 = 0LL;
    //v79 = 0xEFCDAB8967452301LL;
    v79[0] = *((__int64*)(__int64)buf);
    v75 = 8 * a2;
    v74 = (unsigned int)a2 >> 0x1D;
    //v80 = 0x1032547698BADCFELL;
    v79[1] = *(((__int64*)(__int64)buf) + 1);
    h0 = 0x67452301;
    h1 = 0x10325476;
    h2 = 0x98BADCFE;
    h3 = 0xEFCDAB89;
    do
    {
        if (len - v6 <= 63)
            new_len = len - v6;
        else
            new_len = 64;
        new_buf = (char *)(buf + v6);
        if (new_len > 63)
        {
            v5 = 0;
        }
        else
        {
            d = h3;
            v77 = v6;
            a = h0;
            v14 = buf;
            v15 = len;
            v16 = v5;
            memcpy(msg, new_buf, new_len);
            ZeroMemory(&msg[new_len], 64 - new_len);
            if (v16)
            {
                v5 = 1;
                new_buf = msg;
            }
            else
            {
                new_buf = msg;
                msg[new_len] = 128;
                v5 = 1;
            }
            len = v15;
            buf = v14;
            h0 = a;
            v6 = v77;
            h3 = d;
        }
        i_1 = 0LL;
        do
        {
            *(_DWORD *)&v78[i_1] = *(_DWORD *)&new_buf[i_1];
            i_1 += 4LL;
        } while (i_1 != 64);
        if (new_len <= 0x37)
        {
            *(_DWORD *)&v78[56] = v75;
            *(_DWORD *)&v78[60] = v74;
        }
        v18 = 0LL;
       // v19 = (int *)&unk_10089FE28;
        v19 = (int*)((BYTE*)&unk_10089FE20 + 0x8);
        v20 = &v78[8];
        a_1 = h1;
        b_1 = h2;
        c_1 = h3;
        d_1 = h0;
        do
        {
            v25 = (a_1 & ~c_1 | c_1 & b_1) + d_1 + *((_DWORD *)v20 - 2) + *(v19 - 2);
            HIDWORD(v26) = v25;
            LODWORD(v26) = v25;
            d_1 = (v26 >> 25) + c_1;
            v27 = *((_DWORD *)v20 - 1) + a_1 + *(v19 - 1) + (d_1 & c_1 | b_1 & ~d_1);
            HIDWORD(v26) = v27;
            LODWORD(v26) = v27;
            a_1 = (v26 >> 20) + d_1;
            v28 = *(_DWORD *)v20;
            v29 = *((_DWORD *)v20 + 1);
            v20 += 16;
            v30 = v28 + b_1;
            v32 = *v19;
            b = v19[1];
            v19 += 4;
            v33 = v30 + v32 + (a_1 & d_1 | c_1 & ~a_1);
            HIDWORD(v26) = v33;
            LODWORD(v26) = v33;
            b_1 = (v26 >> 15) + a_1;
            v34 = v29 + c_1 + b + (b_1 & a_1 | d_1 & ~b_1);
            HIDWORD(v26) = v34;
            LODWORD(v26) = v34;
            c_1 = (v26 >> 10) + b_1;
            v18 += 4LL;
        } while (v18 < 16);
        v35 = 0LL;
        //v36 = &dword_10089FE6C;
        v36 = (int*)((BYTE*)&unk_10089FE20 + 0x4C);
        v37 = 10LL;
        v38 = &v78[4];
        do
        {
            v39 = (c_1 & a_1 | b_1 & ~a_1) + d_1 + *(_DWORD *)v38 + *(v36 - 3);
            HIDWORD(v40) = v39;
            LODWORD(v40) = v39;
            d_1 = (v40 >> 27) + c_1;
            v41 = *(_DWORD *)&v78[4 * (((_BYTE)v37 - 4) & 0xE)] + a_1 + *(v36 - 2) + (d_1 & b_1 | c_1 & ~b_1);
            HIDWORD(v40) = v41;
            LODWORD(v40) = v41;
            a_1 = (v40 >> 23) + d_1;
            v42 = *(_DWORD *)&v78[8 * (((unsigned __int64)(v37 & 0xE) >> 1) & 7) | 4]
                + b_1
                + *(v36 - 1)
                + (a_1 & c_1 | d_1 & ~c_1);
            HIDWORD(v40) = v42;
            LODWORD(v40) = v42;
            b_1 = (v40 >> 18) + a_1;
            v43 = *v36;
            v36 += 4;
            v44 = *((_DWORD *)v38 - 1) + c_1 + v43 + (b_1 & d_1 | a_1 & ~d_1);
            HIDWORD(v40) = v44;
            LODWORD(v40) = v44;
            c_1 = (v40 >> 12) + b_1;
            v35 += 4LL;
            v38 += 16;
            v37 += 20LL;
        } while (v35 < 16);
        v45 = 0LL;
        //v46 = &dword_10089FEAC;
        v46 = (int*)((BYTE*)&unk_10089FE20 + 0x8C);
        v47 = 5LL;
        v48 = &v78[56];
        do
        {
            v49 = (c_1 ^ b_1 ^ a_1) + d_1 + *(_DWORD *)&v78[4 * (v47 & 0xD)] + *(v46 - 3);
            HIDWORD(v50) = v49;
            LODWORD(v50) = v49;
            d_1 = (v50 >> 28) + c_1;
            v51 = *(_DWORD *)&v78[4 * (((_BYTE)v47 + 3) & 0xC)] + a_1 + *(v46 - 2) + (d_1 ^ c_1 ^ b_1);
            HIDWORD(v50) = v51;
            LODWORD(v50) = v51;
            a_1 = (v50 >> 21) + d_1;
            v52 = *(_DWORD *)&v78[4 * (((_BYTE)v47 + 6) & 0xF)] + b_1 + *(v46 - 1) + (a_1 ^ d_1 ^ c_1);
            HIDWORD(v50) = v52;
            LODWORD(v50) = v52;
            b_1 = (v50 >> 16) + a_1;
            v54 = *(_DWORD *)v48;
            v48 -= 16;
            v53 = v54;
            v55 = *v46;
            v46 += 4;
            v56 = v53 + c_1 + v55 + (a_1 ^ d_1 ^ b_1);
            HIDWORD(v50) = v56;
            LODWORD(v50) = v56;
            c_1 = (v50 >> 9) + b_1;
            v45 += 4LL;
            v47 += 12LL;
        } while (v45 < 0x10);
        v57 = 0LL;
        //v58 = &dword_10089FEEC;
        v58 = (int*)((BYTE*)&unk_10089FE20 + 0xcc);
        v59 = &v78[56];
        v60 = 21LL;
        do
        {
            v61 = ((c_1 | ~a_1) ^ b_1) + d_1 + *(_DWORD *)&v78[4 * (((_BYTE)v60 - 21) & 0xC)] + *(v58 - 3);
            HIDWORD(v62) = v61;
            LODWORD(v62) = v61;
            d_1 = (v62 >> 26) + c_1;
            v63 = *(_DWORD *)&v78[4 * (((_BYTE)v60 - 14) & 0xF)] + a_1 + *(v58 - 2) + ((d_1 | ~b_1) ^ c_1);
            HIDWORD(v62) = v63;
            LODWORD(v62) = v63;
            a_1 = (v62 >> 22) + d_1;
            v64 = *(_DWORD *)v59;
            v59 -= 16;
            v65 = v64 + b_1 + *(v58 - 1) + ((a_1 | ~c_1) ^ d_1);
            HIDWORD(v62) = v65;
            LODWORD(v62) = v65;
            b_1 = (v62 >> 17) + a_1;
            v67 = *v58;
            v58 += 4;
            result = v67;
            v68 = *(_DWORD *)&v78[4 * (v60 & 0xD)] + c_1 + v67 + ((b_1 | ~d_1) ^ a_1);
            HIDWORD(v62) = v68;
            LODWORD(v62) = v68;
            c_1 = (v62 >> 11) + b_1;
            v57 += 4LL;
            v60 += 28LL;
        } while (v57 < 16);
        h0 += d_1;
        h3 += c_1;
        h2 += b_1;
        h1 += a_1;
        v6 += new_len;
    } while (new_len > 55);
    i = 0LL;
    v79[0] = __PAIR__(h3, h0);
    v79[1]= __PAIR__(h1, h2);
    *(_WORD *)v73 = h0;
    *(_BYTE *)(v73 + 2) = BYTE2(h0);
    *(_BYTE *)(v73 + 3) = HIBYTE(h0);
    do
    {
        v70 = *(_DWORD *)((char *)&v79 + i + 4);
        v71 = v73 + i;
        *(_WORD *)(v71 + 4) = v70;
        *(_BYTE *)(v71 + 6) = BYTE2(v70);
        *(_BYTE *)(v71 + 7) = HIBYTE(v70);
        v72 = i + 8;
        i += 4LL;
    } while (v72 < 16);
    return result;
}



void sub_100200BB0(unsigned char *buf,char* outbuf)
{
    const char *set = "0123456789abcdef";
    char  *tmp;
    tmp = outbuf;
    for (int i = 0; i < 0x10;i++)
    {
        *tmp++ = set[(*buf) >> 4];
        *tmp++ = set[(*buf) & 0xF];
        buf++;
    }
    *tmp = '\0';
}



static char * sub_100200C40(string data)
{   
   //string
    string strbuf= data;
    strbuf += byte_100E379FF;
    string strDatahead = strbuf.substr(0, 10);
    string strDataend = strbuf.substr(strbuf.length() - 10, strbuf.length());
    strbuf.replace(0, 10, strDataend);
    strbuf.replace(strbuf.length() - 10, strbuf.length(), strDatahead);
    char szsignbuf[64] = { 0 };
    //md5 
    sub_1002003C4((__int64)strbuf.c_str(), strbuf.length(), (__int64)szsignbuf);
    static char szstrbuf[64] = { 0 };
    //hextostr
    sub_100200BB0((unsigned char*)szsignbuf, szstrbuf);
    return szstrbuf;
}


void fllowclick()
{
    char* pszsign = NULL;
    string strpostdata = "{\"h_model\":\"iPhone 6\",\"h_ch\":\"appstore\",\"uid\":86743096,\"h_app\":\"zuiyou_lite\",\"h_ts\":1559223191423,\"h_av\":\"4.5.5\",\"h_nt\":1,\"h_did\":\"d806be554aa144c474d05ac8a7f56dc1\",\"h_m\":167135249,\"h_pipi\":\"1.5.5\",\"did\":\"d806be554aa144c474d05ac8a7f56dc1\",\"h_os\":910000,\"token\":\"T3KbNjduyS99iBarGqVLRbRQth40at0OX5FiZugjHhEbynKsitGYLASgxkXp44kS1TzBh\",\"h_dt\":1}";
    string strpost =  "POST /attention/add?sign=";
    pszsign = sub_100200C40(strpostdata);
    strpost += pszsign;
    strpost += " HTTP/1.1\r\n";
    strpost += "Host: api.ippzone.com\r\n";
    strpost += "App-Ver: 4.5.5\r\n";
    strpost += "Accept: */*\r\n";
    strpost += "ZYP: mid=167135249\r\n";
    strpost += "Dev-Type: ios\r\n";
    strpost += "Content-Type: application/json\r\n";
    strpost += "Accept-Language: zh-Hans-CN;q=1\r\n";
    strpost += "Accept-Encoding: gzip, deflate\r\n";
    strpost += "ZYP: mid=167135249\r\n";
    strpost += "Content-Length: 342\r\n";
    strpost += "did: d806be554aa144c474d05ac8a7f56dc1\r\n";
    strpost += "User-Agent: PPSocial/1.5.5 (iPhone; iOS 9.1; Scale/2.00)\r\n";
    strpost += "Connection: keep-alive\r\n";
    strpost += "Cookie: aliyungf_tc=AQAAAAxoX0ooZQQAVgISG16+OqOGG0RR\r\n";
    strpost += "\r\n";
    strpost += strpostdata;

    postdata(strpost);
  
}

void postdata(string data)
{
    printf(data.c_str());
    printf("\r\n");
    system("pause");
}

int main()
{
    fllowclick();

    return 0;
}

总结

    整个分析流程大概就这些了,通过界面分析到功能函数静态和动态分析,这也是在逆向中常用的思路。




Iphone6 IOS 9.1
Macos Mojave 10.14.5

三.界面分析

    通过分析app UI界面为突破口,来进行逆向分析。

    打开App后,如下图,可以看到一个关注按钮,通过点击按钮,可以实现关注该用户动态。

 

    通过抓包分析,点击关注按钮时,会发送add?sign=xxxxxx数据包,想要分析数据来源,可以从界面上的关注按钮着手.


   为了更直观分析,这里使用Reveal神器来分析UI界面,在使用Reveal前要在手机设置 > Reveal->Enabled Applications 里面打开需要分析的App.

   配置好Reveal后,在手机上打开需要分析的app界面,再在笔记本上打开Reveal工具,Reveal支持USB和网络连接两种方式,一般选择USB.

   打开Reveal后,如下图,可以看到AppUI 应用结构层级


    通过分析AppUI 应用结构层级,可以明显看到红色关注按钮,直接点击按钮,可以看到的确是一个UIButton,内存地址0x143883f80,视图控制器Class         为 LJJUSerProfileVC,内存地址0x140abb400


    为了验证数据是否正确,使用Cycript(动态调式工具),来获取运行时信息

    使用ssh登录iphone,执行命令 crcript -p pid或进程名

Davinde-iPhone:/var root# cycript -p PPSocial 
cy# #0x143883f80
#"<UIButton: 0x143883f80; frame = (270 29; 50 26); alpha = 0; opaque = NO; layer = <CALayer: 0x1438a4cc0>>"
    通过以上信息可以判断,0x143883f80的确是UIButton按钮的地址,如果不确定,可以通过调用hidden方法,并且观察app对应的按钮是否有变化.
cy# #0x143883f80.hidden=YES  //隐藏按钮
true
cy# #0x143883f80.hidden=NO //显示按钮
false
    光知道UiButton内存地址好像没有用,如何得到UiBtton的点击事件的所调用函数呢? 通过cycript 脚可以来遍历
@import mycript  //导入mycript 脚本
cy# MyBtnTouchUpEvent(#0x143883f80)
[@["followClick"]]
   下面是MyBtnTouchUpEvent代码,获取该Button对象所有的Target对象,并将事件相关所有action遍历输出,顺提一下,action可能有多个
// 获取按钮绑定的所有TouchUpInside事件的方法名
	MyBtnTouchUpEvent = function(btn) { 
		var events = [];
		var allTargets = btn.allTargets().allObjects()
		var count = allTargets.count;
    	for (var i = count - 1; i >= 0; i--) { 
    		if (btn != allTargets[i]) {
    			var e = [btn actionsForTarget:allTargets[i] forControlEvent:UIControlEventTouchUpInside];
    			events.push(e);
    		}
    	}
	   return events;
	};
     通过MyBtnTouchUpEvent得到followClick方法,需要测试下这个方法是否正确,通过调用LJJUSerProfileVC控制器的 followClick方法来测试一下
cy# [#0x140abb400 followClick] //#0x140abb400为LJJUSerProfileVC对象
    通过反复[#0x140abb400 followClick] 方法调用,可以实现手动点击关注效果,现在可以确定LJJUSerProfileVC followClick就是关注按钮的action方法.
四.静态分析

     接下来对app进行脱壳.再使用IDA加载脱壳后的Mach-o文件,恢复App符号表可参考(http://blog.imjun.net/posts/restore-symbol-of-iOS-app/)

为了方便查看,使用F5后的伪代码来进行观察   

    LJJUSerProfileVC followClick 又调用了 LJJUSerProfileVC followClick]_block 函数


    因为函数调用较多,就不一一贴图了,经过分析,以下为主要函数调用流程:

LJJUSerProfileVC followClick
    LJJUSerProfileVC attention:
        LJJAttentionManager userAttentionOther:isAttened:from:success:failure:
            ZYAPIClient postWithPath:parameters:userInfo:progress:success:retry:failure:
                ZYAPIClient postWithURL:parameters:userInfo:progress:success:retry:failure:
                    ZYURLManager requestHeaderDictionaryWithParameters:
                    ZYCrypto signWithParameters:
分析过程中发现比较重要的函数是ZYURLManager requestHeaderDictionaryWithParameters:和ZYCrypto signWithParameters:

ZYURLManager requestHeaderDictionaryWithParameters:主要功能就是h_model,u_id,h_ch,h_app,h_ts,token 等字段进行填充,其中h_ts为时间戳.

以下是ZYURLManager requestHeaderDictionaryWithParameters: 部分伪代码


ZYAPIClient signWithParameters: 从名字可以看出是对参数进行签名的,里面调用了-ZYCrypto signWithParameters:

进入-ZYCrypto signWithParameters:函数

从调用图可以看出sub_100200C40 又调用了sub_1002003C4 和 sub_100200BB0 函数

sub_100200C40 流程图


放大后更清楚

上图中,其中蓝小方块为调用sub_1002003C4 和 sub_100200BB0 函数,是整个函数必经之路

sub_1002003C4  流程图


sub_1002003C4 部分伪代码

我们知道md5算法中需要 0x67452301 0xefcdab89  0x98badcfe 0x10325476 数据做为初始值,以分组为单位进行处理,分别对每一块信息块进行MD5计算,每次计算4轮,每轮16次计算.

根据以上信息可以推断 sub_1002003C4  md5算法或其变形,不过还需要进一步分析才能确定

sub_100200BB0 伪代码



LJJUSerProfileVC followClick
 	LJJUSerProfileVC attention:
		LJJAttentionManager userAttentionOther:isAttened:from:success:failure:
			ZYAPIClient postWithPath:parameters:userInfo:progress:success:retry:failure:
				ZYAPIClient postWithURL:parameters:userInfo:progress:success:retry:failure:
					ZYURLManager requestHeaderDictionaryWithParameters:
					ZYCrypto signWithParameters:
						-ZYCrypto signWithParameters:
							sub_100200C40
								sub_1002003C4
								sub_100200BB0

小结一下

ZYURLManager requestHeaderDictionaryWithParameters: 将需要提交的数据进行字段进行填充,大部分都是都是固定字段的如h_model,u_id,h_ch,h_app,h_ts,token等字段,这里就不做详细分析。

ZYCrypto signWithParameters 调用sub_100200C40 函数将数据进行签名.

五.动态调试

   为了验证推测是否正确,需要对app进行动态调试。

iphone ssh 登录,开启 debugserver 。 

Davinde-iPhone:~ root# debugserver *:10011 -a PPSocial
debugserver-@(#)PROGRAM:debugserver  PROJECT:debugserver-340.3.51.1
 for arm64.
Attaching to process PPSocial...
Listening to port 10011 for a connection from *...
Waiting for debugger instructions for process 0.
   mac lldb 附加
process connect connect://192.168.31.199:10011
  获取ASLR偏移
(lldb) image list -o -f PPSocial
[  0] 0x0000000000024000 /var/mobile/Containers/Bundle/Application/C3170E24-F5A3-4FD8-AB42-532DA856722E/PPSocial.app/PPSocial(0x0000000100024000)
 函数的内存地址(VM Address ) = File Offset + ASLR Offset  IDA  中的地址是未使用ASLR的VM Address.

对关键的函数下断点,查看其参数和返回值

sub_1002003C4 函数头地址为0x100200c40 + 0x24000 = 0x100224c40

(lldb) br s -a 0x100224c40
Breakpoint 1: where = PPSocial`_mh_execute_header + 2079508, address = 0x0000000100224c40
成功下断后,运行app,点击关注。
  
Process 1457 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100224c40 PPSocial`_mh_execute_header + 2100288
PPSocial`_mh_execute_header:
->  0x100224c40 <+2100288>: sub    sp, sp, #0x90             ; =0x90
    0x100224c44 <+2100292>: stp    x22, x21, [sp, #0x60]
    0x100224c48 <+2100296>: stp    x20, x19, [sp, #0x70]
    0x100224c4c <+2100300>: stp    x29, x30, [sp, #0x80]
    
    (lldb) memory read $x0 $x0+400
0x12876f268: 7b 22 68 5f 6d 6f 64 65 6c 22 3a 22 69 50 68 6f  {"h_model":"iPho
0x12876f278: 6e 65 20 36 22 2c 22 68 5f 63 68 22 3a 22 61 70  ne 6","h_ch":"ap
0x12876f288: 70 73 74 6f 72 65 22 2c 22 75 69 64 22 3a 39 37  pstore","uid":97
0x12876f298: 35 31 37 32 39 30 2c 22 68 5f 61 70 70 22 3a 22  517290,"h_app":"
0x12876f2a8: 7a 75 69 79 6f 75 5f 6c 69 74 65 22 2c 22 68 5f  zuiyou_lite","h_
0x12876f2b8: 74 73 22 3a 31 35 35 38 39 36 36 35 30 31 35 36  ts":155896650156
0x12876f2c8: 34 2c 22 68 5f 61 76 22 3a 22 34 2e 35 2e 35 22  4,"h_av":"4.5.5"
0x12876f2d8: 2c 22 68 5f 6e 74 22 3a 31 2c 22 68 5f 64 69 64  ,"h_nt":1,"h_did
0x12876f2e8: 22 3a 22 64 38 30 36 62 65 35 35 34 61 61 31 34  ":"d806be554aa14
0x12876f2f8: 34 63 34 37 34 64 30 35 61 63 38 61 37 66 35 36  4c474d05ac8a7f56
0x12876f308: 64 63 31 22 2c 22 68 5f 6d 22 3a 31 36 37 31 33  dc1","h_m":16713
0x12876f318: 35 32 34 39 2c 22 68 5f 70 69 70 69 22 3a 22 31  5249,"h_pipi":"1
0x12876f328: 2e 35 2e 35 22 2c 22 64 69 64 22 3a 22 64 38 30  .5.5","did":"d80
0x12876f338: 36 62 65 35 35 34 61 61 31 34 34 63 34 37 34 64  6be554aa144c474d
0x12876f348: 30 35 61 63 38 61 37 66 35 36 64 63 31 22 2c 22  05ac8a7f56dc1","
0x12876f358: 68 5f 6f 73 22 3a 39 31 30 30 30 30 2c 22 74 6f  h_os":910000,"to
0x12876f368: 6b 65 6e 22 3a 22 54 30 4b 35 4e 6a 64 75 79 53  ken":"T0K5NjduyS
0x12876f378: 39 39 69 42 61 72 47 71 56 4c 52 62 52 51 74 68  99iBarGqVLRbRQth
0x12876f388: 78 37 63 4b 45 77 46 6e 4b 5f 45 6a 45 54 6a 7a  x7cKEwFnK_EjETjz
0x12876f398: 5f 58 62 53 65 54 71 58 73 61 6d 68 45 79 61 46  _XbSeTqXsamhEyaF
0x12876f3a8: 74 48 4e 77 4c 6b 75 36 42 55 39 22 2c 22 68 5f  tHNwLku6BU9","h_
0x12876f3b8: 64 74 22 3a 31 7d 00 00 00 00 00 00 00 00 00 e0  dt":1}.........�
0x12876f3c8: 00 00 00 00 00 00 00 e0 08 00 ff ff f5 01 00 00  .......�..���...
0x12876f3d8: f5 01 00 00 f5 01 00 00 f5 01 00 00 61 00 00 00  �...�...�...a...
0x12876f3e8: 00 00 00 00 62 00 00 00 70 b5 0d 28 01 00 00 00  ....b...p�.(....
(lldb)

查看参数可以看到就是post字符串数据.接下来再分析sub_1002003c4参数和返回值

分别对sub_1002003c4 入口和返回后地址下断

(lldb) br s -a 0x100224e30
Breakpoint 2: where = PPSocial`_mh_execute_header + 2080004, address = 0x0000000100224e30
(lldb) br s -a 0x100224e34
Breakpoint 3: where = PPSocial`_mh_execute_header + 2080008, address = 0x0000000100224e34
sub_1002003c4 有三个参数分别为 x0,x1,x2 
->  0x100224e30 <+2100784>: bl     0x1002243c4               ; PPSocial.__TEXT.__text + 2077336
    0x100224e34 <+2100788>: sub    x0, x29, #0x38            ; =0x38
    0x100224e38 <+2100792>: mov    x8, sp
    0x100224e3c <+2100796>: bl     0x100224bb0               ; PPSocial.__TEXT.__text + 2079364
Target 0: (PPSocial) stopped.
(lldb) register read
General Purpose Registers:
        x0 = 0x0000000128773ef0
        x1 = 0x0000000000000162
        x2 = 0x000000016fdd8ef8
        x3 = 0x0000000000000016
        x4 = 0x0000000000000158
        x5 = 0x0000000000000010
        x6 = 0x0000000000000022
        x7 = 0x000000016fdd8eb0
        x8 = 0x0000000000000080
x0为字符串数据,x1字符串长度,x2传出参数,字符串头尾进行处理.
lldb) memory read $x0 $x0+$x1
0x128773ef0: 59 30 4d 54 42 6c 4f 44 63 78 3a 22 69 50 68 6f  Y0MTBlODcx:"iPho
0x128773f00: 6e 65 20 36 22 2c 22 68 5f 63 68 22 3a 22 61 70  ne 6","h_ch":"ap
0x128773f10: 70 73 74 6f 72 65 22 2c 22 75 69 64 22 3a 39 37  pstore","uid":97
0x128773f20: 35 31 37 32 39 30 2c 22 68 5f 61 70 70 22 3a 22  517290,"h_app":"
0x128773f30: 7a 75 69 79 6f 75 5f 6c 69 74 65 22 2c 22 68 5f  zuiyou_lite","h_
0x128773f40: 74 73 22 3a 31 35 35 38 39 36 36 35 30 31 35 36  ts":155896650156
0x128773f50: 34 2c 22 68 5f 61 76 22 3a 22 34 2e 35 2e 35 22  4,"h_av":"4.5.5"
0x128773f60: 2c 22 68 5f 6e 74 22 3a 31 2c 22 68 5f 64 69 64  ,"h_nt":1,"h_did
0x128773f70: 22 3a 22 64 38 30 36 62 65 35 35 34 61 61 31 34  ":"d806be554aa14
0x128773f80: 34 63 34 37 34 64 30 35 61 63 38 61 37 66 35 36  4c474d05ac8a7f56
0x128773f90: 64 63 31 22 2c 22 68 5f 6d 22 3a 31 36 37 31 33  dc1","h_m":16713
0x128773fa0: 35 32 34 39 2c 22 68 5f 70 69 70 69 22 3a 22 31  5249,"h_pipi":"1
0x128773fb0: 2e 35 2e 35 22 2c 22 64 69 64 22 3a 22 64 38 30  .5.5","did":"d80
0x128773fc0: 36 62 65 35 35 34 61 61 31 34 34 63 34 37 34 64  6be554aa144c474d
0x128773fd0: 30 35 61 63 38 61 37 66 35 36 64 63 31 22 2c 22  05ac8a7f56dc1","
0x128773fe0: 68 5f 6f 73 22 3a 39 31 30 30 30 30 2c 22 74 6f  h_os":910000,"to
0x128773ff0: 6b 65 6e 22 3a 22 54 30 4b 35 4e 6a 64 75 79 53  ken":"T0K5NjduyS
0x128774000: 39 39 69 42 61 72 47 71 56 4c 52 62 52 51 74 68  99iBarGqVLRbRQth
0x128774010: 78 37 63 4b 45 77 46 6e 4b 5f 45 6a 45 54 6a 7a  x7cKEwFnK_EjETjz
0x128774020: 5f 58 62 53 65 54 71 58 73 61 6d 68 45 79 61 46  _XbSeTqXsamhEyaF
0x128774030: 74 48 4e 77 4c 6b 75 36 42 55 39 22 2c 22 68 5f  tHNwLku6BU9","h_
0x128774040: 64 74 22 3a 31 7d 5a 44 7b 22 68 5f 6d 6f 64 65  dt":1}ZD{"h_mode
0x128774050: 6c 22
按c 继续,查看 sub_1002003c4 返回值
(lldb) c
Process 1457 resuming
Process 1457 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
    frame #0: 0x0000000100224e34 PPSocial`_mh_execute_header + 2100788
PPSocial`_mh_execute_header:
->  0x100224e34 <+2100788>: sub    x0, x29, #0x38            ; =0x38
    0x100224e38 <+2100792>: mov    x8, sp
    0x100224e3c <+2100796>: bl     0x100224bb0               ; PPSocial.__TEXT.__text + 2079364
    0x100224e40 <+2100800>: ldrsb  w20, [sp, #0x17]
Target 0: (PPSocial) stopped.
(lldb) memory read 0x000000016fdd8ef8   
0x16fdd8ef8: 27 dd a1 ce 26 ed 16 f3 00 d5 51 93 5c 4c 10 a4  'ݡ�&�.�.�Q.\L.�
0x16fdd8f08: 76 8e 2b 21 e1 de 00 eb e8 90 dd 6f 01 00 00 00  v.+!��.��.�o....
经过抓包分析对比这个就是返回的签名数据

六.代码还原

    直接将伪代码稍微修改就可以使用,以下是关键代码
   
__int64 sub_1002003C4(__int64 a1, __int64 a2, __int64 a3)
{
    __int64 len; // x2
    __int64 buf; // x3
    signed int v5; // w5
    __int64 v6; // x20
    signed int h0; // w4
    signed int h1; // w22
    unsigned int h2; // w23
    unsigned int h3; // w21
    signed int new_len; // w26
    char *new_buf; // x1
    signed int a; // w21
    __int64 v14; // x25
    __int64 v15; // x20
    signed int v16; // w24
    __int64 i_1; // x8
    unsigned __int64 v18; // x12
    int *v19; // x13
    char *v20; // x14
    signed int a_1; // w8
    unsigned int b_1; // w9
    unsigned int c_1; // w10
    signed int d_1; // w11
    int v25; // w11
    unsigned __int64 v26; // t2
    int v27; // w8
    int v28; // w16
    int v29; // w17
    int v30; // w9
    int b; // w0
    int v32; // w16
    int v33; // w9
    int v34; // w10
    unsigned __int64 v35; // x12
    int *v36; // x13
    signed __int64 v37; // x14
    char *v38; // x15
    int v39; // w11
    unsigned __int64 v40; // t2
    int v41; // w8
    int v42; // w9
    int v43; // t1
    int v44; // w10
    unsigned __int64 v45; // x12
    int *v46; // x13
    signed __int64 v47; // x14
    char *v48; // x15
    int v49; // w11
    unsigned __int64 v50; // t2
    int v51; // w8
    int v52; // w9
    int v53; // w17
    int v54; // t1
    int v55; // t1
    int v56; // w10
    unsigned __int64 v57; // x12
    int *v58; // x13
    char *v59; // x14
    signed __int64 v60; // x15
    int v61; // w11
    unsigned __int64 v62; // t2
    int v63; // w8
    int v64; // t1
    int v65; // w9
    __int64 result; // x0
    unsigned int v67; // t1
    int v68; // w10
    __int64 i; // x8
    int v70; // w10
    __int64 v71; // x11
    unsigned __int64 v72; // x10
    __int64 v73; // [xsp+8h] [xbp-128h]
    unsigned int v74; // [xsp+28h] [xbp-108h]
    int v75; // [xsp+30h] [xbp-100h]
    unsigned int d; // [xsp+3Ch] [xbp-F4h]
    __int64 v77; // [xsp+40h] [xbp-F0h]
    char v78[64]; // [xsp+48h] [xbp-E8h]
    unsigned __int64 v79[64]; // [xsp+88h] [xbp-A8h]
    //unsigned __int64 v80; // [xsp+90h] [xbp-A0h]
    char msg[64]; // [xsp+98h] [xbp-98h]

    v73 = a3;
    len = a2;
    buf = a1;
    v5 = 0;
    v6 = 0LL;
    //v79 = 0xEFCDAB8967452301LL;
    v79[0] = *((__int64*)(__int64)buf);
    v75 = 8 * a2;
    v74 = (unsigned int)a2 >> 0x1D;
    //v80 = 0x1032547698BADCFELL;
    v79[1] = *(((__int64*)(__int64)buf) + 1);
    h0 = 0x67452301;
    h1 = 0x10325476;
    h2 = 0x98BADCFE;
    h3 = 0xEFCDAB89;
    do
    {
        if (len - v6 <= 63)
            new_len = len - v6;
        else
            new_len = 64;
        new_buf = (char *)(buf + v6);
        if (new_len > 63)
        {
            v5 = 0;
        }
        else
        {
            d = h3;
            v77 = v6;
            a = h0;
            v14 = buf;
            v15 = len;
            v16 = v5;
            memcpy(msg, new_buf, new_len);
            ZeroMemory(&msg[new_len], 64 - new_len);
            if (v16)
            {
                v5 = 1;
                new_buf = msg;
            }
            else
            {
                new_buf = msg;
                msg[new_len] = 128;
                v5 = 1;
            }
            len = v15;
            buf = v14;
            h0 = a;
            v6 = v77;
            h3 = d;
        }
        i_1 = 0LL;
        do
        {
            *(_DWORD *)&v78[i_1] = *(_DWORD *)&new_buf[i_1];
            i_1 += 4LL;
        } while (i_1 != 64);
        if (new_len <= 0x37)
        {
            *(_DWORD *)&v78[56] = v75;
            *(_DWORD *)&v78[60] = v74;
        }
        v18 = 0LL;
       // v19 = (int *)&unk_10089FE28;
        v19 = (int*)((BYTE*)&unk_10089FE20 + 0x8);
        v20 = &v78[8];
        a_1 = h1;
        b_1 = h2;
        c_1 = h3;
        d_1 = h0;
        do
        {
            v25 = (a_1 & ~c_1 | c_1 & b_1) + d_1 + *((_DWORD *)v20 - 2) + *(v19 - 2);
            HIDWORD(v26) = v25;
            LODWORD(v26) = v25;
            d_1 = (v26 >> 25) + c_1;
            v27 = *((_DWORD *)v20 - 1) + a_1 + *(v19 - 1) + (d_1 & c_1 | b_1 & ~d_1);
            HIDWORD(v26) = v27;
            LODWORD(v26) = v27;
            a_1 = (v26 >> 20) + d_1;
            v28 = *(_DWORD *)v20;
            v29 = *((_DWORD *)v20 + 1);
            v20 += 16;
            v30 = v28 + b_1;
            v32 = *v19;
            b = v19[1];
            v19 += 4;
            v33 = v30 + v32 + (a_1 & d_1 | c_1 & ~a_1);
            HIDWORD(v26) = v33;
            LODWORD(v26) = v33;
            b_1 = (v26 >> 15) + a_1;
            v34 = v29 + c_1 + b + (b_1 & a_1 | d_1 & ~b_1);
            HIDWORD(v26) = v34;
            LODWORD(v26) = v34;
            c_1 = (v26 >> 10) + b_1;
            v18 += 4LL;
        } while (v18 < 16);
        v35 = 0LL;
        //v36 = &dword_10089FE6C;
        v36 = (int*)((BYTE*)&unk_10089FE20 + 0x4C);
        v37 = 10LL;
        v38 = &v78[4];
        do
        {
            v39 = (c_1 & a_1 | b_1 & ~a_1) + d_1 + *(_DWORD *)v38 + *(v36 - 3);
            HIDWORD(v40) = v39;
            LODWORD(v40) = v39;
            d_1 = (v40 >> 27) + c_1;
            v41 = *(_DWORD *)&v78[4 * (((_BYTE)v37 - 4) & 0xE)] + a_1 + *(v36 - 2) + (d_1 & b_1 | c_1 & ~b_1);
            HIDWORD(v40) = v41;
            LODWORD(v40) = v41;
            a_1 = (v40 >> 23) + d_1;
            v42 = *(_DWORD *)&v78[8 * (((unsigned __int64)(v37 & 0xE) >> 1) & 7) | 4]
                + b_1
                + *(v36 - 1)
                + (a_1 & c_1 | d_1 & ~c_1);
            HIDWORD(v40) = v42;
            LODWORD(v40) = v42;
            b_1 = (v40 >> 18) + a_1;
            v43 = *v36;
            v36 += 4;
            v44 = *((_DWORD *)v38 - 1) + c_1 + v43 + (b_1 & d_1 | a_1 & ~d_1);
            HIDWORD(v40) = v44;
            LODWORD(v40) = v44;
            c_1 = (v40 >> 12) + b_1;
            v35 += 4LL;
            v38 += 16;
            v37 += 20LL;
        } while (v35 < 16);
        v45 = 0LL;
        //v46 = &dword_10089FEAC;
        v46 = (int*)((BYTE*)&unk_10089FE20 + 0x8C);
        v47 = 5LL;
        v48 = &v78[56];
        do
        {
            v49 = (c_1 ^ b_1 ^ a_1) + d_1 + *(_DWORD *)&v78[4 * (v47 & 0xD)] + *(v46 - 3);
            HIDWORD(v50) = v49;
            LODWORD(v50) = v49;
            d_1 = (v50 >> 28) + c_1;
            v51 = *(_DWORD *)&v78[4 * (((_BYTE)v47 + 3) & 0xC)] + a_1 + *(v46 - 2) + (d_1 ^ c_1 ^ b_1);
            HIDWORD(v50) = v51;
            LODWORD(v50) = v51;
            a_1 = (v50 >> 21) + d_1;
            v52 = *(_DWORD *)&v78[4 * (((_BYTE)v47 + 6) & 0xF)] + b_1 + *(v46 - 1) + (a_1 ^ d_1 ^ c_1);
            HIDWORD(v50) = v52;
            LODWORD(v50) = v52;
            b_1 = (v50 >> 16) + a_1;
            v54 = *(_DWORD *)v48;
            v48 -= 16;
            v53 = v54;
            v55 = *v46;
            v46 += 4;
            v56 = v53 + c_1 + v55 + (a_1 ^ d_1 ^ b_1);
            HIDWORD(v50) = v56;
            LODWORD(v50) = v56;
            c_1 = (v50 >> 9) + b_1;
            v45 += 4LL;
            v47 += 12LL;
        } while (v45 < 0x10);
        v57 = 0LL;
        //v58 = &dword_10089FEEC;
        v58 = (int*)((BYTE*)&unk_10089FE20 + 0xcc);
        v59 = &v78[56];
        v60 = 21LL;
        do
        {
            v61 = ((c_1 | ~a_1) ^ b_1) + d_1 + *(_DWORD *)&v78[4 * (((_BYTE)v60 - 21) & 0xC)] + *(v58 - 3);
            HIDWORD(v62) = v61;
            LODWORD(v62) = v61;
            d_1 = (v62 >> 26) + c_1;
            v63 = *(_DWORD *)&v78[4 * (((_BYTE)v60 - 14) & 0xF)] + a_1 + *(v58 - 2) + ((d_1 | ~b_1) ^ c_1);
            HIDWORD(v62) = v63;
            LODWORD(v62) = v63;
            a_1 = (v62 >> 22) + d_1;
            v64 = *(_DWORD *)v59;
            v59 -= 16;
            v65 = v64 + b_1 + *(v58 - 1) + ((a_1 | ~c_1) ^ d_1);
            HIDWORD(v62) = v65;
            LODWORD(v62) = v65;
            b_1 = (v62 >> 17) + a_1;
            v67 = *v58;
            v58 += 4;
            result = v67;
            v68 = *(_DWORD *)&v78[4 * (v60 & 0xD)] + c_1 + v67 + ((b_1 | ~d_1) ^ a_1);
            HIDWORD(v62) = v68;
            LODWORD(v62) = v68;
            c_1 = (v62 >> 11) + b_1;
            v57 += 4LL;
            v60 += 28LL;
        } while (v57 < 16);
        h0 += d_1;
        h3 += c_1;
        h2 += b_1;
        h1 += a_1;
        v6 += new_len;
    } while (new_len > 55);
    i = 0LL;
    v79[0] = __PAIR__(h3, h0);
    v79[1]= __PAIR__(h1, h2);
    *(_WORD *)v73 = h0;
    *(_BYTE *)(v73 + 2) = BYTE2(h0);
    *(_BYTE *)(v73 + 3) = HIBYTE(h0);
    do
    {
        v70 = *(_DWORD *)((char *)&v79 + i + 4);
        v71 = v73 + i;
        *(_WORD *)(v71 + 4) = v70;
        *(_BYTE *)(v71 + 6) = BYTE2(v70);
        *(_BYTE *)(v71 + 7) = HIBYTE(v70);
        v72 = i + 8;
        i += 4LL;
    } while (v72 < 16);
    return result;
}



void sub_100200BB0(unsigned char *buf,char* outbuf)
{
    const char *set = "0123456789abcdef";
    char  *tmp;
    tmp = outbuf;
    for (int i = 0; i < 0x10;i++)
    {
        *tmp++ = set[(*buf) >> 4];
        *tmp++ = set[(*buf) & 0xF];
        buf++;
    }
    *tmp = '\0';
}



static char * sub_100200C40(string data)
{   
   //string
    string strbuf= data;
    strbuf += byte_100E379FF;
    string strDatahead = strbuf.substr(0, 10);
    string strDataend = strbuf.substr(strbuf.length() - 10, strbuf.length());
    strbuf.replace(0, 10, strDataend);
    strbuf.replace(strbuf.length() - 10, strbuf.length(), strDatahead);
    char szsignbuf[64] = { 0 };
    //md5 
    sub_1002003C4((__int64)strbuf.c_str(), strbuf.length(), (__int64)szsignbuf);
    static char szstrbuf[64] = { 0 };
    //hextostr
    sub_100200BB0((unsigned char*)szsignbuf, szstrbuf);
    return szstrbuf;
}


void fllowclick()
{
    char* pszsign = NULL;
    string strpostdata = "{\"h_model\":\"iPhone 6\",\"h_ch\":\"appstore\",\"uid\":86743096,\"h_app\":\"zuiyou_lite\",\"h_ts\":1559223191423,\"h_av\":\"4.5.5\",\"h_nt\":1,\"h_did\":\"d806be554aa144c474d05ac8a7f56dc1\",\"h_m\":167135249,\"h_pipi\":\"1.5.5\",\"did\":\"d806be554aa144c474d05ac8a7f56dc1\",\"h_os\":910000,\"token\":\"T3KbNjduyS99iBarGqVLRbRQth40at0OX5FiZugjHhEbynKsitGYLASgxkXp44kS1TzBh\",\"h_dt\":1}";
    string strpost =  "POST /attention/add?sign=";
    pszsign = sub_100200C40(strpostdata);
    strpost += pszsign;
    strpost += " HTTP/1.1\r\n";
    strpost += "Host: api.ippzone.com\r\n";
    strpost += "App-Ver: 4.5.5\r\n";
    strpost += "Accept: */*\r\n";
    strpost += "ZYP: mid=167135249\r\n";
    strpost += "Dev-Type: ios\r\n";
    strpost += "Content-Type: application/json\r\n";
    strpost += "Accept-Language: zh-Hans-CN;q=1\r\n";
    strpost += "Accept-Encoding: gzip, deflate\r\n";
    strpost += "ZYP: mid=167135249\r\n";
    strpost += "Content-Length: 342\r\n";
    strpost += "did: d806be554aa144c474d05ac8a7f56dc1\r\n";
    strpost += "User-Agent: PPSocial/1.5.5 (iPhone; iOS 9.1; Scale/2.00)\r\n";
    strpost += "Connection: keep-alive\r\n";
    strpost += "Cookie: aliyungf_tc=AQAAAAxoX0ooZQQAVgISG16+OqOGG0RR\r\n";
    strpost += "\r\n";
    strpost += strpostdata;

    postdata(strpost);
  
}

void postdata(string data)
{
    printf(data.c_str());
    printf("\r\n");
    system("pause");
}

int main()
{
    fllowclick();

    return 0;
}

总结

    整个分析流程大概就这些了,通过界面分析到功能函数静态和动态分析,这也是在逆向中常用的思路。




Davinde-iPhone:/var root# cycript -p PPSocial 
cy# #0x143883f80
#"<UIButton: 0x143883f80; frame = (270 29; 50 26); alpha = 0; opaque = NO; layer = <CALayer: 0x1438a4cc0>>"
    通过以上信息可以判断,0x143883f80的确是UIButton按钮的地址,如果不确定,可以通过调用hidden方法,并且观察app对应的按钮是否有变化.
cy# #0x143883f80.hidden=YES  //隐藏按钮
true
cy# #0x143883f80.hidden=NO //显示按钮
false
    光知道UiButton内存地址好像没有用,如何得到UiBtton的点击事件的所调用函数呢? 通过cycript 脚可以来遍历
cy# #0x143883f80.hidden=YES  //隐藏按钮
true
cy# #0x143883f80.hidden=NO //显示按钮
false
    光知道UiButton内存地址好像没有用,如何得到UiBtton的点击事件的所调用函数呢? 通过cycript 脚可以来遍历
@import mycript  //导入mycript 脚本
cy# MyBtnTouchUpEvent(#0x143883f80)
[@["followClick"]]
   下面是MyBtnTouchUpEvent代码,获取该Button对象所有的Target对象,并将事件相关所有action遍历输出,顺提一下,action可能有多个
@import mycript  //导入mycript 脚本
cy# MyBtnTouchUpEvent(#0x143883f80)
[@["followClick"]]
   下面是MyBtnTouchUpEvent代码,获取该Button对象所有的Target对象,并将事件相关所有action遍历输出,顺提一下,action可能有多个
// 获取按钮绑定的所有TouchUpInside事件的方法名
	MyBtnTouchUpEvent = function(btn) { 
		var events = [];
		var allTargets = btn.allTargets().allObjects()
		var count = allTargets.count;
    	for (var i = count - 1; i >= 0; i--) { 
    		if (btn != allTargets[i]) {
    			var e = [btn actionsForTarget:allTargets[i] forControlEvent:UIControlEventTouchUpInside];
    			events.push(e);
    		}
    	}
	   return events;
	};
     通过MyBtnTouchUpEvent得到followClick方法,需要测试下这个方法是否正确,通过调用LJJUSerProfileVC控制器的 followClick方法来测试一下
// 获取按钮绑定的所有TouchUpInside事件的方法名
	MyBtnTouchUpEvent = function(btn) { 
		var events = [];
		var allTargets = btn.allTargets().allObjects()
		var count = allTargets.count;
    	for (var i = count - 1; i >= 0; i--) { 
    		if (btn != allTargets[i]) {
    			var e = [btn actionsForTarget:allTargets[i] forControlEvent:UIControlEventTouchUpInside];
    			events.push(e);
    		}
    	}
	   return events;
	};
     通过MyBtnTouchUpEvent得到followClick方法,需要测试下这个方法是否正确,通过调用LJJUSerProfileVC控制器的 followClick方法来测试一下
cy# [#0x140abb400 followClick] //#0x140abb400为LJJUSerProfileVC对象
    通过反复[#0x140abb400 followClick] 方法调用,可以实现手动点击关注效果,现在可以确定LJJUSerProfileVC followClick就是关注按钮的action方法.
cy# [#0x140abb400 followClick] //#0x140abb400为LJJUSerProfileVC对象
    通过反复[#0x140abb400 followClick] 方法调用,可以实现手动点击关注效果,现在可以确定LJJUSerProfileVC followClick就是关注按钮的action方法.
四.静态分析
LJJUSerProfileVC followClick
    LJJUSerProfileVC attention:
        LJJAttentionManager userAttentionOther:isAttened:from:success:failure:
            ZYAPIClient postWithPath:parameters:userInfo:progress:success:retry:failure:
                ZYAPIClient postWithURL:parameters:userInfo:progress:success:retry:failure:
                    ZYURLManager requestHeaderDictionaryWithParameters:
                    ZYCrypto signWithParameters:
分析过程中发现比较重要的函数是ZYURLManager requestHeaderDictionaryWithParameters:和ZYCrypto signWithParameters:

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 3
支持
分享
最新回复 (12)
雪    币: 2375
活跃值: (433)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
搞ios安全的人真少啊
2019-6-3 01:15
0
雪    币: 3496
活跃值: (749)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
看不太明白,reveal,app脱壳,Ida这些工具有下载吗,咋脱壳啊,没法实践
2019-6-3 05:20
0
雪    币: 5482
活跃值: (3272)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
4
666
2019-6-3 09:29
0
雪    币: 140
活跃值: (125)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
5
总结一下,1.找按钮+抓包确认切入点,2.IDA/Hope静态分析确认函数流程,3.测试,基本上IOS都是这个思路。
2019-6-3 09:45
0
雪    币: 2719
活跃值: (1595)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
6
找到vc 然后全局hook函数  再断点调试?
2019-6-3 13:03
0
雪    币: 216
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
能不能留个联系方式。。
2019-6-17 21:33
0
雪    币: 958
活跃值: (174)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
kxzpy 看不太明白,reveal,app脱壳,Ida这些工具有下载吗,咋脱壳啊,没法实践
这些工具网上都有下载,也有详细使用说明。
2019-6-18 23:35
0
雪    币: 958
活跃值: (174)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
10
karlx 总结一下,1.找按钮+抓包确认切入点,2.IDA/Hope静态分析确认函数流程,3.测试,基本上IOS都是这个思路。
是的,大佬
2019-6-18 23:37
0
雪    币: 958
活跃值: (174)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
11
亿乐社区系统 能不能留个联系方式。。
有什么问题,也可以私信我
2019-6-18 23:40
0
雪    币: 216
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
私信不了,你加下我企业~30944032~有事找你谈下,。
2019-6-19 22:07
0
雪    币: 269
活跃值: (906)
能力值: ( LV12,RANK:345 )
在线值:
发帖
回帖
粉丝
13
遇到个尴尬的情况,通过proxy转发端口ssh连接的,通过 reveal获取到应用ui和按钮的内存地址后,命令行会卡住,也就是说无法再通过cycripy附加进程验证。除非应用退出,命令行就能用了,但是这时内存地址就失效了。。。
2020-4-30 19:18
0
游客
登录 | 注册 方可回帖
返回
//