iOS安全攻防学习笔记

前言

在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

判断设备是否越狱

直接看原文吧

作者: Peter
出处: http://codefunny.github.io/
本文基于
署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 Peter(包含链接)。