Android 中的指令修改与指令删除(基于 AArch64)
作者:访客发布时间:2023-12-24分类:程序开发学习浏览:85
近况
呜呼!天气也是越来越冷了,不知不觉也快到年末了。我在回顾一些技术分享的时候,刚好看到几个大厂们常“玩”的概念,指令修改与指令擦除。 比如抖音基础技术里面就有介绍到,抖音在进行一些性能优化的时候,通过一些手段进行了指令集的修改,从而达到了一些优化的目的。
针对架构指令级别的“核武器”
在认识指令修改与指令擦除之前,我们来一段非常简单的C代码
void callee(int num){
__android_log_print(ANDROID_LOG_ERROR, "mooner", "%s %d", " 我是callee num is",num);
}
void caller(){
callee(1);
}
当我调用caller的时候,会传递一个数值1作为参数调用callee函数,从而打印了本次的一个数值。这段代码即是没有学过C语言的同学,应该都能够看懂。
那么我们来反思一下,这一串代码,背后的逻辑是什么?我们都知道Android可以运行在ARM架构与X86等架构平台之上,手机上CPU也基本是ARM架构。那么CPU会认识C语言吗?显然不会,它不认识Java,也不认识C语言。因此运行在CPU上的,一定是CPU认识的指令集。ARM架构中有很多架构分类,每个架构也有着其运行的状态,比如ARMv8 上面有着64位运行状态AArch64 与 32位运行状态AArch32
通常,我们会在APP项目中gradle配置支持的架构,其实意义就在这里
当然,现在市面上大部分的手机CPU架构,基本都是基于ARMv8架构,运行在64位运行状态,即我们写的C语言或者Android本身的so,会被翻译成符合A64的指令集。
那么,我们写的caller函数,会被编译器翻译成符合A64指令集的汇编代码,我们可以通过objdump这个ndk工具,帮助我们去查看
objdump -d xxx.so
objdump工具包伴随着NDK的下载就带有
我们可以通过把caller函数打包成so,最后查看编译的指令集如下:
需要注意的是,不同的指令集中,生成的指令代码也不一样,比如上图抖音ppt中,就是thumb指令集,即满足aarch32状态的指令集。而我们的是满足aarch64状态的64位指令集,也符合大部分手机。
那么怎么看汇编代码呢?首先我们要建立一个前提,上面一行代码对应着指令,指令由操作码+若干的操作数组成
比如常见的stp指令,其实就是把两个寄存器异常压入操作数中所给的地址中,比如
在aarch64状态中X代表着一个64位寄存器,W也代表着同一个寄存器,只是它只有32位被使用。在A64中,一共有31个通用寄存器,他们会根据一个调用规范(AAPSC64),各司其职。
理解了这些基本概念之后,大家就可以去找ARM的文档,查看各个指令的含义了,下面我们来简单解析我们的caller函数的汇编代码的含义
解析caller函数汇编代码
-
stp x29,x30 [sp,#-16]! :这条指令的操作码是stp,含义就是存储x29,x30 的内容到sp-16(压栈)的地方(这里面!代表着先计算,有兴趣的小伙伴可以多了解一下A64的调用,我这里就不再细节讲了)
-
mov x29,sp:就是把当前sp的值赋值给x29,即FP寄存器,这样我们后续做FP回溯拿堆栈的时候,就会带上了,这也是为什么aarch64状态能够用fp回溯,而aarch32默认不行的原因了(默认可以不遵守fp寄存器的调用规范)
-
mov w0,#0x1 :接着就是把1这个是数,赋值给w0寄存器(x0寄存器同一个,只是它使用了32位),还记得我们调用callee函数吗,传递的数值正是1,又根据上图我们看到的AAPSC64调用规范,第一个参数是不是由X0(W0)寄存器负责存放呀?没错,这里是不是就对上了。
-
bl 6460callee@plt :这里就是跳转到了callee函数的got表地址,callee函数会通过got/plt那一套解析,最终在got表找到真正的callee函数(got/plt hook的知识有说到噢)
-
ldp x29,x30,[sp],#16 :bl指令会把下一条指令PC值保存到LR寄存器中;即执行完callee@plt会回到下一条指令,下一条就是ldp这一条指令。这里就是跟stp一一对应,把sp的数值依次赋值给x29与x30,然后sp = sp+16,其实就是函数结束了,要把栈空间给腾回来。 6.ret :结束调用,其实相当于 b LR 寄存器这个指令一样,跳转到LR寄存器的内容地址以执行下一条指令
至此,我们就结束了整个函数的汇编代码分析,当然,里面涉及很多细节我们还是没有说的,大家可以多参考官网指令集进行更多了解,见AArch64官网
指令修改
上文中,我们针对caller函数的汇编代码进行了分析,下面我们来进行指令修。我们不改动caller函数源代码情况下,把传入callee的参数从1变成2,这究竟要怎么做呢?
很多情况下,我们都不能修改到源代码,因此这个例子是有实际工程意义的,比如修改某个参数为固定某个值避免crash等等,在性能优化中都会用到。
我们从上文分析了caller函数的汇编代码,我们发现,caller调用callee函数进行参数赋值是,整数通过W0寄存器进行复制的,内容就是1
void callee(int num){
__android_log_print(ANDROID_LOG_ERROR, "mooner", "%s %d", " 我是callee num is",num);
}
如果我们能够修改这个参数为2,是不是就实现了我们的目的!我们只需要把mov w0,#0x1 变成 mov w0,#0x2 即可,前面的指令保持不变。
那么我们怎么应用呢?一个函数的指针,所指向的内容,就是具体的汇编指令,指令以16进行的形式存储,我们看看修改mov指令后的16进程代码是什么,然后赋值过去就可以了,这里有一个小工具,大家可以编写汇编代码然后转换为16进制
汇编转换16进制
使用后我们就拿到了符合ARM64的指令码,这个时候我们只需要替换前面12字节的数据就可以了
替换前:
替换后:
替换的时候,我们可以通过memcpy的方式,把我们的汇编代码替换原本的汇编代码,值得注意的是,默认代码段是不可写的,因此我们需要调用mprotect赋予可读可写权限(按照页为单位),同时修改完成之后,还别忘了清除指令缓存
这个时候我们调用以下代码,在手机中可以看到
caller();
hook();
caller();
很棒,调用callee的函数输入就变成了2,符合我们的预期
这个就是指令修改的一个小实现,实际情况下,我们可以通过这种方式,去修改我们想要的指令。inline hook 相关其实也是指令修改的实现之一,只不过它属于嵌入跳转指令。修改指令的前提是我们要对指令集足够了解。
指令删除
学习完指令修改之后,我们学习指令删除就比较轻松了。比如我们想要调用caller函数的时候,不去调用callee函数,那么我们怎么实现呢?
还是回到caller函数的汇编代码,我们看到跳转callee函数的代码,其实是通过bl 6460callee@plt 的方式去实现的,因此这个案例中,想要不去执行callee函数,我们把bl指令删除即可。当然,这里需要注意的是,因为调用callee函数是没有副作用的,即没有修改到其他依赖,因此我们可以直接把这条指令删除,实际情况下,我们还要考虑多种情况,比如bl指令下一条指令是否收到影响等等。
这里比较简单,我们只需要删除bl指令或者在bl指令替换成NOP指令,就能够完成我们的目的了,同样的,我们写上汇编代码,只需要替换bl指令为NOP指令(1F2003D5),
void hook(){
uintptr_t pv = (uintptr_t)caller;
uintptr_t pu = (pv | (PAGE_SIZE - 1)) + 1u;
uintptr_t pd = (pv & ~(PAGE_SIZE - 1));
mprotect((void *) pd, pv + 8u >= pu ? PAGE_SIZE * 2u : PAGE_SIZE,
PROT_READ | PROT_WRITE | PROT_EXEC);
memcpy((void *const) caller, "\xFD\x7B\xBF\xA9\xFD\x03\x00\x91\x20\x00\x80\x52\x1F\x20\x03\xD5\xFD\x7B\xC1\xA8\xC0\x03\x5F\xD6", 24);
__builtin___clear_cache((void *)PAGE_START(pv), (void *)PAGE_END(pv));
}
调用hook函数
caller();
hook();
caller();
__android_log_print(ANDROID_LOG_ERROR, "mooner", "%s", " caller 完成");
当然,本例子中可以这么简单,因为caller和callee函数足够简单,实际复杂任务上,还有考虑进行指令的补齐等等操作,我们可以具体案例具体分析~
总结
通过本文,我们了解到了指令修改与指令删除的基本实现,这些方式在性能优化中或者解决一些疑难杂症时有着奇效,因此也出现在很多国内大厂的方案中。我们了解了这些之后,才能更好的读懂分享出来的方案,从而落地到自己项目当中!
Android中涉及的技术栈可以很深也可以很广,看到这里,说明你已经很棒了!加油吧~
相关推荐
- 轻松上手:
(三)笔记可再编辑 - 如何在iPhone,iPad和Android上使用WordPress应用程序
- 一款简单高效的Android异步框架
- [Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- Android---View中的setMinWidth与setMinimumWidth的踩坑记录
- Android广播如何解决Sending non-protected broadcast问题
- 有关Android Binder面试,你未知的9个秘密
- 开启Android学习之旅-2-架构组件实现数据列表及添加(kotlin)
- Android低功耗蓝牙开发总结
- Android 通知文本颜色获取
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 8Android广播如何解决Sending non-protected broadcast问题
- 9鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版