【Android安全】ARM指令学习

文档地址

ARMv7-A和 ARMv7-R的文档地址:
https://developer.arm.com/documentation/ddi0406/cd/?lang=en

ARMv8-A 的文档地址:
https://developer.arm.com/documentation/ddi0487/ka/?lang=en

ARM64因为指令更长,hook起来更稳定,所以有时候逆向反而更容易

参数和返回值传递

Arm32参数传递

前4个参数,使用r0~r3传递
后面的参数,使用栈传递
在这里插入图片描述

#include <stdio.h>

int main(int argc, char const *argv[])
{
    printf("hello arm:%d %d %d %d %d\r\n",1,2,3,4,5);
    return 0;
}

export PATH=/home/tom/kanxue/Chapter09/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
clang -target arm-linux-android21 -S hello-multiarg.c -o hello-multiarg.s

hello-multiarg.s:

@ %bb.0:
	.save	{r11, lr}
	push	{r11, lr}
	.setfp	r11, sp
	mov	r11, sp
	.pad	#32
	sub	sp, sp, #32
	mov	r2, #0 
	str	r2, [r11, #-4]
	str	r0, [r11, #-8]
	str	r1, [r11, #-12]
	mov	r0, sp
	mov	r1, #5
	str	r1, [r0, #4]
	mov	r1, #4
	str	r1, [r0]
	ldr	r0, .LCPI0_0
.LPC0_0:
	add	r0, pc, r0
	mov	r1, #1
	mov	r3, #2
	mov	r12, #3
	str	r2, [sp, #16]           @ 4-byte Spill
	mov	r2, r3
	mov	r3, r12
	bl	printf

Arm64参数和返回值传递

前8个参数:R0~R7
返回值:

  • 不超过64位:X0
  • 超过64位:将返回值的地址存在X8,函数返回后 从X8存储的地址取出返回值

常用寄存器

ARM32中主要寄存器

(armeabi-v7a,armeabi)
在这里插入图片描述
13+3个寄存器

  • R0-R12
  • SP LR PC

ARM64中主要寄存器

(arm64-v8a)
31+2+32+2个寄存器

  • R0~R30 31个通用寄存器,每个64位
    • 64bit:X0~X30
    • 32bit:W0~W30
    • X30被用作链接寄存器(Link Register,LR),存储函数调用的返回地址
      在这里插入图片描述
  • SP
  • PC
  • V0-V31:SIMD&FP registers, 浮点寄存器
    在这里插入图片描述
  • FPCR, FPSR

LR寄存器(Link Register)

在ARM处理器中使用 lr实现对调用点的记录
ARM处理器执行BL时,会自动将 当前PC减去4的结果 保存到LR寄存器
参考https://www.cnblogs.com/FightingChen/p/12411753.html

流水线

注意 PC值 = 当前程序执行位置 + 8字节,因为ARM是三级流水线

三级流水线:
取指、译码、执行
在这里插入图片描述
所以PC值 = 当前程序执行位置 + 8字节
参考:https://blog.youkuaiyun.com/u012351051/article/details/80898085

负整数的十六进制

执行前
在这里插入图片描述
r0是0xffffeedc,也就是-4388
参见https://www.toolhelper.cn/Digit/BaseConvertNegative

pc是0x0040149c,也就是4,199,580
处理器上pc的实际值是4,199,580+8=4,199,588

所以add r0, pc, r0之后,r0是4,199,588-4388=4,195,200=0x400380
在这里插入图片描述

符号

井号# :代表立即数

立即数要以“#”为前缀

例如:

LDR  X8, [X21,#0x28]

直接跳转与间接跳转

直接跳转:跳转目标是作为指令的一部分编码的
间接跳转:跳转目标是从寄存器或内存位置中读出的

在这里插入图片描述

存储器访问指令

LDR和STR

ldr    r0,  [pc,  #20]	;load to register
						;r0 = *(pc+20) 
						
str	r2, [r11, #-4] 	;store register
						;*(r11-4) = r2

STP

STP: 入栈指令(str 的变种指令,可以同时操作两个寄存器)
如:

stp x29, x30, [sp, #0x10] 	; 将 x29, x30 的值存入 sp + 16个字节的地址 
stp x29, x30, [sp, #-32]! 	; 将 x29, x30 的值存入 sp - 32个字节的地址后,sp=sp-32

CSET

CSET: 比较指令(满足条件,则置 1,否则置 0 )
如:

cmp w8, #2        ; 将寄存器 w8 的值和常量 2 进行比较
cset w8, gt       ; 如果是大于(grater than),则将寄存器 w8 的值设置为 1,否则设置为 0

TBNZ

TBNZ: 特殊判断指令(满足条件,则置 1,否则置 0 )
如:

TBNZ W28, #3, loc_D553E8       ; 如果W28寄存器的第3位不等于0,则跳转到loc_D553E8

push

push {r11, lr} 从右往左依次把寄存器压栈
在函数刚进来时 会保存寄存器

r11,lr 	→	r11
			lr

pop

pop {r11, lr} 从栈中取值放入寄存器
在函数结尾时 会恢复寄存器

	r11	 →	r11,lr 	
	lr

执行前:
在这里插入图片描述

执行后:
在这里插入图片描述

常用指令

MOV

MOV 目的寄存器,源寄存器

mov    r11,  sp ;r11=sp

在这里插入图片描述

BLX(Branch with Link and eXchange)

ARM指令:每条指令都是4字节
Thumb指令:每条指令大多是2字节

从Thumb指令 调用ARM指令,或者从ARM指令 调用 Thumb指令时,
使用BLX,X代表切换

BLX指令会将当前指令的下一条指令地址存入LR寄存器,并跳转到指定位置(更新PC),同时切换处理器状态(ARM ↔ Thumb)

BIC(Bit Clear)

BIC <Rd>, <Rn>, <Operand2>  ; Rd = Rn AND (NOT Operand2)

例如 BIC R1, LR, #1 的功能是:

清除 LR(Link Register)的最低位(bit 0),并将结果存入 R1。

从指令到字节码

比如将B #0x40转换为字节码的时候:

在这里插入图片描述
27~24 = 1010

关于cond:
A8.3 Conditional execution
在这里插入图片描述
所以cond = 1110
31~28 = cond = 1110
27~24 = 1010
imm24之后要被SignExtend转化为imm32
关于SignExtend:
在这里插入图片描述
在这里插入图片描述
SignExtend(x, i) = Replicate(TopBit(x), i-Len(x)) : x

SignExtend(imm24:‘00’, 32)
= Replicate(TopBit(imm24:‘00’), 32-Len(imm24:‘00’)) : imm24:‘00’
= Replicate(0, 32-26) : imm24:‘00’
= ‘0000 00’ : imm24 : ‘00’

我们希望跳转到0x40,由于三级流水线,当我们执行到B #0x40这条指令的时候,我们希望跳转到的偏移量应该是0x40-0x08=0x38(其实也没太理解,这里#0x40应该是绝对地址呀)

所以希望imm32是0x38,也就是 0000 0000 0000 0000 0000 0000 0011 1000
所以imm24应该是00 0000 0000 0000 0000 0011 10

最终B #0x40对应的字节码应该是
31~28 = cond = 1110
27~24 = 1010
imm24 = 00 0000 0000 0000 0000 0011 10
合起来:1110 1010 0000 0000 0000 0000 0000 1110

可以用keystone验证
pip install capstone
pip install keystone-engine

import keystone
import capstone

def ins2bcode(arm_ins):
    ks = keystone.Ks(keystone.KS_ARCH_ARM, keystone.KS_MODE_ARM)
    arm_bytecode=ks.asm(arm_ins,as_bytes=True)
    bytecode =int.from_bytes(arm_bytecode[0],"little") # 将汇编后的字节码转换为整数,使用小端序
    bytecode_bin =format(bytecode, "032b") # 将整数转换为32位的二进制字符串
    print("arm ins: \t",arm_ins)
    print("bytecode hex: \t", hex(bytecode))
    print("bytecode bin: \t", bytecode_bin)
    return arm_bytecode[0]

def bcode2ins(arm_bytecode):
    cs=capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM)
    for ins in cs.disasm(arm_bytecode, 0):
        print("ins.address: \t",ins.address) # 指令的地址
        print("ins.mnemonic: \t",ins.mnemonic) # 指令的助记符(指令名称)
        print("ins.op_str: \t",ins.op_str) # 指令的操作数

arm_ins ="B #0x40"

bytecode = ins2bcode(arm_ins)

bcode2ins(bytecode)

输出:

arm ins:         B #0x40
bytecode hex:    0xea00000e
bytecode bin:    11101010000000000000000000001110
ins.address:     0
ins.mnemonic:    b
ins.op_str:      #0x40

c++filt

可以使用c++filt 命令解析 C++中被修饰的函数名
例如:

c++filt _ZN7CNumber7setNumlEi

输出:

CNumber::setNuml(int)

每个编译器都有一套自己内部的名字,比如对于linux下g++而言。以下是基本的方法: 每个方法都是以_Z开头,对于嵌套的名字(比如名字空间中的名字或者是类中间的名字,比如Class::Func)后面紧跟N , 然后是各个名字空间和类的名字,每个名字前是名字字符的长度,再以E结尾。(如果不是嵌套名字则不需要以E结尾)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值