60dc46748530c5c9dcc4584ad543f7ca
安全攻与防篇:RESTRICT 攻与防

1.防护原理分析

之前我们通过 TheOS(工具篇:TheOS),获取了微信密码。实际上 TheOS 是将我们编写的 dylib 库,附加到微信进程,使用的是dyld 的【DYLD_INSERT_LIBRARIES】环境变量来实现的。这种方式该怎么防护呢?

我们从 dyld 的源码入手。从 Apple 源码下载好 dyld( 地址:https://opensource.apple.com/tarballs/dyld/ )的代码(本人这里下载的是 dyld-551.4)后,打开工程,搜索【DYLD_INSERT_LIBRARIES】 ,从源码中,我们可以看到在【dyld.cpp】 的【_main】方法中,有这么一段:

_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    //...
    // load any inserted libraries
    if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
        for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) 
            loadInsertedDylib(*lib);
    }
    //...

}

从上面的代码可以看到,这段是遍历需要插入的 libraries,并进行插入。这里我们也做不了什么,继续看上面的代码,看看有没有对环境变量进行删除的地方。还是在 _main 函数,有这么一段:

uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, 
        int argc, const char* argv[], const char* envp[], const char* apple[], 
        uintptr_t* startGlue)
{
    if ( gLinkContext.processIsRestricted ) {
        pruneEnvironmentVariables(envp, &apple);
        // set again because envp and apple may have changed or moved
        setContext(mainExecutableMH, argc, argv, envp, apple);
    }
}

根据函数名称,能大概猜测【pruneEnvironmentVariables】是移除环境变量的方法,我们可以定位到这个函数:

static void pruneEnvironmentVariables(const char* envp[], const char*** applep)
{
#if SUPPORT_LC_DYLD_ENVIRONMENT
    checkLoadCommandEnvironmentVariables();
#endif

    // Are we testing dyld on an internal config?
    if ( _simple_getenv(envp, "DYLD_SKIP_MAIN") != NULL ) {
        if ( dyld3::loader::internalInstall() )
            sSkipMain = true;
    }

    // delete all DYLD_* and LD_LIBRARY_PATH environment variables
    int removedCount = 0;
    const char** d = envp;
    for(const char** s = envp; *s != NULL; s++) {

        if ( (strncmp(*s, "DYLD_", 5) != 0) && (strncmp(*s, "LD_LIBRARY_PATH=", 16) != 0) ) {
            *d++ = *s;
        }
        else {
            ++removedCount;
        }
    }
    *d++ = NULL;
    // slide apple parameters
    if ( removedCount > 0 ) {
        *applep = d;
        do {
            *d = d[removedCount];
        } while ( *d++ != NULL );
        for(int i=0; i < removedCount; ++i)
            *d++ = NULL;
    }

    // disable framework and library fallback paths for setuid binaries rdar://problem/4589305
    sEnv.DYLD_FALLBACK_FRAMEWORK_PATH = NULL;
    sEnv.DYLD_FALLBACK_LIBRARY_PATH = NULL;

    if ( removedCount > 0 )
        strlcat(sLoadingCrashMessage, ", ignoring DYLD_* env vars", sizeof(sLoadingCrashMessage));
}

从函数实现来看,这个函数是删除环境变量的,既然这个函数是删除环境变量的,那么我们继续看一下上面那段代码的判断条件:processIsRestricted,因为只要满足这个条件为 true,就可以执行到删除环境变量的方法。那么怎么设置为 true 呢?全局搜索【processIsRestricted = true】,可以看到在【dyld.cpp】文件的【configureProcessRestrictions】方法中,有相关设置的代码:

static void configureProcessRestrictions(const macho_header* mainExecutableMH)
{
if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) {
        gLinkContext.processIsRestricted = true;
    }
}

从代码看,【issetugid()】和【hasRestrictedSegment(mainExecutableMH)】满足一个条件就可以了。【issetugid()】我们控制不了,【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;
}

【hasRestrictedSegment()】函数是判断有没有受限的【segment】,如果有,则返回 true。原理分析完了,接下来我们进行防护。

2.防护与攻击

2.1 创建原始工程

这里我们直接创建一个工程,代码也很简单,只是在 VC 中添加一个点击弹出 Alert 的效果。
```
@implementation ViewController

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"GOF" message:@"原始弹框" delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil];
    [alert show];
    }
top Created with Sketch.