MachO文件转dylib测试
看了某大佬的文章:
大佬是这么说这个库的有点的
好处主要有下面几点:
1.可以直接调用别人的算法
逆向分析别人的应用时,可能会遇到一些私有算法,如果搞不定的话,直接拿来用就好。
2.掌控程序的控制权
程序的主体是自己的App,第三方应用的代码只是以动态库的形式加载,主要的控制权还是在我们自己手里,所以可以直接绕过应用的检测代码(文章最后有关于这部分攻防的讨论)。
3.同个进程内加载多个应用
重签名打包毕竟只能是原来的应用,但是如果是动态库的话,可以同时加载多个应用到进程内了,比如你想同时把美图秀秀和饿了么加载进来也是可以的(秀秀不饿,想想去年大众点评那个APPmixer的软广 - -! )。
当时没细想,你把其他应用打包到一个宿主程序,那不是可以切换嘛!
先看看大佬的转化原理:
应用和动态库的异同
我们要把应用转成动态库,首先要知道这两者之前有什么相同与不同,有相同的才存在转换的可能,而不同之处就是我们要重点关注的了。
image.png
可执行文件和动态库都是标准的 Mach-O 文件格式,两者的文件头部结构非常类似,特别是其中的代码段(TEXT),和数据段(DATA)结构完全一致,这也是后面转换工作的基础(这个前面说静态库和动态库的本质的时候说过动态库可以认为是类似macho文件的文件)
不同点
1.头部的文件类型
一个是 MH_EXECUTE 可执行文件, 一个是 MH_DYLIB 动态库, 还有各种头部的Flags,要特别留意下可执行文件中Flags部分的 MH_PIE 标志。
image.png
2.动态库文件中多一个类型为 LC_ID_DYLIB 的 Load Command, 作用是动态库的标识符,一般为文件路径。路径可以随便填,但是这部分必须要有,是codesign的要求.
image.png
3.可执行文件会多出一个 PAGEZERO段,动态库中没有。这个段开始地址为0(NULL指针指向的位置),是一个不可读、不可写、不可执行的空间,能够在空指针访问时抛出异常。这个段的大小,32位上是0x4000,64位上是4G。这个段的处理也是转换工作的重点之一,之前有人尝试转换,不成功就是因为没有处理好 PAGEZERO.
image.png
实现细节
修改文件类型
第一步是修改文件的头部信息,把文件类型从可执行文件修改成动态库,同时把一些Flags修改好。
这里一个比较关键的Flag是可执行文件中的 MH_PIE 标志位,(position-independent executable)。
这个标志位,表明可执行文件能够在内存中任意位置正确地运行,而不受其绝对地址影响的特性,这一特性是动态库所必须的一个特性。没有这个标志位的可执行文件是没有办法转换成动态库的。iOS系统中,arm64架构下,目前这个标志位是必须的,不然程序无法运行(系统的安全性要求),但是armv7架构下,可以没有这个标志位,所以支付宝armv7版本的可执行文件是不能转成动态库的,就是这个原因。不过所有的arm64的应用都是可以转换的,后面演示时用的支付宝是arm64架构的。
头部中添加 LC_ID_DYLIB
直接在文件头部中按照文档格式插入一个Load Command,并填入合适的数据。这里要注意下插入内容的字节数必须是8字节对齐的。
修改PAGEZERO段
这部分是最重要的一部分,因为arm64上这个段的大小有4G,直接往内存中加载,会提示没有足够的连续的地址空间,所以必须要调整这个段的大小,而要调整 PAGEZERO 这个段的大小, 又会引起一连串的地址空间的变化,所以不能盲目的直接改,必须结合dyld的源码来对应修改。(注意这里不能直接把 PAGEZERO 这个段给去掉,也不能直接把大小调成0,因为涉及到dyld的rebase操作,详细看后面)
1. 所有段的地址都要重新计算
单纯减少 PAGEZERO 段的占用空间,作用不大,因为dyld加载动态库的时候,要求是所有的段一起进行mmap(详细可以查看dyld源码的ImageLoaderMachO::assignSegmentAddresses函数),所以必须把接下来所有的段的地址都重新计算一次。
同时要保证,前后两个段没有地址空间重叠,并且每个段都是按0x4000对齐。因为 PAGEZERO 是所有段中的第一个,所以可以直接把 PAGEZERO 的大小调整到0x4000,然后后面每一个段都按顺序依次减少同样大小(0xFFFFC000 = 0x100000000 - 0x4000),同时能保证每个段在文件内的偏移量不变。
修改前:
image.png修改后:
image.png
- 对动态库进行rebase操作
这里的rebase是系统为了解决动态库虚拟内存地址冲突,在加载动态库时进行的基地址重定位操作。
这一步操作是整个流程里最重要的,因为按照前面的操作,整个文件地址空间已经发生了变化,如果dyld依然按照原来的地址进行rebase,必然会失败。
那么rebase操作需要做哪些工作呢?
相关的信息储存在 Mach-O 文件的 LINKEDIT 段中, 并由 LC_DYLD_INFO_ONLY 指定 rebase info 在文件中的偏移量
image.png image.png红框里那些Pointer的意思是说,在内存地址为 0x367C698 的地方有一个指针,这个指针需要进行rebase操作, 操作的内容就是和前面调整地址空间一样,每个指针减去 0xFFFFC000。
image.png- 为什么不能直接去掉PAGEZERO这个段
这个原因要涉及到文件中rebase信息的储存格式,上面的图中,可以看出rebase要处理的是一个个指针,但是实际上这些信息在文件中并不是以指针数组的形式存在,而是以一连串rebase opcode的形式存在,上面看到的一个个指针其实是 Mach O View 这个软件帮我们将opcode整理得到的。
image.png这些opcode中有一种操作比较关键,REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB。
image这个opcode的意思是, 接下去需要调整文件的中的第2个段,就是图中segment(2)所表示的含义。
所以说,如果把PAGEZERO这个段给去掉了,文件中各个段的序号也就都错位了,与rebase中的信息就对应不上了。
而且把这个段大小改为0,也是不行的,因为dyld在加载的过程中,会重新自动过滤掉大小为0的段,也会导致同样的段序号错位的问题。(有兴趣的同学可以看下dyld的源码,在ImageLoaderMachO类的构造函数里)
这就是为什么必须要保留PAGEZERO这个段,同时大小不能为0。
修改符号表
正常的线上应用是不存在符号表的,但是如果你之前用了我的另一个工具 restore-symbol 来恢复符号表的话,这个地方自然也需要做一些处理,处理方法同rebase类似,减去0xFFFFC000.
不过有一些符号需要单独过滤,比如这个:
image这个radr://5614542是个什么神奇的符号呢,google就能发现,念茜的twitter上提过这个奇葩的符号。(女神果然是女神, 棒~ 😂)
上面就是大佬说的原理和操作:
我这边激动的也操作了一下
1.下载源码编译:
git clone --recursive https://github.com/tobefuturer/app2dylib.git
cd app2dylib && make
./app2dylib
2.创建一个test工程和另一个准备暴露的工程.
要转化dylib的工程
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor cyanColor];
NSTimer*time = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(logino) userInfo:nil repeats:YES];
[time fire];
[[NSRunLoop mainRunLoop] addTimer:time forMode:NSRunLoopCommonModes];
}
- (void)logino{
NSLog(@"我看你死不死");
}
- (void)login:(NSInteger)tag{
//self.view.backgroundColor = [UIColor yellowColor];
[UIApplication sharedApplication].windows[0].rootViewController = YViewController.new;
NSLog(@"就是要搞yy");
}
然后编译成.app然后复制出来 macho文件,然后
./app2dylib 去操作
./app2dylib/app2dylib -o /Users/电脑/Desktop/逆向研究/mutiO/alive.dylib /Users/电脑/Desktop/逆向研究/mutiO/Alive
最后把生成的alive.dylib 引入到工程test工程.
Run Script里的代码(目的是为了对dylib进行签名)
cd ${BUILT_PRODUCTS_DIR}
cd ${FULL_PRODUCT_NAME}
/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --timestamp=none libAlipayApp.dylib
然后
调用
NSString * dylibName = @"alive";
NSString * path = [[NSBundle mainBundle] pathForResource:dylibName ofType:@"dylib"];
if (dlopen(path.UTF8String, RTLD_NOW) == NULL){
NSLog(@"dlopen failed ,error %s", dlerror());
return 0;
};
去反射调用你的动态库方法
确实是可以.
再试一下游戏.
试了一个游戏,要导入的FrameWork有点多配置起来麻烦不少.
然后想了一下,没有资源文件,没有图片等等这个动态库有什么用呢,很多动态库里面的方法是依赖配置文件的,同时也有很多平台的接口调用都是依赖配置文件的(比如加速器).直接调用他的方法两个不是在一个线程的.加速器也没有效果.那么集成进去多个应用程序(严格来说不是应用程序,其实也就是macho文件)
所以我感觉这个除了理解一下原理没什么用啊.
(仅做笔记,欢迎探讨)