mac app 逆向之动态注入
2020年05月04日
跑通了一个最简单的动态注入的 demo,记录一下以防忘记。
原理相关
在 Mac 上,应用最终会 build 成可执行的二进制文件,而逆向就是为了改变应用原本的功能。这里实现的一种方法是:通过编写动态链接库将额外逻辑注入到应用中。 本文用到了 insert_dylib 注入工具:
Command line utility for inserting a dylib load command into a Mach-O binary.
这个工具应该是可以直接修改应用二进制文件,使应用加载时加载自己编写的动态库。
Objective-C 的首选 hook 方案为 Method Swizzle。
步骤
先用 objective-c 实现一个最简单的应用
Person.h
1#import <Foundation/Foundation.h> 2 3NS_ASSUME_NONNULL_BEGIN 4 5@interface Person : NSObject 6 7@property (nonatomic) NSString *name; 8@property (nonatomic) NSNumber *age; 9 10@end 11 12NS_ASSUME_NONNULL_END
Person.m
1#import "Person.h" 2 3@implementation Person 4 5- (NSString *)description 6{ 7 return [NSString stringWithFormat:@"<%@: %@>", self.name, self.age]; 8} 9 10@end
main.m
1#import <Foundation/Foundation.h> 2#import "Person.h" 3 4int main(int argc, const char * argv[]) { 5 @autoreleasepool { 6 NSLog(@"hello liqiang"); 7 8 Person *p = [[Person alloc] init]; 9 [p setName:@"liqiang"]; 10 [p setAge:@26]; 11 12 NSLog(@"person: %@", p); 13 } 14 return 0; 15}
编译执行后会输出:
2020-05-04 17:09:19.146 hello[45102:759505] hello liqiang
2020-05-04 17:09:19.147 hello[45102:759505] person: <liqiang: 26>
hook 项目
新建 Library
类型的项目。下面的 hookCommon
相关代码实现的功能是替换应用中对应的方法为自己编写的方法,应该是 Method Swizzl
。
hookCommon.h
1#import <Foundation/Foundation.h> 2#import <objc/runtime.h> 3 4@interface hookCommon : NSObject 5 6/** 7 替换对象方法 8 9 @param originalClass 原始类 10 @param originalSelector 原始类的方法 11 @param swizzledClass 替换类 12 @param swizzledSelector 替换类的方法 13 */ 14void hookMethod(Class originalClass, SEL originalSelector, Class swizzledClass, SEL swizzledSelector); 15 16/** 17 替换类方法 18 19 @param originalClass 原始类 20 @param originalSelector 原始类的类方法 21 @param swizzledClass 替换类 22 @param swizzledSelector 替换类的类方法 23 */ 24void hookClassMethod(Class originalClass, SEL originalSelector, Class swizzledClass, SEL swizzledSelector); 25 26@end
hookCommon.m
1#import "hookCommon.h" 2 3@implementation hookCommon 4 5/** 6 替换对象方法 7 8 @param originalClass 原始类 9 @param originalSelector 原始类的方法 10 @param swizzledClass 替换类 11 @param swizzledSelector 替换类的方法 12 */ 13void hookMethod(Class originalClass, SEL originalSelector, Class swizzledClass, SEL swizzledSelector) { 14 Method originalMethod = class_getInstanceMethod(originalClass, originalSelector); 15 Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector); 16 if (originalMethod && swizzledMethod) { 17 method_exchangeImplementations(originalMethod, swizzledMethod); 18 } 19} 20 21/** 22 替换类方法 23 24 @param originalClass 原始类 25 @param originalSelector 原始类的类方法 26 @param swizzledClass 替换类 27 @param swizzledSelector 替换类的类方法 28 */ 29void hookClassMethod(Class originalClass, SEL originalSelector, Class swizzledClass, SEL swizzledSelector) { 30 Method originalMethod = class_getClassMethod(originalClass, originalSelector); 31 Method swizzledMethod = class_getClassMethod(swizzledClass, swizzledSelector); 32 if (originalMethod && swizzledMethod) { 33 method_exchangeImplementations(originalMethod, swizzledMethod); 34 } 35} 36 37@end
然后替换掉 Person
类中的 description
方法,功能是调用原逻辑前先打印一行自己的逻辑代码。
Person_hook.h
1#import <Foundation/Foundation.h> 2 3NS_ASSUME_NONNULL_BEGIN 4 5@interface NSObject (PersonHook) 6 7+ (void) hookPerson; 8 9@end 10 11NS_ASSUME_NONNULL_END 12
Person_hook.m
#import "Person_hook.h"
#import "hookCommon.h"
@implementation NSObject (PersonHook)
// hook person description
- (NSString *) personDescription
{
NSLog(@"in personDescription");
return [self personDescription];
}
+ (void) hookPerson
{
NSLog(@"in hookPerson");
hookMethod(objc_getClass("Person"), @selector(description), [self class], @selector(personDescription));
}
@end
main.m
1#import <Foundation/Foundation.h> 2#import "Person_hook.h" 3 4static void __attribute__((constructor)) initialize(void) { 5 NSLog(@"hook common inject success!"); 6 7 [NSObject hookPerson]; 8} 9
__attribute__((constructor))
修饰函数之后,便会在应用加载前执行,也就是相当于我们注入代码的逻辑入口。上面逻辑中会用方法 personDescription
替换 Person
类中的 description
方法,注意此方法中会调用自己,其实不会造成死循环,因为运行时两个方法已经交换了,所以是调用的原始逻辑。
实验
将上面 hook 项目 build 之后,得到 libhookCommon.dylib
,然后将此动态库和第一步应用二进制文件 hello
放在同一目录,加上用到 insert_dylib
注入工具,然后执行注入命令:
1./insert_dylib libhookCommon.dylib hello
生成 hello_patched
程序,运行此程序:
1./hello_patched
得到的输出为:
2020-05-04 17:23:18.461 hello_patched[45156:766500] hook common inject success!
2020-05-04 17:23:18.461 hello_patched[45156:766500] in hookPerson
2020-05-04 17:23:18.461 hello_patched[45156:766500] hello liqiang
2020-05-04 17:23:18.461 hello_patched[45156:766500] in personDescription
2020-05-04 17:23:18.462 hello_patched[45156:766500] in personDescription
2020-05-04 17:23:18.462 hello_patched[45156:766500] person: <liqiang: 26>
通过输出可以看出已经加入了自己的逻辑进来了。
思考
一般要想修改某个应用的逻辑功能,肯定是拿不到源代码的,所以通过二进制代码理解程序逻辑这一步,才是难点中的难点。必须要了解汇编,一些逆向的工具,还需要经验等等。而本文也仅是作为新手的我所能了解到的一些皮毛而已。