美文网首页iOS开发之常用技术点iOS (Swift & Objective-C & Xcode)
iOS逆向之旅(防护篇) — 防护Tweak插件

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

作者: 洪呵呵_ | 来源:发表于2019-03-06 22:48 被阅读50次

    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);
    }
    

    相关文章

      网友评论

        本文标题:iOS逆向之旅(防护篇) — 防护Tweak插件

        本文链接:https://www.haomeiwen.com/subject/uqykuqtx.html