美文网首页yue狱检测和反yue狱检测
iOS 开发随笔 (__restrict 引起的 dyld 加载

iOS 开发随笔 (__restrict 引起的 dyld 加载

作者: _涼城 | 来源:发表于2022-03-24 18:22 被阅读0次

前因

App 接入了动态库后,iOS 9.x 版本启动 Crash,报错如下:

dyld: warning, LC_RPATH @excutable_path/Frameworks in ... being ignored in restricted program because of @excutable_path/

dyld: warning, LC_RPATH @loader_path/Frameworks in ... being ignored in restricted program because of @loader_path/


dyld: Library not loaded : @rapth/...
Referenced from:
...
Reason: image not found 

为什么 iOS 9.x 会 Crash ?

由报错可知,dyld 未加载到对应动态库导致崩溃,通过对比不同版本手机的崩溃栈信息以及提示,发现dyld栈信息不同并且其他版本无dyld:warning,推测由于 dyld 版本不同导致。前往 dyld 源码查找 iOS 9.x 大致版本为 360.22 并下载。

通过 Crash 的栈信息,可知执行顺序为 _dyld_start -> dyldbootstrap::start() -> dyld::main() -> dyld::setContext() -> dyld::checkEnvironmentVariables() -> dyld::checkLoadCommandEnvironmentVariables() -> ...-> dyld::load() -> dyld::link()

dyld::link

在工程中搜索 error 关键字,通过下列信息找到 dyld::link() 调用时异常,通过函数名可确认为链接动态库时失败

  • 搜索 Library not loaded:,只在 ImageLoader::recursiveLoadLibraries 有报错

      void ImageLoader::recursiveLoadLibraries(const LinkContext& context, bool preflightOnly, const RPathChain& loaderRPaths)
      {
       //...
           const char* newMsg = dyld::mkstringf("Library not loaded: %s\n  Referenced from: %s\n  Reason: %s", 
        //..
    
    
  • 搜索 recursiveLoadLibraries(, 只在 ImageLoader::link 中有调用

  • 搜索 link(, 只在 dyld::link(中有调用

getRPaths

由于控制台 dyld:warning ,推测是因为路径被忽略导致的动态库无法链接

  • 搜索 being ignored in restricted program

    void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vector<const char*>& paths) const
    {
     //..
        if ( (strncmp(path, "@loader_path", 12) == 0) && ((path[12] == '/') || (path[12] == '\0')) ) {
         if ( context.processIsRestricted && !context.processRequiresLibraryValidation && (context.mainExecutable == this) ) {
          dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @loader_path\n", path, this->getPath());
          break;
         }
         //...
        }
        else if ( (strncmp(path, "@executable_path", 16) == 0) && ((path[16] == '/') || (path[16] == '\0')) ) {
         if ( context.processIsRestricted && !context.processRequiresLibraryValidation ) {
          dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @executable_path\n", path, this->getPath());
          break;
         }
        //...
        }
        else if ( (path[0] != '/') && context.processIsRestricted && !context.processRequiresLibraryValidation ) {
         dyld::warn("LC_RPATH %s in %s being ignored in restricted program because it is a relative path\n", path, this->getPath());
         break;
        }
        //...
    }
    
    

在上面的代码中,发现警告出现的情况为 context.processIsRestricted && !context.processRequiresLibraryValidation

processIsRestricted

  • 在工程中搜索 processIsRestricted,发现其实是 sProcessIsRestricted,值初始化为 false

    
    bool processIsRestricted()
    {
     return sProcessIsRestricted;
    }
    
    
  • processRestricted() 函数返回值会赋值给 sProcessIsRestricted

    sProcessIsRestricted = processRestricted(mainExecutableMH, &ignoreEnvironmentVariables, &sProcessRequiresLibraryValidation);
    
  • 查看 processRestricted 中的注释,发现 Respect __RESTRICT,__restrict section for root processes

    static bool processRestricted(const macho_header* mainExecutableMH, bool* ignoreEnvVars, bool* processRequiresLibraryValidation)
    { 
    #if TARGET_IPHONE_SIMULATOR
     gLinkContext.codeSigningEnforced = true;
    #else
        // ask kernel if code signature of program makes it restricted
        uint32_t flags;
     if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) {
      if (flags & CS_REQUIRE_LV)
       *processRraryValequiresLibidation = true;
    
      #if __MAC_OS_X_VERSION_MIN_REQUIRED
      if ( flags & CS_ENFORCEMENT ) {
       gLinkContext.codeSigningEnforced = true;
      }
      if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0) ) {
       sRestrictedReason = restrictedByEntitlements;
       return true;
      }
      #else
      if ((flags & CS_ENFORCEMENT) && !(flags & CS_GET_TASK_ALLOW)) {
       *ignoreEnvVars = true;
      }
      gLinkContext.codeSigningEnforced = true;
      #endif
     }
    #endif
    
     // all processes with setuid or setgid bit set are restricted
        if ( issetugid() ) {
      sRestrictedReason = restrictedBySetGUid;
      return true;
     }
      
     // <rdar://problem/13158444&13245742> Respect __RESTRICT,__restrict section for root processes
     if ( hasRestrictedSegment(mainExecutableMH) ) {
      // existence of __RESTRICT/__restrict section make process restricted
      sRestrictedReason = restrictedBySegment;
      return true;
     }
        return false;
    }
    
    
  • 查看 hasRestrictedSegment 是去 Mach-O 查找 __RESTRICT segement,通过 MachOView 打开工程可执行文件发现对应的 Segment,可知由于 __RESTRICT 导致

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

__RESTRICT

在工程中搜索 RESTRICT,发现 Other Linker Flags 中有这个字段 -Wl,-sectcreate,__RESTRICT,__restrict,/dev/null,将其删除后,App运行成功成功。

为什么 iOS 10.0及以后正常呢?

  • 下载 dyld 421.1 源码,发现 dyld::main 函数中多了宏判断 __MAC_OS_X_VERSION_MIN_REQUIRED,宏判断为 Mac 情况下调用 pruneEnvironmentVariables()函数

    #if __MAC_OS_X_VERSION_MIN_REQUIRED
        if ( gLinkContext.processIsRestricted ) {
      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);
     }
    
    
  • 查看 pruneEnvironmentVariables() 函数,在此函数会移除全部 DYLD_*LD_LIBRARY_PATH environment variables,并且禁用 disable framework and library fallback paths

      static void pruneEnvironmentVariables(const char* envp[], const char*** applep)
      {
       // 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;
      // <rdar://11894054> Disable warnings about DYLD_ env vars being ignored.  The warnings are causing too much confusion.
      #if 0
       if ( removedCount != 0 ) {
        dyld::log("dyld: DYLD_ environment variables being ignored because ");
        switch (sRestrictedReason) {
         case restrictedNot:
          break;
         case restrictedBySetGUid:
          dyld::log("main executable (%s) is setuid or setgid\n", sExecPath);
          break;
         case restrictedBySegment:
          dyld::log("main executable (%s) has __RESTRICT/__restrict section\n", sExecPath);
          break;
         case restrictedByEntitlements:
          dyld::log("main executable (%s) is code signed with entitlements\n", sExecPath);
          break;
        }
       }
      #endif
       // 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));
      }
    

总结

-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null 在 iOS 10 以前对于防护动态库注入有效

相关文章

网友评论

    本文标题:iOS 开发随笔 (__restrict 引起的 dyld 加载

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