大脸猫讲逆向之ARM汇编中PC寄存器详解
近日,在研究一些开源native层hook方案的实现方式,并据此对ARM汇编层中容易出问题的一些地方做了整理,以便后来人能有从中有所收获并应用于现实问题中。当然,文中许多介绍参考了许多零散的文章,本文重点工作在于对相关概念的整理收集,并按相对合理顺序引出后文中对hook技术中的一些难点的解读。
Android平台大多采用了ARM架构的CPU,而ARM属RISC,与X86架构的处理器有不同的特征,本文讲介绍ARM中不容易理解的PC寄存器各种问题,包括ARM流水线、PC寄存器指向问题、ARM和Thumb指令的区分及相关概念在native层hook中的应用问题。
1.ARM寄存器介绍
ARM微处理器共有37个32位寄存器,其中31个为通用寄存器,6个为状态寄存器。但是这些寄存器不能被同时访问,具体哪些寄存器是可以访问的,取决ARM处理器的工作状态及具体的运行模式。但在任何时候,通用寄存器R14~R0、程序计数器PC、一个状态寄存器都是可访问的。
简言之,在用户模式下ARM可见的寄存器有16个32位的寄存器(R0到R15)和一个当前程序状态寄存器CPSR,其中R15是程序计数器PC,R14用于存储子程序的返回地址LR,R13用于存储堆栈栈顶SP。
本文我们将着重介绍一下R15寄存器,即PC寄存器。理论上,PC寄存器应指向即将执行的下一条指令的地址,然而在实际应用中却发现PC寄存器总不是如此。经过翻看资料发现,该问题是由于ARM存在的指令流水线导致的。
2.ARM流水线介绍
流水线技术通过多个功能部件并行工作来缩短程序执行时间,提高处理器核的效率和吞吐率,从而成为微处理器设计中最为重要的技术之一。也就是说,通过划分指令执行过程中的不同阶段,并通过并行执行,从而提高指令的执行效率。ARM7处理器采用了三级流水线结构,包括取指(fetch)、译码(decode)、执行(execute)三级。


3.PC寄存器的指向问题
如上图所示,在执行add r0, r1, #5指令时,第二条指令正在译码阶段,而第三条指令正在取指阶段。在执行第一条指令时,PC寄存器应指向第三条指令。也即,当处理器为三级流水线结构时,PC寄存器总是指向随后的第三条指令。
- 当处理器处于ARM状态时,每条ARM指令为4个字节,所以PC寄存器的值为当前指令地址 + 8字节
- 当处理器处于Thumb状态时,每条Thumb指令为2字节,所以PC寄存器的值为当前指令地址 + 4字节
此外,在ARM9中,采用了五级流水线结构,是在ARM7的三级流水线结构后面添加了两个新的过程。因此,指令的执行过程和取指过程还是相隔一个译码过程,因而PC还是指向当前指令随后的第三条指令。
另外,关于PC寄存器需要注意的一点是:当使用指令STR或STM对R15进行保存时,保存的可能是当前指令地址加8或当前指令地址加12。具体是加8还是加12,取决于具体的处理器设计。但是,同一个芯片只能是其中一种的方案,即只能是加8或加12中的一种。
可以通过如下的代码确定处理器采用的那种方式:
SUB R1,PC, #4 ;R1中存放STR指令地址
STR PC,[R0] ;用STR指令将PC保存到R0指向的地址单元中,PC=STR指令地址+偏移量(偏移量为8或者12)。
LDR R0,[R0] ;读取STR指令地址+偏移量的值
SUB R0,R0,R1 ; STR指令地址+偏移量的值减去STR指令的地址,得到偏移量值(8或者12)。
4. ARM/Thumb指令的区分
众所周知,ARM体系结构分为ARM状态和Thumb状态及Thumb-2状态。在ARM状态时执行32位长度的字对齐的ARM指令,Thumb状态时执行16位长度的半字对齐的Thumb指令。
Thumb指令集与 ARM 指令的区别一般有如下几点:
- 跳转指令
程序相对转移,特别是条件跳转与 ARM 代码下的跳转相比,在范围上有更多的限制,转向子程序是无条件的转移.
- 数据处理指令
数据处理指令是对通用寄存器进行操作,在大多数情况下,操作的结果须放入其中一个操作数寄存器中,而不是第 3 个寄存器中.数据处理操作比 ARM 状态的更少,访问寄存器 R8~R15 受到一定限制.除 MOV 和 ADD 指令访问器 R8~R15 外,其它数据处理指令总是更新 CPSR 中的 ALU 状态标志.访问寄存器 R8~R15 的 Thumb 数据处理指令不能更新 CPSR 中的 ALU 状态标志.
- 单寄存器加载和存储指令
在 Thumb 状态下,单寄存器加载和存储指令只能访问寄存器 R0~R7
- 批量寄存器加载和存储指令
LDM 和 STM 指令可以将任何范围为 R0~R7 的寄存器子集加载或存储. PUSH 和 POP 指令使用堆栈指令 R13 作为基址实现满递减堆栈.除 R0~R7 外,PUSH 指令还可以存储链接寄存器 R14,并且 POP 指令可以加载程序指令PC
而程序在执行过程中,是如何区分ARM状态和Thumb状态的呢?在逆向分析过程中,经常会看到许多函数调用过程为形如BX sub_84C0 + 1,即函数地址为奇数。在ARM运行过程中,函数调用的地址最后一位为1时,表示目标函数为Thumb指令;否则为ARM指令。然而,不管是ARM和Thumb状态指令,均是偶数字节对齐的,即函数地址最后一位肯定为0。因此,可以用最后一位判断目标函数是否为Thumb和ARM状态。
综上,程序状态切换可以用如下方式实现:
- 从ARM切换到Thumb:
LDR R0, =label + 1
BX R0
- 从Thumb切换到ARM:
LDR R0, = label
BX R0
上文中,label为符号的地址,因为字节对齐缘故,最后一位肯定为0。
5.native层hook技术解析
以上 问题均是我在分析开源hook框架adbi源码时遇到的问题的解答,下面我将介绍一下上述几个问题在hook中的应用。
在adbi源文件中,hijack.c文件中的sc数组用于存储在对目标函数前几个字节的指令修改过程中的相关指令和寄存器值。

第一条指令为ldr r0, [pc, #64],即将pc + 64位置处的内存的4个字节读到r0寄存器中。经过上面几个章节的介绍,且此处看出指令均为4个字节即ARM指令,可知读取的位置为当前位置 + 8 + 64 位置处的内容,即sc[18]处的内容,也即addr of libname的内容,即将函数调用时第一个参数R0设置为动态库的字符串名。
第二条指令将r1寄存器,即函数的第二个参数设置为0。
第三条指令将pc的值赋值给lr寄存器,即将随后函数调用后的返回地址设置为559行的那条指令,即第五条指令。
第四条指令将pc + 56位置处存储的值赋值给pc寄存器,即当前位置 + 8 + 56位置处的值,也即sc[16]处的值,即被hook的dlopen函数的地址。
随后函数调转到目标函数执行,并在返回后执行559行的执行,从此开始恢复寄存器环境。也即通过这种方式完成了对目标函数的hook劫持过程。
在hook.c文件中,实现了对目标函数的查找及指令替换功能,详情如下图所示。图中,通过对find_name函数的调用,得到了目标so库中的函数funcname的地址并存储于addr中。在代码编译过程中,编译为Thumb指令的函数,通过函数名得到的目标函数地址最后一位为1,用于表示目标函数为Thumb指令集。在adbi代码中,即根据该方法判断需要hook的目标函数是Thumb指令集还是ARM指令集。ARM指令集由于是4字节对齐的,因此最后2位总是为0,据此判断是否是ARM指令。根据不同的指令集实现不同的指令替换,并完成hook功能。

以上即是我在研究adbi源码过程中碰到的需要深入了解的一些基本概念及其具体应用,了解这些能对hook的实现原理能有较为深刻的理解,并据此编写自己的简易的hook方案。
6.总结
在ARM处理器架构中,PC寄存器通常是指向当前指令后的第三条指令地址,即在ARM指令是+8,Thumb指令时+4。
ARM/Thumb状态切换是根据目标函数地址最后一位是否为0来进行判断,并用BX指令实现。
参考链接
https://blog.csdn.net/Sandeldeng/article/details/52954781
https://blog.csdn.net/zhi_yong_chen/article/details/51314377
http://www.eepw.com.cn/article/201611/318735.htm
有问题大家可以留言哦,也欢迎大家到春秋论坛中来耍一耍 >>>点击跳转
大脸猫讲逆向之ARM汇编中PC寄存器详解的更多相关文章
- 逆向知识第八讲,if语句在汇编中表达的方式
逆向知识第八讲,if语句在汇编中表达的方式 一丶if else的最简单情况还原(无分支情况) 高级代码: #include "stdafx.h" int main(int argc ...
- C++中的STL中map用法详解(转)
原文地址: https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html C++中的STL中map用法详解 Map是STL的一个关联容器,它提供 ...
- php中关于引用(&)详解
php中关于引用(&)详解 php的引用(就是在变量或者函数.对象等前面加上&符号) 在PHP 中引用的意思是:不同的变量名访问同一个变量内容. 与C语言中的指针是有差别的.C语言中的 ...
- Objective-C中的@Property详解
Objective-C中的@Property详解 @Property (属性) class vairs 这个属性有nonatomic, strong, weak, retain, copy等等 我把它 ...
- springcloud中Feign配置详解
Spring Cloud中Feign配置详解 到目前为止,小伙伴们对Feign的使用已经掌握的差不多了,我们在前文也提到Feign是对Ribbon和Hystrix的整合,那么在Feign中,我们要如何 ...
- 【转】linux中inittab文件详解
原文网址:http://www.2cto.com/os/201108/98426.html linux中inittab文件详解 init的进程号是1(ps -aux | less),从这一点就能看出, ...
- 【转】linux 中fork()函数详解
在看多线程的时候看到了这个函数,于是学习了下,下面文章写的通俗易懂,于是就开心的看完了,最后还是很愉快的算出了他最后一个问题. linux 中fork()函数详解 一.fork入门知识 一个进程,包括 ...
- SVD在推荐系统中的应用详解以及算法推导
SVD在推荐系统中的应用详解以及算法推导 出处http://blog.csdn.net/zhongkejingwang/article/details/43083603 前面文章SVD原理及推 ...
- MyBatis中@MapKey使用详解
MyBatis中@MapKey使用详解我们在上一篇文章中讲到在Select返回类型中是返回Map时,是对方法中是否存在注解@MapKey,这个注解我也是第一次看到,当时我也以为是纯粹的返回单个数据对象 ...
随机推荐
- 旅行家的预算(NOIP1999&水题测试2017082301)
题目链接:旅行家的预算 这题还可以,不算太水. 这题贪心即可. 我们采取如下动作: 如果在装满油的情况下能到达的范围内,没有加油站,则无解. 如果在装满油的情况下能到达的范围内,油价最低的加油站的油价 ...
- LD_LIBRARY_PATH
LD_LIBRARY_PATH是Linux环境变量名,该环境变量主要用于指定查找共享库(动态链接库)时除了默认路径之外的其他路径. 在linux下可以用export命令来设置这个值,比如 在linux ...
- mysql之表与表关联和表操作
一 表于表之间的关联 foregin key:设置外键表于表之间建立关联. 多对一关联: 创建步骤,应该先创建好被关联的那一张表,然后再去创建关联的那一张表. 关联表的多条对应着被关联的那张表的一条记 ...
- centos7 sqoop 1 搭建笔记
1.require : java环境,hadoop,hive ,mysql2.下载解压sqoop13.设置环境变量 export SQOOP_HOME=/data/spark/bin/sqoop ex ...
- 如何将mysql卸载干净
一.在控制面板中卸载mysql软件 二.卸载过后删除C:\Program Files (x86)\MySQL该目录下剩余了所有文件,把mysql文件夹也删了 三.windows+R运行“regedit ...
- 2019.01.19 codeforces343D.Water Tree(树剖+ODT)
传送门 ODTODTODT板子题. 支持子树01覆盖,路径01覆盖,询问一个点的值. 思路:当然可以用树剖+线段树,不过树剖+ODTODTODT也可以很好的水过去. 注意修改路径时每次跳重链都要修改. ...
- s5-14 链路状态路由选择
为什么DV逐渐让位于LS? DV 站的不高,看得不远 完全相信邻居 LS 想办法站得高,看更远 多高.多远? 怎么做? 链路状态路由(Link State) 主要思想 发现 它的邻 ...
- FPGA速度等级
转自http://wenku.baidu.com/view/ea793deef8c75fbfc77db263.html?from=rec 最初接触speed grade这个概念时,很是为Altera的 ...
- PHP上传文件参考配置大文件上传
PHP用超级全局变量数组$_FILES来记录文件上传相关信息的. 1.file_uploads=on/off 是否允许通过http方式上传文件 2.max_execution_time=30 允许脚本 ...
- winSockets编程(二)socket函数
初始化DLL之后,接着创建套接字,通过socket()和WSASocket()函数实现此功能. SOCKET socket( int af, int type, int protocol ); af: ...