一.通过DYLD的dyld_insert_library环境变量防护
1.原理(查看dyld-519.2.2源码)
// 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_INSERT_LIBRARIES,如果不是NULL,就直接调用loadInsertedDylib进行动态库的插入。
在调用以上代码插入动态库之前,DYLD还是有对DYLD_INSERT_LIBRARIES进行限制的:
if ( gLinkContext.processIsRestricted ) {
pruneEnvironmentVariables(envp, &apple);
// set again because envp and apple may have changed or moved
setContext(mainExecutableMH, argc, argv, envp, apple);
}
首先调用 gLinkContext.processIsRestricted判断DYLD_INSERT_LIBRARIES,如果是受限制, 就直接移除环境变量。
直接全局搜索 processIsRestricted = true查看什么情况下会受限制.
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;
}
通过上述DYLD代码,我们知道matchO中需要有segment为__RESTRICT,且段中需要有section为__restrict,即可限制动态库的插入。
2.实现防护
(1)配置环境变量
(2)编译,查看MatchO
这样就能防止插入动态库了。但是如果仅仅只是这只环境变量的话,黑客只需要修改MatchO的这个段,就可以破解这个防护了。
为了增加破解难度,我们可以自己检测这个段是否被修改。
(3)检测段是否被修改,增加破解难度。
#import "ViewController.h"
#import
#import
@interface ViewController ()
@end
#if __LP64__
#define macho_header mach_header_64
#define LC_SEGMENT_COMMAND LC_SEGMENT_64
#define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT
#define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO
#define macho_segment_command segment_command_64
#define macho_section section_64
#else
#define macho_header mach_header
#define LC_SEGMENT_COMMAND LC_SEGMENT
#define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64
#define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO_64
#define macho_segment_command segment_command
#define macho_section section
#endif
@implementation ViewController
+(void)load
{
const struct mach_header_64 * header = _dyld_get_image_header(0);
if (hasRestrictedSegment(header)) {
NSLog(@"正常");
}else{
NSLog(@"Restrict被修改了");
}
}
static bool hasRestrictedSegment(const struct macho_header* mh)
{
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(struct 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;
printf("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;
}
@end
(4).总结:支付宝早期的版本也有使用这种方式防护。但是很遗憾,从iOS 10以后,dyld已不在检测__RESCRICT这个段,因此这种防护在iOS 10以后,已经失效。
那么iOS 10以后如何防护呢?
二.白名单检测
(1).获取到APP中需要的所有库,保存下来。
(2).APP启动后,检测所有第三方库,并查找每个库是否在之前保存的库中,如果找到不存在的库,即认为是有风险的。
bool HKCheckWhitelist(){
int count = _dyld_image_count();//加载了多少数量
for (int i = 1; i < count; i++) {
//遍历拿到库名称!
const char * imageName = _dyld_get_image_name(i);
if (!strstr(libraries, imageName)) {
printf("该库非白名单之内!!\n%s",imageName);
return NO;
}
}
return YES;
}
判断是否越狱手机的方式(判断DYLD_INSERT_LIBRARY是否有值)
char * dlname = getenv("DYLD_INSERT_LIBRARY");
if (dlname) {
NSLog(@"越狱设备");
}
注意点:不同系统,不同型号的设备,所依赖的库可能不一样,因此需要进行处理。
三、越狱检测
char * dlname = getenv("DYLD_INSERT_LIBRARIES");
if (dlname) {
NSLog(@"越狱手机,关闭部分功能");
}else{
NSLog(@"正常手机!");
}