1、介绍
在应用开发过程中,我们需要考虑到应用安全的问题。而应用安全的问题涉及到很多方面的内容,随着iOS系统的不断更新,我们需要在防护的手段上发生一些改变。
如下所示:
【1】防止静态分析:代码混淆、逻辑混淆
【2】防止重签名:应用ID 检测、代码的HASH检测
2、代码的注入方式
代码的注入的方式大致分为两类:
【1】越狱注入:通过修改DYLD_INSERT_LIBRARIES环境变量的值,来插入动态库并执行
【2】非越狱注入:直接将自定义的Framework库或者dylib/tbd库打包进入App并重新签名,或者利用yololib修改MachO文件,添加库路径,在应用启动时,dyld会加载并执行。
3、早期的防护方式
在工程中的编译设置中添加字段,其操作的作用是在可执行文件中添加一个Section,当MachO文件中拥有这个字段,那么我们通过越狱环境插入动态库的方式就会失效,起到防护的作用,其原理在DYLD源码中可以分析到。
使用MachOView分析如下:
Project —> Targets —> BuildSetting —> Other Linker Flags —> -Wl,-sectcreate,__RESTRICT,__raestrict,/dev/null
4、dyld源码分析
我们可以通过检索DYLD_INSERT_LIBRARIES定位到_main函数加载插入动态库的代码,如下所示:
// 加载时,插入库
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_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开始,这种防护手段已失效。
5、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
6、白名单检测
那么上面的检测只可以检测越狱环境中的代码注入,在非越狱环境中,逆向工程师可以利用yololib工具注入动态库.所以我们可以检索一下自己的应用程序所加载的动态库是否是我们源程序所有。其中libraries变量是“白名单”。
如下所示:
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;
}