一、前言

Android 的 UI 自动化测试可以通过注入式和非注入式分别实现,通过注入式可以更加方便地与应用进行交互。QTA 团队提供的 Android UI 自动化测试框架QT4A, 是通过动态注入的方式来获取被测应用的控件树信息等,从而达到自动化测试的目的。本文主要介绍该动态注入的原理。

二、Android 动态注入概述

QT4A 中的动态注入是借助 ptrace 函数,该函数常用于断点调试或系统调用跟踪,由于其动态附着到远程进程的特性,我们可以在 Android UI 自动化测试中加以利用。QT4A 框架中将测试桩 so 动态库链接到被测应用进程空间,使得 so 中的函数在被测进程有对应地址,通过该地址即可在被测进程中调用 QT4A 的函数,与被测应用进行交互。

三、Android 动态注入条件限制

需要注意的是,通过 ptrace 函数虽然可以跟踪进程,修改被跟踪进程的内存和寄存器值,但正因其强大的能力,它也需在以下任一条件下满足才能成功执行:

设备已越狱 (root)

设备未 root 情况下,只能注入具有相同 uid 的进程。在 Android 中,可以通过如下两种方法达到:

重打包 QT4A so 到被测 apk 包中实现 ;

部分支持 run-as 命令的 Android 设备,也可以通过该命令切换到被测应用 uid 下再进行注入,该命令可用情况下则无需重打包。

QT4A 结合了这两种方案实现非 root 下的动态注入。

四、Android 动态注入整体流程

为了方便看结果,我们以注入一个简单的 so(hello.so) 为例,而不以 QT4A 真正的 so 为例。hello.so 主要包括了一个入口函数,主要代码如下:

int hook_entry(char * a){

LOGD("Hook success, pid = %d\n", getpid());

LOGD("Hello %s\n", a);

return 0;

}

我们目标是将其注入到被测 Android 应用进程中,预期结果是在被测应用中输出上述日志内容。整体注入流程图如下:

首先通过 PTRACE_ATTACH 附着到远程进程:

ptrace(PTRACE_ATTACH, pid, NULL, 0)

在开始加载我们的 so 之前,我们先把远程进程的现场进行保护:

ptrace_getregs( target_pid, &regs )

如上,获取远程进程 (进程 id 为 target_pid) 的寄存器,然后将其保存到 original_regs 中:

memcpy( &original_regs, &regs, sizeof(regs) );

加载 hello.so 后可以恢复现场并解除进程跟踪:

ptrace_setregs( target_pid, &original_regs );

ptrace_detach( target_pid );

接下来重点介绍如何加载 hello.so。

五、获取远程函数地址

由于 hello.so 不在远程进程中,在远程进程中并没有 hello.so 相关的地址,要在远程进程加载 hello.so,首先需要分配内存空间写入 so,我们可以在远程进程中调用 mmap 函数为 hello.so 分配内存空间,但只有知道了函数地址才能开始调用,如何获取远程进程中的 mmap 函数地址呢?本节以获取 mmap 远程函数地址为例说明如何获取远程函数地址。

5.1 mmap 远程函数地址获取公式

同一系统库 (例如 mmap 所在的系统库 libc.so) 的 mmap 地址与 libc.so 基地址的偏移量,在当前进程和远程进程 (Android 应用) 中是相同的,所以,只要获取到当前进程的 libc 基地址 (假设用变量 local_handle 表示)、当前进程 mmap 地址 (local_addr)、远程进程 libc.so 基地址 (remote_handle),即可根据如下公式获取远程 mmap 地址 (remote_addr):

如上图,可获得公式:

local_addr - local_handle + = remote_addr - remote_handle,式子可转化为remote_addr = local_addr + remote_handle - local_handle

接下来首先获取 libc.so 基地址 (local_handle/remote_handle) 和当前进程 mmap 函数地址 (local_addr)。

5.2 获取 libc.so 基地址

获取进程中 libc.so 模块基地址 (local_handle/remote_handle) 的方法为:

即在/proc/{pid}/maps路径中找到模块名,其中 pid 替换为目标进程的进程 id,对应的行首地址即为模块的基地址。如果在当前进程中读取当前进程的模块基地址,可读取/proc/self/maps路径下的模块地址即可。通过该方法可求得 local_handle/remote_handle 的值。

5.3 获取当前进程 mmap 函数地址

获取当前进程的 mmap 函数地址,有两种方法:

方法一:通过 dlopen/dlsym 的方式获取,如下图:

方法二:根据 elf 文件内容格式获取符号相对基地址的偏移量,加上当前进程中 libc.so 基地址,即可求得当前进程函数地址。实现在get_symbol_offset函数中,后续可详细见开源后的源码。

两种方法可以结合调用,更为可靠,整体调用代码如下:

/*

* 获取当前进程中的函数地址

* 调用:void* local_mmap_addr = get_func_addr(libc_path, "mmap");

*/

void* get_func_addr(const char* module_path, const char* func_name) {

void* handle = dlopen(module_path, RTLD_NOW);

if(handle != NULL){

void* addr = dlsym(handle, func_name);

if(addr != NULL) return addr;

}

uint32_t addr = get_symbol_offset(module_path, func_name);

if(addr == 0) return NULL;

return get_module_base(-1, module_path) + addr;

}

其中get_symbol_offset读取到了函数偏移值,get_module_base获取了 libc.so 基地址 (详细见《获取 libc.so 基地址》一节),两者相加即为当前进程 mmap 函数地址 (local_addr)。

至目前为止,根据公式remote_addr = local_addr + remote_handle - local_handle,我们知道了 local_handle/remote_handle/local_addr 三个变量的值,从而可求得远程 mmap 地址 (remote_addr)。

类似的,其他远程函数地址的获取方法类似上述过程,区别在于函数所在的库不同、函数名不同而已,后续不再赘述。

六、远程进程函数调用

6.1 调用远程函数 mmap 分配内存空间

通过上一节分析,可知远程进程函数的地址获取方法,然后开始调用远程进程函数 mmap 分配内存空间,需要借助 ptrace 函数进行调用:

void* ret = ptrace_call( pid, remote_mmap_addr, parameters, 6, regs );

如上,传入所需的参数,可调用 mmap 函数分配内存空间并返回分配的内存地址。ptrace_call 函数首先会将调用的函数 (mmap) 所需的参数 (parameters) 从右到左压入堆栈,同时写入返回地址到对应寄存器中,并同步修改栈顶指针。请注意,不同的 CPU 架构所用的寄存器和数据压入方式有一定的差异,请按不同的 CPU 架构对应处理,这里总结了部分的差异:

将堆栈和寄存器值都设置完毕后,通过调用 ptrace 函数,并传入参数PTRACE_CONT使 mmap 函数得以执行。

6.2 往 mmap 分配的内存空间写入 hello.so 路径和参数 int ptrace_writedata( pid_t pid, uint8_t *dest, uint8_t *data, size_t size )

该函数实现往地址中写入字符串的功能,其中了利用 ptrace 函数提供的写内存空间的方法,通过传入参数PTRACE_POKETEXT及其他所需参数进行写入,我们首先将 hello.so 路径写入 mmap 分配的内存空间中 (remote_memory)。同理,hello.so 中的入口函数(hook_entry) 如果需要传入参数,也可通过这种方法写入 remote_memory 中。更多细节请参考后续开源出来的源码。而对应的,如果需要进行读操作,则传入参数PTRACE_PEEKTEXT及其他参数。

6.3 远程进程中调用 hello.so 的函数

目前为止,我们已经在远程进程中分配了内存,写入了 hello.so 和其函数hook_entry的参数。而我们又可以通过《获取远程函数地址》一节的方法,获取 hello.so 的函数地址,用变量 remote_func_addr 表示,接下来可以调用hook_entry函数:

ret = (long)ptrace_call( target_pid, remote_func_addr, parameters, param_size, &regs );

上述 ptrace_call 的函数的详细过程参考《6.1 调用远程函数 mmap 分配内存空间》一节。

调用结果如下图:

可以看到,hello.so 中的hook_entry函数中的日志 (Hook success……) 在目标进程(2422)中打印出来了,证明我们的注入已成功。

七、总结

本文分析了 QT4A 所涉及的 Android 动态注入过程,QT4A 利用该过程注入 QT4A 测试桩到被测 Android 应用进程中,达到与应用通信的目的。整个注入过程比较关键的是获取远程函数地址和调用远程函数。调用远程函数需要首先通过 mmap 分配内存写入待注入 so(hello.so) 和其函数所需参数,同时需要维护寄存器和堆栈状态,不同 CPU 架构有所差别。

感兴趣的同学可以加入 QQ 群和公众号交流

如果你想要了解更多资讯,欢迎关注我们的微信公众号

我们会定时向大家推送团队同学分享的经验文章哦。

linux qt getpid,[QTA] Android 动态注入原理分析相关推荐

  1. Android App加固原理分析

    Android App加固原理分析 对App进行加固,可以有效防止移动应用被破解.盗版.二次打包.注入.反编译等,保障程序的安全性.稳定性.对于金融类App,尤其重要. 对App dex进行加固的基本 ...

  2. Java程序员进阶——Spring依赖注入原理分析

    Spring依赖注入原理分析 下面谈谈Spring是如何实现反转模式IOC或依赖注入模式DI: 平时,我们需要生成一个对象,使用new语法,如一个类为A public class A{public v ...

  3. mysql注入原理_Mysql报错注入原理分析

    报错类型Duplicate entry报错:多次查询插入重复键值导致count报错从而在报错信息中带入了敏感信息. Xpath报错:从mysql5.1.5开始提供两个XML查询和修改的函数,语法错误导 ...

  4. 【java】CGLIB动态代理原理分析

    1.概述 前一篇文章介绍了CGLIB中常用的API,实际上使用了Enhancer和MethodInterceptor之后会生成代理子类,这篇文章就是分析一下CGLIB动态代理的原理. 2.CGLIB动 ...

  5. Java动态代理原理分析

    JDK动态代理原理分析 interface Foo {void foo(); }class Target implements Foo {@Overridepublic void foo() {Sys ...

  6. 可能是最详细的Android图片压缩原理分析(二)—— 鲁班压缩算法解析

    本篇文章已授权微信公众号guolin_blog(郭霖)独家发布 稀土掘金链接 前言 通过上一篇,我们了解了一些关于图片压缩的基础知识,这篇文章我们主要讲解一下鲁班压缩的算法逻辑,很多博客都是从Gith ...

  7. 可能是最详细的Android图片压缩原理分析(一)—— Android图片压缩必备基础知识

    本篇文章已授权微信公众号guolin_blog(郭霖)独家发布 稀土掘金链接 前言: 最近在研究图片压缩原理,看了大量资料,从上层尺寸压缩.质量压缩原理到下层的哈夫曼压缩,走成华大道,然后去二仙桥,全 ...

  8. linux进程文件描述符 vnode,Linux C编程详解:进程原理分析、文件描述符和文件记录表、文件句柄和文件原理...

    一.引言 文件操作是Linux C编程中其中的一项核心技术,实际上也相当重要,这里并不是说狭义上的那种文件操作,它也非常有助于理解和学习Linux系统.为什么这样说呢?因为在Unix/Linux的世界 ...

  9. Android Retrofit实现原理分析

    retrofit有几个关键的地方. 1.用户自定义的接口和接口方法.(由动态代理创建对象.) 2.converter转换器.(把response转换为一个具体的对象) 3.注解的使用. 让我们跟随Ap ...

最新文章

  1. CMakeLists.txt学习记录
  2. JVM笔记 3 3 垃圾回收算法
  3. 如何利用隐写术配合四个重定向连接到C2服务器
  4. Nancy之Forms验证
  5. UVa712 S-Trees满二叉树
  6. Fiori elements执行过程解析:When click go in table list, odata service is sent
  7. 怎么用odbc连接mysql数据库连接_PowerDesigner通过ODBC来实现Mysql数据库的连接操作...
  8. Cocos2d-x 3.2 异步动态加载 -- 保卫萝卜开发总结
  9. elementui Cascader 省市区联动选择器,应用与回显
  10. AD批量修改电阻封装记得按CTRL+A
  11. php正则表达式小括號,php使用正則表達式提取字符串中尖括號、小括號、中括號、大括號中的字符串...
  12. Batch Normalization(BN层)详解
  13. 计算机的输入法如何使用简短描述,应用电脑(1)第一章 计算机组成与中文输入法...
  14. C# 实现对三维点数据的 显示
  15. bldc不同载波频率_三相BLDC弦波驱动器-PT2511
  16. android自定义listview 显示数组,android TextView控件如何显示Listview数组内容到一个Textview控件上?...
  17. 怎样快速实现两台电脑硬盘文件共享?
  18. 区块链技术与应用-----区块链概念
  19. did you register the component correctly? For recursive components, make sure to provide the “name“
  20. 苹果xr十大隐藏功能_苹果手机隐藏的功能

热门文章

  1. 前端学习(1948)vue之电商管理系统电商系统之排序
  2. 前端学习(1723):前端系列javascript之uniapp语法下
  3. 前端学习(872):注册事件兼容性处理
  4. 前端学习(602):集成vue插件
  5. 第十二题:设int x=1,float y=2,则表达式x/y的值是:
  6. 74 param动作
  7. CM3计算板RTC闹钟唤醒系统
  8. rsync同步时,删除目标目录比源目录多余文件的方法(--delete)
  9. JS之Boolean的toString方法
  10. BDD框架之Cucumber研究