feature: App 逆向防护

This commit is contained in:
杭城小刘
2024-07-15 20:03:01 +08:00
parent 13f7457be9
commit 83fefff66b
109 changed files with 2549 additions and 672 deletions

View File

@@ -24,7 +24,7 @@ NSTimer、CADisplayLink 的 基础 API `[NSTimer scheduledTimersWithTimeInterval
栈、堆、BSS、数据段、代码段
<img src="./../assets/iOS-MemoryLayout.png" style="zoom:50%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/iOS-MemoryLayout.png" style="zoom:50%" />
@@ -53,7 +53,7 @@ BSS段bss segment通常用来存储程序中未被初始化的全局变
代码段code segment编译之后的代码。通常是指用来存储程序可执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定并且内存区域通常属于只读某些架构也允许代码段为可写即允许修改程序。
![内存](./../assets/ram.png)
![内存](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ram.png)
上 Demo 验证
@@ -132,7 +132,7 @@ Tagged Pointer 格式下,指针值不再是有效抵制,而是表示值。
当对 TaggedPointer 数据调用方法的时候objc_msgSend 能识别出如果是 Tagged Pointer比如 NSNumber 的 intValue 方法,直接从指针提取数据,节省了调用开销。所以使用了 TaggedPointer 技术不仅节约内存空间,又能提高方法查找速度。
<img src="./../assets/TaggedPointerStructure.png" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerStructure.png" />
@@ -233,7 +233,7 @@ Tagged Pointer 也就是一个伪指针,对象的指针中存储的数据变
Demo
<img src="./../assets/TaggedPointerSaveMemoryUsage.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerSaveMemoryUsage.png" style="zoom:30%" />
在 64 位的 cpu 下,如果使用 Tagged Pointer 技术的话,则 NSNumber 对象的值直接存储在了指针中系统不会为其在堆上分配内存可以节省很多内存开销。此时NSNumber 对象的指针中存储的数据变成了 Tag + Data 的形式Tag 为特殊标记用于区分NSNumber、NSDate、NSString 等小内存对象的类型Data 为具体的值)。这样使用一个 NSNumber 对象只需要在栈中开辟 8 个字节的指针内存。当栈中 8 个字节的指针内存不够存储数据时,才会再将 NSNumber 对象存储到堆中
@@ -253,7 +253,7 @@ Demo
路径Xcode - Edit Scheme - Run - Arguments - Environment Variables - 添加环境变量 `OBJC_DISABLE_TAG_OBFUSCATION` 设置为 YES 即可。
<img src="./../assets/XcodeDisableTaggedPointerConfuse.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/XcodeDisableTaggedPointerConfuse.png" style="zoom:30%" />
@@ -422,7 +422,7 @@ _objc_decodeTaggedPointer_noPermute_withObfuscator(const void * _Nullable ptr, u
}
```
<img src="./../assets/TaggedPointerDecode.png" style="zoom:40%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerDecode.png" style="zoom:40%" />
@@ -430,7 +430,7 @@ _objc_decodeTaggedPointer_noPermute_withObfuscator(const void * _Nullable ptr, u
#### Tagged Pointer 与 isa
<img src="./../assets/TaggedPointerNSNumberDemo.png" style="zoom:40%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerNSNumberDemo.png" style="zoom:40%" />
通过参考 objc 源码,针对对象指针进行解密后发现:
@@ -461,7 +461,7 @@ b 也就是11二进制为 `1011`Tagged Pointer 中iOS 侧第一位是 T
3 区分数据类型。具体是什么数据类型,继续做个实验看看
<img src="./../assets/TaggedPointerDataTypeDemo.png" style="zoom:40%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerDataTypeDemo.png" style="zoom:40%" />
@@ -482,7 +482,7 @@ b 也就是11二进制为 `1011`Tagged Pointer 中iOS 侧第一位是 T
Objc 源码中NSInteger、NSUInteger 都是别名。初始化 NSNumber 的时候用的是 `NSNumber numberWithInteger:<#(NSInteger)#>`
<img src="./../assets/ObjcNSIntegerIsLong.png" style="zoom:30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ObjcNSIntegerIsLong.png" style="zoom:30%" >
@@ -560,7 +560,7 @@ Tagged Pointer 如何区分是较小的对象,比如 NSString、NSDate、NSNum
验证下
<img src="./../assets/TaggedPointerKind.png" style="zoom:30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerKind.png" style="zoom:30%" >
可以看到:
@@ -573,11 +573,11 @@ Tagged Pointer 如何区分是较小的对象,比如 NSString、NSDate、NSNum
下面是针对 double 包装成 NSNumber 的 Tagged Pointer 指针结构拆分:
<img src="./../assets/NSNumberTaggedPointerStructure.png" style="zoom:90%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSNumberTaggedPointerStructure.png" style="zoom:90%" />
<img src="./../assets/NSNumberAddressToBinary.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSNumberAddressToBinary.png" style="zoom:30%" />
@@ -770,7 +770,7 @@ Demo1
运行该代码会 Crash报错信息如下
<img src="./../assets/TaggedPointerCrash.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerCrash.png" style="zoom:30%" />
@@ -795,11 +795,11 @@ Demo1
改法1将 property 改为 **atomic** 修饰的。
<img src="./../assets/TaggedPointerCrashFix1.png" style="zoom:50%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerCrashFix1.png" style="zoom:50%" />
改法2对 name 加锁
<img src="./../assets/TaggedPointerCrashFix2.png" style="zoom:50%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerCrashFix2.png" style="zoom:50%" />
@@ -807,7 +807,7 @@ Demo1
Demo2
<img src="./../assets/TaggedPointerWillNotCrash.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TaggedPointerWillNotCrash.png" style="zoom:30%" />
@@ -854,7 +854,7 @@ NSString、NSMutableString 继承关系如下:
<img src="./../assets/NSStringClassClusterAndTaggedPointer.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSStringClassClusterAndTaggedPointer.png" style="zoom:30%" />
通过 `[[NSString alloc] initWithString:@"**"]` 方式创建的 NSString 字符串
@@ -922,27 +922,27 @@ iOS 中使用引用计数来管理 OC 对象的内存。一个新创建的 OC
调用 retain/copy 会让 OC 对象的引用计数 +1调用 release 会让 OC 对象的引用计数 -1。
<img src="./../assets/PersonAndCatMRCIssue.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue.png" style="30%" >
可以看到,如果我们提前将 cat 释放了,那后续赋值给 person 的 _cat 成员变量就没法使用了,因为已经释放了,否则就会造成 `EXC_BAD_ACCESS`。这样子太不灵活了。需要改进下:
调用 setCat 的时候,对传入的 cat 进行 retain引用计数 +1谁用谁管理同样的最后在 Person 对象释放的时候对 cat 进行 release引用计数 -1.
<img src="./../assets/PersonAndCatMRCIssue1.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue1.png" style="30%" >
但上面的代码不完美,还是存在问题。假设 cat1、cat2 2个对象当作参数调用2次 setCat 方法,如果 setCat 方法内部不做处理会导致第2次调用 setCat 后,之前调用时传入的 cat1 会无法释放。
<img src="./../assets/PersonAndCatMRCIssue2.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue2.png" style="30%" >
修改下。调用 setCat 方法时,对之前的 _cat 调用 release对旧的引用计数-1再对新传入的对象调用 retain让引用计数+1然后赋值
<img src="./../assets/PersonAndCatMRCIssue3.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue3.png" style="30%" >
上面的代码还是存在问题,会造成僵尸对象问题
<img src="./../assets/PersonAndCatMRCIssue4.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue4.png" style="30%" >
分析下 cat 的引用计数情况:
@@ -954,7 +954,7 @@ iOS 中使用引用计数来管理 OC 对象的内存。一个新创建的 OC
改进
<img src="./../assets/PersonAndCatMRCIssue5.png" style="30%" >
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/PersonAndCatMRCIssue5.png" style="30%" >
@@ -1403,14 +1403,14 @@ class StripedMap {
```
- iOS 侧 StripeCount 为8
- `indexForPointer` 方法根据传入的指针,将指针地址转换为 uintptr_t 类型然后将地址右移4位和右移9位的结果进行或运算,然后将结果取模 StripeCountiOS 侧为8用于确定索引的范围范围在[0, stripeCount -1]
- `indexForPointer` 方法根据传入的指针,将指针地址转换为 uintptr_t 类型然后将地址右移4位和右移9位的结果进行或运算,然后将结果取模 StripeCountiOS 侧为8用于确定索引的范围范围在[0, stripeCount -1]
- Operator 重写了运算符 [],底层调用 `indexForPointer` 方法,使之使用起来更像一个数组。
### 引用计数表
<img src="./../assets/ReferenceCountintStructure.png" style="zoom:40%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ReferenceCountintStructure.png" style="zoom:40%" />
@@ -1418,7 +1418,7 @@ class StripedMap {
weak_table_t 结构如下:
<img src="./../assets/WeakReferenceCountTable.png" style="zoom:70%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/WeakReferenceCountTable.png" style="zoom:70%" />
```c++
#define WEAK_INLINE_COUNT 4
@@ -1473,7 +1473,7 @@ struct weak_entry_t {
#### 存 weak 对象
<img src="./../assets/ObjcStoreWeakWhenUseWeak.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ObjcStoreWeakWhenUseWeak.png" style="zoom:30%" />
@@ -2275,7 +2275,7 @@ void sel_init(size_t selrefCount){
在 gone 处加断点,利用 runtime 查看类中的方法信息
![](./../assets/cxx_destructDemo1.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/cxx_destructDemo1.png)
发现存在 `.cxx_destruct` 方法。
@@ -2310,7 +2310,7 @@ void sel_init(size_t selrefCount){
@end
```
![](./../assets/cxx_destructdemo3.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/cxx_destructdemo3.png)
Tips@property 会自动生成成员变量,另外类后面加 `{}` 在内部也可以加成员变量,假如成员变量是对象类型,比如 NSString则叫实例变量。
@@ -2324,7 +2324,7 @@ Tips@property 会自动生成成员变量,另外类后面加 `{}` 在内部
在 gone 的地方加断点,输入 `watchpoint set variable p->_name`,则会将 `_name` 实例变量加入 watchpoint当变量被修改时会触发断点可以看出从某个值变为 0x0也就是 nil。此时边上调用堆栈显示在 `objc_storestrong` 方法中,被设置为 nil.
![](./../assets/cxx_destructDemo2.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/cxx_destructDemo2.png)
@@ -2567,7 +2567,7 @@ Person *person2 = [Person personWithName:@"FantasticLBP"];
隐式调用工厂方法
<img src="./../assets/ARCWillCrashWhenCallNewMethodDirectly.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ARCWillCrashWhenCallNewMethodDirectly.png" style="zoom:30%" />
@@ -2577,7 +2577,7 @@ Person *person2 = [Person personWithName:@"FantasticLBP"];
如何修改?加一个 bridge 即可。
<img src="./../assets/FixARCWillCrashWhenCallNewMethodDirectly.png" style="zoom:30%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/FixARCWillCrashWhenCallNewMethodDirectly.png" style="zoom:30%" />
由于 ARC 没有加 retain。所以 `person = (__bridge id)result;` 这里完成了对象的 retain。ARC 在退出方法的作用域时给对象加上release。前后对应内存正确。
@@ -2688,7 +2688,7 @@ class AutoreleasePoolPage {
- 每个 AutoreleasePoolPage 对象占用 4096 16的3次方0x2000字节内存除了用来存放它内部的成员变量内部成员固定有7个56个字节即 `0x18`, `0x1000 + 0x38 = 0x1038` ),剩下的空间用来存放 autorelease 对象的地址
- 所有的 AutoreleasePoolPage 对象通过**双向链表**的形式连接在一起。child 指向下一个 AutoreleasePoolPage 对象parent 指向上一个 AutoreleasePoolPage 对象
![](./../assets/autoreleasepool.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/autoreleasepool.png)
```objectivec
id * begin() {
@@ -3014,7 +3014,7 @@ class AutoreleasePoolPage : private AutoreleasePoolPageData {
举个例子for 循环创建1000个 Person 对象,用 autorelease 修饰,如何工作?
<img src="./../assets/AutoreleasePoolPageWithForIterator.png" style="zoom:80%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AutoreleasePoolPageWithForIterator.png" style="zoom:80%" />
分析:
@@ -3053,7 +3053,7 @@ int main(int argc, const char * argv[]) {
main 方法内部3个 autoreleasepool 底层怎么样工作的?
<img src="./../assets/AutoreleasePoolMoreItem.png" style="zoom:60%" />
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/AutoreleasePoolMoreItem.png" style="zoom:60%" />
分析:
@@ -3920,7 +3920,7 @@ iOS 在主线程的 Runloop 中注册了2个 Observer
结合 RunLoop 运行图
![](./../assets/RunLoop-SourceCode.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunLoop-SourceCode.png)
- 01 通知 Observer 进入 Loop 会调用 `objc_autoreleasePoolPush`
@@ -4017,7 +4017,7 @@ IMP Caching 比其他方法快2倍。
### OC 中有没有不对内存进行强持有的集合类型?
NSHashMapNSMapTable 都可以描述 key、value 的内存修饰。
`NSHashMap`、`NSMapTable` 都可以描述 key、value 的内存修饰。
数组有 NSPointerArray 内部持有的是对象的指针,并非直接保存对象。不过 oc 转指针需要加 `(__bridge void*)` 进行修饰。NSPointerArray 的构造方法中可以通过 NSPointerFunctionsOptions 来声明内存的控制。
@@ -4045,6 +4045,22 @@ NSHashMap、NSMapTable 都可以描述 key、value 的内存修饰。
(lldb)
```
再来2个实验
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSDictionaryCopyTheKey.png" style="zoom:30%" />
分析:可以看到 p1 的地址,在刚初始化后,和当作 key 加入到 NSDictionary 后,地址发生了变化。对 p1 执行了 copy 操作。
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSMapTableNSDictionaryMemoryControl.png" style="zoom:30%" />
分析:
- table1 只有1个元素是因为对 p1 执行的是 `NSPointerFunctionsWeakMemory` 所以不会产生2个对象。同一个对象的 hash 值一样。所以仅存在1个元素
- table2 的 key 是 `NSPointerFunctionsCopyIn` copy 产生2个不同的 p且 hash 值不一样所以存在2个元素
- 2个 NSMapTable 对 key 的内存操作不一样,其结果也不一样。如果 NSMapTable key 用 `NSPointerFunctionsCopyIn` 修饰,其效果等价于 NSMutableDictionary。
### NSError 内存泄漏的 case
同事问了一个问题,下面的代码存在什么问题?
@@ -4086,7 +4102,7 @@ NSHashMap、NSMapTable 都可以描述 key、value 的内存修饰。
这段代码运行会 crash信息如下
![](./../assets/NSErrorZombieCrash.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSErrorZombieCrash.png)
原因是 NSError 构造方法内部会加 autorelease。源码如下
@@ -4149,7 +4165,7 @@ MRC 下的 `[(id)(object) autorelease]` 等价于 ARC 下的 `id __autoreleasing
我写了个僵尸对象检测工具,效果如下
![](./../assets/ZombieSniffer.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ZombieSniffer.png)
可以定位僵尸对象,并且打印出具体堆栈,并模拟系统行为调用 `abort` 。对监控原理和工具实现感兴趣的可以查看这里[带你打造一套 APM 监控系统-内存监控之野指针/内存泄漏监控](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.74.md#zombieSniffer)