iOS——APP安全防护

本文只是用于记录,在工作中遇到有人攻击我们APP,我们所做的一些事。

  • 在无意间,查看数据库数据,发现有许多数据,像是利用脚本提交的。至于怎么看出来的,在这儿不好说,毕竟是公司项目。但是有一点是可以确定的,就是他的设备是iOS设备,那么说明我们的APP存在安全隐患。
  • 最开始,我们的APP,数据层没有进行任何形式的加密,后端也没有对ip进行校验,同时网络层,也没有做HTTPS加密,同时,APP本地更没有做安全加固。

于是乎,我们想到的第一件事,就是对重要的数据进行加密处理,请求包数据进行了MD5(可参考:https://www.jianshu.com/p/d7fcb503bd15),本地用户敏感数据进行钥匙串保存(demo:https://github.com/deng690990/SF_KeyChain),同时我们为网络请求加上了HTTPS证书校验。但是很悲剧的事情发生了,我们做的一切仅仅维持了一周,又有脚本数据出现在了数据库。

这个时候,我们意识到,别人可能是对我们的IPA包植入了动态库,同时结合静态分析,把我们app的主要逻辑代码摸清了,以至于人家能用脚本直接提交数据。下面我就来谈谈我们是怎么一步步防住了对方。

一、埋点,采集数据。

埋点可以用三方,也可以用自己的服务器,友盟就有自定义事件采集。

  • 埋点需要采集的信息主要是:重签名,越狱设备进行的操作,还有动态调试等。
一、检测越狱机:

封装一个工具类,头文件导入:

//防越狱相关
#import <sys/stat.h>
#import <dlfcn.h>
#import <stdlib.h>
#import <mach-o/dyld.h>
#define kApplicationIdentifier @"这里是你的证书的组织标识,可在本地钥匙串查看"
/**
 以下方法,防止越狱设备,防止hook,防止代码注入,命名故意错误方式命名,防止别人逆向
 */
+ (BOOL)Youmeng1 {
    //可能存在hook了NSFileManager方法,此处用底层C stat去检测
    //    /Library/MobileSubstrate/MobileSubstrate.dylib 最重要的越狱文件,几乎所有的越狱机都会安装MobileSubstrate
    //    /Applications/Cydia.app/ /var/lib/cydia/绝大多数越狱机都会安装
    struct stat stat_info;
    if (0 == stat("/Library/MobileSubstrate/MobileSubstrate.dylib", &stat_info)) {
        return YES;
    }
    if (0 == stat("/Applications/Cydia.app", &stat_info)) {
        return YES;
    }
    if (0 == stat("/var/lib/cydia/", &stat_info)) {
        return YES;
    }
    if (0 == stat("/var/cache/apt", &stat_info)) {
        return YES;
    }
    if (0 == stat("/var/lib/apt", &stat_info)) {
        return YES;
    }
    if (0 == stat("/etc/apt", &stat_info)) {
        return YES;
    }
    if (0 == stat("/bin/bash", &stat_info)) {
        return YES;
    }
    if (0 == stat("/bin/sh", &stat_info)) {
        return YES;
    }
    if (0 == stat("/usr/sbin/sshd", &stat_info)) {
        return YES;
    }
    if (0 == stat("/usr/libexec/ssh-keysign", &stat_info)) {
        return YES;
    }
    if (0 == stat("/etc/ssh/sshd_config", &stat_info)) {
        return YES;
    }
    return NO;
}
+ (BOOL)Youmeng2 {
    //可能存在stat也被hook了,可以看stat是不是出自系统库,有没有被攻击者换掉
    //这种情况出现的可能性很小
    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);      //如果不是系统库,肯定被攻击了
        if (strcmp(dylib_info.dli_fname, "/usr/lib/system/libsystem_kernel.dylib")) {   //不相等,肯定被攻击了,相等为0
            return YES;
        }
    }
    return NO;
}
+ (BOOL)Youmeng3 {
    //列出所有已链接的动态库:
    //通常情况下,会包含越狱机的输出结果会包含字符串: Library/MobileSubstrate/MobileSubstrate.dylib 。
    uint32_t count = _dyld_image_count();
    for (uint32_t i = 0 ; i < count; ++i) {
        NSString *name = [[NSString alloc]initWithUTF8String:_dyld_get_image_name(i)];
        if ([name containsString:@"Library/MobileSubstrate/MobileSubstrate.dylib"]) {
            return YES;
        }
    }
    return NO;
}
+ (BOOL)Youmeng4 {
    //如果攻击者给MobileSubstrate改名,但是原理都是通过DYLD_INSERT_LIBRARIES注入动态库
    //那么可以,检测当前程序运行的环境变量
    char *env = getenv("DYLD_INSERT_LIBRARIES");
    if (env != NULL) {
        return YES;
    }
    return NO;
}
  • 这里我是故意对方法名乱命名,是为了混淆逆向人员
二、检测重签名:
/**
 以下是防重签的方法
 */
+(void)Youmeng5{
    //重签名检测
    NSString *teamIdentifier = bundleTeamIdentifier();
    if ([teamIdentifier isNotEmptyObject] && ![teamIdentifier isEqualToString:kApplicationIdentifier]) {
        [MobClick updateDataWithString:@"APPResign"];
        #ifdef __arm64__
            asm volatile(
                         "mov x0,#0\n"
                         "mov w16,#1\n"
                         "svc #0x80\n"
                         );
        #endif
        #ifdef __arm__
            asm volatile(
                         "mov r0,#0\n"
                         "mov r12,#1\n"
                         "svc #0x80\n"
                         );
        #endif
    }
}
/// 拿到证书里的组织标识
static NSString *bundleTeamIdentifier(void)
{
    NSString *mobileProvisionPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"embedded.mobileprovision"];
    FILE *fp=fopen([mobileProvisionPath UTF8String],"r");
    char ch;
    if(fp==NULL) {
        return NULL;
    }
    NSMutableString *str = [NSMutableString string];
    while((ch=fgetc(fp))!=EOF) {
        [str appendFormat:@"%c",ch];
    }
    fclose(fp);

    NSString *teamIdentifier = nil;
    NSRange teamIdentifierRange = [str rangeOfString:@"<key>com.apple.developer.team-identifier</key>"];
    if (teamIdentifierRange.location != NSNotFound) {
        NSInteger location = teamIdentifierRange.location + teamIdentifier.length;
        NSInteger length = [str length] - location;
        if (length > 0 && location >= 0) {
            NSString *newStr = [str substringWithRange:NSMakeRange(location, length)];;
            NSArray *val = [newStr componentsSeparatedByString:@"</string>"];
            NSString *v = [val firstObject];
            NSRange startRange = [v rangeOfString:@"<string>"];

            NSInteger newLocation = startRange.location + startRange.length;
            NSInteger newLength = [v length] - newLocation;
            if (newLength > 0 && location >= 0) {
                teamIdentifier = [v substringWithRange:NSMakeRange(newLocation, newLength)];
            }
        }
    }
    return teamIdentifier;
}
三、混淆方法名,防止别人class_dump。这个可以用宏定义去混淆,也可以用三方库。
四、防止动态调试:
  • 防止反调试的方法最好用一个私有库来做,为什么呢?将代码放到framework里,逆向成本高得多,当然并不是不可能破解,毕竟没有绝对的安全。
  • 头文件导入#import <sys/sysctl.h>
static __attribute__((always_inline)) void workwell() {
#ifdef __arm64__
    asm volatile(
                 "mov x0,#0\n"
                 "mov w16,#1\n"
                 "svc #0x80\n"
                 );
#endif
    
#ifdef __arm__
    asm volatile(
                 "mov r0,#0\n"
                 "mov r12,#1\n"
                 "svc #0x80\n"
                 );
#endif
}

//检测调试
BOOL isDlnaInitialize(){
    int name[4];//里面放字节码。查询的信息
    name[0] = CTL_KERN;//内核查询
    name[1] = KERN_PROC;//查询进程
    name[2] = KERN_PROC_PID;//传递的参数是进程的ID
    name[3] = getpid();//PID的值
    
    struct kinfo_proc info;//接受查询结果的结构体
    size_t info_size = sizeof(info);
    if(sysctl(name, 4, &info, &info_size, 0, 0)){
//        NSLog(@"查询失败");
        return NO;
    }
    //看info.kp_proc.p_flag 的第12位。如果为1,表示调试状态。
    //(info.kp_proc.p_flag & P_TRACED)
    
    return ((info.kp_proc.p_flag & P_TRACED) != 0);
}

static dispatch_source_t timer;
void dlnaInitialize(){
    timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 30.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        if (isDlnaInitialize()) {
            workwell();
        }
    });
    dispatch_resume(timer);
}

static __attribute__((always_inline)) void UPnPInitialize() {
#ifdef __arm64__
    asm volatile(
                 "mov x0,#26\n"
                 "mov x1,#31\n"
                 "mov x2,#0\n"
                 "mov x3,#0\n"
                 "mov x16,#0\n"//中断根据x16 里面的值,跳转syscall
                 "svc #0x80\n"//这条指令就是触发中断(系统级别的跳转!)
    );
#endif
}

+ (void)load {
    UPnPInitialize();
    dlnaInitialize();
}

  • 强调一下这几句代码的作用:利用汇编代码,强制退出程序,比exit(0)要安全一些,至少不会被hook。
#ifdef __arm64__
    asm volatile(
                     "mov x0,#0\n"
                     "mov w16,#1\n"
                     "svc #0x80\n"
                     );
    #endif
    #ifdef __arm__
        asm volatile(
                     "mov r0,#0\n"
                     "mov r12,#1\n"
                     "svc #0x80\n"
                     );
    #endif
}

这是目前我们做了的技术防护。还有一部分是逻辑防护,这个就得根据项目的实际情况来,比如我们的项目,就是一个用户只能一台设备一个ip,当用户提交的数据不满足任意条件就会失败。同时,我们在登录模块加了图形验证码,还有短信验证码,就是一旦检测到用户换了设备,只能手机验证码登录。
本来我们还想做防hook操作,可是这一步已经拦住了破我们app的人。这个时候,我们就对数据库里,脚本提交的数据,做了清理,进一步让那些依靠逆向人员提交假数据的人和逆向人员之间出现了信任问题,从此,太平了。希望以后我们的APP能一帆风顺。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 有时候公司APP有安全方面的要求,那APP安全防护到底有几种方式,应该如何去做?下面介绍简单的几种供参考。 一、U...
    杰小冷_4957阅读 1,724评论 0 1
  • 一、iOS安全攻防 1.本地数据攻防 1.1 文件存储 每个App的文件都保存在一个沙盒目录中。每个沙盒都包含Do...
    zgsddzwj阅读 1,517评论 3 4
  • 谷歌在自己官方博客发布公告称,将从今年6月开始禁止网络广告推广数字货币、ICO,以及其他投机性金融工具。 过去几年...
    64b274c6b741阅读 118评论 0 0
  • 寻访讯(李彬) 近日,北和镇一位小女孩为自己七岁患病的妹妹筹款和乌石镇三小孩被烧伤的事,以及其家庭遭遇,...
    生活主题阅读 755评论 1 1
  • 登上华山中峰抬头望去,但见奇峰耸立,直插天际,令人望而生畏。关键是还有一千多米才能登顶,腿肚子已经开始转筋了,只好...
    张澜风阅读 182评论 0 2