ARM汇编相关的iOS逆向理论基础
ARM官方资料:http://infocenter.arm.com
1.1 ARM汇编基础
“ARM汇编的基本概念相当于26个字母和音标;指令相当于单词,它们的变种相当于单词的各种形态;调用规则相当于语法,定义句子之间的联系。”
1.1.1
1.寄存器、内存和栈
在ARM汇编里,操作对象是寄存器(register)、内存和栈(stack)。
其中,寄存器可以看成CPU自带的变量,它们的数量一般是很有限的;
当需要更多变量时,就可以把它们存放在内存中;
不过,数量上去了,质量也下来了,对内存的操作比对寄存器的操作要慢得多。
栈其实也是一片内存区域,但它具有栈的特点:先进后出。ARM的栈是满递减(Full Descending)的,向下增长,也就是开口朝下,新的变量被存放到栈底的位置;越靠近栈底,内存地址越小,”
“一个名为“stack pointer”(简称SP)的寄存器保存栈的栈底地址,称为栈地址;可以把一个变量给入(push)栈以保存它的值,也可以让它出(pop)栈,恢复变量的原始值。在实际操作中,栈地址会不断变化;但是在执行一块代码的前后,栈地址应该是不变的,不然程序就要出问题了。”
2.特殊用途的寄存器
ARM处理器中的部分寄存器有特殊用途
R0-R3 传递参数与返回值
R7 帧指针,指向母函数与被调用子函数在栈中的交界
R9 在iOS 3.0以前被系统保留
R12 内部过程调用寄存器,dynamic linker会用到它
R13 SP寄存器
R14 LR寄存器,保存函数返回地址
R15 PC寄存器
3.分支跳转与条件判断
处理器中名为“program counter”(简称PC)的寄存器用于存放下一条指令的地址。一般情况下,计算机一条接一条地顺序执行指令,处理器执行完一条指令后将PC加1,让它指向下一条指令,
乱序”的学名叫“分支”(branch):指令的执行顺序被打乱”
条件分支:满足一定条件才得以触发的分支是最实用的
·操作结果为0(或不为0);
·操作结果为负数;
·操作结果有进位;
·运算溢出(比如两个正数相加得到的数超过了寄存器位数)。”
1.1.2 ARM/THUMB指令解读
ARM处理器用到的指令集分为ARM和THUMB两种;
ARM指令长度均为32bit,
THUMB指令长度均为16bit。
所有指令可大致分为3类,分别是数据操作指令、内存操作指令和分支指令。
1.数据操作指令
1)所有操作数均为32bit;
2)所有结果均为32bit,且只能存放在寄存器中。
基本格式:op{cond}{s} Rd, Rn, Op2
“cond”和“s”是两个可选后缀;
“cond”的作用是指定指令“op”在什么条件下执行”
17种条件
EQ 结果为0(EQual to 0)
NE 结果不为0(Not Equal to 0)
CS 有进位或借位(Carry Set)
HS 同CS(unsigned Higher or Same)
CC 没有进位或借位(Carry clear)
LO 同CC(unsigned LOwer)
MI 结果小于0(MInus)
PL 结果大于等于0(PLus)
VS 溢出(oVerflow Set)
VC 无溢出(oVerflow Clear)
HI 无符号比较大于(unsigned HIgher)
LS 无符号比较小于等于(unsigned Lower or Same)
GE 有符号比较大于等于(signed Greater than or Equal)
LT 有符号比较小于(signed Less Than)
GT 有符号比较大于(signed Greater Than)
LE 无符号比较小于等于(signed Less than or Equal)
AL 无条件(ALways,默认)
"cond"的用法:
比较 R0, R1
移动 GE R2, R0
移动 LT R2, R1
等价:
if(R0 >=R1){
R2 =R0
}else{
R2=R!
}
“s”的作用是指定指令“op”是否设置flag
有下面4种flag:
N(Negative)
如果结果小于0则置1,否则置0;
Z(Zero)
如果结果是0则置1,否则置0;
C(Carry)
对于加操作(包括CMN)来说,如果产生进位则置1,否则置0;对于减操作(包括CMP)来说,Carry相当于Not-Borrow,如果产生借位则置0,否则置1;对于有移位操作的非加/减操作来说,C置移出值的最后一位;对于其他的非加/减操作来说,C的值一般不变;
V(oVerflow)
如果操作导致溢出,则置1,否则置0。
C flag表示无符号数运算结果是否溢出;V flag表示有符号数运算结果是否溢出。
算数操作:
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
ADD和SUB是基础,其他是变种;
RSB=Reverse SuB =把SUB的两个操作数调换位置;
以C结尾=Carry=代表有进位和错位的加减法,当产生进位或没有借位的时,将Carryflage置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
LSL 逻辑左移;
LSR 逻辑右移;
ASR算数右移;
ROR循环右移;
比较操作:
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
乘法操作的操作数必须来自寄存器。
2.内存操作指令
基本格式:
op{cond}{type} Rd, [Rn,?Op2]
其中Rn是基址寄存器,用于存放基地址;
“cond”的作用与数据操作指令相同;
“type”指定指令“op”操作的数据类型,共有4种:
B(unsigned Byte)
无符号byte(执行时扩展到32bit,以0填充);
SB(Signed Byte)
有符号byte(仅用于LDR指令;执行时扩展到32bit,以符号位填充);
H(unsigned Halfword)
无符号halfword(执行时扩展到32bit,以0填充);
SH(Signed Halfword)
有符号halfword(仅用于LDR指令;执行时扩展到32bit,以符号位填
充)。
如果不指定“type”,则默认数据类型是word。
ARM内存操作基础指令只有两个:
LDR(LoaD Register)将数据从内存中读出来,存到寄存器中;STR(STore Register)将数据从寄存器中读出来,存到内存中。
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
变种,操作双字,一次性操作2个寄存器:LDRD和STRD
格式:
op{cond} Rt, Rt2, [Rn {, #offset}]
STRD
STRD R4, R5, [R9,#offset] ; *(R9 + offset)= R4; *(R9 + offset + 4)= R5
·LDRD
LDRD R4, R5, [R9,#offset] ; R4 = *(R9 + offset); R5 = *(R9 + offset + 4)
LDM(LoaD Multiple)和STM(STore Multiple)进行块传输;
一次性操作多个寄存器;
基本格式:
op{cond}{mode} Rd{!}, reglist
其中Rd是基址寄存器,
可选的“!”指定Rd变化后的值是否写回Rd;
reglist是一系列寄存器,用大括号括起来,它们之间可以用“,”分隔,也可以用“-”表示一个范围,比如,{R4–R6,R8}表示寄存器R4、R5、R6、R8;这些寄存器的顺序是按照自身的编号由小到大排列的,与大括号内的排列顺序无关。
需要特别注意的是,LDM和STM的操作方向与LDR和STR完全相反:LDM是把从Rd开始,地址连续的内存数据存入reglist中,STM是把reglist中的值存入从Rd开始,地址连续的内存中。
“cond”的作用与数据操作指令相同。“mode”指定Rd值的4种变化规律,如下所示:
IA(Increment After)
每次传输后增加Rd的值;
IB(Increment Before)
每次传输前增加Rd的值;
DA(Decrement After)
每次传输后减少Rd的值;
DB(Decrement Before)
每次传输前减少Rd的值。
例子:
foo():
LDMIA R0, {R4 – R6} ; R4 = 5, R5 = 6, R6 = 7
LDMIB R0, {R4 – R6} ; R4 = 6, R5 = 7, R6 = 8
LDMDA R0, {R4 – R6} ; R4 = 5, R5 = 4, R6 = 3
LDMDB R0, {R4 – R6} ; R4 = 4, R5 = 3, R6 = 2
3.分支指令
无条件分支和条件分支;
无条件分支:
B Label ; PC = Label
BL Label ; LR = PC – 4; PC = Label
BX Rd ; PC = Rd并切换指令集
例子:
foo():
B Label ; 跳转到Label处往下执行
...... ; 得不到执行
Label:
......
条件分支
cond flag
EQ Z = 1
NE Z = 0
CS C = 1
HS C = 1
CC C = 0
LO C = 0
MI N = 1
PL N = 0
VS V = 1
VC V = 0
HI C = 1 & Z = 0
LS C = 0 | Z = 1
GE N = V
LT N != V
GT Z = 0 & N = V
LE Z = 1 | N != V
在条件分支指令前会有一条数据操作指令来设置flag,分支指令根据flag的值来决定代码走向:
Label:
LDR R0, [R1], #4
CMP R0, 0 ; 如果R0 == 0,Z = 1;否则Z = 0
BNE Label ; Z == 0则跳转
4.THUMB指令
THUMB指令集是ARM指令集的一个子集,每条THUMB指令均为16bit;因此THUMB指令比ARM指令更节省空间,且在16位数据总线上的传输效率更高。有得必有失,除了“b”之外,所有THUMB指令均无法条件执行;桶式移位无法结合其他指令执行;大多数THUMB指令只能使用R0~R7这8个寄存器等。
THUMB特点:
1.指令数减少;
2.没有条件执行
3.所有指令默认附带"s";
4.桶式移位无法结合其他指令执行;
5.寄存器使用受限;
6.立即数和第二操作数使用受限
7.不支持数据
1.1.3ARM 调用规则
当一个函数调用另一个函数时,常常需要传递参数和返回值;如何传递这些数据,称为ARM汇编的调用规则。
函数的前4个参数存放在R0到R3中,其他参数存放在栈中;返回值放在R0中
2.tweak的编写套路
1.进入进程
$cycript -p XXX
2.查看UI层次
开启expand
cy#?expand
expand==true
cy# [[UIApp keyWindow] recursiveDescription]
3.通过Cycript的"#"操作符可以拿到windows上的任意view
cy# tabView = #0x146e1af0
4.app所有windows
cy# [UIApp windows]
5.隐藏控件:
cy# [#0x146e6060 setHidden:YES]
6.获取响应函数:
“cy# button = #0x14798410
#"; layer = >"
cy# [button allTargets]
[NSSet setWithArray:@[#""]]]
cy# [button allControlEvents]
64
cy# [button actionsForTarget:#0x14609d00 forControlEvent:64]
@["_sendAction:withEvent:"]”
7.nextResponder
利用响应者链,可以找到某个view的父类;
cy# [#0x17f92890 nextResponder]
#"; layer = ; contentOffset: {0, 0}; contentSize: {320, 504}>"
cy# [#0x17eb4fc0 nextResponder]
#"; layer = ; contentOffset: {0, -64}; contentSize: {320, 717.5}>"
cy# [#0x16c69e00 nextResponder]
3LLDB的使用技巧
...待完善