ios应用逆向工程5-6章笔记
理论篇
1.依据维基百科的定义,tweak指的是对电子系统进行轻微调整来增强其功能的工具;在iOS中,tweak特指那些能够增强基他进程功能的dylib,是越狱iOS的最重要组成部分.这种增强原有工具功能的方法是通过进程注入
实现的,就像书籍的知识注入大脑的,让大脑变得更强
2.一般来说,编写tweak会用到C,C++和Objective-C三种语言
3.iOS是个封闭的系统,它暴露给我们的中是冰山一角,有太多太多的功能还有待我们进一步挖掘.每次越狱发布后,都会有人把最新的头文件发布出来,google一下iOS private headers
即可轻松找到下载链接,省去了自己class-dump的麻烦
4.定位目标文件
- 固定位置
我们的逆向目标一般是dylib,bundle或daemon,它们在系统中的位置几乎是固定的:
- 基于CydiaSubstrate的dylib全部位于
/Library/MobileSubstrate/DynamicLibraries/
下,几乎不费吹灰之力就可以轻松定位 - bundle主要分为App和framework两类,其中Appstore App全部位于
/var/mobile/Containers/Bundle/Application
下,系统App全部位于/Applications
下,framework全部位于/System/Library/Frameworks
或/System/Library/PrivateFrameworks
下 - daemon的配置文件均位于
/System/Library/LaunchDaemons/
,/Library/LaunchDaemons
或/Library/LaunchAgents/
下,是一个plist格式的文件.其中的ProgramArguments
字段,即是daemon可执行文件的绝对路径
- 基于CydiaSubstrate的dylib全部位于
- cydia
通过
dpkg -i
安装的的deb包,内容会被cydia如实记录,可在cydia中的Filesystem Content
中找到路径 - PreferenceBundle PreferenceBundle是寄生在Settings应用里的App,它的功能界定有些模糊,既可以作为单纯的配置文件,由别的进程读取后执行,也可以估计有实际功能,自己来执行一些操作.来自AppStore的第三方PreferenceBundle仅可作为配置文件存在,不会含有实际功能;来自Cydia的也不是问题,可通过上面的定位方式找到路径;但对于iOS自带的PreferenceBundle来说,定位的过程就要复杂一些.PreferenceBundle的界面可以用代码编写,也可以用固定格式的plist文件构造.
5.使用cycript
可快速测试函数功能,比theos方便
6.解析函数参数
getClassName
函数能够把对象的类名表示成一个char*
,如object_getClassName(arg1)
获取arg1这个对象的的对象类名description
函数能够把对象的内容表示成一个NSString,如[arg1 description]
获取arg1这个对象的对象内容
%hook SBScreenFlash
- (void)flashColor:(id)arg1 withCompletion:(id)arg2
{
%orig;
NSLog(@"iOSRE: flashColor: %s, %@", object_getClassName(arg1),arg1);//[arg1 description]可以直接写成arg1
}
end
上面的示例中打印的结果为iOSRE: flashColor: UICachedDeviceWhiteColor, UIDeviceWhiteColorSpace 1 1
,其中UICachedDeviceWhiteColor
是对象类名,UIDeviceWhiteColorSpace 1 1
是对象内容
7.书籍作者强烈建议大家通览class-dump出的头文件,把那些语义明显,自己感兴趣的函数放到iOS上实测一下,这个过程能极大地增加对iOS底层的熟悉程度
8.ARM处理器中的特殊用途的寄存器
- R0-R3 传递参数与返回值
- R7 帧指针,指向母函数与被调用子函数在栈中的交界(相当于ebp?)
- R9 在ios3.0以前被系统保留
- R12 内部过程调用寄存器,dynamic linker会用到它
- R13 SP寄存器(相当于esp?)
- R14 LR寄存器,保存函数返回地址(居然有一个单独的寄存器用来保存函数返回地址)
- R15 PC寄存器(相当于eip?)
9.ARM调用规则 在执行一块代码时,其前后栈地址应该是不变的.这个操作是通过被执行代码块的前言和后记完成的.
- 前言
- 将LR入栈
- 将R7入栈(push ebp)
- R7=SP
- 将需要保留的寄存器原始值入栈
- 为本地变量开辟空间
- 后记
- 释放本地变量占用的空间
- 将需要保留的原始值出栈
- 将R7出栈
- 将LR出栈,PC=LR
10.函数调用
- 32位ARM函数调用时,函数的前4个参数存放在R0到R3中,其他参数存放在栈中;返回值放在R0中
- ARM64汇编语言函数前8个参数使用x0-x7寄存器(或w0-w7寄存器)传递,多于8个的参数均通过堆栈传递,并且返回值通过x0寄存器(或w0寄存器)返回
11.ARM处理器用到的指令集分为ARM和THUMB两种;ARM指令长度均为32bit,THUMB指令长度均为16bit.THUMB指令比ARM指令更节省空间,且在16位数据总线上的传输效率更高,相对于ARM指令,THUMB指令的缺点如下
- 指令数量减少
- 没有条件执行
- 所有指令默认附带”s”
- 桶式移位无法结合其他指令执行
- 寄存器使用受限
- 立即数和第二操作数使用受限
- 不支持数据写回
12.ARM32位处理器的所有指令可大致分为3类,分别是数据操作指令
,内存操作指令
和分支指令
- 数据操作指令
所有操作数均为32位,所有的结果均为32位,且只能存放在寄存器中
- 算术操作
ADD R0,R1,R2 ;R0=R1+R2 ADC R0,R1,R2 ;R0=R1+R2+C(arry) SUB R0,R1,R2 ;R0=R1-R2 SBC R0,R1,R2 ;R0=R1-R2-!C RSB R0,R1,R2 ;R0=R2-R1 RSC R0,R1,R2 ;R0=R2-R1-!C 以"C"(即Carry)结尾的变种代表有进位和借位的加减法,当产生进位或没有借位时,将Carry flag置1
- 逻辑操作
AND R0,R1,R2 ;R0=R1&R2 ORR R0,R1,R2 ;R0=R1|R2 EOR R0,R1,R2 ;R0=R1^R2 BIC R0,R1,R2 ;R0=R1&~R2 MOV R0,R2 ;R0=R2 MVN R0,R2 ;R0=~R2
- 比较操作
CMP R1,R2 ;执行R1-R2并依据结果设置flag CMN R1,R2 ;执行R1+R2并依据结果设置flag TST R1,R2 ;执行R1&R2并依据结果设置flag TEQ R1,R2 ;执行R1^R2并依据结果设置flag
- 乘法操作
MUL R4,R3,R2 ;R4=R3*R2 MLA R4,R3,R2,R1 ;R4=R3*R2+R1
- 算术操作
- 内存操作指令
ARM内存操作只有2个基础指令,
LDR
(load register)将内存中的数据存到寄存器(cpu),STR
(store register)将寄存器(cpu)中的数据保存到内存.除了LDR和STR外,还可以通过LDM(load multiple)
和STM(store multiple)
进行块传输,一次性操作多个寄存器.需要特别注意的是,LDM和STM的操作方向与LDR和STR完全相反:LDM是把从Rd开始,地址连续的内存数据存入reglist中,STM是把reglist中的值存入从Rd开始,地址连续的内存中.- LDR
LDR Rt,[Rn{,#offset}] ;Rt=*(Rn{+offset}),{}代表可选 LDR Rt,[Rn,#offset]! ;Rt=*(Rn+offset);Rn=Rn+offset LDR Rt,[Rn,#offset] ;Rt=*Rn;Rn=Rn+offset
- STR
STR Rt,[Rn{,#offset}] ;*(Rn{+offset})=Rt STR Rt,[Rn,#offset]! ;*(Rn{+offset})=Rt;Rn=Rn+offset STR Rt,[Rn],#offset ;*Rn=Rt;Rn=Rn+offset
- LDR
- 分支指令
13.函数和UI之间的关联非常紧密,如果能拿到感兴趣的UI对象,就可以找到它所对应的函数,我们称该函数为UI函数.这个过程,一般是利用cycript,结合UIView中的神奇私有函数recursiveDescription
和UIResponder
中的nextResponder
来实现的.注意,游戏一般不是采用UIKit来构建UI的,recursiveDescription和nextResponder不适用于游戏.