iOS 逆向华章目录 :
1️⃣、RSA加密原理&暗码学&HASH
2️⃣、运用签名原理及重签名 (重签微信运用实战)
3️⃣、shell 脚本主动重签名与代码注入
4️⃣、重签运用调试与代码修正 (Hook)
5️⃣、Mach-O文件
6️⃣、Hook / fish{ ` ^ z H h n lHo{ k zok 原u l F l理与符号表
7️⃣、LLDB由浅至深
8️⃣、lldb; ! ] Z d h高级篇 Chisel 与 Cycript
9️⃣、越狱初探
、实战篇-钉钉打卡插件
前言
逆向华章从Z 1 # , 前导常识到东西运用和原理剖析咱A – F ] n # #们都现已叙述结束了 , 也结合了实践事例来进行巩固 . 那么接下来 , 进入到咱们学习逆向的最重要的方针华章 , 运用安全攻防 .
这是一个大华章 , 文章假如过长会分两篇叙述 .
学习逆向最重要的便是知道怎么防护 , 本文会罗列一些现在市面上较为常见的逆向进犯办法应该讲讲怎么防护 , 这些做法并不仅有也不一定最好 , 如有心得欢迎交流 .
关于防护
关于V = y i m , ( a k运用防护 , 咱们首要要有几个前提概念要清楚 .
-
1️⃣ : 没有肯定安全的程序 .
没有肯定安全的运用 , 咱们所要做的便是尽最大或许混杂
or
浪费进犯者的时刻 , 加大进犯本钱 . -
2️⃣ : 针对检测到调试和注入时的操作 , 尽量# ^ .不要做非常显着的退出运用
or
提示 等操作 .-
关于一个经验比较丰富 ( 相对防护者来说 ) 的逆向工! ) j程师 , 当发现调试 / 注入代码工程运转时与正版运用正式运转有显着的差异时 , 很简略顺藤摸瓜找到相应的防护或许监测处理逻辑和技术 .
-
( 比方经常也有一p j 1 { o 5些同学问到 , 为2 3 M _ A J 6 `什么重签名了之后工程一运转就闪退 , 正版运用就没问题 , 那么就很简略得知很或许是运用了
ptrace
或许引进包监测 /. – # / ` 8 Q 包名监测之类的处理 ) . -
相对而言现在市] k e面上许多大厂所运l e # w % E x N用的的 , 监测到此行为会记载设d 3 K ^ . a S备 / 账户等信息进行上报和封号等办s 7 V C法则是在无形之中做到了较为有效的防护 .
-
1. 动态调试
进攻 :
关于非越狱环境来说 , 重签名进行动态调试 , yololib
修正 mach-o
中 Load Commands
让 DYLD
加载进犯者所编写的动态库从而进行 hook
来完成代码注入是最为常见的了 . (B q 2 – m T 4 4 $ 这儿不会具体叙述进攻进程和作用 , 不熟悉的同学能够翻一翻目录中A G U c # F Q A E 2,3,4
这三篇文V G b T ( e o 3 &章有具体的操A r P作演示 ) .
运用重签名工程的特性 , 其B [ r实这种处理计划有特别多 , 这儿挑几个比较有代表性的防护办法罗列一下 .
防护办法 1 : 干掉 lldb – ptrace
运用重签v T 名工程运转调试w z ! * q F A特性 , 咱们能够经1 E Z l过制止开发环境运用 lldb
来完成重签名工程运转闪退的作用 .
lldb
的原理这儿提一点 :
lldb
本质上是一个GUI
断点和指令搜集东西 +debug server
经过进程附加到其时运转进程中来完成的 .
( Xcode
自带的 Debug - Attach to process
便是依据 DebugServer
来完成的 )
ptrace
ptrace
是 指令行工程以及 Mac OS
工程里的 <sys/ptrace.h>
供给的一个函数 , 能够用来来操控进程附加管理 , 它能够完成制止运用程序r A Q 1 T }进程被附加的作用 . 在 iOS
中并没有露出出来 , 可是 iOS
是能够运用的 .
笔者这R ) 8 B儿将该头文件导出 , ( 链接 – 暗码: iaqp
) 下载o * : : 1 v Q =后导入工程中就能够运用了 .
运用如下 :
/**
arg1: ptrace要做的事情: PT_DENY_ATTACH 表明要操控的Y / W p a Y是其时进程不允许被附加
arg2: 要操作进程的PID , 0就代表自己
arg3: 地址 取决于第一个参数要做的处理不同传递不同
ar_ ` [ 4 l ]g4: 数据 取决于第一个参数要做的处理不同传递不同
*h 4 ^ , I ] M 3 M/
ptrace(PT_DENY_ATTACH, 0, 0, 0);
处理后作用
代码增加结束后 :
- 运转* F 5 U O y 3工程 , 程序闪退 .
- 从手机点开运用 , 运用正常 .
- 运用
Xcode
自带的Debug - Attach to proX t $ H qc| s t # ]ess
发现附加失败 .
防护手法评价
- 1️⃣ : 越狱环境下
lldb$ n Y-debug server
相同能够防护 .- 2Z } M w E U =️⃣ : 这种做法比较暴力 , 并且影响自身正向开发 , 只能在正向开发时不运用 , 打包上架f ! h U * _ S时再翻开 .
- 3️⃣ : 这种做法作 U } f C X i ^ P用比较显着 , 很简略推断出来运用了这个机制 , 一个符号断点就能轻松查到 .
- 4️⃣ : 破解起来也比较简略 , 运用
fishhook
能够很简略的hook
掉ptrace4 V 2 ~ 1 % ? r
这个函数 .
( 坊间风闻前期支付宝有运用过这种办法 , 不过并没有实据 , 全当一听 )
提示
Cycript
自身是从正在运转的进程中读取数据 , 并不是进程附加的原理 , 并不能经过 pa M h - htrace
防护 .
防护手法破解
破解这个也有较多办法 , 比方直接修正二进制汇编代码 ( bl -> nop )
, hook
掉 ptrace
等等 .
最简略的办法便是刺进一个动态库 , 在这个库的 load
中G H , m运用 fishhoo u D N I Gk
直接把0 P x 4 ptrace
hook
掉即可 . ( 关于 fishht k ? y + [ook 参阅 fishHook 原理与符号表 这篇文章U B 1 H s有从运用到原理解释的完整内容 , 这儿就不再演示了 ) .
Monkeydev
中默许就现已运用了 fishhook
交流了 ptrace
.
防护办法 2 : sysctl
sysctl
( system5 F P control ) 是由 <sys/sysctl.h>
供给的一个函数 , 它有许多作用 , 其间一个是能够监测其时进程有没有被附加 . 可是由于其特性 , 仅仅监测其时时刻运用有没有被附加 .
因而正向开发中咱们往往结合守时器一同运用 , 或. 8 o I许 守时
/ 定时
/ 在特守时期
去运用 .
运用如下 :
#import "ViewController.h"
#import <sys/sysctl.h>
@interface ViewController ()
@end
@implementation ViewController
BOOL isDebug(){
int name[4]; //里边放字节码。x j `查询的信息
name[0] = CTL_KERN; //内核查询
name[1] = KERN_PROC; //查询进程
name[2] = KERN_PROC_PID; //传递的参数是进程的ID
name[3] = getpid(); //获取其时进程ID
struct kinfo_proc info; //接受查询成果的结构体
size_t info_size = sizeof(infO ) M f r ] Yo); //结构体大小
if(sysctl(name, 4, &info, &info_size, 0, 0)){
NSLog(@"查询失败"? { 6 B j z A ();
return N] s ZO;
}
/**
查询成果看info.kp_proc.p_flag 的第12位。假如为1,表明调试状况。
(info.kp_proc.p_flag & P_TRACED) 便是0x800, 即可获取第12位
*j { 7 1 2 n g m/
return ((info.kp_proc.p_flag &P y [ . m @ + *; P_TRACED) != 0);
}
static dispatch_source_t timer;
void debugCG C } A ( Eheck(){E F U
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_gl@ K r Uobal_queue(0, 0));
dispatch_source_set_timer(timer, DISPATCH] ! v_TIME_NOW, 1.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
dispatch_soR O | x 2 C a } zurce_set_event_handler(timer, ^{
if (isDebug()) {//( e 1 R Y 7 ~ V ;在这儿写你检测到调试要做的操作
Np M U } n ;SLog(@"调试状n 4 p 况!");
}else{
NSLog(@"正常!");
}
});
dispatch_resume(timer);
}
- (void) X o u g i d ]viewDidA v - b jLoad {
[super vi| - V l ewDidLoad];
debugCheck();
}
处理后作用
这个显现层面的作用依据你自界说的成果决议 , 你检测到后决议做上报仍是闪退都能够 .
防护手法评价
- 1️⃣ : 越狱环境下
lldb-debug server
相同能够检测到 , 但相同会影响自身正向开发 .- 2️⃣ : 这种做法比较灵敏 , 能够自界说处理成果 , 上报很简略做到无形 .
- 3️⃣ : 破解起来也相对简略 , 运用
fishhook
hook
掉sysctl
这个函数 ( 可是由于用户运用 sysctl 获取到回来成果 , 因而这儿4 3 _ q 7 E G ^留意hook
之后不能直接啥也不做 , 需求找到回来的flag
第 12 位改为 0 , 参考下图 ) . 可是得益于针对运用层面作用可做到无形 , 也被一些公司% 5 7 { [ V ] 2 [在运用 .- 4️⃣ : 由于其特性 , 检测其时当刻 , 需求针对需求去操作何时检测
or
检测M – ; {多久的逻辑 .
( 坊间风闻f $ F它又来了 . 传说前期 c 9 A q I抖音团队便是运用了这个检测 , 检测到之后直接 exit
相同没有有实据 , 全当一听.. )
提示
这儿并不引荐检测到有调试之后直接 exit
.
由于逆向进程中经过对 exit
增加符号断点检查函数调用栈就能够检查到调用 exit
的函数地址 , 再减去经过 image list
指令获取 mach-o
首地址就能够获取到函数偏移量 , 然后在 Hopper
中很简略就能够拿到调用 exit
的函数了 , 那进犯人员就能找到这个函数中你是经过 sysctl
来监测的 .
因而 ,I p c 咱们说防护最好是不要有显着的痕迹给进犯者 , 不然便是给他们供给一个很好的头绪和思路去找到你的防护逻辑 .
防护手法破解
sysctl
的 hook
做法如下 :
相同 , Monkeydev
中默许就现已运用了 fishhook
交流了 sysctl
..
问题
或许咱们都留意到了 , 上面所说这两种处理动态调试的| H z S / g ?计划好像都差了点意思 , 破解本钱也比较小 . 只需确保注入代码在 sysctl
检测代码或许 ptrace
调用之前就能够完全处理这个防护手法 .
处理上述动态调试防护计划问= 6 G B U 3 f @ *题其实有v T 6 { W 6 T c Q许多计划 , 例如 :
- 确保
sysctl
检测代码或许ptrace
的履行在hook
注入代~ / P码之前 . - 禁用掉
fishhook
.
防护办法 3 : ptrace / sysctl 优化版 – 提早履行
原理叙述
依据 ld
以及 llvm
的编译特性以及 dyld
的加载逻辑 . 实践上当I K q h 1 u m咱们在正向开发运用 framework
中的 load
中编写防护代码时 , 是会比 yol1 d y R s 3 U Jolib
注入的动态库更早被履行的 .
- 之O f e ]前文章中咱们也提到过 ,
Mach-O
在__DATA
段后边是有空余内存位置的 .yololib
增加的hook framework
是增加在这个空缺位置中的 ,load commandU & z Q l E O K _s
也会往后刺进.- 也便是说 , 用户自己引进的
framework
是会在 注入的framework
之前被链接的 . ( 越狱环境Insert Library
跟这个是两个东西 , 后边咱们会叙述越狱插件的作业原理 ) .
具体做法
那么运用这个特性 , 咱们能够自己增加一个 framework
在它的 load
中编写防护代码~ ` a # . 这些防护代码会比注入的更早被履行 . 也便是说 , 这时现已检测到有被调试了 . sysctl
后续尽管会被注入代码 hook
掉可是咱们现已完成监测 .
并且由于是单独的x I ) W f e framework
, 假如是经过符号断点获t v } |取函数地址去计算根据首地址偏移量还会有所不同 , 由于每个 framework
都是一个独r ) 5 )立的 mach-o
, 需求去找对应的文件首地址而非主程序的首地址 ,+ { { n 剖析的 mach-O
也应该是 framework
而非主程序 . 也对进犯人员产生了额定的时刻消耗 .
( 经验丰富的逆向人员实践上也很简略忘掉这一点 , 一般会以为自己算错了 .. )
防护手法评价
这种办法适用于非越狱环境重签名调试 , 现已做到比较好的作用了.
进犯人员针对在 framework
的 load
中所做的防护办法 , 由于永远比注入代码早被履行而相对更安全一些 , 想要进犯也只能经过静态汇编剖析逻辑修正汇编了 . 而这种就相较于动态调试会愈加复杂和耗时 .
( 相同 , 越{ * 狱插件跟这个不是一个原q i 1 Z理 , 并不能防护到 , 后边咱们会将越狱插件怎么防护 )
提示
- 主张不要为了防护专门开一个
framework
, 并且取一个很显着的名字 , 这样进犯人员能够很简略地看出这个framework
便是为了防护写的 , 再结合load
办法汇编剖析 , 发现K s } Z M你只做了一个sysctl
, 他静态修正也是很简略的 .load
办法直接ret
就行了 .- 能够放到一个有实践功用逻辑的库中增加这个防护逻辑 , 这样能到达更好地防护作用 .
- 根据这个机制 ( 咱们的防护代码能够 m r ! # 8比注入代码更j m (早被履行 ) , 咱们能够做许多处理 , 比方
runtime
的办法交流屏蔽 ,fishhook
屏蔽 , 这个后边我会补充一下 .
防护办法 4 : 函数地址保存绕过 fishhook
绕过 fishhookR k v d 调用体系函数
咱们有这样一个设想 , 在我工程. E t z D + _ h %开端我就获取 ptrac^ ! m 0 3e
/ sysctl
的地址 , 后边直接运用地址调用这个函数 . 实践上是可行的 , 运用 dlsym
这个函数~ y Q W | ? o E .
上述 防护办法3 在咱们看来 , 尽管能确保防护代码的履行优先权 , 但好像看起来仍然是1 Z 0 L % g s治标不治本 . 那么怎么能确保咱们需求用的体系函数不会被
fishhook
干掉呢 ?
在 启动优化之Clang插桩完成二进制重排 文章中咱们提到过经过符号获取函数地址 ( dladdr
函数 ) , 并且运用了经过函数M D K ; X内部地址找到函数符号 ( dlsym
函数 ) .
运用展现
#import "MyPtrR = .aceHeader.h"
#import <% m 2dlfcn.h>
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLo~ U r f dad];
//这儿做法是躲藏常量字符串
unsigned chaS r * Jr str[] = {
('a' ^ 'p'),
('a' ^ 't'),
('a' ^ 'r'),
('a' ^ 'a'),
('a' ^ 'c'),
('a' ^ 'e'),
('a' ^ '')
};
unsigned char * p = str;
while (((*p) ^= 'a') != '')l _ % P H y ) p++;
v_ h l joid * handle = dlopen("/usr/lib/system/libsystem_kernel.dylib", RTLD_LAZY);
int (*ptrace_p)(int _request, pid_t _pid, caddr_t _addr, i( r % G + 7 [ Mnt _data);
//获取ptrace函数指针
pt{ K : Z ^ : = C .race_p =5 e u E % ( I n 6 dlsym(handle,(const char *)str);
if (ptrace_p) {
//假如有值,就能够顺利调用
ptrace_p(PT_DENY_ATTACH,0,0,0);
}
}
上述做法中首要运用的是常量字符串的躲藏 , 本文第三章节有具体叙述 .
假如咱们期望自己运用的体系函数不会被 hook
, 就– X } o o +能够采用这种办法 .
提示
( 实践上 MonkeyDev
相同就现已对 dlsym
进行了 hook
) , 因而% | w }能够结合 防护办法3
公同运用 , 也便是确保获取地址办法提早履行即可 , 这儿不多提了.
防护手法评价
这种防护办法间接的绕过了 fishhook
, 但也不是肯定的 . 很显着 , 咱们相同运用了 dk e h slopen
与 dlsym
这两个体系函数 , 那他们就相同有被 fishhook
干掉的或许 .
不着急 , 后续我会持续讲怎么更好地处理这个问题 .
防护办法 5 : 绕过符号断点 syscall
syscall
是体系等级的调用函数的一个函数 . 例如咱们期望调用 ptrace
, 可是O 2 C v : ,又不期望符号断点能够段住 ptrace
. 那么你不用导入任何头文件就能够直接运用 syscall
函数来调用 ptK I o V !race
.
运用演示
/**
1.参数w p ^ X f 是函数编号 26 指的是 ptrace 函数
2.其他的便是参数顺序. 31 指的是 ptrace 的参数 PT_DENY_ATTACH
*/
syscall(26,31,0,0);
关于每个序号对应什么函数 , 你能够导入一下 #F t n A z F #import <sys/syscall.h>
即可检查 .
除了 ptrace
, 还有 exitX ^ u
等等 , 当你需求绕过符号断点就能够运用这个函数| u o v u h Y .
当然 , syscall
也是一$ d C t y个体系函数 , 因而除了汇编 , 你能够结合 防护办法 4 与 防护办法 3 来处理会被 fishhook
干掉的问题 .( k F l 并且增加 syscall
的符号断点是相同能够断住的 , 然后能够读寄存器检查你是否调用了 ptrace
, exit
等等 .
怎么处理呢 ? 接着往下看 .
防护办法 6 : 汇编调用 ( 引荐版本 )
上述所写的这么多种防护办法 , 看起来都不是很完美 , 仍是存在 fishhook
或许对各种你运用的办法进行 hook
的危险 ( 尽管你或许运用了 framework
提早履行 , 绕过符号断Y V n E Y D I u点 , 运用体系等级的调用 , 但仍然会留下一些 ‘蛛丝马} M : 9 z迹’ ) .$ $ b Z
该怎么处理呢 ?
咱们能够经过汇编来直接调用 syscall
.
运用演练
- (void)viewDidLoad {
[super viewDidLoad];
//运用汇编调用syscall调起ptrace
#ifdef __arm64__
asm volatile(
"mov x0,#26n"
"mov x1,#3u r + s j .1n} h 3"
"mov x2,#0n"
"mov x3,#0n"3 | 5 [ D L 0 P M
"mom - [ 0v x16,#0n"
"svc #0x80n"//这条指令便是@ U A [ Z [触发中断(体系等级的跳转!)
);
#endif
//运用汇编直接调用 ptrace
#ifdef __arm64__
asm volatile(
"mov x0,#31! 4 , w -n"
"mov x1,#0n"
"m@ i p `ov x2,#0n"
"mov x16,#26n"$ e r
"svc #0x80n"
);
#endif
}
x16
寄存器就放调用 syscall
需求调用的函数对应编号就能够 . 当然 , 不同架构寄存器指令不同 , 例如调用 exit
咱们能够这& L = ,么写 :
#ifdef __arm64__
asm volatile(
"mov x0,#0n"
"mov x16,#A s + _ Y $ f 91n"
"svc3 y Y u C T #0x80n"
);
#endif
#ifdef __arm__//32位下
asm volatile(
"mov r0,#0n"
"mov r16,#1n"
"svc #80n"
);
#endif
防护计划评价
运用汇编指令调用 syscall
- 能够避免体系函数被
fishhook
干掉 . - 增加符号断点并不? ~ %能断住 .
- 进犯者静态剖析也比较难以查找 .
比较引荐这种办法 .
动态调试其他防护办法
动态调试除了上述防护办法以外 , 还有许多计划 , 这儿罗列几个供咱们参考 .
- 引进动态库监测 . 运用白名单监测自己工程其时引进三方库 , 查找是否有不知道库注入 , 获取引进库写法如下 .
- 留意: 由于程序D _ I z J R ] d自身
mach-o
这儿也能监测出来 , 并且是第一个 , 因而 , 循f : q ~环应该从 1 开端 , 也便是剔除自身mach-o
. - 并且该办法相同能够监测越狱环境
DYLD_INSERT_LIBRARIES
动态注入的插件 . - 此办法能够有效地检测到 Cycript 越狱与非越狱的调试# j ; .
- 留意: 由于程序D _ I z J R ] d自身
-
Bundle ID
检测 , 重签名工程是需求修正包名的 , 能够以此监测 .
2. 代码混杂
由于砸壳后运用恢复符号后运用 lldb
, class-dump
或许 Mach-O
的一些检查东西很简略的能够看到咱们的类名与办法名 , 而一般贯彻了代码规范的正向开发人员会已规范的驼峰和英文来命名 , 这就为进犯人员供给了极大的便利性和可寻性 .
代码混杂能够在不影响正向开发人员的情况下对办法名和类名进行混杂 , 使生q & J * * 4成的二进制文件中没有去除符号的类和办法也变的没有可参考性 .
运用办法
宏界说的机制能够协助咱们很好地满意混杂的需求 . 关于一个现已开发完成的工程咱们能够很便利的来对其进行混杂 .
例如代码如下 :
- (void)vi@ 5 s O 8 L ewDidLoad {
[super viewDidLoad];
LBOZ / o - L XbjectClass * objc = [LBObjectCla| n ^ R m o } 5 fss new];
[objc LBObjectTestFunc];
BOOL result = [objc checkIsVipWithkey; y + 2 . [ ~ +String:@"12311" Toke[ U % ! ) I 8n:@"jfkfqwe"];
NSLog(@"%d",result);
}
@interface LBObje; s o f [ctClass : NSObjecF k F y P z Tt
- (voi5 o = :d)LBObjectTestFunc;
- (BOOL)checkIsVipWithkeyString:(NSString *)string Token:(NSString *)token;
@end
@implementation LBObjectClass
- (void)LBObjectTestF/ , @ & _ 5unc{
NSLog(@F @ } n"td , 6 / j p nhis is a coH f Lnfusion func");
}
- (BOOL)checkIsVipWithkeySt; a 6 F gring:(NSString *)string Token:(NSString *)token{
if ([string containsString:@"111"] &&! s 4amp; (token !U m v 2 ( m= nil) ) return YES;W C V
return NO;
}
@end
如上事例中咱们 , 有一个类名叫 LBObject
, 他有两个办法 , 一个无参无回来值 , 一个有参有回来值 .
怎么进行` k o混杂呢 ?
做法很简略 . 为其界说对应的n : c z R } l 宏 即可 .
我这儿在F z C W pch 文件中增加如下 :
#define LBObject HDJSNWOIJNWPKFWD
#define LBObjectTestFuncl g # + M ^ 0 b LKNWFMWJFNJMSLW
#define checkIsVipWithb _ ! v =keyString IWRNWKJNDS
#dk ` f :efine Token NFKA{ ` K ] YOWRL
增加结束后发现1 X m c类名和办法称号色彩发生了改动 . 原本代码无须进行任何修正 .
作用展现
MachOVieg r Uw 检查混杂前 :
MachOView 检查混杂后 :
class-dump 检查混杂前 :
提示
class-dump
是对别人的已脱壳运用从 mach-O
中读取头文件 , 能够获得类以及其办法 and
特点的界说U ^ @ . & + . ( 运用办法参考 重签运用调试T h c { t n L与代码修正 ) .
class-dump 检查混杂后 :
Hopper
中检查办法汇编完成假如做了混杂也A A ? C b K u C m是相同的作用 . 这儿就不再展现了 .
宏界说代码混杂计划评价
长处 :
- 1️⃣ : 代码混杂能够很有效的大量增加进犯者的剖析耗时 , 增大干扰性 .
- 2️⃣ : 代码混杂能够很轻松的对已有工程进行处理 .
- 3️⃣ : 加入混杂后对正向人员正常开发根本没有影响 .
- 4️⃣ : 完全能够运用R W v e v } [ S v宏界说的机制 , 灵敏处理 , 例如类名办法名每次运转都是随机字符串 .
缺O j O x v H陷
- 1️⃣ : 代~ s x g码混杂运用宏界说的机制 , 因而在预编译阶段会增加一定耗时 , 但对运转期间也便是用户来说没有影响 , 能够针对部分重要代码
or
功用增加混杂.- 2️⃣ M 2 W f : 运用宏界说需求留意工程中有其他相同m G % j q ( F .字符串产生影响 . ( 例如上述事例中我用了一个
Token
的宏 , 那么一定要留意其他运用Token
字样的当地 , 尽+ ? $ E * H (量运用仅有字符串替代 . )
网上也有许多运用编译器来完成的代码混杂的三方库 , 主动完成的 , 思路大体上都是如此 , 宏界说是手动做的 . 但也因而比较灵敏和简略 , 自界说程度较高 .
3. 字符串| ? + + *常量躲 3 J z V 6 1藏
咱们工程中的一些常量字符串在逆向开发B ] 2 : K y H i #中 , 运用 hopper
检查汇s + l编时会直接以注释的办法写在汇编/ j q r U指令之后 , 这种注释会给进犯人t _ n 3 [ _ I ^员供给极大的头绪和引导作用 .
比方如下代码 :
#defineb D ( STRING_ENCRYPT_KEY "demo_AES_key"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LBObject * objc = [LBObject new];
BOOL result = [objc; 3 U 2 | ; . checkIsVipWithkeyString:@"12311" TM _ - E 7oken:@"jfkfqwe"];
lbCFunc(STRING_ENCRYM y & * s _ VPT_KEY);
}
void lbCFunc(const char *] h v ) _ P O str){
printf("%s",str);
}
@end
当采用了字符t D L s J i 7串常量 or
宏界说的做法 , 进犯人员运用 hopper
检查汇编时如下 :
( 留意 : 笔者这儿 hopper
检查的现已是出产环Y R P I [ ]境的包 )
乃至 hopper
自带的汇编复原伪代码能够0 d ? 7 1 k 6 i b做到如下 :
细思极恐 ..
那么怎么做到. q 躲藏常量字符串呢 ?
其实简略做法[ – [ %便是把字符串常量换为一个办法 , 在这个办法中回来. Q ^ `需求的字符串即s _ L I ;可 , 例如 :
比方许多同学的项目中 , 对称性加密的 key
或许是写在本地的 , 一些重要的 key / secret / token
或许也是 ( 这儿仅仅举个例子 , 为了阐明一些比较重要的常量字符串 , 现在相似 key
这种现在遍及都是服务器下发了 ) , 那么针对这种比较显着又比较重要的字符串 , 咱们最好是对其做一下躲藏处理的 .i V u
简略做法演示
#define STRING_ENCRYPT_KEY @"demo_AE# d aS_key"
@impli & Y U r eementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//[self uploadDataWithKey:STRING_ENCR^ c Q k x &YPT_KEY]; //运用宏/常量字符串
[se( o 9 7 + Vlf uploadDataWithKey:AES_X + ( w b * 5 p ~K& d +EY()]; //运用函数替代字符串
}
- (void)uploadDataWithKey:(N= ) xSString *)key{
NSLog(@"%@",key);
}
sV ` 1 P ] % _ :tatic NSString *A C c i 6 ~ AES_KEY(){
unsigned char key[] = {
'd','e','m','o','_','A','E'T [ @ 3 C,'S','_q q l 5 } Z','k'x . o f d ~,'e','y',| i s b I O'',
};
return [NSString stringWithUTF8String:(const char *)key];
}
@end
作用展现
运用常量字符串 / 宏
运用函数
能够看到现已没有显现的字符串直接被书写出来了 , 当然 , 合作上办法混杂会更好 .
可是或许有Z * l q同学会问了 , 假如进犯者静态剖析V E 5 d ! 1 l w定位到咱们回来 key
这个函数怎么办 .
函数躲藏字符串升级版
#define STRING_ENCRYPT_KEY @"demo_AES_key"
#define ENCRYPT_KEY 0xAC
@interface ViewController ()
@end
@. g B uimplementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];m V 2 : y w , K y
// [9 T j S ? $ uself uploadDataWithKey:STRING_ENCRYPT_KEY]; //运用宏/常量字符串
[self uploadDataWithKey:AES_KEY()]; //运用函数替代字符串
}
- (void)uploadDataWithKey:(NSString *[ [ f * 6 s | k)key{
NSLog(@"%@",key);
}
sv e . ; i D wtatic NSString * AES_KEY(){
unsigned char key[] = {
(ENCRYPT_KEY ^ 'd'),
(ENCRYPT_KEY ^ 'e'),
(ENCRYPT_KEY ^ 'm'),
(ENCRj / ^ A O nYPT_KEY ^ 'o'),
(ENCRYPT_KEY ^ '_'),
(ENCl j u ] SRYE _ e k ; E f wPT_KEY ^ 'A'),
(ENCRYPT_KEY ^ 'E'),
(ENCRYPT_KEY ^ 'S'),
(ENCX : 0RYPT_P a | ` D / $ NKEY ^ '_'),
(ENCRYPT_KEY ^ ''),
};
unsigned char * p = key;
while (((*p) ^= ENCRYPT_KEY) != '') {
p++;
}
re! N w 8 s k ! Xturn [NSString stringWithUTF8String:(const char *)A S Qkey];
}
@eo Ond
采用这样的办法,这些~ n r字符不会进入字符7 j V @ u常量区 . 编译器直接换算成异或成果 .S K ( Q
4. 越狱环境防护
在越狱环境下 ,Q d e 1 I . I H 最为知名的便是越狱插件 – tweak
的运用了 . Monkey
也供给了 Xc* 8 U + ! B -ode
的插件能够很简略地编P E – ) , F 2 写一个自己的插件 . ( 感兴趣的同学能够阅览一下笔者在实战篇-钉钉打卡插件 中有完整的逆向探究和编写流程 ) .
插件的作业原理
在笔; e o Z者 iOS 底层 – 从头整理 dA 9 a O V &yld 加载流程 篇中 , 咱们知道一个环境变量 DYLD_INSERT_LIBZ h 9 u o L a LRARIES
的存在 . dyld
会由此标识来决议是否加载刺进动态库 .I n t 3
越T e [ H ) = O狱环境下 ,+ r * j
CH V } a V Fydia
的基石:Mobile] _ FSubstrate
会将SpringBoard
的[FBApplicationInfo environmentVariables]
函数做hook
,W y 0 * e 0 9 ^将环境变量DYLD_I# 1 Z qNSERT_LIBRARIES
设置增加需求加载的插件 ( 动态库 ) . 而运用的二进制包无须做任y 0 F T何改动 , 可是dyld
在加载运用的时候就会由于DYLD_INSE0 G ) 6 bRT_LIBRARIES
的机制 , 会去加载指定的刺进库 .插件在开发时就需求指定需求附加的进程 , 因而1 m V 1 M @ W就能够知道加载哪个运用时需求刺进这个插件 .
越狱环境的插件便是此原理 . tweak
插件编译经过 .o
等中间产物其实终究会生成一个 dyli= - ~ 6b
, 终究打包生成一个 .deb
的包 , 将其修正为 zip
你会看到咱们的 dylib
.
而 Monk1 S 9ey
以及 theos
中会f 9 2 Y q S将生成的 dylib
经过 openssh
拷贝到手机 /Ln F kibrary/MobileSubstrate/DynamicLibraries
里 , 等待 dyld
去加载附加 . 这也是为什么插件安装了之后会杀掉进程重新启动的原因 . 由于需求 dyld` _ B j a 4 #
再次作业将插件动态加载进来 .
它与重签名运用的代码) i U g I D U | ^注入 , 也便是经过修正运用
mach-o
的load commaR ) Wnds
, 尽管都是经过动态库注入 , 但能够说原理上完Y 0 y * P全是两个东西 . 越狱环境插件并不需求修正方针文件 .
知道了越狱环境插件的原理u O J ~ u ] , 那咱们再来谈谈插件怎么防护 .
越狱环境防护办法 1 : __RESTRICT
原理剖I * i e V析
在 dyld
源码中 , 咱们发现了如下代码 :
if ( gLinkCob ! N R t _ Q #ntext.processIsRestri~ U G i t lcted ) {
pruneEnvironmentVariables(envp, &apple);
// set again because envp and appl% ke may have changed or moved
setContext(mainExecutable0 Q K 4 , ^ Q v BMH, u 0 + s 9 ! m |, argc, argv, envp, apple);
}
检查后发= V g # – g w o现 , 当 processIsRestricted
为 truU F n Be
, 会删除相应的环境变量 , 也就意味着R : v P . 2 ~ DYLD_INSERT_LIBRARId l M v 5 ( 9 q :ES
能够被疏忽掉 . 持续搜索检查到如下 :
if ( issetugid() || hap l z u =sRestrictedSegment(m! v } fainExecutableMH) ) {
gLinkContext.processx h 3 d tIsRestricted = true;
}
也便是说 hasRestrictedSegment
时 , processIsRestT ] X l ; q oricted
这个标识会被设置为 true
.
点进去办法$ O S中 , 如上图 , 发现其实很简略 , 当咱们的 mach-o
中有 _7 I -_R; R U a mESTRICT 段
以及 __restrif H p X $ct 节
时 , 这个函数G s s = q就会回来 true
. 也就意味着c x B咱们只剩下一个问题 , 怎么给咱们的运用增加 __RESTRICT 段
以及 __restrict 节
.
操作演示
操作很简略 , 在 Other Linker Flags
中增加 : -Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
即可 ( 这个指令不能写错 , 写错会直接影响越狱插件能否注入成功 ) .
增加} * 9 e R V t K v后编译 , 检查 mach-o
.
增加结束后 , 一切越狱插件都会增加无效 , 笔者V q , 8 ^ r d这儿就不演示了 .8 w Z
防护手法评价
- 该计划能有效地屏蔽越狱环境下根本一切插件 .
- 该计D B j ( A & W S划作用比较明细 , 也因而比较简略被查出 .
- 该计, B C % | Y L J $划存在或许由于
dyld
跟着体系更新修正了这个机制 , 那么防护手法就失效的危险 .
防护手法破解
由于这个计划能够屏蔽一切的插件 , 因而关于进犯人员来说 , 很简略会想到是运用了 __RESTRICT
的机制来防护的 . 翻开 mach-o
直接检查你有没有这个段和节就能查找到 .
而由于 dyld
加载的机制 , 只需段与节不叫这个名字 , 就不认为是受限制的进程 . 因而 , 只需求改一下二进制即可 . 修正二进制的办法许多 , 有专门的二进制修正器 Synalize It! , 乃至 Mach[ 1 y s | nOView
, Hopper
, IDER
等可视化东西都能够{ H m } g u W 1 {直接修正 .
修正结束发现如下 :
此时 , 重新翻开你要逆向的工程 , 你会发现直接r : ( | @ |闪退 . 网上各种论坛里特别多这个问题 .. 问为什么修正了会_ ^ q闪退 .
实践上咱们在 运用签名原理及. P [ 2 m重签名 (重签微信运用实战)n U o ^ N + | 0 9 中讲过 i= 5 = K g J % 3 ZOS 运用签名和验证的原理 , 上述问题的根本在于 当咱们修正了二进制 , 那么就需求对运用进行重签名 , 不然 iOS
的验签中运用被修正 , hash
值一定 W z C T 2 e u B变化 , 那么验签一: % M 3 p M 5 . /定Y H v过不去 .
重签名的进程咱们就不o U _ N讲了 , 上述文章里有具体演示 . 下面咱们讲讲怎么防护这种做法 .
破解手法防护
受 fishhook
源码 , 与 阿里开源的组件化结构 BeeHive 中的启示 , 咱们能够自己去干 dyld
在做的事 . 换句话说 , 在咱们现已增加了 __R} k % LESTRICT
段和节的前提下 , 咱们能够自己在运转时去读 mach-o
中段和节的称号 , 以到达检查 __RESTRICT 段
与 __restrict 节
有没有% / C k e T =被修正的需求 .
不知道咱们这个思路理清楚了没 , 解释一下 :
为了防护咱们的二进制9 X , , q { ( G中
__RESTRICT 段
与__restrict 节
被修正掉 . 咱们在运转时自己去检查一b 1 / ] s & p U遍 .
- 有则代表没有被修正 .
- 没有则代表现已被修正 . ( 由N L –于我明明增加了这个段和节 , 却没有查到 )
防护手法运用演示
#import <mav 9 L ~ W ( 3ch-o/loader.h>
#imp; s : # c Iort <mach-o/dyld.h&! 8 $ $ |gt;
#if __LP64__
#define LC_SEGMENT_COMMAND LC_SEGMENT_64
#define LC_SEGMENT_COMMAND_WRONG LC_SEGMENY $ I o G b D X +T
#define LC_ENCRYPT_COMMANK ! l h uD LC_ENCRYPTION_INFa D } U N D qO
#define macho_segment_commandS q 2 0 O segmena = * Wt_comman2 M ) 8 z W ad_64E & j 1 Q
#define m6 ) J q 8 W N `acho_section section_64
#define mf N 7 - Gacho_header mach_header_64
#else
#define macho_header mach_header
#define LC_SEGMENT_COMMAND LC_SEGMENT
#define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64
#define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO_64
#define macho_sn t - A G S }egment_command segment_command
#define macho_secti( u $ u J r .on section
#endif
@implemeZ + 4ntation ViewContQ m 2 U _ o @ 6 (roller
+(void)load
{
//imagelist 里第0个是咱们自己的可履行文件
const struct mach_headeA D I ` S Hr * header = _dyld_get_image_header(0);
if (hasRestrictedSegment(header)) {
NSLog(@"没问题!");
}else{
NSLog(@M X q"检测到!!");
// 退出程序 , 能够上报 or 记载 ..
#ifdef __arm64__
asm volatile(
"mov x0,#0n"
"mov x16,#1n"
"svc #0x80n"
);S t k p
#endif
#ifdef __arm__//32位下
asm volatile(
"mov r0,#0n"
"mov r16,#1n"
"I T G I .svc #80n"
);
#endif
}
}
static bool hasRestrictedSegment(const struct macho_header* mh)
{
const uint32_t cmd_count = mh->ncmds;
const struct load_commd F Sand* conl f i -st cmds = (struct loG t o lad_cT ` a 1 3ommand*)(((char*)mh)+sizeof(struct macho_header)C ; d ; n c ~ 3);
const struct load_command* cD A | 6 Z @ b Smd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_SEGMENT_COMMAND:
{
const struct macho_segment_command* seg = (struct macho_segment_command*@ b = T 9 N K U)cmd;
iX i } 1 c [ U xf (strcmp(seg->segname, "__RESTRICTC ? y ,") == 0) {
con/ ( Z @ X T Zst struct macho_section* const sectionsSD C * W b Ctart = (struct macho_section*)((9 S | + O 9 4 ~ Lchar*)seg + sizeof(struz g mct macho_segment_command));
const struct macho_section* ck R F M ; ; h Nonst sectionsEnd = §ionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect <5 7 ~ s H n 7 ? ; sectionsEnd; ++sect) {
if (strcmp(sect->sectname, "__ro 1 = sestrict") == 0)
return true;
}
}
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return false;
}
@end
当然 , 相同为了避免咱们的 hasRestricteP 8 d r j hdSegment
代码被 hook
, 你能够结合混杂 , 提早履行机制 ( 本文的动态调试-防] ; 2 F护办法 3 : ptrace / sysctl 优化版 – 提早履行) 来共同运用 .
相同仍是那9 , % =句话 , 不主张直接退出运用 , 不要留下显着的防护痕迹 , 不然进犯人员首要不会怀疑B c o $ } g ]自己有没有哪里没有理清楚 , 而是会针对这个点打开剖析 .
吃瓜R A w ( f 4 A环节
这个防护手法是从哪出来的呢 ? 不知道咱们有没有人还知道 ‘念茜’ , 支付宝的逆向工程师 ,
CSDN
里活跃的一位前辈 , 14 年头就开端分享逆向常识 , 也很感谢这些前辈为咱们铺下了路 , 让咱们站在巨人的肩膀上 .
防护办法 2 : 监测环境变量
运用和原理都比较简略 , 咱们都叙述过了 . 便是监测 DYLD_INSERT_LIBRARIES
. 因而就能够检测到有没有越狱插件 , 或许说是不是越狱环境了 ( 由于越狱环境默许就有 Mobile Substrate
等插件了 ) .
//越狱检测
char * dlna* q T o r Xme = getenv("DYLD_INSERT_LIBRARIES");
if (dlname) {
NSLog(@"越狱手机,关闭部分功用");
}else{
NSLog(@"正常手机!");
}
防护办法 3 : image list
这个防护手法其实便是 咱们本文 动态调试其他防护办法 中的第一种 . 运用白名单监测自己工程8 A s 0其时引进三方库 , 查找是否有不知道库注入 .
由于 DYLD_INSERT_LIBRARIES
的原理和特性 , 其本质上也是动态库注入 , 只不过是由 dyld
动态去加载完成的动态注入 , 无须修正二进制文件自身 .
咱们相同能够运用如2 5 m下 :
来检查是否有其他动态库注入 , 当发现
这些库时 , 你根本就能够判定这个用户是在越狱环境中了 .
总结
防护技巧 :
- 单一的防护手法往往不足以确保安全 ,| m D 多重防护手法结合运用 , 能起到更好作用 .
- 不要给防护代码留下u L & 9显着的痕迹 , 尤其在
UI
层面 ( 例如闪退 , 弹框等等 )E * X 0 { .- 没有肯定安全的防护 , 增加进犯者的查找和剖析花费= 7 f Q 7即可到达防护的意图 .
- 不明白进攻 , 何谈V – +防护 .