mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
feature: App 逆向防护
This commit is contained in:
@@ -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):编译之后的代码。通常是指用来存储程序可执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。
|
||||
|
||||

|
||||

|
||||
|
||||
上 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位的结果进行抑或运算,然后将结果取模 StripeCount(iOS 侧为8),用于确定索引的范围(范围在:[0, stripeCount -1] )
|
||||
- `indexForPointer` 方法根据传入的指针,将指针地址转换为 uintptr_t 类型,然后将地址右移4位和右移9位的结果进行异或运算,然后将结果取模 StripeCount(iOS 侧为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 查看类中的方法信息
|
||||
|
||||

|
||||

|
||||
|
||||
发现存在 `.cxx_destruct` 方法。
|
||||
|
||||
@@ -2310,7 +2310,7 @@ void sel_init(size_t selrefCount){
|
||||
@end
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
Tips:@property 会自动生成成员变量,另外类后面加 `{}` 在内部也可以加成员变量,假如成员变量是对象类型,比如 NSString,则叫实例变量。
|
||||
|
||||
@@ -2324,7 +2324,7 @@ Tips:@property 会自动生成成员变量,另外类后面加 `{}` 在内部
|
||||
|
||||
在 gone 的地方加断点,输入 `watchpoint set variable p->_name`,则会将 `_name` 实例变量加入 watchpoint,当变量被修改时会触发断点,可以看出从某个值变为 0x0,也就是 nil。此时边上调用堆栈显示在 `objc_storestrong` 方法中,被设置为 nil.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -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 对象
|
||||
|
||||

|
||||

|
||||
|
||||
```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 运行图
|
||||
|
||||

|
||||

|
||||
|
||||
- 01 通知 Observer 进入 Loop 会调用 `objc_autoreleasePoolPush`
|
||||
|
||||
@@ -4017,7 +4017,7 @@ IMP Caching 比其他方法快2倍。
|
||||
|
||||
### OC 中有没有不对内存进行强持有的集合类型?
|
||||
|
||||
NSHashMap、NSMapTable 都可以描述 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,信息如下
|
||||
|
||||

|
||||

|
||||
|
||||
原因是 NSError 构造方法内部会加 autorelease。源码如下
|
||||
|
||||
@@ -4149,7 +4165,7 @@ MRC 下的 `[(id)(object) autorelease]` 等价于 ARC 下的 `id __autoreleasing
|
||||
|
||||
我写了个僵尸对象检测工具,效果如下
|
||||
|
||||

|
||||

|
||||
|
||||
可以定位僵尸对象,并且打印出具体堆栈,并模拟系统行为调用 `abort` 。对监控原理和工具实现感兴趣的可以查看这里[带你打造一套 APM 监控系统-内存监控之野指针/内存泄漏监控](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.74.md#zombieSniffer)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user