13

VirtualAPP源码解析-Native Hook技术

 3 years ago
source link: https://zhuanlan.zhihu.com/p/313924345
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

VirtualAPP源码解析-Native Hook技术

58同城 Android工程师

本篇文章主要介绍VirtualAPP使用的Native Hook技术,不是很深入,因为涉及很多C++,ELF和指令集相关的知识点,很多知识还没有融会贯通,目前只是停留在名词的概念上。后续理解了在进行补充。

VirtualAPP中使用了Native Hook技术,主要用于虚拟APP的文件访问重定向。这句话怎么理解和为什么这么做呢,我们先回顾一下VirtualAPP的大致原理。在VirtualAPP中启动一个虚拟APP,大致分为如下几部:

  1. VirtualAPP通过虚拟服务端启动APP B(虚拟APP)
  2. 虚拟服务端通过Provider创建APP B对应的进程,同时替换Intent数据指向代理组件
  3. APP B进程启动,同时将系统服务代理对象,通过动态代理的方式全部替换,指向虚拟服务端
  4. APP B进程 收到intent数据,将intent中数据解析,重新替换为目标组件。从而实现狸猫换太子。
v2-79c34a2e94389fe3b26f8de79df456cd_720w.jpg

在上述步骤我们可以看出,虚拟APP是VirtaulAPP的一个子进程。可想而知我们在虚拟app中进行文件存储或者sp操作时,最终的存储路径也是在VirtaulAPP的data目录下,这样就会带来一个问题。如果允许多个app就会可能出现文件访问冲突,同时也没有做到APP间隔离的目的。而VirtualAPP就是通过Native Hook技术解决了该问题。每个APP都有自己各自的文件存储路径。

下面我们来简单了解下他是如何实现的,关键方法是VClientImpl的startIOUniformer方法,可以看出进行了存储路径映射,如在子进程当我们访问

/data/data/http://com.xxx/目录时会直接映射到io.virtualapp/virtual/data/user/0/http://com.xxx

NativeEngine.redirectDirectory("/sys/class/net/wlan0/address", wifiMacAddressFile);
NativeEngine.redirectDirectory("/sys/class/net/eth0/address", wifiMacAddressFile);
NativeEngine.redirectDirectory("/sys/class/net/wifi/address", wifiMacAddressFile);

NativeEngine.redirectDirectory("/data/data/" + info.packageName, info.dataDir);
NativeEngine.redirectDirectory("/data/user/0/" + info.packageName, info.dataDir);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    NativeEngine.redirectDirectory("/data/user_de/0/" + info.packageName, info.dataDir);
}
String libPath = VEnvironment.getAppLibDirectory(info.packageName).getAbsolutePath();
String userLibPath = new File(VEnvironment.getUserSystemDirectory(userId), info.packageName + "/lib").getAbsolutePath();
NativeEngine.redirectDirectory(userLibPath, libPath);
NativeEngine.redirectDirectory("/data/data/" + info.packageName + "/lib/", libPath);
NativeEngine.redirectDirectory("/data/user/0/" + info.packageName + "/lib/", libPath);  

该方法最终会调用IOUniformer.cpp的startUniformer方法

void IOUniformer::startUniformer(const char *so_path, int api_level, int preview_api_level) {

void *handle = dlopen("libc.so", RTLD_NOW);
if (handle) {
    HOOK_SYMBOL(handle, faccessat);
    HOOK_SYMBOL(handle, __openat);
    HOOK_SYMBOL(handle, fchmodat);
    HOOK_SYMBOL(handle, fchownat);
    HOOK_SYMBOL(handle, renameat);
    HOOK_SYMBOL(handle, fstatat64);
    HOOK_SYMBOL(handle, __statfs);
    HOOK_SYMBOL(handle, __statfs64);
    HOOK_SYMBOL(handle, mkdirat);
    HOOK_SYMBOL(handle, mknodat);
    HOOK_SYMBOL(handle, truncate);
    HOOK_SYMBOL(handle, linkat);
    HOOK_SYMBOL(handle, readlinkat);
    HOOK_SYMBOL(handle, unlinkat);
    HOOK_SYMBOL(handle, symlinkat);
    HOOK_SYMBOL(handle, utimensat);
    HOOK_SYMBOL(handle, __getcwd);

我们知道android系统是基于Linux内核,文件读写操作也是间接的通过库函数进行系统调用,如我们在应用开发中使用的inputStream与outputStream进行文件读写最终也是调用libc.so库函数提供的方法。

所以需要做到就是将libc库函数的方法进行Hook,将输入参数替换为我们的虚拟app路径,该过程即为native Hook。还有一个疑问点是我们怎么知道要hook哪些函数呢,只能通过查看libc的源码,当然源码也是公开的,可以直接查看如下地址。
https://www.androidos.net.cn/android/9.0.0_r8/xref/bionic/libc/bionic

以faccessat方法为例,我们可以看到方法参数有个pathname我们需要将改方法参数替换掉,然后重新调用系统方法。

extern "C" int ___faccessat(int, const char*, int);

int faccessat(int dirfd, const char* pathname, int mode, int flags) 
{
  // "The mode specifies the accessibility check(s) to be performed,
  // and is either the value F_OK, or a mask consisting of the
  // bitwise OR of one or more of R_OK, W_OK, and X_OK."
  if ((mode != F_OK) && ((mode & ~(R_OK | W_OK | X_OK)) != 0) &&
      ((mode & (R_OK | W_OK | X_OK)) == 0)) {
    errno = EINVAL;
    return -1;
  }

经过上述步骤我们知道了,需要对libc链接库的方法进行hook,但是如何做到呢,这就不得不提Native Hook的具体实现了,Native Hook的实现方式有两种一个是PLT Hook Inline Hook,实现原理涉及so动态链接过程与ELF文件格式,汇编指令等,这块大家可以百度一下。而罗迪使用的是一个第三方开源项目Cydia Substrate(http://www.cydiasubstrate.com/),该项目即是inline Hook的一种具体实现。爱奇艺开源的xHook则是PLT Hook方案的具体实现。与PLT Hook方案比较,inline Hook实用场景更广泛,能力更强大。而VirualAPP通过灵活的运用宏定义,Hook一个方法只需要两个步骤:

1. Hook调用

 HOOK_SYMBOL(handle, faccessat);

2. 定义替换的方法

// int faccessat(int dirfd, const char *pathname, int mode, int flags);
HOOK_DEF(int, faccessat, int dirfd, const char *pathname, int mode, int flags) {
    int res;
    const char *redirect_path = relocate_path(pathname, &res);
    int ret = syscall(__NR_faccessat, dirfd, redirect_path, mode, flags);
    return ret;
}

下面我们分析一下 HOOK_SYMBOL与HOOK_DEF宏展开过程

#define HOOK_SYMBOL(handle, func) hook_function(handle, #func, (void*) new_##func, (void**) &orig_##func)
#define HOOK_DEF(ret, func, ...) \
  static ret (*orig_##func)(__VA_ARGS__); \
  static ret new_##func(__VA_ARGS__)

在编译期间会进行宏替换,HOOK_SYMBOL(handle, faccessat)最终替换为如下格式 ;

hook_function(handle,faccessat,(void*) new_faccessat,(void**)&orig_faccessat)

HOOK_DEF最终会替换为如下格式:

static int (*orig_faccessat)(int dirfd, const char *pathname, int mode, int flags);
static int new_faccessat(int, faccessat, int dirfd, const char *pathname, int mode, int flags) {
    int res;
    const char *redirect_path = relocate_path(pathname, &res);
    int ret = syscall(__NR_faccessat, dirfd, redirect_path, mode, flags);
    return ret;
}

可以看出通过宏替换,我们定义了一个函数指针,和一个newfaccessat的替换函数,最终调用hook_function方法实现Hook,hook_function内部调用MSHookFunction函数,该函数即为Cydia Substrate提供的能力。

static inline void hook_function(void *handle, const char *symbol, void *new_func, void **old_func) {
    void *addr = dlsym(handle, symbol); 
    if (addr == NULL) { return; } 
    MSHookFunction(addr, new_func, old_func); 
}

相信大家对Native Hook在整体上有了初步的认识,学习Native Hook不能一蹴而就而是个缓慢的过程。后续文章为大家分享MSHookFunction的具体实现原理


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK