iOS应用代码注入防护
在应用开发过程中,我们不仅仅需要完成正常的业务逻辑,考虑应用性能、代码健壮相关的问题,我们有时还需要考虑到应用安全的问题。
那么应用安全的问题涉及到很多方面。比如防止静态分析的,代码混淆、逻辑混淆;防止重签名的,应用ID检测、甚至是代码的HASH检测等等。那么这篇文章我想聊聊关于代码的注入检测,因为发现随着iOS系统的更新,我们防护的手段发生了一些变化。
代码注入的方式
代码注入的方式大致分为两种
- 越狱注入:通过修改
DYLD_INSERT_LIBRARIES
环境变量的值,来插入动态库并执行 - 非越狱注入: 直接将自定义的Framwork或者dylib库打包进入APP并重签名。
- 利用yololib修改MachO文件,添加库路径.在应用启动时,dyld会加载并执行.
早期防护方式
在工程的Build Settings中找到Other Linker Flages 并添加字段
-Wl,-sectcreate,__RESTRICT,__raestrict,/dev/null
此操作的作用是在可执行文件中添加一个Section.我们使用MachOView分析如下:
当MachO文件中拥有这个字段,那么我们通过越狱环境插入动态库的方式就会失效.起到防护的作用.其原理在DYLD源码中可以分析到.
dyld源码分析
首先这里分析的DYLD源码版本是519.2.2版本.
我们可以通过检索DYLD_INSERT_LIBRARIES定位到_main函数加载插入动态库的代码如下.
// load any inserted libraries if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) { for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) loadInsertedDylib(*lib); }
但是早在这个环境变量判断之前,dyld已经做了一个判断
if ( gLinkContext.processIsRestricted ) { pruneEnvironmentVariables(envp, &apple); // set again because envp and apple may have changed or moved setContext(mainExecutableMH, argc, argv, envp, apple); }
如果判断出进程是restricted!也就是当前进程是限制插入动态库的!就会调用pruneEnvironmentVariables函数移除相关的环境变量.
那么我们的processIsRestricted值什么时候为true呢?
继续分析源码可以发现两个关键函数影响其值.其中 hasRestrictedSegment 函数专门检测RESTRICT段
// any processes with setuid or setgid bit set or with __RESTRICT segment is restricted if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) { gLinkContext.processIsRestricted = true; }
通过注释也能发现.任意进程的__RESTRICT段设置为restricted动态库插入将被限制.
我们进入到processIsRestricted函数内,实现如下.
#if __MAC_OS_X_VERSION_MIN_REQUIRED static bool hasRestrictedSegment(const macho_header* mh) { const uint32_t cmd b6e _count = mh->ncmds; const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header)); const struct load_command* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { switch (cmd->cmd) { case LC_SEGMENT_COMMAND: { const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; //dyld::log("seg name: %s\n", seg->segname); if (strcmp(seg->segname, "__RESTRICT") == 0) { const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if (strcmp(sect->sectname, "__restrict") == 0) return true; } } } break; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } return false; }
所以通过添加Other Linker Flags 在MachO中设置RESTRICT段赋值为restricted可以用来防护越狱的代码注入.
但是新版的dyld源码中去掉了__RESTRICT检测.从iOS10开始,这种防护手段已失效
DYLD_INSERT_LIBRARIES 检测
那么既然dyld加载过程不再检测__RESTRICT段了我们就手动的检测
DYLD_INSERT_LIBRARIES环境变量.通过函数可查看当前进程环境变量的值.
char *env = getenv("DYLD_INSERT_LIBRARIES"); NSLog(@"%s",env);
在没有插入动态库时,env为null.
那么一旦为自己的应用写入插件时,我们就可以看到控制台的输出
2019-01-03 19:20:37.285 antiInject[7482:630392] /Library/MobileSubstrate/MobileSubstrate.dylib
白名单检测
那么上面的检测只可以检测越狱环境中的代码注入,在非越狱环境中,逆向工程师可以利用yololib工具注入动态库.所以我们可以检索一下自己的应用程序所加载的动态库是否是我们源程序所有
bool HKCheckWhitelist(){ int count = _dyld_image_count(); for (int i = 0; i < count; i++) { //遍历拿到库名称! const char * imageName = _dyld_get_image_name(i); //判断是否在白名单内,应用本身的路径是不确定的,所以要除外. if (!strstr(libraries, imageName)&&!strstr(imageName, "/var/mobile/Containers/Bundle/Application")) { printf("该库非白名单之内!!\n%s",imageName); return NO; } } return YES; }
其中libraries变量是<q style="box-sizin 5b4 g: border-box;">白名单</q>.
- 第四章:iOS应用漏洞利用 ——4.25 代码注入和DYLD_INSERT_LIBRARIES
- iOS 数据库篇3—SQL代码应用示例
- iOS 代码实现获得应用的版本号(Version/Build)
- iOS 自动布局扩展应用:代码中动态调整布局常量
- 【iOS 应用瘦身】使用 Clang 插件扫描无用代码(Part2)
- 学习Swift的IOS应用的代码教程-5:让应用变的美观
- iOS 代码实现获得应用的版本号(Version/Build)
- iOS开发数据库篇—SQL代码应用示例
- iOS应用开发最佳实践:编写高质量的Objective-C代码
- c++继承应用->写一套代码跨越IOS和Android两个平台
- iOS应用开发最佳实践:编写高质量的Objective-C代码
- 构建iOS稳定应用架构时方案选择的思考,主要涉及工程结构,数据流思想和代码规范
- iOS应用开发最佳实践:编写高质量的Objective-C代码
- 使用代码创建一个IOS应用的视图
- ios在系统代码中注入自己的代码
- iOS 11开发教程(十四)iOS11应用代码添加视图
- 【iOS 应用瘦身】使用 Clang 插件扫描无用代码(Part3)
- 几维安全介绍iOS应用加密常用算法和代码实践
- iOS 9应用开发教程之使用代码添加按钮美化按钮
- iOS高仿城觅应用客户端项目(开发思路和代码)