阅读 628

ARM汇编基础(iOS逆向)

ARM汇编基础

在逆向一个功能的时候,往往需要分析大量的汇编代码,在iOS逆向中,ARM汇编是必须掌握的语言,本文总结了ARM汇编的基础知识,如果你想了解更多,请参考狗神的小黄书《iOS逆向逆向工程》或ARM官方手册.

寄存器,内存和栈

在ARM汇编里,操作对象是寄存器,内存和栈
ARM的栈遵循先进后出,是满递减的,向下增长,也就是开口向下,新的变量被存到栈底的位置;越靠近栈底,内存地址越小
一个名为stackPointer的寄存器保存栈的栈底地址,成为栈地址.
可以把一个变量给入栈(push)以保存它的值,也可以让它出栈(pop),恢复变量的原始值.在实际操作中,栈地址会不断变化;但是在执行一块代码的前后,栈地址应该是不变的,不然程序就要出问题,

特殊用途的寄存器

ARM处理器中的部分寄存器有特殊用途 如下所示:

寄存器用途
R0-R3传递参数与返回值
R7帧指针,指向母函数与被调用子函数在栈中的交界
R9在iOS3.0以前被系统保留
R12内部过程调用存储器,dynamic linker会用到它
R13sp寄存器
R14LR寄存器,保存函数返回地址
R15PC寄存器

分支跳转与条件判断

处理器名为”Program counter”(简称PC)的寄存器用于存放下一条指令的地址.一般情况下,计算机一条接一条地顺序执行指令,处理器执行完一条指令后将PC加1,让它指向下一条指令.例如处理器顺序执行指令1到指令5,但是如果把PC的值变一变,指令执行的顺序就完全不同指令执行顺序被打乱,变成了指令1,指令5,指令4,指令2,指令3,指令6,这种乱序的学名叫做”分支”,或者”跳转”,它使循环和subroutime(子程序)成为可能,例如:

// endless() 函数
复制代码

在实际情况中,满足一定条件才得以触发的分支是最实用的,这种分支成为条件分支.if else 和 while都是基于条件分支实现的,在ARM汇编中,分支的条件一般有4种:

  • □ 操作结果为0(或不为0);
  • □ 操作结果为负数;
  • □ 操作结果有进位;
  • □ 运算溢出(比如两个正数相加得到的数超过了寄存器位数).

这些条件的判断准则(flag)存放在程序状态寄存器(Program Status Register,PSR)中,数据处理相关指令会改变这些flag,分支指令再根据这些flag决定是否跳转.下面的伪代码展示了一个for循环

for:
复制代码

ARM/THUMB指令解读

ARM处理器用到的指令集分为ARM和THUMB两种:ARM指令长度均为32bit,THUMB指令长度为16bit.所有指令可大致分为三类,分别为,数组操作指令,内存操作指令和分支指令.

数据操作指令

数据操作指令有以下2条规则:

  • 所有的操作数均为32bit;
  • 所有的结果均为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 Higer or Same)
CC没有进位或借位(Carry Clear)
LO同CC(unsigned LOwer)
MI结果小于0(MInus)
PL结果大于等于0(PLus)
VS溢出(Overflow Set)
VC无溢出(Overflow Clear)
HI无符号比较大于(unsigned HIger)
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
复制代码

比较R0和R1的值,如果R0大于等于R1,则R2 = R0;否则R2 = R1.
“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表示有符号数运算结果是否溢出.

算数操作指令可以大致分为4类:

算数操作

ADD R0,R1,R2; ——————> R0 = R1 + R2ADC R0,R1,R2; ——————> R0 = R1 + R2 + C(array)SUB R0,R1,R2; ——————> R0 = R1 - R2SBC R0,R1,R2; ——————> R0 = R1 - R2 - !CRSB R0,R1,R2; ——————> R0 = R2 - R1RSC R0,R1,R2; ——————> R0 = R2 - R1 - !C算数操作中,ADD和SUB为基础操作,其他均为两者的变种.RSB是”Reverse Sub”的缩写,仅仅是把SUB的两个操作数调换了位置而已;以”C”结尾的变种代表没有进位和借位的加减法,当产生进位或者借位时,将Carrry flag 置为1.

逻辑操作

AND R0,R1,R2; ——————> R0 = R1 & R2ORR R0,R1,R2; ——————> R0 = R1 | R2EOR R0,R1,R2; ——————> R0 = R1 ^ R2BIC R0,R1,R2; ——————> R0 = R1 &~ R2MOV RO,R2; ——————> R0 = R2MVN R0,R2; ——————> R0 = ~R2逻辑操作指令都已经用C操作符说明了作用,但是C操作符里的移位操作并没有对位的逻辑操作指令,ARM采用了桶式移位,共有四种指令:LSL 逻辑左移 LSR 逻辑右移 ASR 算术右移ROR 循环右移

比较操作

CMP R1,R2; ——————> 执行R1 - R2并依结果设置flag

CMN R1,R2; ——————> 执行R1 + R2并依结果设置flagTST R1,R2; ——————> 执行R1 & R2并依结果设置flagTEQ R1,R2; ——————> 执行R1 ^ R2并依结果设置flag比较操作其实就是改变flag的算术操作或逻辑操作,只是操作结果不保留在寄存器里而已.

乘法操作

MUL R4,R3,R2 ——————> R4 = R3 * R2MLA R4,R3,R2,R1 ——————> R4 = R3 * R2 + R1乘法操作的操作数必须来自寄存器

内存操作指令

内存操作指令的基本格式是:

op{cond}{type} Rd,[Rn,Op2]
复制代码

其中Rn是基址寄存器,用于存放基地址;”cond”的作用与数据操作指令相同;”type”指定指令”op”操作的数据类型,共有四种:

B(unsigned Byte)
复制代码

如果不指定”type”,则默认是word
ARM内存操作基础指令只有2个,LDR(loaD Register)将数据从内存中读出来,存到寄存器中;STR(STore Register)将数组从寄存中读出来,存到内存中.两个指令的使用情况如下:

LDR

LDR Rt,[Rn {,#offset}]          ;   Rt = *(Rn {+ offset}),{}代表可选
复制代码

STR

STR Rt,[Rn {,#offset}]          ;   *(Rn {+ offset}) = Rt
复制代码

此外,LDR和STR的变种LDRD和STRD还可以操作双字(DoubleWord),即一次性操作两个寄存器,其基本格式如下:

op{cond} Rt,Rt2, [Rn {, #offset}]
复制代码

其用法与原型类似,如下:

STRD

SRTD R4,R5, [R9,#offset]    ; *(R9 + offset) = R4;*(R9 + offset + 4) = R5
复制代码

LDRD

LDRD R4,R5,[R9,#offset]     ; R4 = *(R9 + offset); R5 = *(R9+offset+4)
复制代码

除LDR和STR外,还可以通过LDM(LoaD Multiple)和STM(STore Multipe)进行块传输,一次性操作多个寄存器.块传输指令的基本格式是

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”指定R4值得变化的4中规律,如下所示:

IA(Increament After)每次传输后增加Rd的值;
复制代码

这是什么意思呢?下面以LDM为代表,举一个简单的例子,相信大家一看就明白了.

在执行以下命令后,R4,R5,R6的值分别变成:

foo():
复制代码

STM指令的作用方式与此类似,不再赘述.LDM和STM的操作与LDR和STR完全相反

分支指令

分支指令可以分为无条件分支和条件分支两种.

无条件分支

B Label;PC = Label
复制代码

无条件分支

跳转分支的cond是依照前面的flag来判断的,它们的对应关系如下:

condflag
EQZ = 1
NEZ = 0
CSC = 1
HSC = 1
CCC = 0
LOC = 0
MIN = 1
PLN = 0
VSV = 1
VCV = 0
HIC = 1 & Z = 0
LSC = 0
GEN = V
LTN != V
GTZ = 0 & N = V
LEZ = 1

在条件分支指令钱会有一条数据操作指令来设置flag,分支指令根据falg的值来决定代码走向,举例如下:

Label:
复制代码

THUMB指令

THUMB指令集是ARM指令集的一个子集,每条THUMB指令均为16bit;因此THUMB指令比ARM指令更节省空间,且在16位数据总线上的传输效率更高.有得必有失,除了”b”之外,所有的THUMB指令均无法条件执行;桶式移位无法结合其他指令执行;大多数THUMB指令只能使用R0-R7这8个寄存器等.相对于ARM指令,THUMB指令的特点如下:

  • 指令数量减少
  • 没有条件执行
  • 所有指令默认附带*
  • 桶式移位无法结合其他指令执行
  • 寄存器使用受限
  • 立即数和第二操作数使用有限
  • 不支持数据写回
文章分类
iOS
文章标签