安卓逆向肯定要学点 arm汇编 ,几个月前学过一次汇编,学叉了。。。 学的 8086汇编 😭(因为菜,当时不知道汇编还分类,囧~),就很尴尬,不过应该有很多相同的知识点,也不算白学吧,这两天抽空把 arm汇编 过一遍,记录一些常见的汇编指令集和知识。本篇文章是 abi-v7a 架构的汇编,记录一下方便以后查询。有问题的话直接加微信叼我,我会虚心接受😁。

  1. # Arm 指令集

    • # Arm架构 中关于字,字节,双字

      1 个字节表示 8 位: 1 Byte = 8 bit

      1 个字表示 2 个字节: 1 Word = 4 Byte = 32 bit

      1 个双字表示 2 个字: 1 DWord = 2 Word = 8 Byte = 64 bit

      1 个四字表示 2 个双字: 1 QWord = 2 DWord = 4 Word = 16 Byte = 128 bit

    • # 寄存器分类:通用寄存器和状态寄存器
      R0-R7		; 不分组寄存器
      R8-R12 	; 分组寄存器
      R0-R3		; 传递参数和返回值
      R13			; 保存栈顶地址 (R13/SP)
      R14			; 保存函数的返回地址 (R14/LR)
      R15			; 程序计数器 (PC 寄存器)	
      CPSR			; 程序状态寄存器,包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。
      SPSR			; 程序状态保存寄存器。SPSR 用于保存 CPSR 的状态,以便异常返回后恢复异常发生时的工作状态。
      指令条件标志位 ( CPSR 的状态)含义
      EQZ 置位相等
      NEZ 清零不相等
      CSC 置位无符号数大于或等于
      CCC 清零无符号数小于
      MIN 置位负数
      PLN 清零正数或零
      VSV 置位溢出
      VCV 清零未溢出
      HIC 置位 Z 清零无符号数大于
      LSC 清零 Z 置位无符号数小于或等于
      GEN 等于 V带符号数大于或等于
      LTN 不等于 V带符号数小于
      GTZ 清零且 ( N 等于 V )带符号数大于
      LEZ 置位或 ( N 不等于 V )带符号数小于或等于
      AL忽略无条件执行
    • # 跳转指令
      B 		; 无条件跳转,直接跳转,例:B loc_地址 
      BL 		; 带链接的无条件跳转 
      BX 		; 带状态切换的无条件跳转
      BLX		; 带链接和状态切换的无条件跳转
      BNE		; 不相等
      BEQ		; 相等
    • # 存储器与寄存器交互数据指令

      存储器:内存,主存
      存储器中放的数据:可以是字符串,数字,也可以是地址,可以放各种类型的数据
      存储地址单元:地址与地址中存在的值

      LDR 	; 从存储器中加载的 32 位数据到寄存器 ← load
      LDR R8, [R9, #4]  	; R8 为待加载数据的寄存器,加载值为 R9+0x4 所指向的存储单元
      STR		; 将寄存器的 32 位数据存储到存储器 → store
      STR R8, [R9, #4]	; 将 R8 寄存器的数据存储到 R9+0x4 指向的存储单元
      LDM		; 将存储器的数据加载到一个寄存器列表 →
      LDM R0, {R1-R3} 	; 将 R0 指向的存储单元的数据依次加载到 R1,R2,R3 寄存器
      STM		; 将一个寄存器列表的数据存储到指定的存储器	←
      STM R0, {R1-R3}		; 将 R1,R2,R3 寄存器的数据依次存储到 R0 存储器
      ; LDM 和 STM 可跟的类型 
      ;	IA: 每次传送后地址加 4
      ; 	IB: 每次传送前地址加 4
      ;	DA: 每次传送后地址减 4
      ; 	DB: 每次传送前地址减 4	
      ; 	FD: 满递减堆栈	
      ; 	ED: 空递减堆栈
      ;	FA: 满递增堆栈
      ;	EA: 空递增堆栈
      ; [满堆栈]: 堆栈指针 SP 指向最后压入堆栈的有效数据
      ; [空堆栈]: 堆栈指针 SP 指向下一个要放入得数据的空位置
      PUSH	; 将寄存器值推入堆栈	
      POP		; 将堆栈值推出到寄存器
      SWP		; 将寄存器与存储器之间的数据进行交换
      SWP R1, R1, [R0] 	; 将 R1 寄存器与 R0 指向的存储单元的内容进行交换
    • # 传送指令
      MOV		; 将立即数或寄存器的数据传送到目标寄存器 ←
      MOV R0, #8		; R0 = 0x8
      MVN		; 将立即数或寄存器的数据取反,然后再传送到目标寄存器
      MVN R0, #0xff	; R0 = 0xffffff00
    • # 算数运算指令
      ADD		; 加
      ADD R0, R1, R2		; R0=R1+R2
      SUB		; 减
      MUL		; 乘
      DIV		; 除
    • # 逻辑运算符
      AND	; 与
      ORR	; 或
      ; 移位:实质是乘除,类似于小数点移位,但相反。小数点左移,数变小;右移变大。
      ; 逻辑移位:左移变大,右移变小,且按 2 的倍数进行,因为是 2 进制
      LSL	; 逻辑左移和算术左移 (ASL) 等价
      LSR	; 逻辑右移
      LSL R0, R1, #2	; R0=R1 * 4
      ASR	; 算术右移
      ROR	; 循环右移,按操作数所指定的数量向右循环移位,左端用右端移出的数来填充
      RRX	; d
    • # 比较指令
      CMP	; 比较
      CMP R0, #0		; R0 寄存器中的值与 0 比较,结果再 CPSR 状态寄存器中展示
      ; 标志位:如 z 位,这个都可以在动态调试时,寄存器窗口看到
    • # 异常中断指令
      SWI		; 软件中断指令
      BKPT	; 断点中断指令
    • # 其他指令
      SWT		; 协处理器指令,切换用户模式
      TST		; 把一个寄存器的内容和另一个寄存器的内容或立即数进行按位与运算,结果在 CPSR 中展示,结果为 0 时,EQ 被设置
      TST R1, #%1		; 用于测试在 R1 中是否设置了最低位(% 表示二进制数 ) 
      BIC		; 用于清楚寄存器中的某些位,并把结果存放在寄存器中,由后面的二进制数决定,哪个位为 1,哪个位就清零
      BIC R0, R0, #%1011	; 将 R0 的 0,1,3 位清零,其余位不变
      MRS		; 用于将程序状态寄存器的内容传送到通用寄存器中
      MRS R0, CPSR	; 传送 CPSR 的内容到 R0
      MRS R0, SPSR	; 传送 SPSR 的内容到 R0
      MRS CPSR, R0	; 传送 R0 的内容到 CPSR
      MRS CPSR_c, R0	; 传送 R0 的内容到 SPSR,但仅仅修改 CPSR 的控制位域,
      ;32 位程序状态寄存器可分为 4 个域: [31:24]> 条件标志位域,用 f 表示;[23:16]> 状态位域,用 s 表示;[15:8]> 扩展位域,用 x 表示;[7:0]> 控制位域,用 c 表示

      ! 后缀,当数据传送完毕,将最后的地址写入基址寄存器,否则基址寄存器内容不变;基址寄存器不允许为 R15 ,寄存器列表可以为 R0-R15 的任意组合。

      ^ 后缀,当指令为 LDM 且寄存器列表中包含 R15 ,选用改后缀表示:除了正常的数据传送以外,还将 SPSR 复制到 CPSR ;同时,该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。

  2. # 寄存器寻址方式

    • # 立即寻址:操作数在指令中直接给出,要求以 # 为前缀,对于十六进制表示的立即数,还要求在 # 后加 0x&
      MOV R0, #1234	; R0=0x1234
    • # 寄存器寻址:利用寄存器中的数值作为操作数
      MOV R0, R1		; R0=R1
    • # 寄存器间接寻址:以寄存器中的值作为地址,而操作数存放在该地址指向的存储器中
      LDR R0, [R1]	; 将 R1 寄存器中的值作为地址,取出地址中的值赋值给 R0
    • # 基址变址寻址:将寄存器的内容与指令给出的地址偏移量相加,得到地址,操作数存储在该地址指向的存储器中。
      LDR R0, [R1, #4]	; R0=[R1+4]
      LDR R0, [R1, #4]!	; R0=[R1+4]、R1=R1+4, 先赋值 R0, 再修正 R1 地址
      LDR R0, [R1], #4	; R0=[R1]、R1=R1+4, 先赋值 R0, 再修正 R1 地址
      LDR R0, [R1, R2]	; R0=[R1+R2]
    • # 相对寻址:与基址变址寻址类似,以程序计数器 PC 的值为基地址,指令中的地址标号为偏移量,两者相加后得到操作数的有效地址
      BL NEXT		; 跳转到子程序 NEXT 处执行
    • # 多寄存器寻址:采用多寄存器寻址方式,一条指令可以完成多个寄存器值得传送,最多一次 16 个通用寄存器。
      LDMIA R0, {R1,R2,R3,R4}	 ; R1=[R0]、R2=[R0+4]、R3=[R0+8]、R4=[R0+12], 后缀 IA 表示每次执行完,R0 按 4 长度 (1g) 增加
  3. # 伪操作:仅在汇编时有效,比如变量的定义,内存空间的分配。

    • # 标号 symbol (或 label

      标号只能由 a-z,A-Z,0-9,.,_等组成,除局部标号外,不能以数字开头。

      symbol 分类说明
      基于 PC 的标号位于目标指令前的标号或程序中数据定义伪操作前的标号。这种标号再汇编时讲被处理成 PC 值加上或减去一个数字常量,常用于表示跳转指令 "B" 等的目标地址,或代码段中所嵌入的少量数据。
      基于寄存器的标号常用 MAPFIELD 来定义,也可用 EQU 定义。这种标号再汇编时将被处理成寄存器的值加上或减去一个数字常量,常用于访问数据段中的数据。
      绝对地址:32 位常量绝对地址是一个 32 位数据,寻址范围为 [0, 2^32-1] 即可以直接寻址整个内存空间
    • # 局部标号:主要在局部范围使用,且可以重复出现
      • 定义语法格式: N

        ​ N: 0-99 之间的数字

        ​ routname: 当前局部范围的名称

      • 引用语法格式:%{F|B}{A|T} N

        ​ %: 表示引用操作

        ​ N: 为局部变量的数字号

        ​ routname: 为当前作用范围的名称 (用 ROUT 伪操作定义的)

        ​ F: 指示编译器只向前搜索

        ​ B: 指示编译器只向后搜索

        ​ A: 指示编译器搜索宏的所有嵌套层次

        ​ T: 指示编译器搜索宏的 d 层次

      1:
      subs r0, r0, #1  ; 每次循环使 r0=r0-1
      bne 1F			; 跳转到 1 标号去执行
      ; 如果 F 和 B 都没指定,编译器先向前搜索,再向后搜索,
      ; 如果 A 和 T 都没指定,编译器搜索所有从当前层次到宏的最高层次,比当前层次低的层次不再搜索,
      ; 如果制定了 routname, 编译器向前搜索最近的 ROUT 伪操作,若 routname 与该 ROUT 伪操作定义的名称不匹配,编译器报告错误,汇编失败。
    • # 常数

      十进制数以非 0 数字开头,如:123 和 9876;

      二进制数以 0b 开头,其中字母也可以为大写;

      八进制数以 0 开始,如:0456,0123;

      十六进制数以 0x 开始,如:0xabcd,0x123f;

      字符串常量需要引号括起来,中间也可以使用转义字符,如:"you are welcome!\n";

      当前地址以 "." 表示,在 GNU 汇编程序中可以使用这个符号代表当前指令的地址;

      表达式:在汇编程序中的表达式可以使用常数或者数值,其他的符号如:+、-、*、/、%、<、<<、>、>>、|、&、^、!、==、>=、<=、&&、|| 跟 c 语言中的用法相似

    • # 分段
      • section 伪操作

        .section <section_name>

      • section_name

        代码段:.text

        初始化的数据段:.data

        未初始化的数据段:.bss

        字符串和 #define 定义的常量:.rodata

        heap 堆、stack 栈、常量段

      • flags

        a:可分配

        w:可写段

        x:执行段

      .section .text, "x"		; 用.section 伪操作定义了代码段
      .global add			; 声明符号 add 给编译器用户链接程序
      add:
      	ADD r0, r0, r1		; add 输入参数
      	MOV pc, lr		; 从子函数返回
    • # 定义入口点

      汇编程序缺省入口是_start 标号,用户也可以在连接脚本中用 ENTRY 标志指明入口点

      .section .data		; 初始化数据段
      .section .bss		; 初始化 bss 段
      .section .text		; 初始化代码段
      .global _start		; 声明入口
      _start:
      	...
    • # 数据定义伪操作
      标号含义
      .byte单字节定义 0x12'a'23
      .short定义 2 字节数据 0x123465535
      .long/.word定义 4 字节数据 0x12345678
      .quad定义 8 字节 .quad 0x1234567812345678
      .float定义浮点数 .float 0f3.2
      .string/.asciz/.ascii定义字符串 .ascii "abcd\0" ,注意: .ascii 伪操作定义的字符串需要每行添加结尾字符 \0 ,其他不需要
      .space/.skip用于分配一块连续的存储区域并初始化为指定的值,如果后面的填充值省略不写则在后面填充为 0
    • # 杂项伪操作标识符
      标号含义
      .global / .globl用来声明一个全局的符号
      .arm / .code 32定义一下代码使用 ARM 指令集编译
      .thumb / .code 16定义一下代码使用 Thumb 指令集编译
      .section.section expr 定义一个段。 expr 可以是 .text .data .bss
      .text.text {subsection} 将定义符开始的代码编译到代码段
      .data.data {subsection} 将定义符开始的代码编译到数据段,初始化数据段
      .bss.bss {subsection} 将变量存放到 .bss 段,未初始化数据段
      .align.align{alignment}{,fill}{,max} 通过用零或指定的数据进行填充来使当前位置与指定边界对齐;
      .align 4 --- 16 字节对齐 2 的 4 次方
      .align (4) --- 4 字节对齐
      .org.org offset{,expr} 指定从当前地址加上 offset 开始存放代码,并且从当前地址到当前地址加上 offset 之间的内存单元,用零或指定的数据进行填充
      .extern用于声明一个外部符号,用于兼容性其他汇编
      .weak用于声明一个弱符号,如果这个符号没有定义,编译就忽略,而不会报错
      .end文件结束
      .include.include “filename” 包含指定的头文件,可以把一个汇编常量定义放在头文件中
      .equ格式: .equ symbol, expression 把某一个符号 ( symbol ) 定义成某一个值 ( expression ). 该指令并不分配空间,类似于 c 语言的 #define
      .set给一个全局变量或局部变量赋值,和 .equ 的功能一样
    • # .rept 伪操作

      重复执行接下来的指令,以.rept 开始,以.endr 结束

      .rept cnt		; cnt 代表重复次数
      	...		
      .endr		; 结束重复
  4. # 伪指令

    • # LDR 伪指令和 LDR 指令区分

      4 条伪指令:

      1. ADR 小范围地址读取伪指令
      2. ADRL 中等范围地址读取伪指令
      3. LDR 大范围地址读取伪指令
      4. NOP 空操作伪指令
      ; 伪指令,将 val 标号地址赋给 r1
      LDR r1, =val
      ; arm 指令,将标号 val 地址里的内容给 r1
      LDR r1, val
    • # 数据定义伪指令
      • DCB:用于分配一片连续的字节存储单元并用指定的数据初始化。
      • DCW (DCWU):用于分配一片连续的半字存储单元并用指定的数据初始化。
      • DCD (DCDU):用于分配一片连续的字存储单元并用指定的数据初始化。
      • DCFD (DCFDU):用于为双精度的浮点数分配一片连续的字存储单元并用指定的数据初始化。
      • DCFS (DCFSU):用于为单精度的浮点数分配一片连续的字存储单元并用指定的数据初始化。
      • DCQ (DCQU):用于分配一片以 8 个字节 (双字) 为单位的连续的存储单元并用指定的数据初始化。
      • SPACE:用于分配一片连续的存储单元。
      • MAP:用于定义一个结构化的内存表首地址。
      • FIELD:用于定义一个结构化的内存表的数据域。
    • # 符号定义伪指令
      • GBLA、GBLL 和 GBLS:定义全局变量
      • LCLA、LCLL 和 LCLS:定义局部变量
      • SETA、SETL 和 SETS:对变量赋值
      • RLIST:为通用寄存器列表定义名称
    • # 汇编控制伪指令

      • IF、ELSE、ENDIF:根据条件的成立与否决定是否执行某个指令序列
      • WHILE、WEND:根据条件的成立与否决定是否循环执行某个指令序列
      • MACRO、MEND:可以将一段代码定义为一个整体,称为宏指令,然后就可以在程序中通过宏指令多次调用该段代码
      • MEXIT:用于从宏定义中跳转出去
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

渣渣文 微信支付

微信支付

渣渣文 支付宝

支付宝