diff --git a/Chapter1 - iOS/1.102.md b/Chapter1 - iOS/1.102.md index 2e6d1a4..926d297 100644 --- a/Chapter1 - iOS/1.102.md +++ b/Chapter1 - iOS/1.102.md @@ -298,7 +298,7 @@ Tips:ninja 如果安装失败,可以直接从 [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 @@ Tips:ninja 如果安装失败,可以直接从 [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"` diff --git a/Chapter1 - iOS/1.105.md b/Chapter1 - iOS/1.105.md index 265216c..247abd3 100644 --- a/Chapter1 - iOS/1.105.md +++ b/Chapter1 - iOS/1.105.md @@ -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 都有一个 CALayer,layer 属性都有 contents,contents 其实是一块缓存,叫做 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 是有损压缩,可以指定0~100%压缩比。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中使用的是大端模式,为了兼容,我们使用kCGBitmapByteOrder32Host,32位字节顺序,该宏在不同的平台上面会自动组装换成不同的模式。 + /* + #ifdef __BIG_ENDIAN__ + # define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Big + # define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Big + #else //Little endian. + # define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Little + # define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Little + #endif + */ + + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host; + //根据是否含有alpha通道,如果有则使用kCGImageAlphaPremultipliedFirst,ARGB否则使用kCGImageAlphaNoneSkipFirst,RGB + 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); + }); + }); +} +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Chapter1 - iOS/1.110.md b/Chapter1 - iOS/1.110.md index fdf318d..fd78a18 100644 --- a/Chapter1 - iOS/1.110.md +++ b/Chapter1 - iOS/1.110.md @@ -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) diff --git a/Chapter1 - iOS/1.68.md b/Chapter1 - iOS/1.68.md index a51d021..b3d6442 100644 --- a/Chapter1 - iOS/1.68.md +++ b/Chapter1 - iOS/1.68.md @@ -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 ` + +```c++ +syscall(26, 31, 0, 0); +// 等价于 syscall(SYS_ptrace, PT_DENY_ATTACH, 0, 0); +``` + + + +syscall 调用的时候第一个参数是调用函数的名称,后面的参数是调用函数的参数。 + + + +## syscall 安全吗 + +syscall 本质也是系统函数,最后还是躲不开 fishhook 的追杀。所以是不安全的。有没有什么办法可以解决? + +这里就不再去写一遍 fishhook 的代码了,很重复... + + + +## syscall 安全性改进 + +- 隐藏符号,还原符号 + +- 使用 dlopen、dlsym 的方式,找到 syscall 符号的地址 +- syscall 发起系统调用,调用 sysctl 能力 +- GCD 定时器检测,判断是否处于调试模式 +- 如果处于调试模式,调用汇编 quit_process 结束进程 + +可以看到:即使 fishhook hook 了 ptrace、sysctl 绕过 hook,但是这种方式还是可以对非法调试 App 的行为进行了保护,立马会结束进程。 + + + + + +## 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]; +``` + + + + + 更安全的是不让分析者在 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 第一步:可以看到 NSLog 位于 Lazy Symbol Pointers 里的第一个。lazy 说明只有在用到的时候才去绑定。下断点验证下 diff --git a/Chapter1 - iOS/1.91.md b/Chapter1 - iOS/1.91.md index 68c6b24..769aba9 100644 --- a/Chapter1 - iOS/1.91.md +++ b/Chapter1 - iOS/1.91.md @@ -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。 diff --git a/Chapter2 - Web FrontEnd/2.1.md b/Chapter2 - Web FrontEnd/2.1.md index c13a5d1..33bdc29 100644 --- a/Chapter2 - Web FrontEnd/2.1.md +++ b/Chapter2 - Web FrontEnd/2.1.md @@ -10,7 +10,7 @@ :last-child、:last-of-type - +