动手打造Android7.0以上的注入工具

本文详细介绍了如何在Android 7.0及以上版本开发SO注入工具,包括面临的技术挑战,如系统限制与绕过、注入器编写、注入SystemServer与com.android.phone等。还涉及到NDK编译系统隐藏API的使用,以及如何自动化加入OLLVM混淆以增强安全性。文章提供了解决这些问题的具体代码片段和开发经验。
摘要由CSDN通过智能技术生成

动手打造Android7.0以上的注入工具

在不使用Xposed的一些场景下,想要Hook进入目标APK的方法,最直接有效的方法是注入代码到目标APK,进而完成Hook操作。

面临的挑战

编写注入工具的原理是借助安卓系统的ptrace接口,操纵目标进程的内存,修改进程空间的数据与代码,然后调用dlopen()dlsym()等接口加载与使用动态库,完成达到注入so的目的。

ptrace接口是Linux层面的东西,在网络上可以大量找到这个API的使用介绍与方法,这里不打算深研它的基础原理与使用方法,而是把目光聚集在安卓系统上其特定使用场景上。

把so注入到了目标进程中后,并没有就此完事,而是需要做更多有意义的事情,比如Hook目标APK的代码,so的代码Hook这里不讲,Java层的话,就需要获取其VM环境上下文,从而调用Java的API,手动的在目标进程中加载DEX或APK,最后再执行Hook这个操作。

从安卓7.0对系统的限制,以及注入工具的使用流程上看,我们面临着如下的挑战:

  • 7.0系统的限制与绕过。7.0系统不允许调用很多私有或限制的API,很多函数调用受到了阻碍,再者,SeLinux的限制,让so动态库的注入与加载也遇到了问题,并不能直接加载一个不受系统信任的so动态库到目标不进程中去。

  • 编写注入器与注入代码。如何编写一个通用的框架,可以与注入工具配合好在目标APK中加载so与APK文件,你想好了么?

  • 注入系统进程。有时候为了选择Hook多个目标APK的方法,会选择一劳永逸的注入它们的系统父进程,比如SystemServer与com.android.phone进程。注入这些进程与普通进程有区别吗?

  • NDK编译系统隐藏API。在编写注入工具时,会调用到很多安卓系统中使用频繁,但NDK中却没有提供的接口。这个时候就需要想办法来调用它们了。

  • 代码混淆。最后,作为功能的增强,可以加入OLLVM的自动化编译,对目标so进行代码混淆。

开发实战

这里使用了低版本的NDK r10e进行开发。代码的编译通过命令行完成,编写使用Visual Studio Code。

7.0系统的限制与绕过

首先是7.0系统的限制与绕过。不解决这个问题,后面的开发工作无从谈起。

dlopen()dlsym()的调用限制网络上有一个优雅的绕过方法。代码仓库是:https://github.com/avs333/Nougat_dlfunctions。核心代码位于jni/fake_dlfcn.c文件中,fake_dlopen()fake_dlsym()可以代替dlopen()dlsym()来使用,它的原理是在当前进程的内存中,搜索符号表的方式,在内存中找到函数的内存地址。当然,它是有限制的:只能dlopen()已经加载进入内存的so,即系统或自己预先加载的动态库,并且参数flags加载标志被忽略。

以上解决了调用系统限制API的问题,但加载外部so的限制却还在那。SeLinux的强制实施,使得dlopen()外部的so动态库有可能会失败返回。SeLinux会检测so动态库的label标签与权限是否满足可加载的要求,不满足就会无情的拒绝!为了解决这个问题,需要调用setxattr()修改so的属性信息。这里封装的代码如下:

int setxattr(const char *path, const char *value) {
    if (!file_exists("/sys/fs/selinux")) {
        return 0;
    }
    return syscall(__NR_setxattr, path, "security.selinux", value, strlen(value),
                   0);
}

当我们注入so前,可以插入如下代码来解决第三方so加载的问题:

snprintf(so_path, sizeof(so_path), "/data/local/tmp/libsvr.so");
...
setxattr(so_path, "u:object_r:system_file:s0");

编写注入器

注入器是一个ELF格式的安卓可执行文件,使用Android.mk配置好它的开发信息如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
​
LOCAL_C_INCLUDES       := $(LOCAL_PATH)
LOCAL_MODULE           := inject
LOCAL_LDLIBS           := -ldl -llog
LOCAL_CFLAGS           := -std=c99
# 基于pie
LOCAL_CFLAGS           += -fvisibility=hidden
LOCAL_CFLAGS           += -fPIE
LOCAL_LDFLAGS          += -pie -fPIE
LOCAL_SRC_FILES        := inject/inject.c
include $(BUILD_EXECUTABLE)

注入器的代码网络上流传了一个inject。早先的一个版本是由古河放出,后来github上也有了很多的版本。例如https://github.com/shutup/libinject2。当然,它们很多都年久失修,并不能在新的系统上运行起来,需要对代码做一些修正。

其中一处是对ptrace_attach()的处理,如zygote进程,很多时候是不能一次attach成功的,需要进行更加细致的处理。这里修正代码如下:

int ptrace_attach(pid_t pid, bool is_zygote) {
    struct pt_regs regs;
    int status = 0;
    if (ptrace(PTRACE_ATTACH, pid, NULL, 0) < 0) {
        perror("ptrace_attach");
        return -1;
    }
​
    if (is_zygote) {
        while (waitpid(pid, &status, __WNOTHREAD) == -1 && (EINTR == errno)) {
            LOGI("waitpid EINTR, status = %d\n", status);
        }
​
        int times = 50;
        while ((times--) != 0) {
            if (ptrace(PTRACE_SYSCALL, pid, NULL, 0) < 0) {
                perror("ptrace_syscall");
                ptrace_detach(pid);
                kill(pid, SIGCONT);
                return -1;
            }
​
            while (waitpid(pid, &status, __WNOTHREAD) == -1 && (EINTR == errno)) {
                LOGI("waitpid EINTR, status = %d\n", status);
            }
            ptrace_getregs(pid, &regs);
            bool is_async_syscall = false;
#if defined(__arm__)
            if (regs.ARM_r7 <= NR_faccessat) {
                if ((NR_ioctl == regs.ARM_r7) ||
                        (NR__newselect == regs.ARM_r7) ||
                        (NR_poll == regs.ARM_r7)) {
                    is_async_syscall = true;
                }
            } else {
                //if (regs.ARM_r7 > NR_epoll_pwait) {
                //    is_async_syscall = false;
                //}
                #define _BYTE  unsigned char
                #define BYTEn(x, n)   (*((_BYTE*)&(x)+n))
                #define LOBYTE(x)  BYTEn(x, 0)
                is_async_syscall = regs.ARM_r7 > NR_epoll_pwait ? 0 :
                            ((1 << (LOBYTE(regs.ARM_r7) - 0x4F)) & 0x803) != 0;
            }
#elif defined(__aarch64__)
            //FIXME aarch64 ptr
  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值