feature: App 逆向防护

This commit is contained in:
杭城小刘
2024-07-17 23:30:50 +08:00
parent 83fefff66b
commit e3fde7a1df
15 changed files with 380 additions and 38 deletions

View File

@@ -298,7 +298,7 @@ Tipsninja 如果安装失败,可以直接从 [github]( https://github.com/n
```shell
mkdir llvm_xcode_build
cd llvm_xcode_build
cmake -S ../../llvm-project/llvm -B ./ -G Xcode -DLLVM_ENABLE_PROJECTS="clang"
cmake -S .https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/llvm-project/llvm -B ./ -G Xcode -DLLVM_ENABLE_PROJECTS="clang"
```
@@ -357,7 +357,7 @@ Tipsninja 如果安装失败,可以直接从 [github]( https://github.com/n
```
由于新做了配置,并且要开发 `CodeStyleValidatePlugin.cpp` ,所以重新生成 `cmake -S ../../llvm-project/llvm -B ./ -G Xcode -DLLVM_ENABLE_PROJECTS="clang"`
由于新做了配置,并且要开发 `CodeStyleValidatePlugin.cpp` ,所以重新生成 `cmake -S .https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/llvm-project/llvm -B ./ -G Xcode -DLLVM_ENABLE_PROJECTS="clang"`

View File

@@ -57,13 +57,13 @@ Tips`[UIView setNeedsDisplay]` 之后并不会立马调用 `[view.layer setN
## 渲染机制
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/RenderStructure.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RenderStructure.png)
iOS 渲染框架可以分为4层顶层是 UIKit包括图形界面的高级 API 和常用的各种 UI 控件。UIKit 下层是 Core Animation不要被名字误解了它不光是处理动画相关也在做图形渲染相关的事情(比如 UIView 的 CALayer 就处于 Core Animation 中)。Core Animation 之下就是由 OpenGL ES 和 CoreGraphics 组成的图形渲染层OpenGL ES 主要操作 GPU 进行图形渲染CoreGraphics 主要操作 CPU 进行图形渲染。上面3层都属于渲染图形软件层再下层就是图形显示硬件层。
iOS 图形界面的显示是一个复杂的流程,一部分数据通过 Core Graphics、Core Image 有 CPU 预处理,最终通过 OpenGL ES 将数据传输给 GPU最终显示到屏幕上。
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/RenderPipeline.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RenderPipeline.png)
- Core Animation 提交会话(事务),包括自己和子树(view hierarchy) 的布局状态
@@ -75,13 +75,13 @@ iOS 图形界面的显示是一个复杂的流程,一部分数据通过 Core G
## Core Animation
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/APM-CoreAnimationPipeline.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/APM-CoreAnimationPipeline.png)
可以看到 Core Animation pipeline 由4部分组成Application 层的 Core Animation 部分、Render Server 中的 Core Animation 部分、GPU 渲染、显示器显示。
### Application 层 Core Animation 部分
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/CoreAnimationCommit.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/CoreAnimationCommit.png)
- 布局(Layout)`layoutSubviews``addSubview`,这里通常是 CPU、IO 繁忙
@@ -107,7 +107,7 @@ Render Server 是一个独立的渲染进程,当收到来自 Application 的 (
## UIView 绘制流程
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/UIRenderPipeline.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/UIRenderPipeline.png)
- 每个 UIView 都有一个 CALayerlayer 属性都有 contentscontents 其实是一块缓存,叫做 backing store
@@ -115,7 +115,7 @@ Render Server 是一个独立的渲染进程,当收到来自 Application 的 (
- 当 backing store 写完后,通过 Render Server 交给 GPU 去渲染,最后显示到屏幕上
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/UIViewRenderPipeline.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/UIViewRenderPipeline.png)
- 调用 `[UIView setNeedsDisplay]` 方法时,并没有立即执行绘制工作,而是马上调用 `[view.layer setNeedsDisplay]` 方法,给当前 layer 打上标记
@@ -131,7 +131,7 @@ Render Server 是一个独立的渲染进程,当收到来自 Application 的 (
### 系统绘制流程
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/iOSRenderProcess.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/iOSRenderProcess.png)
- 首先 CALayer 内部会创建一个 CGContextRef在 drwaRect 方法中,可以通过上下文堆栈取出 context拿到当前视图渲染上下文也就是 backing store
@@ -143,7 +143,7 @@ Render Server 是一个独立的渲染进程,当收到来自 Application 的 (
### 异步绘制流程
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/iOSAsyncRender.png)
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/iOSAsyncRender.png)
- 如果 layer 有代理对象,且代理对象实现了代理方法,则可以进入异步绘制流程
@@ -170,7 +170,7 @@ Render Server 是一个独立的渲染进程,当收到来自 Application 的 (
这个流程看起来没有什么问题但是注意Core Animation库自身虽然支持异步线程渲染在macOS上可以手动开启但是UIKit的这套内建的pipeline全部都是发生在主线程的。
因此当一个CGImage是采取了惰性解码通过Image/IO生成出来的那么将会在主线程触发先前提到的惰性解码callback实际上Core Animation的调用触发了一个`CGDataProviderRetainBytePtr`这时候Image/IO的具体解码器会根据先前的图像元信息去分配内存创建Bitmap Buffer这一步骤也发生在主线程。
因此当一个CGImage是采取了惰性解码通过Image/IO生成出来的那么将会在主线程触发先前提到的惰性解码callback实际上Core Animation的调用触发了一个`CGDataProviderRetainBytePtr`这时候Image/IO的具体解码器会根据先前的图像元信息去分配内存创建Bitmap Buffer这一步骤也发生在主线程。
这个流程带来的问题在于主线程过多的频繁操作会造成渲染帧率的下降。实验可以看出通过原生这一套流程对于一个1000*1000的PNG图片第一次滚动帧率大概会降低5-6帧iPhone 5S上当年有人的测试。后续帧率不受影响因为是惰性解码解码完成后的Bitmap Buffer会复用。
@@ -187,3 +187,212 @@ Render Server 是一个独立的渲染进程,当收到来自 Application 的 (
缺点提前解码会立即分配Bitmap Buffer的内存增加了内存压力。举例子对于一张大图2048*2048像素32位色来说就会立即分配16MB(2048 * 2048 * 4 Bytes)的内存。
由此可见这是一个拿空间换时间的策略。但是实际上iOS设备早期的内存都是非常有限的UIKit整套渲染机制很多地方采取的都是时间换空间因此最终苹果没有使用这套Pipeline而是依赖于高性能的硬件解码器+其他优化来保证内存开销稳定。当然作为图片库和开发者这就属于仁者见仁的策略了。如大量小图渲染的时候开启Force Decode能明显提升帧率同时内存开销也比较稳定。
## iOS 图片解压缩到渲染过程
- 假设我们使用 `+imageWithContentsOfFile:` 方法从磁盘中加载一张图片,这个时候的图片并没有解压缩
- 然后将生成的 `UIImage` 赋值给 `UIImageView`
- 接着一个隐式的 `CATransaction` 捕获到了 `UIImageView` 图层树的变化
- 在主线程的下一个 `runloop` 到来时,`Core Animation` 提交了这个隐式的 `transaction` ,这个过程可能会对图片进行 `copy` 操作,而受图片是否字节对齐等因素的影响,这个 `copy` 操作可能会涉及以下部分或全部步骤
- 分配内存缓冲区用于管理文件 IO 和解压缩操作
- 将文件数据从磁盘读到内存中
- 将压缩的图片数据解码成未压缩的位图形式,这是一个非常耗时的 CPU 操作
- 最后 `Core Animation``CALayer` 使用未压缩的位图数据渲染 `UIImageView` 的图层
- CPU 计算好图片的 Frame,对图片解压之后.就会交给 GPU 来做图片渲染
- 渲染流程
- GPU 获取图片的坐标
- 将坐标交给顶点着色器(顶点计算)
- 将图片光栅化(获取图片对应屏幕上的像素点)
- 片元着色器计算(计算每个像素点的最终显示颜色值)
- 从帧缓存区中渲染到屏幕上
我们提到了图片的解压缩是一个非常耗时的 CPU 操作,并且它默认是在主线程中执行的。那么当需要加载的图片比较多时,就会对我们应用的响应性造成严重的影响,尤其是在快速滑动的列表上,这个问题会表现得更加突出
## 为什么要解压缩图片
既然图片的解压缩很耗费 CPU 时间,那么为什么还要对图片进行解压缩?是否可以不解压缩直接显示图片?不能
其实位图,就是一个像素数组,数组中的每个像素就代表图片中的一个点。平时遇到的 png、jpeg 就是位图。
```objective-c
UIImage *image = [UIImage imageNamed:@"text.png"];
CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
```
rawData 就是图片原始数据。
jpg、png 都是一种压缩格式,只不过 png 是无损压缩,支持 alpha 通道。而 jpeg 是有损压缩可以指定0100%压缩比。iOS 提供2个函数来生成 png、jpeg 图片。
```objective-c
// return image as PNG. May return nil if image has no CGImageRef or invalid bitmap format
UIKIT_EXTERN NSData * __nullable UIImagePNGRepresentation(UIImage * __nonnull image);
// return image as JPEG. May return nil if image has no CGImageRef or invalid bitmap format. compression is 0(most)..1(least)
UIKIT_EXTERN NSData * __nullable UIImageJPEGRepresentation(UIImage * __nonnull image, CGFloat compressionQuality);
```
所以,在磁盘的图片渲染到屏幕之前,必须先得到图片的原始像素数据,才可以执行后续的操作。所以必须先解压缩。
## 图片解压缩原理
既然图片的解压缩不可避免,而我们也不想让它在主线程执行,影响 App 性能,那么是否有比较好的解决方案呢?
我们前面已经提到了,当未解压缩的图片将要渲染到屏幕时,系统会在主线程对图片进行解压缩,而如果图片已经解压缩了,系统就不会再对图片进行解压缩。因此,也就有了业内的解决方案,在**子线程提前对图片进行强制解压缩**。
而强制解压缩的原理就是**对图片进行重新绘制,得到一张新的解压缩后的位图**。其中,用到的最核心的函数是 `CGBitmapContextCreate`
```objective-c
CG_EXTERN CGContextRef __nullable CGBitmapContextCreate(void * __nullable data,
size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow,
CGColorSpaceRef cg_nullable space, uint32_t bitmapInfo)
CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
```
参数说明:
- data如果不为 `NULL` ,那么它应该指向一块大小至少为 `bytesPerRow * height` 字节的内存;如果 为 `NULL`,那么系统就会为我们自动分配和释放所需的内存,所以一般指定 `NULL` 即可;
- width 和 height :位图的宽度和高度,分别赋值为图片的像素宽度和像素高度即可;
- bitsPerComponent像素的每个颜色分量使用的 bit 数,在 RGB 颜色空间下指定 8 即可;
- bytesPerRow :位图的每一行使用的字节数,大小至少为 `width * bytes per pixel` 字节。当我们指定 0/NULL 时,系统不仅会为我们自动计算,而且还会进行 `cache line alignment` 的优化
- space :就是我们前面提到的颜色空间,一般使用 RGB 即可;
- bitmapInfo :位图的布局信息.`kCGImageAlphaPremultipliedFirst`
参考 YYImage/SDWebImage 都有图片解压缩的实现
```objective-c
CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) {
...
if (decodeForDisplay) { // decode with redraw (may lose some precision)
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = NO;
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaPremultipliedFirst ||
alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaFirst) {
hasAlpha = YES;
}
// BGRA8888 (premultiplied) or BGRX8888
// same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
if (!context) return NULL;
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
CGImageRef newImage = CGBitmapContextCreateImage(context);
CFRelease(context);
return newImage;
} else {
...
}
}
```
自己也可以实现
```objective-c
- (void)setImage {
SP_BEGIN_LOG(custome, gl_log, imageSet);
[self decodeImage:[UIImage imageNamed:@"peacock"] completion:^(UIImage *image) {
self.imageView.image = image;
SP_END_LOG(imageSet);
}];
}
- (void)decodeImage:(UIImage *)image completion:(void(^)(UIImage *image))completionHandler {
if (!image) return;
//在子线程执行解码操作
dispatch_async(dispatch_get_global_queue(0, 0), ^{
CGImageRef imageRef = image.CGImage;
//获取像素宽和像素高
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
if (width == 0 || height == 0) return ;
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = NO;
//判断颜色是否含有alpha通道
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaPremultipliedFirst ||
alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaFirst) {
hasAlpha = YES;
}
//在iOS中使用的是小端模式在mac中使用的是大端模式为了兼容我们使用kCGBitmapByteOrder32Host32位字节顺序该宏在不同的平台上面会自动组装换成不同的模式。
/*
#ifdef __BIG_ENDIAN__
# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Big
# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Big
#else //Little endian.
# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Little
# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Little
#endif
*/
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
//根据是否含有alpha通道如果有则使用kCGImageAlphaPremultipliedFirstARGB否则使用kCGImageAlphaNoneSkipFirstRGB
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
//创建一个位图上下文
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, CGColorSpaceCreateDeviceRGB(), bitmapInfo);
if (!context) return;
//将原始图片绘制到上下文当中
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
//创建一张新的解压后的位图
CGImageRef newImage = CGBitmapContextCreateImage(context);
CFRelease(context);
UIImage *originImage =[UIImage imageWithCGImage:newImage scale:[UIScreen mainScreen].scale orientation:image.imageOrientation];
//回到主线程回调
dispatch_async(dispatch_get_main_queue(), ^{
!completionHandler ?: completionHandler(originImage);
});
});
}
```

View File

@@ -401,7 +401,7 @@ OrderSumitValidatorFactory {
- 优先级的关系维护在不同的子类中,各司其职,独立维护
最后选什么?组合优于继承,个人倾向使用责任链模式去组织代码。关于责任链设计模式的文章也可以看这篇[文章](./../Chapter6%20-%20Design%20Pattern/6.23.md)
最后选什么?组合优于继承,个人倾向使用责任链模式去组织代码。关于责任链设计模式的文章也可以看这篇[文章](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/Chapter6%20-%20Design%20Pattern/6.23.md)

View File

@@ -270,7 +270,7 @@ int hooked_sysctl(int *name, u_int namelen, void *info, size_t *infosize, void *
## sysctl 安全版本
## sysctl 安全性改进
修改思路参考上面的 ptrace知道 fishhook 的原理,绕开懒加载符号表,绕开 dyld 修正符号和填充地址这个过程。
@@ -284,10 +284,75 @@ int hooked_sysctl(int *name, u_int namelen, void *info, size_t *infosize, void *
## syscall 简易版本
`int syscall(int, ...) ``syscall`函数是一种用于调用系统调用的方法。系统调用是用户空间程序请求操作系统内核服务的一种机制。
在用户空间和内核空间之间,有一个叫做 Syscall (系统调用, system call )的中间层是连接用户态和内核态的桥梁。这样即提高了内核的安全型也便于移植只需实现同一套接口即可。Linux系统用户空间通过向内核空间发出 syscall产生软中断从而让程序陷入内核态执行相应的操作。对于每个系统调用都会有一个对应的系统调用号比很多操作系统要少很多。
引入头文件 `#import <sys/syscall.h>`
```c++
syscall(26, 31, 0, 0);
// 等价于 syscall(SYS_ptrace, PT_DENY_ATTACH, 0, 0);
```
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/SyscallParam.png" style="zoom:30%" />
syscall 调用的时候第一个参数是调用函数的名称,后面的参数是调用函数的参数。
## syscall 安全吗
syscall 本质也是系统函数,最后还是躲不开 fishhook 的追杀。所以是不安全的。有没有什么办法可以解决?
这里就不再去写一遍 fishhook 的代码了,很重复...
## syscall 安全性改进
- 隐藏符号,还原符号
- 使用 dlopen、dlsym 的方式,找到 syscall 符号的地址
- syscall 发起系统调用,调用 sysctl 能力
- GCD 定时器检测,判断是否处于调试模式
- 如果处于调试模式,调用汇编 quit_process 结束进程
可以看到:即使 fishhook hook 了 ptrace、sysctl 绕过 hook但是这种方式还是可以对非法调试 App 的行为进行了保护,立马会结束进程。
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HiddenSyscallSymbol.png" style="zoom:30%" />
## svc 调用
SVC指令在ARM体系中被归于异常处理类指令该指令能允许用户程序调用内核其格式如下
```c++
SVC{cond} #imm // Supervisor call, allows application program to call the kernel (EL1)
```
传统 arm中使用 svc 0 表示中断,在 xnu 中使用的是 svc 0x80。具体的看下面的例子
## 更安全的版本
### 隐藏符号名称
iOS 中常量字符串可以在 Mach-O 文件的 `__TEXT` 段中找到。如果加密的 salt、一些支付的 key、地图的 key直接明文存储很不安全一个可能的方案是采用 c 字符脱符号,比如通过下面的方式获取字符串
```objective-c
char name[] = {'s', 'y', 's', 'c', 'a', 'l', 'l', '\0'};
NSString *funcName = [NSString stringWithUTF8String:name];
```
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/StringValueVisableInMachO.png" style="zoom:30%" />
更安全的是不让分析者在 MachO 中显示的看到 ptrace、sysctl 符号名称。所以采用异或运算一个固定的 key再根据指针指向字符串初始值再次异或得到原始字符串。
隐藏 ptrace 符号名称的方法,如下所示
@@ -376,8 +441,8 @@ asm volatile(
"mov x1,#31\n"
"mov x2,#0\n"
"mov x3,#0\n"
"mov x16,#0\n" //这里就是syscall的函数编号
"svc #0x80\n" //这条指令就是触发中断(系统级别的跳转)
"mov x16,#0\n" // 这里就是syscall的函数编号
"svc #0x80\n" // 这条指令就是触发中断(系统级别的跳转)
);
```
@@ -385,12 +450,12 @@ asm volatile(
```assembly
asm volatile(
"mov x0,#31\n" //参数1
"mov x1,#0\n" //参数2
"mov x2,#0\n" //参数3
"mov x3,#0\n" //参数4
"mov x16,#26\n"//中断根据x16 里面的值跳转ptrace
"svc #0x80\n" //这条指令就是触发中断去找x16执行(系统级别的跳转!)
"mov x0,#31\n" // 参数1
"mov x1,#0\n" // 参数2
"mov x2,#0\n" // 参数3
"mov x3,#0\n" // 参数4
"mov x16,#26\n"// 中断根据 x16 里面的值,跳转 ptrace
"svc #0x80\n" // 这条指令就是触发中断去找 x16 执行
);
```
@@ -401,7 +466,7 @@ static __attribute__((always_inline)) void quit_process () {
#ifdef __arm64__
asm(
"mov x0,#0\n"
"mov x16,#1\n" //这里相当于 Sys_exit,调用exit函数
"mov x16,#1\n" // 这里相当于 Sys_exit,调用exit函数
"svc #0x80\n"
);
return;
@@ -409,7 +474,7 @@ static __attribute__((always_inline)) void quit_process () {
#ifdef __arm__
asm(
"mov r0,#0\n"
"mov r16,#1\n" //这里相当于 Sys_exit
"mov r16,#1\n" // 这里相当于 Sys_exit
"svc #80\n"
);
return;
@@ -424,6 +489,72 @@ static __attribute__((always_inline)) void quit_process () {
## 一些其他的思路
### 符号混淆
做 iOS 开发的同学,在类名、方法名等命名上,都会做到见名知意,这点在开发阶段、日常维护阶段是好事情。但是站在黑客和攻击者角度来看的话,这对他们来说也是一件好事,但对 App 的安全来讲就是一件坏事。所以需要做**符号混淆**。
在静态分析应用的时候,常常会使用 `class-dump` 导出应用的头文件,通过头文件中的函数名或变量名 猜测这些函数的功能,然后进行 `Hook` 动态分析窥探大概逻辑。如果让这些方法名、变量名、类名从名称上看没有任何意义,那么就能从一定程度干扰攻击者猜测,这叫做代码混淆
如果一个登陆注册如下所示(伪代码):
```objective-c
@interface LoginViewController: UIViewController
- (void)handleLoginAction;
@end
```
这样的代码上传到 App Store 后,攻击者利用 class-dump 还原后,还是很清晰的,见名知意,一下子就可以判断这是登陆事件的处理函数。如果对符号进行混淆,如下所示
```objective-c
@interface $38wiewh81_Controller: UIViewController
- (void)0jjd1;
@end
```
攻击者看到这样的符号,无疑会增大破解难度,至少不会像以前的一样,代码做到裸奔。
### 动态库白名单检测
除了保护应用中的关键代码,还可以通过代码检测应用中动态库是否是合法的。无论是越狱环境还是非越狱环境,如果要入侵除了修改二进制就是注入动态库,所以可以写段逻辑判断 App 中除了我们自己项目中的动态库,是否还存在入侵的动态库。
通过 dyld API 函数获取应用中的动态库名称,把这些字符串名称合并作为一个白名单,如果发现动态库不在白名单中,则结束进程。
```objective-c
const char *whitstr= "";
void checkWhiteStr(){
    uint32_t count= _dyld_image_count();
    for(int i=1;i<count;i++){
      const char* dyname=_dyld_get_image_name(i);
        //printf(dyname);
        if(!strstr(whitstr, dyname)){ // 不在白名单中
quit_process();
        }
    }
}
```
当然,更严谨的做法就是 App 启动完成,主页加载显示完毕后,拉取接口,服务端告诉当前版本的 App 有哪些库App 内存保存这些数据GCD 定时器检测,发现有库不在该白名单内存中,则结束进程。
服务端白名单数据怎么来App 打出 release 模式的包,然后运行上述代码,包库的名称数组,上传到服务端。
### 逻辑混淆
一些核心逻辑还是有必要混淆的。编译工程时使用这个自定义的 `OLLVM` 工具可以编写 `Pass` 来混淆 `IR`。
具体查看 [obfuscator](https://github.com/obfuscator-llvm/obfuscator) 这个 Repo。具体使用这里不展开。
完整代码可以这里:
- [AppHook](https://github.com/FantasticLBP/BlogDemos/tree/master/AppHook)

View File

@@ -136,7 +136,9 @@ Position-Independent Code即位置无关代码。这是一种编译技术
### 实践探索
做个实验验证下,完整流程
做个实验验证下,完整流程(新版本的位于
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/MachOLazySymbolLeatstVersionLocation.png" style="zoom:25%" />
第一步:可以看到 NSLog 位于 Lazy Symbol Pointers 里的第一个。lazy 说明只有在用到的时候才去绑定。下断点验证下

View File

@@ -1371,7 +1371,7 @@ xcodebuild -workspace Person.xcworkspace \
xcodebuild -create-xcframework \
-framework 'archives/Person.framework-iphoneos.xcarchive/Products/Library/Frameworks/Person.framework' \
-framework 'archives/Person.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/Person.framework' \
-output './../xcframework/Person.xcframework'
-output 'https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/xcframework/Person.xcframework'
```
结果如下:
@@ -1390,7 +1390,7 @@ xcodebuild -create-xcframework \
-debug-symbols '/Users/unix_kernel/Desktop/LDAndFramework/MultipleArchMerge/Person/archives/Person.framework-iphoneos.xcarchive/dSYMs/Person.framework.dSYM' \
-framework 'archives/Person.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/Person.framework' \
-debug-symbols '/Users/unix_kernel/Desktop/LDAndFramework/MultipleArchMerge/Person/archives/Person.framework-iphonesimulator.xcarchive/dSYMs/Person.framework.dSYM' \
-output './../xcfrmaework/Person.xcframework'
-output 'https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/xcfrmaework/Person.xcframework'
```
结果如下
@@ -1574,7 +1574,7 @@ OTHER_LDFLAGS = $(inherited) -l"AFNetworking" -l"AFNetworking2" -Xlinker -force_
遵循原则是谁链接库,谁就提供库所需要的 rpath 信息。所以 `NetworkManager.Framework` 提供 rpath 信息。xcconfig 文件的 `LD_RUNPATH_SEARCH_PATHS` 是 Xcode 项目中的一个设置它在编译时告诉链接器在生成的可执行文件的运行时路径rpath中包含特定的目录 `install_name_tool -add_rpath` 是在二进制文件生成后对其进行修改)
```shell
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks'
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/.https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/Frameworks'
```
dyld 在运行起来后,会根据 `LD_RUNPATH_SEARCH_PATHS` 提供的 rpath 信息,和 AFNetworking 的 `name` 拼接去查找。但我们面前的例子,路径下没有 AFNetworking。

View File

@@ -10,7 +10,7 @@
<head>
<meta charset="UTF-8">
<title>:last-child、:last-of-type</title>
<script src="../../lib/jquery-2.1.0.js"></script>
<script src=".https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/lib/jquery-2.1.0.js"></script>
<style>
ul {
margin: 100px 0;
@@ -45,7 +45,7 @@
<head>
<meta charset="UTF-8">
<title>:last-child、:last-of-type</title>
<script src="../../lib/jquery-2.1.0.js"></script>
<script src=".https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/lib/jquery-2.1.0.js"></script>
<style>
ul {
margin: 100px 0;
@@ -92,7 +92,7 @@
<head>
<meta charset="UTF-8">
<title>:last-child、:last-of-type</title>
<script src="../../lib/jquery-2.1.0.js"></script>
<script src=".https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/lib/jquery-2.1.0.js"></script>
<style>
ul {
margin: 100px 0;
@@ -130,7 +130,7 @@
<head>
<meta charset="UTF-8">
<title>:last-child、:last-of-type</title>
<script src="../../lib/jquery-2.1.0.js"></script>
<script src=".https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/lib/jquery-2.1.0.js"></script>
<style>
ul {
margin: 100px 0;
@@ -166,7 +166,7 @@
<head>
<meta charset="UTF-8">
<title>:last-child、:last-of-type</title>
<script src="../../lib/jquery-2.1.0.js"></script>
<script src=".https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/lib/jquery-2.1.0.js"></script>
<style>
ul {
margin: 100px 0;

View File

@@ -94,8 +94,8 @@ function sql_encode($sql){
header('content-type:text.html;charset=utf-8');
error_reporting(0);
require_once '../../model/PdoMySQL.class.php';
require_once '../../model/config.php';
require_once '.https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/model/PdoMySQL.class.php';
require_once '.https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/model/config.php';
require_once 'Response.php';
class HotelList
{

View File

@@ -66,13 +66,13 @@ module.exports = fetchCodeFiles
6. 为了方便测试,在 `package.json` 文件中的 `scripts` 下添加描述 `"test": "mocha"`
7. 为了更方便,我使用的是 iterm2在 .zshrc 文件里设置别名 `alias nt="node test"`
提升效率的配置 .zshrc 可以查看文章: [Mac 终端效率神技](./../Chapter7&#32;-&#32;Geek&#32;Talk/7.10.md)
提升效率的配置 .zshrc 可以查看文章: [Mac 终端效率神技](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/Chapter7&#32;-&#32;Geek&#32;Talk/7.10.md)
上面开发代码的测试代码如下:
```javascript
// fetchCodeFiles.test.js
const fetchCodeFiles = require('./../src/fetchCodeFiles'),
const fetchCodeFiles = require('https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/src/fetchCodeFiles'),
assert = require('assert')
describe("fetch all code files", () => {

View File

@@ -173,7 +173,7 @@ public class Application {
## 使用场景
在之前的文章利用[责任链模式设计了一套校验器](./../Chapter1%20-%20iOS/1.110.md)
在之前的文章利用[责任链模式设计了一套校验器](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/Chapter1%20-%20iOS/1.110.md)
- Node 的洋葱模型
- Redux 中间件思想都是责任链的使用场景

View File

@@ -49,7 +49,7 @@
## 重构的质量保证
除了 QA 测试之外,最有效、可落地的测试就是单元测试可。当重构完成后,如果新的代码可通过单元测试所有 case那么说明之前的逻辑没有被破坏原有系统的行为、外部可见性没有改变。
那 iOS 侧如何开展单元测试,可以见[这篇文章](./../Chapter1%20-%20iOS/1.75.md)
那 iOS 侧如何开展单元测试,可以见[这篇文章](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/Chapter1%20-%20iOS/1.75.md)
## 代码是否需要“解耦”?
间接的衡量标准有很多,比如,看修改代码是否牵一发而动全身。直接的衡量标准是把模块与模块、类与类之间的依赖关系画出来,根据依赖关系图的复杂性来判断是否需要解耦重构。

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 KiB

BIN
assets/SyscallParam.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 KiB