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;
-
@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();
- (XXUtil_t *)sharedUtil;
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
使用:
//添加混淆作用的头文件(这个文件名是脚本confuse.sh中定义的)
-
import "codeObfuscation.h"
效果:
[图片上传失败...(image-7b2a62-1591782762772)]
网上也有一些工具。混淆方法比较多。