前言
在CSDN上发现程序媛念茜的iOS安全攻防专栏系列文章,在程序猿这个男性居多的行业里见到一女中豪杰,真是巾帼不让须眉,十分佩服念茜,这里就记录一下学习的笔记吧。感谢分享!
工具和命令
ps |
显示进程,cpu使用率,内存使用情况等 |
sysctl |
检查设定Kernel配置 |
netstat |
显示网络连接,路由表,接口状态等 |
route |
路由 |
renice |
调整程序运行优先级 |
ifconfig |
查看网络配置 |
tcpdump |
截获分析网络数据包 |
lsof |
列出当前系统打开的文件列表 |
otool |
查看程序依赖的动态库信息,反编代码段。。。 |
nm |
显示符号表 |
ldid |
签名工具 |
otool -L exe
: 显示可执行程序连接了哪些库
otool -tV exe
: 反编译exe的TEXT段内容
nm -g exe
: 显示程序符号表
阻止GDB依附
常规的办法是:
1
2
3
4
5
6
7
8
9
10
11
| #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的,但是可以通过dlopen拿到它。
dlopen:当path参数为0时,他会自动查找$LD_LIBRARY_PATH,$DYLD_LIBRARY_PATH,$DYLD_FALLBACK_LIBRARY_PATH和当前工作目录中的动态链接库。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| #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]));
}
}
|
上述方法经过校验可行,但是不知道放在正式版app中是否会被apple驳回。
二进制和资源文件自检
hackers们破解app,一般动2个地方,一个是二进制,一个是资源文件。二进制都重新编译了,当然是盗版,但修改资源文件是不需要重新编译二进制文件。
那么,我们有必要在敏感的请求报文中,增加正版应用的二进制和资源文件的标识,让服务器知道,此请求是否来自正版未经修改的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];
}
|
static和被裁的符号表
原理:
如果函数属性为static,那么编译时该函数符号就会被解析为local符号。在发布release程序时(xcode打包编译二进制)默认会strip裁掉这些函数符号,加大破解难度。
局限:static函数,只在本文件可见。
1
2
3
4
5
6
7
8
9
| 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;
}
|
方法名混淆
常规思路:
- 花代码花指令,即随意往程序中加入迷惑人的代码指令
- 易读字符替换
念茜写了一个混淆工具,主要思路是把敏感方法名集中写在一个名叫func.list的文件中,逐一#define成随机字符,追加写入.h
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
| #!/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
|
这里有人用c写了一个获取m中方法的程序。
敏感代码保护
Object-C代码容易被hook,暴露信息太多,为了安全,改用C来写敏感的业务逻辑吧。
示例:
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
| //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
|
判断设备是否越狱
直接看原文吧