dumpdecrypted原理解析及源码分析报告


#1

源代码位于:
https://github.com/stefanesser/dumpdecrypted/blob/master/dumpdecrypted.c
这个程序只有一个函数就是dylib_entry: dumptofile
这个程序并没有解密的逻辑,当他被执行时,其实加载器已经完成了目标mach-o文件的装载工作,对应的解密工作也已经完成。
这个工具所做的工作是,遍历loadcommand中所有LC_ENCRYPTION_INFO或LC_ENCRYPTION_INFO_64的信息,将对应解密后的数据从内存中dump出来,复写到mach-o文件中。
他支持fat格式,这时他会从fat文件中找出和当前内存上这份程序arch相同的mach-o文件进行处理。同时生成的文件仍是fat格式,只有arch和memory相同的这份thin mach-o文件被修改,这是显而易见的,因他是通过dump来实现decrypt,而不同arch的程序就不会被加载,所以也不可能有decrypted之后的内容。
有一些细节需要通过实例来分析,例如为什么动态库的entry会有这个ProgramVars参数呢?
void dumptofile(int argc, const char **argv, const char **envp, const char **apple, struct ProgramVars *pvars)

有同学反应:美团4.8.1 无法通过这个工具来解密,可以顺便试试看。
解密使用的命令为:
DYLD_INSERT_LIBRARIES=/home/jerry/dumpdecrypted.dylib /var/mobile/Applications/6B3DFE51-2BD1-43E8-83D9-7D95062637FD/imeituan.app/imeituan
我在iOS6.1.6的环境上使用是没有问题的。
但iOS7的环境下就会出错,没有7的环境无法验证。
snakeninny给出的分析报告在这里:
技术心得] 防止tweak依附,App有高招;破解App保护,tweak留一手
http://bbs.iosre.com/t/tweak-app-app-tweak/438
里面提到有3中原因会导致DYLD_INSERT_LIBRARIES指定的动态库不被加载:

  1. 可执行文件被setuid或setgid了;
  2. 可执行文件含有__RESTRICT/__restrict这个section;
  3. 可执行文件被签了某个entitlements。

用IDA观察V7S这份mach-o可以看到有一个LC_SEGMENT后面带上了一个叫做
HEADER:00004988 ; LC_SEGMENT - segment of this file to be mapped
HEADER:00004988 segment_command <1, 0x7C, “__RESTRICT”, 0xB8C000, 0, 0xB7C000, 0, 3, \ ; LC_SEGMENT - segment of this file to be mapped
HEADER:00004988 3, 1, 0>
HEADER:000049C0 ; Sections
HEADER:000049C0 stru_49C0 section <"__restrict", “__RESTRICT”, 0xB8C000, 0, 0xB7C000, 0, 0, 0, 0, 0, 0>

readmacho也可以看到segname[__RESTRICT] sectname[__restrict]
(04/49) cmd [00000001] size[124]

LC_SEGMENT:
segname [__RESTRICT]
vmaddr [00b8c000] vmsize[00000000]
fileoff [00b7c000] filesize[00000000]
maxprot[00000003] initprot[00000003]
nsects [1]
flags [00000000]
sections:
segname[__RESTRICT] sectname[__restrict]
addr [00b8c000] size[00000000] offset[00b7c000] align[0]
reloff [00000000] flags[00000000]
type[0] [regular section]
attributes[00000000]
no section attributes ?
所以他属于上面描述的第2 种情况。
解决的办法就是把__RESTRICT和__restrict改成别的名字就可以了**。**
修改字符串导致mach-o的md5值被更改,导致Apple签名失效。
使用respring,这是一个GUI工具,打开点击里面的按钮会导致springboard重启。
猜测是重启后,之前闪退的程序,这时可以运行,未验证!!!

**下面分析dumpdecrypted.dylib的实现
**先分析输入输出。
decrypted前的内容:
(14/49) cmd [00000021] size[20]
LC_ENCRYPTION_INFO
cryptoff[00004000], cryptsize[10207232], cryptid[1]

decrypted后的内容:
(14/49) cmd [00000021] size[20]
LC_ENCRYPTION_INFO
cryptoff[00004000], cryptsize[10207232], cryptid[0]

Note:
一个thin mach-o文件只包含一个LC_ENCRYPTION_INFO(LC_ENCRYPTION_INFO_64)区域?

分析:
cryptoff[00004000] 是相对于mach_header的偏移地址。
mach_header_addr + cryptoff + cryptsize = 9c4000 刚好到了__TEXT segment的尾部。
__dof_RACCompou:009C3FFE
__nl_symbol_ptr:009C4000
他说明的意思是加密的数据范围为整个 __TEXT segment。
cryptid[1]在decrypted后变成cryptid[0],1说明内容被加密,0说明内容没有被加密。

观察dumpdecrypted.dylib解密流程的执行结果:

DYLD_INSERT_LIBRARIES=/home/jerry/dumpdecrypted.dylib /var/mobile/Applications/6B3DFE51-2BD1-43E8-83D9-7D95062637FD/imeituan.app/imeituan

mach-o decryption dumper
DISCLAIMER: This tool is only meant for security research purposes, not for application crackers.
+] detected 32bit ARM binary in memory.
+] offset to cryptid found: @0x47b50(from 0x47000) = b50
+] Found encrypted data at address 00004000 of length 10207232 bytes - type 1.
+] Opening /private/var/mobile/Applications/6B3DFE51-2BD1-43E8-83D9-7D95062637FD/imeituan.app/imeituan for reading.
+] Reading header
+] Detecting header type
+] Executable is a FAT image - searching for right architecture
+] Correct arch is at offset 16384 in the file
+] Opening imeituan.decrypted for writing.
+] Copying the not encrypted start of the file
+] Dumping the decrypted data into the file
+] Copying the not encrypted remainder of the file
+] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset 4b50
+] Closing original file
+] Closing dump file
**
分析:**
offset to cryptid found: @0x47b50(from 0x47000) = b50
0x47000是PIE导致的mach-o的base addr
0x47b50是内存中cryptid的offset
b50是两者的差值,也就是在mach-o文件中的offset
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000B40 21 00 00 00 14 00 00 00 00 40 00 00 00 C0 9B 00 ! @ 罌
00000B50 01 00 00 00
Found encrypted data at address 00004000 of length 10207232 bytes - type 1.
这个00004000、10207232、1是从LC_ENCRYPTION_INFO解析出的值。
这个1说明即使内存上的信息已经解完了,也并不会去更改cryptid的值。
+] Opening /private/var/mobile/Applications/6B3DFE51-2BD1-43E8-83D9-7D95062637FD/imeituan.app/imeituan for reading.
+] Reading header
+] Detecting header type
+] Executable is a FAT image - searching for right architecture
+] Correct arch is at offset 16384 in the file

打开原始mach-o文件,读文件头,发现是fat格式,于是找出对应的arch的mach-o文件在fat文件中偏移0x4000(16384)
+] Opening imeituan.decrypted for writing.
在命令执行的当前目录创建.decrypted文件。
+] Copying the not encrypted start of the file
产生的decrypted文件分3块区域:

  1. 位于encryped data前未被加密的数据
    /* calculate address of beginning of crypted data */
    n = fileoffs + eic->cryptoff;
  2. 加密的数据
  3. 剩下的数据(__TEXT之外还有很多信息,当然还可能有别的arch的mach-o)
    [table]
    [tr][td]n = fileoffs + eic->cryptoff;[/td][/tr]
    [tr][td]restsize = lseek(fd, 0, SEEK_END) - n - eic->cryptsize;
    [/td][/tr]
    [/table]
    这里复制第一部分
    +] Dumping the decrypted data into the file
    这里从内存中dump出第二部分,写入输出文件
    r = write(outfd, (unsigned char *)pvars->mh + eic->cryptoff, eic->cryptsize);
    if (r != eic->cryptsize) {
    printf("-] Error writing file\n");
    _exit(1);
    }

    pvars->mh + eic->cryptoff可以看出这个offset是从mach-o_header开始算的。对应IDA(或者NOPIE)就是0x4000+0x4000=0x8000,这里解释了前面少了0x4000的问题。
    再次说明加密的范围为整个__TEXT segment。
    而且编解码是等长的。
    +] Copying the not encrypted remainder of the file
    复制第三部分
    +] Setting the LC_ENCRYPTION_INFO->cryptid to 0 at offset 4b50
    将cryptid设置成0告诉加载器,我们是不加密的哦。
    +] Closing original file
    +] Closing dump file

    最后扫尾关闭两个文件
    还有一点要注意的是程序里最后调用了这个_exit(1);
    这样做是为了让imeituan进程结束,返回到cli。

Note:

  1. IDA上看到的load command LC_ENCRYPTION_INFO信息:
    HEADER:00004B40 ; LC_ENCRYPTION_INFO - encrypted segment information
    HEADER:00004B40 encryption_info_command <0x21, 0x14, 0x4000, 0x9BC000, 1> ;
  2. readmacho给出的__TEXT segment里所包含的sections:
    (02/49) cmd [00000001] size[872]

LC_SEGMENT:
segname [__TEXT]
vmaddr [00004000] vmsize[009c0000]
fileoff [00000000] filesize[009c0000]
maxprot[00000005] initprot[00000005]
nsects [12]
flags [00000000]
sections:
segname [__TEXT] sectname[__text]
addr [0000a2f0] size[00843968] offset[000062f0] align[4]
reloff [00000000] flags[80000400]
type[0] [regular section]
attributes[80000400]
section contains only true machine instructions
segname [__TEXT] sectname[__picsymbolstub4__TEXT]
addr [0084dc58] size[00003d10] offset[00849c58] align[2]
reloff [00000000] flags[80000408]
type[8] [section with only symbol stubs, byte size of stub in the reserved2 field]
attributes[80000400]
section contains only true machine instructions
segname [__TEXT] sectname[__stub_helper]
addr [00851968] size[00002a30] offset[0084d968] align[2]
reloff [00000000] flags[80000400]
type[0] [regular section]
attributes[80000400]
section contains only true machine instructions
segname [__TEXT] sectname[__cstring]
addr [008543a0] size[00080b0a] offset[008503a0] align[4]
reloff [00000000] flags[00000002]
type[2] [section with only literal C strings]
attributes[00000000]
no section attributes ?
segname [__TEXT] sectname[__objc_methname]
addr [008d4eaa] size[0006fb9d] offset[008d0eaa] align[0]
reloff [00000000] flags[00000002]
type[2] [section with only literal C strings]
attributes[00000000]
no section attributes ?
segname [__TEXT] sectname[__ustring]
addr [00944a48] size[0000862e] offset[00940a48] align[1]
reloff [00000000] flags[00000000]
type[0] [regular section]
attributes[00000000]
no section attributes ?
segname [__TEXT] sectname[__objc_classname__TEXT]
addr [0094d076] size[0000cbb7] offset[00949076] align[0]
reloff [00000000] flags[00000002]
type[2] [section with only literal C strings]
attributes[00000000]
no section attributes ?
segname [__TEXT] sectname[__objc_methtype]
addr [00959c2d] size[000132ae] offset[00955c2d] align[0]
reloff [00000000] flags[00000002]
type[2] [section with only literal C strings]
attributes[00000000]
no section attributes ?
segname [__TEXT] sectname[__const]
addr [0096cee0] size[000481a0] offset[00968ee0] align[4]
reloff [00000000] flags[00000000]
type[0] [regular section]
attributes[00000000]
no section attributes ?
segname [__TEXT] sectname[__gcc_except_tab__TEXT]
addr [009b5080] size[0000e91c] offset[009b1080] align[2]
reloff [00000000] flags[00000000]
type[0] [regular section]
attributes[00000000]
no section attributes ?
segname [__TEXT] sectname[__dof_RACSignal]
addr [009c399c] size[0000037b] offset[009bf99c] align[0]
reloff [00000000] flags[0000000f]
type[15] [section contains DTrace Object Format]
attributes[00000000]
no section attributes ?
segname [__TEXT] sectname[__dof_RACCompou]
addr [009c3d17] size[000002e8] offset[009bfd17] align[0]
reloff [00000000] flags[0000000f]
type[15] [section contains DTrace Object Format]
attributes[00000000]
no section attributes ?

最后来解释这个问题:
例如为什么动态库的entry会有这个ProgramVars参数呢?
void dumptofile(int argc, const char **argv, const char **envp, const char **apple, struct ProgramVars *pvars)
是谁决定了init函数就是会有这些参数呢?
gdb给出了线索:
(gdb) bt
#0 0x00cbc4e2 in dumptofile ()
#1 0x2febd5b8 in __dyld__ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE ()
(gdb) i r
r0 0x4 4
r1 0x2fd67778 802584440
r2 0x2fd6778c 802584460
r3 0x2fd677d4 802584532
r4 0x2fed114c 804065612
(gdb) x /10wx $sp
0x2fd64100: 0x00000000 0x00000000 0x00000000 0x00000000
0x2fd64110: 0x00000000 0x00000000 0x00000000 0x00000000
0x2fd64120: 0x00000000 0x00000000
看第5个参数:struct ProgramVars *pvars
(gdb) x /10wx $r4
0x2fed114c <__dyld__ZN4dyld12gLinkContextE+96>: 0x0009c000 0x3c3c4168 0x3c3c416c 0x3c3c4170
0x2fed115c <__dyld__ZN4dyld12gLinkContextE+112>: 0x3c3c4174 0x2fed3624 0x00000000 0x00000000
0x2fed116c <__dyld__ZN4dyld12gLinkContextE+128>: 0x00000000 0x00000000

$ c++filt __ZN16ImageLoaderMachO18doModInitFunctionsERKN11ImageLoader11LinkContextE
ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&)
$ c++filt __ZN4dyld12gLinkContextE
dyld::gLinkContext

dylib的init函数dumptofile是由ImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&)调用的,通过地址0x2febd5b8
得知这是dyld里的函数:
2 dyld - 0x2feb0000 dyld Y Y /usr/lib/dyld at 0x2feb0000 (offset 0xb0000) with prefix “_dyld

苹果的代码告诉我们他的确为init函数准备了5个参数:
func(context.argc, context.argv, context.envp, context.apple, &context.programVars);

http://www.opensource.apple.com/source/dyld/dyld-195.6/src/ImageLoaderMachO.cpp

void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)
{
     if ( fHasInitializers ) {
#if __IPHONE_OS_VERSION_MIN_REQUIRED// <rdar://problem/8543820> verify initializers are in first segment for dylibs
if ( this->isDylib() && !fGoodFirstSegment ) {
               if ( context.verboseInit )
                    dyld::log("dyld: ignoring all initializers in %s\n", this->getPath());
               return;
          }
          uintptr_t textStart = this->segActualLoadAddress(0);
          uintptr_t textEnd = this->segActualEndAddress(0);
#endifconst uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds;
          const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)];
          const struct load_command* cmd = cmds;
          for (uint32_t i = 0; i < cmd_count; ++i) {
               if ( cmd->cmd == LC_SEGMENT_COMMAND ) {
                    const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
                    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) {
                         const uint8_t type = sect->flags & SECTION_TYPE;
                         if ( type == S_MOD_INIT_FUNC_POINTERS ) {
                              Initializer* inits = (Initializer*)(sect->addr + fSlide);
                              const uint32_t count = sect->size / sizeof(uintptr_t);
                              for (uint32_t i=0; i < count; ++i) {
                                   Initializer func = inits*;
#if __IPHONE_OS_VERSION_MIN_REQUIRED// <rdar://problem/8543820> verify initializers are in first segment for dylibs
if ( this->isDylib() && (((uintptr_t)func >= textEnd) || ((uintptr_t)func < textStart)) ) {
                                        if ( context.verboseInit )
                                             dyld::log("dyld: ignoring out of bounds initializer function %p in %s\n", func, this->getPath());
                                   }
                                   else {
#endifif ( context.verboseInit )
                                             dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath());
                                        func(context.argc, context.argv, context.envp, context.apple, &context.programVars);
#if __IPHONE_OS_VERSION_MIN_REQUIRED     
                                   }
#endif
                              }
                         }
                    }
               }
               cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
          }
     }
}

而最后这个参数**&context.programVars**http://www.opensource.apple.com/source/dyld/dyld-195.6/src/ImageLoader.h

struct ProgramVars
{
     const void*          mh;
     int*               NXArgcPtr;
     const char***     NXArgvPtr;
     const char***     environPtr;
     const char**     __prognamePtr;
};

struct LinkContext {
          ImageLoader*     (*loadLibrary)(const char* libraryName, bool search, const char* origin, const RPathChain* rpaths);
          void               (*terminationRecorder)(ImageLoader* image);
          bool               (*flatExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image);
          bool               (*coalescedExportFinder)(const char* name, const Symbol** sym, const ImageLoader** image);
          unsigned int     (*getCoalescedImages)(ImageLoader* images]);
          void               (*undefinedHandler)(const char* name);
          MappedRegion*     (*getAllMappedRegions)(MappedRegion*);
          void *               (*bindingHandler)(const char *, const char *, void *);
          void               (*notifySingle)(dyld_image_states, const ImageLoader* image);
          void               (*notifyBatch)(dyld_image_states state);
          void               (*removeImage)(ImageLoader* image);
          void               (*registerDOFs)(const std::vector<DOFInfo>& dofs);
          void               (*clearAllDepths)();
          unsigned int     (*imageCount)();
          void               (*setNewProgramVars)(const ProgramVars&);
          bool               (*inSharedCache)(const char* path);
          void               (*setErrorStrings)(unsigned errorCode, const char* errorClientOfDylibPath,
                                                  const char* errorTargetDylibPath, const char* errorSymbol);
#if SUPPORT_OLD_CRT_INITIALIZATIONvoid               (*setRunInitialzersOldWay)();
#endif
          BindingOptions     bindingOptions;
          int                    argc;
          const char**     argv;
          const char**     envp;
          const char**     apple;
          const char*          progname;
          ProgramVars          programVars;
          ImageLoader*     mainExecutable;
          const char*          imageSuffix;
          const char**     rootPaths;
          PrebindMode          prebindUsage;
          SharedRegionMode sharedRegionMode;
          bool               dyldLoadedAtSameAddressNeededBySharedCache; 
          bool               preFetchDisabled;
          bool               prebinding;
          bool               bindFlat;
          bool               linkingMainExecutable;
          bool               startedInitializingMainExecutable;
          bool               processIsRestricted;
          bool               verboseOpts;
          bool               verboseEnv;
          bool               verboseMapping;
          bool               verboseRebase;
          bool               verboseBind;
          bool               verboseWeakBind;
          bool               verboseInit;
          bool               verboseDOF;
          bool               verbosePrebinding;
          bool               verboseCoreSymbolication;
          bool               verboseWarnings;
          bool               verboseRPaths;
          bool               verboseInterposing;
     };


是LinkContext里第24(0based)个成员。24*4=96和<__dyld__ZN4dyld12gLinkContextE+96>匹配。

Note:
1.高版本的dyld,例如dyld-239.4,LinkContext结构体里有多出来几个成员,这样offset就不会等于96了。
2. 我一直图省事基本这个init函数都没有使用过这5个参数,我看到很多代码也是这样写的。attribute((constructor)) void dylib_init(void)这5个参数都是通过寄存器传递的,没有用到堆栈,也就没有平衡堆栈的问题了。*


closed #2