iOS加固

1. 需求背景

App越狱机存在反编译的情景。存在刷金币,等类似外挂的行为。App加固。使越狱者不能hookApp的方法。

2. 旧方案问题

玩吧App越狱可逆向。

3. iOS上线版本

v10.5

4. 影响范围/ 风险预警

需要测试详细测试。改动全局。 需要越狱测试机,我这边做加固完模拟攻防。

开发周期 3-5天,测试周期:3-5天。

5. 测试注意点

测开需要也做下模拟逆向攻防。并且做全局测试回归。

建议逐步改进。

6.技术方案

6.1.使用NSFileManager判断设备是否安装了如下越狱常用工具:

eg:

/Applications/Cydia.app
/Library/MobileSubstrate/MobileSubstrate.dylib
/bin/bash
/usr/sbin/sshd
/etc/apt

if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/Cydia.app"]){

return YES;

}

6.1.1 尝试打开cydia应用注册的URL scheme:

if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]){
NSLog(@"Device is jailbroken");
}

6.1.2 尝试读取下应用列表,看看有无权限获取:
if ([[NSFileManager defaultManager] fileExistsAtPath:@"/User/Applications/"]){
NSLog(@"Device is jailbroken");
NSArray *applist = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:@"/User/Applications/"
error:nil];
NSLog(@"applist = %@",applist);
}

攻击者可能会hook NSFileManager 的方法,让你的想法不能如愿。可以回避 NSFileManager,

6.1.3 尝试打开cydia应用注册的URL scheme:使用stat系列函数检测Cydia等工具:

import

void checkCydia(void)
{
struct stat stat_info;
if (0 == stat("/Applications/Cydia.app", &stat_info)) {
NSLog(@"Device is jailbroken");
}
}

6.1.4 如果攻击者可能会利用 Fishhook原理 hook了stat。

import

void checkInject(void)
{
int ret ;
Dl_info dylib_info;
int (*func_stat)(const char *, struct stat *) = stat;
if ((ret = dladdr(func_stat, &dylib_info))) {
NSLog(@"lib :%s", dylib_info.dli_fname);
}
}

如果结果不是 /usr/lib/system/libsystem_kernel.dylib 的话,那就100%被攻击了。

6.1.5 还可以检索一下自己的应用程序是否被链接了异常动态库。

列出所有已链接的动态库:

import

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)

{

char *env = getenv("DYLD_INSERT_LIBRARIES");

NSLog(@"%s", env);

}

未越狱设备返回结果是null,越狱设备就各有各的精彩了,尤其是老一点的iOS版本越狱环境。

6.2 为了不让攻击者理清自己程序的敏感业务逻辑,于是我们想方设法提高逆向门槛。利用static关键字裁掉函数符号。

如果函数属性为 static ,那么编译时该函数符号就会被解析为local符号。
在发布release程序时(用Xcode打包编译二进制)默认会strip裁掉这些函数符号,无疑给逆向者加大了工作难度。

eg:

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;

}

怎么让别的文件也能调到本文件的static方法呢?
在本文件建造一个结构体,结构体里包含函数指针。把static函数的函数指针都赋在这个结构体里,再把这个结构体抛出去。
这样做的好处是,既隐藏了函数代码也丰富了调用方式。

6.2.1 为了不让攻击者理清自己程序的敏感业务逻辑,于是我们想方设法提高逆向门槛。利用static关键字裁掉函数符号。为了安全,将敏感的方法改写为C函数。

改写方法如下:

@interface XXUtil : NSObject
    • (BOOL)isVerified;
    • (BOOL)isNeedSomething;
    • (void)resetPassword:(NSString *)password;
  1. @end

    改写为:

    //XXUtil.h

    import

    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

    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

    调用 XXUtil->isVerified();

6.3 Objective-C代码混淆,阻止 class-dump出可读的信息有效方法是易读字符替换。

利用#define的方法有一个好处,就是可以把混淆结果合并在一个.h中,在工程Prefix.pch的最前面#import这个.h。不导入也可以编译、导入则实现混淆。

以下脚本。放进Xcode 的Build Phrase 中设定在编译之前进行方法名的字符串替换。 结合外部plist要替换的方法使用。

!/usr/bin/env bash

TABLENAME=symbols
SYMBOL_DB_FILE="symbols"
STRING_SYMBOL_FILE="func.list"
HEAD_FILE="PROJECT_NAME/codeObfuscation.h"
export LC_CTYPE=C

维护数据库方便日后作排重

createTable()
{
echo "create table SYMBOL_DB_FILE
}

insertValue()
{
echo "insert into 1' ,'SYMBOL_DB_FILE
}

query()
{
echo "select * from 1';" | sqlite3 $SYMBOL_DB_FILE
}

ramdomString()
{
openssl rand -base64 64 | tr -cd 'a-zA-Z' |head -c 16
}

rm -f HEAD_FILE
createTable

touch $HEAD_FILE
echo '#ifndef Demo_codeObfuscation_h

define Demo_codeObfuscation_h' >> $HEAD_FILE

echo "//confuse string at date" >> STRING_SYMBOL_FILE" | while read -ra line; do
if [[ ! -z "line line line HEAD_FILE
fi
done
echo "#endif" >> $HEAD_FILE

使用:

  1. //添加混淆作用的头文件(这个文件名是脚本confuse.sh中定义的)

  2. import "codeObfuscation.h"

效果:

[图片上传失败...(image-7b2a62-1591782762772)]

网上也有一些工具。混淆方法比较多。

你可能感兴趣的:(iOS加固)