个人感觉这个分析用的手段比较典型,所以记录下。个人感觉ios有些封装特点在逆向过程中还是要有一定的认识的,不然就真是分析半天头疼的不行了。
这一部分大致说一下,大致看了以了一下,没有看到跟登录的手机号和密码相关的关键字。应该是加密了,倒是path路径有个passport-sign-mobile/v2的网址路径,不过这是后话,开始的时候没看到这里。不过个人感觉从这里入手,应该也不会太容易,不过应该也可行。
既然抓不出来啥,我就分析下登录界面
0x1308a2410; frame = (0 0; 320 460); gestureRecognizers = 0x1307a3dc0>; layer = 0x13084e910>>
| | | | | | | | 0x13087aa00; frame = (40 30; 240 44); text = '18104849732'; clipsToBounds = YES; opaque = NO; gestureRecognizers = ; layer = >
| | | | | | | | | 0x13099c850; frame = (0 2; 105 40); layer = 0x1305373d0>>
| | | | | | | | | | 0x130742b60; frame = (0 0; 80 40); opaque = NO; tag = 100; layer = 0x130585e80>>
| | | | | | | | | | | 0x130868ce0; frame = (60 13; 14 14); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = 0x1308ae3a0>>
| | | | | | | | | | | 0x130797e60; frame = (17 10.5; 30 19.5); text = '+86'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x13058bc90>>
| | | | | | | | | | 0x13092f0e0; frame = (90 10; 1 20); layer = 0x130958750>>
| | | | | | | | | 0x1306a4380; frame = (105 0; 135 42.5); text = '18104849732'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x130833700>>
| | | | | | | | 0x1309865c0; frame = (40 89; 240 44); text = 'fyg'; clipsToBounds = YES; opaque = NO; gestureRecognizers = ; layer = >
| | | | | | | | | 0x1302f5290; frame = (0 7; 30 30); opaque = NO; userInteractionEnabled = NO; layer = 0x1306b43d0>>
| | | | | | | | | 0x130936840; frame = (30 0; 210 42.5); text = '●●●'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x13075aef0>>
| | | | | | | | | | <_UILabelContentLayer: 0x13092a690> (layer)
| | | | | | | | 0x130889bd0; frame = (40 148; 240 40); opaque = NO; layer = 0x13084ffb0>>
| | | | | | | | | 0x1302f7920; frame = (104 10.5; 32 19.5); text = '登录'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x13063f460>>
| | | | | | | | | | <_UILabelContentLayer: 0x13058d3d0> (layer)
| | | | | | | | 0x1307fb070; frame = (220 194; 60 27); opaque = NO; layer = 0x13095da50>>
| | | | | | | | | 0x1307dbbb0; frame = (0 6.5; 60 14.5); text = '忘记密码?'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x13098ae30>>
| | | | | | | | | | <_UILabelContentLayer: 0x13075bed0> (layer)
| | | | | | | | 0x1309ae010; frame = (30 261; 260 20); layer = 0x13075c690>>
| | | | | | | | | 0x1305a3600; frame = (88 0; 84 20); text = '其他方式登录'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x13091a880>>
| | | |
我这里用的工具是Frida写的一个界面的工具,为什么?因为这个东西,支持中文格式,很好用。当然如果用不惯可以用 cycript 或reveal等。
至于frida工具,可以参看我的博客ios逆向神器frida。
这里我们找到“登录”关键字,看到它所属的类XMRegisterAndLoginView。可以看到0x1308a2410是他的地址。
验证下
#cycript -p 程序名称
#[#0x1302f7920 setHidden:YES]
登录标签消失了。说明我们分析正确。
这里重点分析XMRegisterAndLoginView
-[XMRegisterAndLoginView dealloc] __text 000000010023E5B4 000000A4 R . . . B T .
-[XMRegisterAndLoginView initWithFrame:] __text 000000010023E658 000000A8 R . . . B T .
-[XMRegisterAndLoginView layoutSubviews] __text 000000010023E700 000008B4 R . . . B T .
-[XMRegisterAndLoginView customInit] __text 000000010023EFB4 00001C5C R . . . B T .
-[XMRegisterAndLoginView resignTextfieldAllResponder] __text 0000000100240C10 0000007C R . . . B T .
-[XMRegisterAndLoginView becomeSelected] __text 0000000100240C8C 00000080 R . . . B T .
-[XMRegisterAndLoginView tapPressed] __text 0000000100240D0C 0000000C R . . . . T .
-[XMRegisterAndLoginView registerButtonPressed] __text 0000000100240D18 000000B8 R . . . B T .
-[XMRegisterAndLoginView loginButtonPressed] __text 0000000100240DD0 000000B8 R . . . B T .
-[XMRegisterAndLoginView forgetPasswordButtonPressed] __text 0000000100240E88 000000B8 R . . . B T .
-[XMRegisterAndLoginView thirdPartyLoginPressed:] __text 0000000100240F40 00000124 R . . . B T .
-[XMRegisterAndLoginView showLastLoginTip:] __text 0000000100241064 000001E4 R . . . B T .
-[XMRegisterAndLoginView updateCountryCode:] __text 0000000100241760 000000B8 R . . . B T .
-[XMRegisterAndLoginView countryCode:] __text 0000000100241818 000000B0 R . . . B T .
-[XMRegisterAndLoginView usernameTextField] __text 00000001002418C8 00000010 R . . . . T .
-[XMRegisterAndLoginView setUsernameTextField:] __text 00000001002418D8 00000014 R . . . . T .
-[XMRegisterAndLoginView passwordTextField] __text 00000001002418EC 00000010 R . . . . T .
-[XMRegisterAndLoginView setPasswordTextField:] __text 00000001002418FC 00000014 R . . . . T .
-[XMRegisterAndLoginView loginButton] __text 0000000100241910 00000010 R . . . . T .
-[XMRegisterAndLoginView setLoginButton:] __text 0000000100241920 00000014 R . . . . T .
-[XMRegisterAndLoginView forgetPasswordButton] __text 0000000100241934 00000010 R . . . . T .
-[XMRegisterAndLoginView setForgetPasswordButton:] __text 0000000100241944 00000014 R . . . . T .
-[XMRegisterAndLoginView delegate] __text 0000000100241958 00000020 R . . . B T .
-[XMRegisterAndLoginView setDelegate:] __text 0000000100241978 00000014 R . . . . T .
-[XMRegisterAndLoginView countryCodeButton] __text 000000010024198C 00000010 R . . . . T .
-[XMRegisterAndLoginView setCountryCodeButton:] __text 000000010024199C 00000014 R . . . . T .
-[XMRegisterAndLoginView .cxx_destruct] __text 00000001002419B0 0000012C R . . . B T .
通过cycript lldb等各种工具,验证以上可以函数的功能。显而易见,loginButtonPressed登录按钮激发函数,passwordTextField获取密码框字符串。其他其实大概也从名字也能开出来。当然这里其实我在这走了一点弯路,因为thoes挂钩不上,我还以为分析错误了。搞了半天,但用cycript和lldb又验证了下上面一些函数才发现是正确的,这里离应该有反挂钩。反反挂钩也懒得做了,不用挂钩了。
void __cdecl -[XMRegisterAndLoginView loginButtonPressed](#928 *self, SEL a2)
{
#928 *v2; // x19
#927 *v3; // x0
void *v4; // x22
int v5; // w23
#927 *v6; // x0
void *v7; // x19
v2 = self;
objc_msgSend((void *)self, "resignTextfieldAllResponder");
v3 = ((#927 *(__cdecl *)(#928 *, SEL))objc_msgSend)(v2, "delegate");
v4 = (void *)objc_retainAutoreleasedReturnValue(v3);
v5 = (unsigned __int64)objc_msgSend(v4, "respondsToSelector:", "registerAndLoginViewDidClickedLogin");
objc_release(v4);
if ( v5 )
{
v6 = ((#927 *(__cdecl *)(#928 *, SEL))objc_msgSend)(v2, "delegate");
v7 = (void *)objc_retainAutoreleasedReturnValue(v6);
objc_msgSend(v7, "registerAndLoginViewDidClickedLogin");
JUMPOUT(objc_release);
}
}
这里registerAndLoginViewDidClickedLogin属于
XMRegisterAndLoginViewController registerAndLoginViewDidClickedLogin
void __cdecl -[XMRegisterAndLoginViewController registerAndLoginViewDidClickedLogin](#4797 *self, SEL a2)
{
#4797 *v2; // x19
#2972 *v3; // x0
void *v4; // x21
void *v5; // x0
void *v6; // x0
void *v7; // x22
void *v8; // x0
__int64 v9; // x24
#2972 *v10; // x0
void *v11; // x20
void *v12; // x0
void *v13; // x0
void *v14; // x21
void *v15; // x0
__int64 v16; // x22
v2 = self;
if ( (unsigned int)objc_msgSend((void *)self, "baseSanityCheck") )
{
v3 = ((#2972 *(__cdecl *)(#4797 *, SEL))objc_msgSend)(v2, "registerAndLoginInfo");
v4 = (void *)objc_retainAutoreleasedReturnValue(v3);
v5 = objc_msgSend(*((void **)v2 + 24), "usernameTextField");
v6 = (void *)objc_retainAutoreleasedReturnValue(v5);
v7 = v6;
v8 = objc_msgSend(v6, "text");
v9 = objc_retainAutoreleasedReturnValue(v8);
objc_msgSend(v4, "setUsername:", v9);
objc_release(v9);
objc_release(v7);
objc_release(v4);
v10 = ((#2972 *(__cdecl *)(#4797 *, SEL))objc_msgSend)(v2, "registerAndLoginInfo");
v11 = (void *)objc_retainAutoreleasedReturnValue(v10);
v12 = objc_msgSend(*((void **)v2 + 24), "passwordTextField");
v13 = (void *)objc_retainAutoreleasedReturnValue(v12);
v14 = v13;
v15 = objc_msgSend(v13, "text");
v16 = objc_retainAutoreleasedReturnValue(v15);
objc_msgSend(v11, "setLoginPassword:", v16);
objc_release(v16);
objc_release(v14);
objc_release(v11);
JUMPOUT(objc_msgSend);
}
}
发现这里只是给一个类XMRegisterAndLoginItem赋值
根据XMRegisterAndLoginItem loginpassword调用函数找到了XMRegisterAndLoginViewController requestSecurityLogin
当然这里是通过动态lldb验证的,因为这里调用的函数并不多。
LABEL_8:
v33 = objc_msgSend(v19, "registerAndLoginInfo");
v34 = (void *)objc_retainAutoreleasedReturnValue(v33);
v35 = (__int64)v34;
v36 = objc_msgSend(v34, "username");
v37 = objc_retainAutoreleasedReturnValue(v36);
objc_msgSend(v26, "setObject:forKey:", v37, CFSTR("account"));
objc_release(v37);
v38 = v35;
}
else
{
v39 = objc_msgSend(v24, "substringFromIndex:", 1LL);
v40 = objc_retainAutoreleasedReturnValue(v39);
v41 = objc_msgSend(v19, "registerAndLoginInfo");
v42 = (void *)objc_retainAutoreleasedReturnValue(v41);
v43 = v42;
v44 = objc_msgSend(v42, "username");
v45 = objc_retainAutoreleasedReturnValue(v44);
v46 = v45;
v47 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@-%@"), v40, v45);
v48 = objc_retainAutoreleasedReturnValue(v47);
objc_msgSend(v26, "setObject:forKey:", v48, CFSTR("account"));
objc_release(v48);
objc_release(v46);
objc_release(v43);
v38 = v40;
}
objc_release(v38);
v49 = objc_msgSend(v19, "registerAndLoginInfo");
v50 = (void *)objc_retainAutoreleasedReturnValue(v49);
v51 = v50;
v52 = objc_msgSend(v50, "loginPassword");
v53 = (void *)objc_retainAutoreleasedReturnValue(v52);
v54 = v53;
v55 = objc_msgSend(v53, "md5String");
v56 = objc_retainAutoreleasedReturnValue(v55);
objc_msgSend(v26, "setObject:forKey:", v56, CFSTR("password"));
objc_release(v56);
objc_release(v54);
objc_release(v51);
v57 = objc_msgSend(v19, "loginToken");
v58 = objc_retainAutoreleasedReturnValue(v57);
objc_msgSend(v26, "setObject:forKey:", v58, CFSTR("nonce"));
objc_release(v58);
v59 = objc_msgSend(&OBJC_CLASS___NSString, "securetyStringFromDictionary:", v26);
v60 = (void *)objc_retainAutoreleasedReturnValue(v59);
v61 = v60;
v62 = objc_msgSend(v60, "lowercaseString");
v63 = (void *)objc_retainAutoreleasedReturnValue(v62);
v64 = v63;
v65 = objc_msgSend(v63, "md5String");
v66 = objc_retainAutoreleasedReturnValue(v65);
objc_msgSend(v26, "setObject:forKey:", v66, CFSTR("signature"));
objc_release(v66);
objc_release(v64);
v67 = objc_msgSend(&OBJC_CLASS___NSJSONSerialization, "dataWithJSONObject:options:error:", v26, 1LL, 0LL);
v68 = objc_retainAutoreleasedReturnValue(v67);
v69 = objc_msgSend(&OBJC_CLASS___NSString, "alloc");
v70 = objc_msgSend(v69, "initWithData:encoding:", v68, 4LL);
v71 = v70;
v72 = objc_msgSend(&OBJC_CLASS___NSString, "RSAStringFromString:", v70);
v73 = (void *)objc_retainAutoreleasedReturnValue(v72);
v74 = v73;
v75 = objc_msgSend(v73, "dataUsingEncoding:", 4LL);
v76 = objc_retainAutoreleasedReturnValue(v75);
objc_msgSend(v17, "setPostData:", v76);
动态 在v67 = objc_msgSend(&OBJC_CLASS___NSJSONSerialization, “dataWithJSONObject:options:error:”, v26, 1LL, 0LL);上下断点
对应的汇编
00000101042C2C MOV X0, X19
__text:0000000101042C30 BL _objc_release
__text:0000000101042C34 ADRP X8, #classRef_NSJSONSerialization@PAGE
__text:0000000101042C38 LDR X0, [X8,#classRef_NSJSONSerialization@PAGEOFF] ; void *
__text:0000000101042C3C ADRP X8, #selRef_dataWithJSONObject_options_error_@PAGE
__text:0000000101042C40 LDR X1, [X8,#selRef_dataWithJSONObject_options_error_@PAGEOFF] ; char *
__text:0000000101042C44 MOV W3, #1
__text:0000000101042C48 MOV X2, X24
__text:0000000101042C4C MOV X4, #0
__text:0000000101042C50 BL _objc_msgSend
__text:0000000101042C54 MOV X29, X29
#subr 0x0000000101042C50
这里我用了个超级断点的工具,可以自动根据偏移计算真实地址。感兴趣的可以在我的博客里面找到。
(lldb) po $x24
{
account = 1810484xxxx;
nonce = "6907381121332755797@bjc|bgi|d|fj";
password = 1aabac6d068eef6a7bad3fdf50a05cc8;
signature = 117af45c60a1b178934d9a6631567468;
}
通过分析password是密码md5后的值,acccount是登录手机号。
这里还有个RSAStringFromString:
跟以下这个类相关
-[XMRSAEncryptor dealloc] __text 0000000101293154 0000007C R . . . B T .
-[XMRSAEncryptor initWithCertificationName:OfType:] __text 00000001012931D0 000000AC R . . . B T .
-[XMRSAEncryptor encryptWithRawString:] __text 000000010129327C 000000F0 R . . . B T .
-[XMRSAEncryptor getPublicKey] __text 000000010129336C 000001CC R . . . B T .
-[XMRSAEncryptor RSAEncrypotoTheData:] __text 0000000101293538 000002D0 R . . . B T .
-[XMRSAEncryptor certificationName] __text 0000000101293808 00000010 R . . . . T .
-[XMRSAEncryptor setCertificationName:] __text 0000000101293818 00000014 R . . . . T .
-[XMRSAEncryptor certificationType] __text 000000010129382C 00000010 R . . . . T .
-[XMRSAEncryptor setCertificationType:] __text 000000010129383C 00000014 R . . . . T .
-[XMRSAEncryptor toBeEncryptString] __text 0000000101293850 00000010 R . . . . T .
-[XMRSAEncryptor setToBeEncryptString:] __text 0000000101293860 00000014 R . . . . T .
-[XMRSAEncryptor .cxx_destruct] __text 0000000101293874 00000054 R . . . B T .
这里很容易可以得到密钥等关键信息。
id __cdecl -[XMRSAEncryptor RSAEncrypotoTheData:](#5934 *self, SEL a2, id a3)
{
id v3; // x19
#5934 *v4; // x20
__int64 v5; // x22
void *v6; // x19
void *v7; // x19
void *v8; // x0
void *v9; // x25
signed int v10; // w22
unsigned int v11; // w20
void *v12; // x0
__int64 v13; // x23
__int64 v14; // x26
unsigned __int64 v15; // x24
__int64 v16; // x21
char *v17; // x0
char *v18; // x2
void *v19; // x0
void *v20; // x22
int v21; // w20
void *v22; // x0
__int64 v23; // x0
void *v24; // x22
void *v25; // x20
void *v26; // x0
id v27; // x0
__int64 v28; // x0
__int64 v29; // x21
void *v30; // x0
__int64 v31; // x20
struct objc_object *v33; // [xsp-90h] [xbp-90h]
void *v34; // [xsp-78h] [xbp-78h]
void *v35; // [xsp-70h] [xbp-70h]
size_t v36; // [xsp-68h] [xbp-68h]
v3 = a3;
v4 = self;
v5 = objc_retain(a3, a2);
v35 = objc_msgSend((void *)v4, "getPublicKey");
v36 = SecKeyGetBlockSize();
v34 = malloc(v36);
bzero(v34, v36);
v6 = objc_msgSend(v3, "dataUsingEncoding:", 4LL);
objc_release(v5);
v7 = (void *)objc_retainAutoreleasedReturnValue(v6);
v8 = objc_msgSend(&OBJC_CLASS___NSNumber, "numberWithLong:", v36 - 11);
v9 = (void *)objc_retainAutoreleasedReturnValue(v8);
v10 = (unsigned __int64)objc_msgSend(v9, "intValue");
objc_release(v9);
v11 = vcvtpd_s64_f64((double)(unsigned __int64)objc_msgSend(v7, "length") / (double)v10);
v12 = objc_msgSend(&OBJC_CLASS___NSMutableData, "alloc");
v33 = (struct objc_object *)objc_msgSend(v12, "init");
if ( (signed int)v11 >= 1 )
{
v13 = 0LL;
v14 = 0LL;
v15 = v10;
v16 = v11;
do
{
v17 = (char *)objc_msgSend(v7, "length");
if ( v15 >= (unsigned __int64)&v17[v13] )
v18 = &v17[v13];
else
v18 = (char *)v15;
v19 = objc_msgSend(&OBJC_CLASS___NSNumber, "numberWithLong:", v18);
v20 = (void *)objc_retainAutoreleasedReturnValue(v19);
v21 = (unsigned __int64)objc_msgSend(v20, "intValue");
objc_release(v20);
v22 = objc_msgSend(v7, "subdataWithRange:", v14, v21);
v23 = objc_retainAutoreleasedReturnValue(v22);
v24 = (void *)objc_retainAutorelease(v23);
v25 = objc_msgSend(v24, "bytes");
v26 = objc_msgSend(v24, "length");
if ( !(unsigned int)SecKeyEncrypt(v35, 1LL, v25, v26, v34, &v36) )
objc_msgSend(v33, "appendBytes:length:", v34, v36);
objc_release(v24);
v14 += v15;
v13 -= v15;
--v16;
}
while ( v16 );
}
if ( v34 )
free(v34);
if ( v35 )
CFRelease(v35);
v27 = ((id (__cdecl *)(#5929 *, SEL, id))objc_msgSend)((#5929 *)&OBJC_CLASS___GTMBase64, "stringByEncodingData:", v33);
v28 = objc_retainAutoreleasedReturnValue(v27);
v29 = v28;
v30 = objc_msgSend(&OBJC_CLASS___NSString, "stringWithFormat:", CFSTR("%@"), v28);
v31 = objc_retainAutoreleasedReturnValue(v30);
objc_release(v29);
objc_release(v33);
objc_release(v7);
return (id)objc_autoreleaseReturnValue(v31);
}
工具太多,如果不能灵活应用,往往容易走到死路。我个人感觉,千万不能根据关键字大量的验证猜测。因为用很多相似的类和方法,但我们的目标却不在这些中。为了避免走弯路,我们的分析思路一定要清晰。发包封装的特点往往是,seturl setdata setpost requset等步骤,这些步骤可能不再同一个方法中。setpost是这些当中的关键,虽然不能直接找setpost的方法,但一定要在确定的request请求附近关注setpost.