mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
feature: App 逆向防护
This commit is contained in:
264
Chapter1 - iOS/1.140.md
Normal file
264
Chapter1 - iOS/1.140.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Aspects
|
||||
|
||||
> Aspects 核心原理涉及3个技术点:
|
||||
>
|
||||
> - objc_msgForward(触发消息转发机制)
|
||||
> - NSInvocation
|
||||
> - block 的本质
|
||||
|
||||
|
||||
## 函数指针、指针函数的区别
|
||||
|
||||
定义不同:
|
||||
|
||||
- 函数指针:本质是一个指针,该指针指向一个函数
|
||||
- 指针函数:本质是一个函数,函数的返回值是一个指针类型
|
||||
|
||||
写法不同
|
||||
|
||||
- 函数指针:`int (*fun)(int x,int y)`
|
||||
- 指针函数:`int* fun(int x,int y)`
|
||||
|
||||
用法不同:
|
||||
|
||||
- 函数指针
|
||||
|
||||
```c++
|
||||
typedef int (*FuncPtr)(int, int);
|
||||
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
FuncPtr addPtr = add;
|
||||
int result = addPtr(3, 2);
|
||||
```
|
||||
|
||||
- 指针函数
|
||||
|
||||
```c++
|
||||
int (*getAddFunction())(int, int) {
|
||||
return add;
|
||||
}
|
||||
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
int (*addPtr)(int, int) = getAddFunction();
|
||||
int result = addPtr(3, 2);
|
||||
```
|
||||
|
||||
|
||||
|
||||
## block 本质
|
||||
|
||||
block 详细探索步骤请查看这篇文章 [block 底层原理](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.89.md)。接下去查看建议版本的分析。
|
||||
|
||||
第一步:编写一个基础 block
|
||||
|
||||
第二步:用 `xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 ViewController.m -o ViewController-arm64.cpp` 转成 c++ 查看原理
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AspectsBlockExplore.png" style="zoom:30%" />
|
||||
|
||||
分析:可以发现 block 本质就是结构体,和 OC 对象一样,也有 isa 指针。block 传递进去的方法,被包装成 block 的成员变量,是一个叫做 FuncPtr 的函数指针了。
|
||||
|
||||
是不是我们可以按照系统定义,来构造一个 struct,承接一个 block,然后发起调用呢?
|
||||
|
||||
```objective-c
|
||||
typedef NS_OPTIONS(int, AspectBlockFlags) {
|
||||
AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
|
||||
AspectBlockFlagsHasSignature = (1 << 30)
|
||||
};
|
||||
|
||||
typedef struct AspectBlock {
|
||||
__unused Class isa;
|
||||
AspectBlockFlags Flags;
|
||||
__unused int Reserved;
|
||||
void (__unused *invoke)(struct AspectBlock *block, ...);
|
||||
|
||||
struct {
|
||||
size_t reserved;
|
||||
size_t Block_size;
|
||||
void (*copy)(void *dst, const void *src);
|
||||
void (*dispose)(const void *);
|
||||
} *descriptor;
|
||||
} *AspectBlockRef;
|
||||
|
||||
void(^printBlock)(NSString *) = ^void(NSString *msg) {
|
||||
NSLog(@"%@", msg);
|
||||
};
|
||||
printBlock(@"Hello world");
|
||||
|
||||
struct AspectBlock *fakeBlock = (__bridge struct AspectBlock *)printBlock;
|
||||
((void (*)(void *, NSString *))fakeBlock->invoke)(fakeBlock, @"Hello world");
|
||||
```
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AspectMockBlock.png" style="zoom:30%" />
|
||||
|
||||
思考:我们目前已经用自定义的 struct 来承接了 block 并成功执行了。能否用 NSInvocation 来触发 block?
|
||||
|
||||
|
||||
|
||||
## NSInvocation 触发 block
|
||||
|
||||
一个方法需要成功调用并执行需要3要素:
|
||||
|
||||
- 方法名称 `SEL`
|
||||
- 方法签名(参数个人、参数类型、返回值类型等信息) `Method Type Encoding`
|
||||
- 方法地址、方法实现 `IMP`
|
||||
|
||||
如何从自定义的 block 结构体中获取这些信息呢?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AspectsProcess.png" style="zoom:60%" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AspectsIdentifier:每做一次方法交换,都会转换为一次 AspectsIdentifier。是核心逻辑。
|
||||
|
||||
|
||||
|
||||
以一个例子作为源码探索切入口,点击跳转到源码中
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AspectsIdentifierModule.png" style="zoom:30%" />
|
||||
|
||||
可以看到给 NSObject 添加分类,核心 2 个 API。一个对象方法、一个类方法:
|
||||
|
||||
```objective-c
|
||||
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
|
||||
withOptions:(AspectOptions)options
|
||||
usingBlock:(id)block
|
||||
error:(NSError **)error;
|
||||
|
||||
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
|
||||
withOptions:(AspectOptions)options
|
||||
usingBlock:(id)block
|
||||
error:(NSError **)error;
|
||||
```
|
||||
|
||||
内部都走到 `aspect_add` 方法中。其中都会生成 `AspectIdentifier` ,看看是如何生成的?
|
||||
|
||||
|
||||
|
||||
第一步:生成 block 签名。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AspectsBlockSignature.png" style="zoom:30%" />
|
||||
|
||||
第二步:因为我们通过 AOP 给原始方法添加了 block,最后的效果是既可以调用原始方法,又可以调用 block 添加的代码。实现的前提是什么?
|
||||
|
||||
比较 block 和 hook 类的方法的签名信息需要 Match。具体逻辑查看注释。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AspectsSignatureCompare.png" style="zoom:30%" />
|
||||
|
||||
```shell
|
||||
(lldb) po blockSignature
|
||||
<NSMethodSignature: 0x600003829c20>
|
||||
number of arguments = 2
|
||||
frame size = 224
|
||||
is special struct return? NO
|
||||
return value: -------- -------- -------- --------
|
||||
type encoding (v) 'v'
|
||||
flags {}
|
||||
modifiers {}
|
||||
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
|
||||
memory {offset = 0, size = 0}
|
||||
argument 0: -------- -------- -------- --------
|
||||
type encoding (@) '@?'
|
||||
flags {isObject, isBlock}
|
||||
modifiers {}
|
||||
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
|
||||
memory {offset = 0, size = 8}
|
||||
argument 1: -------- -------- -------- --------
|
||||
type encoding (@) '@"<AspectInfo>"'
|
||||
flags {isObject}
|
||||
modifiers {}
|
||||
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
|
||||
memory {offset = 0, size = 8}
|
||||
conforms to protocol 'AspectInfo'
|
||||
```
|
||||
|
||||
|
||||
|
||||
OC 方法签名和 block 方法签名是有区别的
|
||||
|
||||
- 比如在 Aspects 框架中,block 方法签名的参数个数是比 oc 方法参数个数少的。oc 方法自带 `id self, SEL _cmd` 2个参数。
|
||||
- 都使用相同的类型编码系统,但是 block 签名可能包含额外的信息,例如捕获的变量类型。比如上面的 block 方法签名的最后一个参数 `'@"<AspectInfo>"'`,其中的 `AspectInfo` 就代表捕获的变量类型。
|
||||
|
||||
比如2个不带参数的 OC 方法签名和 block 方法签名:
|
||||
|
||||
- oc 方法签名:`v@:` = `v` + `@` + `:`,返回值 `void`、参数1 `@` 代表对象、参数2 `:` 代表 SEL 类型
|
||||
|
||||
- block 方法签名:`v@?` = `v` + `@?`,返回值 `void`、参数1 `@?` 代表既是对象,又是 block
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AspectInvokeBlock.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
## objc_msgForward
|
||||
|
||||
骚操作:
|
||||
|
||||
- 将待 hook 的方法,和 `objc_msgForward` 进行交换。 `objc_msgForward` 不管对象有没有实现,都会触发消息转发流程
|
||||
- 此时会走 Runtime 的 NSObject `forwardInvocation` 流程。且 Aspects 将 `forwardInvocation` 方法指向了 `__ASPECTS_ARE_BEING_CALLED__` 方法。
|
||||
|
||||
经历这么一波处理,hook 最后都守口到了 `__ASPECTS_ARE_BEING_CALLED__` 方法中。
|
||||
|
||||
前面研究过了 `AspectIdentifier` 的逻辑。接下去继续看看后续步骤。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AspectsHookForwardInvocation.png" style="zoom:30%" />
|
||||
|
||||
可以看到内部执行 `aspect_prepareClassAndHookSelector`,其内部会调用 `aspect_hookClass`,又会调用 `aspect_swizzleClassInPlace`,最后调用 `aspect_swizzleForwardInvocation` 方法。
|
||||
|
||||
```objective-c
|
||||
static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
|
||||
static void aspect_swizzleForwardInvocation(Class klass) {
|
||||
NSCParameterAssert(klass);
|
||||
// If there is no method, replace will act like class_addMethod.
|
||||
IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
|
||||
if (originalImplementation) {
|
||||
class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
|
||||
}
|
||||
AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
|
||||
}
|
||||
```
|
||||
|
||||
该方法将被 hook 对象的 `forwardInvocation:` 方法替换为 `__aspects_forwardInvocation:`。
|
||||
|
||||
回归头继续看下面的逻辑。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AspectsHookMethodWithObjcMsgForward.png" style="zoom:30%" />
|
||||
|
||||
`class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding)` 可以实现将被 hook 类的 hook 方法,替换为 `_objc_msgForward` 或者某些版本下需要的 `_objc_msgForward_stret`。
|
||||
|
||||
|
||||
|
||||
比如 ViewController 的 viewWillAppear hook 流程就是:
|
||||
|
||||
`[UIViewController viewWillAppear:]` -> `_objc_msgForwar` -> `[UIViewController forwardInvocation:]` -> `__ASPECTS_ARE_BEING_CALLED__`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 总结
|
||||
|
||||
Aspects 是 Runtime 使用的一个经典库,处理好核心逻辑后,也做了一些黑名单、线程安全等的保护。也有一些类似日志回放功能的处理。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AspectsProcessXmind.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user