iOS逆向之旅(防护篇) — 防护Tweak插件

Tweak的原理

要防护某种技术,首先你得知道这种技术是通过什么原理实现的

  • Tweak 在Make package的时候,会生成一个deb包,我们解压缩看看是什么鬼

这里面保护了一个动态库和一个plist文件



这个plist文件里面包含该dylib要注入到进程的BundleId

  • 当这个deb包安装到手机上时,就会把这两个文件放到/var/Library/MobileSubstrate/DynamicLibraries这个路径下,通过iFunBox可以看到
  • App启动时,就会通过DYLD_INSERT_LIBRARIES这种方式将动态库注入到进程中,从而实现注入

结论:所以我们防护Tweak,就是要防止DYLD_INSERT_LIBRARIES注入

dlyd关于DYLD_INSERT_LIBRARIES 源码阅读 【最新一版dyld-635.2】

首先我们可以在苹果开源代码下载dyld的源码,了解DYLD_INSERT_LIBRARIES注入的流程,逆向分析源码

  • 首先定位关键字DYLD_INSERT_LIBRARIES

注释写的很清楚,加载任何注入的动态库

  • 好像只需要让这段代码不执行就可以了,所以接着往回跟,看看如何让这段代码不执行【看看哪里修改了sEnv.DYLD_INSERT_LIBRARIES
void processDyldEnvironmentVariable(const char* key, const char* value, const char* mainExecutableDir)
{
    // ...
    else if ( strcmp(key, "DYLD_INSERT_LIBRARIES") == 0 ) {
        sEnv.DYLD_INSERT_LIBRARIES = parseColonList(value, NULL);
    // ...
}

继续跟踪 processDyldEnvironmentVariable

static void checkEnvironmentVariables(const char* envp[])
{
      // ..
      processDyldEnvironmentVariable(key, value, NULL);
      //...
}

继续跟踪 checkEnvironmentVariables

static void pruneEnvironmentVariables(const char* envp[], const char*** applep)
{
#if SUPPORT_LC_DYLD_ENVIRONMENT
    checkLoadCommandEnvironmentVariables();
#endif
#if __MAC_OS_X_VERSION_MIN_REQUIRED
    if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
        pruneEnvironmentVariables(envp, &apple);
        // set again because envp and apple may have changed or moved
        setContext(mainExecutableMH, argc, argv, envp, apple);
    }
    else
#endif
    {
        checkEnvironmentVariables(envp);
        defaultUninitializedFallbackPaths(envp);
    }

上述代码说明,(!gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache)为假的时候才会去加载注入的动态库信息,反之就是只要为真app将不会加载各种注入的dylib。

  • 接着跟踪(!gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache,定位到4896行
static void configureProcessRestrictions(const macho_header* mainExecutableMH)
{
    uint64_t amfiInputFlags = 0;
#if TARGET_IPHONE_SIMULATOR
    amfiInputFlags |= AMFI_DYLD_INPUT_PROC_IN_SIMULATOR;
#elif __MAC_OS_X_VERSION_MIN_REQUIRED
    if ( hasRestrictedSegment(mainExecutableMH) )
        amfiInputFlags |= AMFI_DYLD_INPUT_PROC_HAS_RESTRICT_SEG;
#elif __IPHONE_OS_VERSION_MIN_REQUIRED
    if ( isFairPlayEncrypted(mainExecutableMH) )
        amfiInputFlags |= AMFI_DYLD_INPUT_PROC_IS_ENCRYPTED;
#endif
    uint64_t amfiOutputFlags = 0;
    if ( amfi_check_dyld_policy_self(amfiInputFlags, &amfiOutputFlags) == 0 ) {
        gLinkContext.allowAtPaths               = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_AT_PATH);
        gLinkContext.allowEnvVarsPrint          = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_PRINT_VARS);
        gLinkContext.allowEnvVarsPath           = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_PATH_VARS);
        gLinkContext.allowEnvVarsSharedCache    = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_CUSTOM_SHARED_CACHE);
        gLinkContext.allowClassicFallbackPaths  = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_FALLBACK_PATHS);
        gLinkContext.allowInsertFailures        = (amfiOutputFlags & AMFI_DYLD_OUTPUT_ALLOW_FAILED_LIBRARY_INSERTION);
    }
        //...
}

有点乱,我正序串一下

hasRestrictedSegment(mainExecutableMH)
    -> amfiInputFlags
    -> gLinkContext.allowEnvVarsPrint 、 gLinkContext.allowEnvVarsPath、gLinkContext.allowEnvVarsSharedCache
    -> checkEnvironmentVariables(envp)
    -> sEnv.DYLD_INSERT_LIBRARIES
    -> load any insered libraries

所以最终要确认的是hasRestrictedSegment方法,到底是怎么判断的

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 = &sectionsStart[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;
}

根据参数名的类型可知,传进去的是一个macho文件头,然后进行遍历,如果有一个段名是“ __RESTRICT”,里面是“ __restrict”,就会返回true,也就最终决定不会去加载注入的动态库

防护手段1

  • 通过在Other Linker Flags 添加 -Wl,-sectcreate,__RESTRICT,__restrict,/dev/null,这样就能在macho文件头部添加一个段名是“ __RESTRICT”,里面是“ __restrict”,满足了hasRestrictedSegment的要求
  • 然后我们用烂苹果观察一下生成的macho文件是否有着一个段

防护手段1的破解方式

直接利用工具,修改app的macho文件把“__RESTRICT,__restrict”名字随便改动一两个字符,就可以直接破解了

防护手段2

把苹果的代码直接拿过来用,我们自己判断我们有没有这个段【如果被恶意破坏了,我们自己也可以检测出来】,我就直接上我封装好的代码

#import "AntiInsertLibrary.h"
#import <mach-o/loader.h>
#import <mach-o/dyld.h>

#define CPU_SUBTYPES_SUPPORTED  ((__arm__ || __arm64__ || __x86_64__) && !TARGET_IPHONE_SIMULATOR)

#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

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;
                
                //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 = &sectionsStart[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;
}


@implementation AntiInsertLibrary

+ (BOOL) hasRestrictedSegment {
    struct mach_header * header = _dyld_get_image_header(0);
    return hasRestrictedSegment(header);
}

@end

使用方式也很简单

if ([AntiInsertLibrary hasRestrictedSegment]) {
     NSLog(@"安全着呢");
} else {
     NSLog(@"防护受到破坏");
     exit(0);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,565评论 6 525
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,696评论 3 406
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,935评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,327评论 1 303
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,338评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,760评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,085评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,091评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,656评论 1 327
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,657评论 3 348
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,767评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,360评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,088评论 3 341
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,493评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,654评论 1 278
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,374评论 3 383
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,841评论 2 367

推荐阅读更多精彩内容

  • [TOC] 修改系统应用 目标:消除对手机桌面的提醒气泡 通过cycript分析气泡连接手机并登陆,通过进程列表指...
    _顺_1896阅读 1,845评论 0 3
  • 概叙 越狱防护是指防止别人修改自己的APP作出的防护手段。 1.了解代码注入方式 了解防护之前需要了解代码注入的方...
    Hanfank阅读 1,441评论 4 14
  • 在应用开发过程中,我们不仅仅需要完成正常的业务逻辑,考虑应用性能、代码健壮相关的问题,我们有时还需要考虑到应用安全...
    喵喵唔的老巢阅读 851评论 0 0
  • tweak插件开发 1、连接手机 usb或者wifi 通过ssh连手机 2、ps -A查看所有应用 3、导出目标...
    looha阅读 1,652评论 0 1
  • 在应用开发过程中,我们不仅仅需要完成正常的业务逻辑,考虑应用性能、代码健壮相关的问题,我们有时还需要考虑到应用安全...
    iOS猿_员阅读 3,051评论 1 4