Android Studio微型技术报告(二)

Android的基础服务大部分都是使用Java写的,只是C层的注入,想要直接访问的话还是比较麻烦。所以需要在远端加载一个dex包(代码中写死了加载/data/local/inj目录下的dex)。加载dex需要Dalvik虚拟机的Context,也就是JNIEnv,这个稍微遇到了一些麻烦,直接在源码上面编译的话,构建源码的编译环境就比较费时间了,最后还是从源码中拉出Header文件,adb pull下来了android_runtime和nativehelper这两个.so才解决了编译、链接的问题。

C层加载dex之后,可以调用Java的main方法执行Java代码了,但是想做一些访问系统服务的事情的话,没有Context是不行的,所以通过反射调用ActivityThread,拿到Application。

最初的想法是使用inj_dalvik注入到system_server进程,然后访问里面所有的Android系统服务,但后来发现这样有两个问题,dex在system_server进程执行时不能使用PackageManager这样的远端服务代理,只能使用ServiceManager.getService("package")直接获取PackageManagerService实例。而且发现PackageManagerServer这样的服务,接口变动比较频繁,版本适配上比较麻烦。所以最后还是决定注入到一个user为system的客户进程,比如com.android.settings。然后整个流程就跑通了。

现在面临的一个未解决的问题是com.android.setting这样的进程并不是系统Daemon,不是常驻进程,我们的代码注入后不能永远存活。但其它像/system/bin/surfaceflinger这样的进程,因为本身是C程序,启动时并没有AndroidRuntime,无法加载我们的Dex并Java代码。所以,要么在需要执行远端代码时后台偷偷启动com.android.settings进程,要么自己fork出一个具有AndroidRuntime的进程来,并把它注册到init.rc中。

    手机助手中通常都需要用到这样的功能,停用某个用户不使用的系统应用,或者为了禁止应用被后台唤起而停用应用中的某个组件。通过Root后有一种比较简单的办法就是在su下执行pm命令。但这种方式有几个比较大的缺陷:一个是效率低,因为需要新开进程。二个是如果一个组件的类是个内部类,类名中包含$这样的符号的话,pm命令会执行失败。三个是应用自身可以恢复自身组件的停用状态。这里提供一个比较简单的实现方案。

我们研究PackageManagerService代码发现,调用setComponentEnabledSetting、setApplicationEnabledSetting这样方法如果是针对其它应用的话,检查的是system权限,所以我们只要造一个具有system权限的进程,并在这个进程内部调用这两个方法就OK。这一点已经通过上面的Android进程注入方案中,通过注入到com.android.settings包中得到了验证。我们需要这几步:

一、参考pm、am等工具的实现方式,在源码下编译一个Jar包,在代码中启动一个Socket服务,接收外部命令,进行校验后执行。

二、把Jar包复制到/system/framework下,并在system/bin下建立启动入口,然后使用app_process启动该服务。

三、在init.rc中增加一个services项,以system权限启动该Jar包,这样下次开机时也能自动启动。

  进程注入的流程如下:

一、attach目标进程。

使用ptrace来attach目标进程,内核将发送 SIGSTOP 将目标进程挂起,我们使用waitpid接收到SIGSTOP以后,使用ptrace_getregs把当前寄存器的内容保存在regs_t中(regs_t这个数据结构的定义在ptrace.h),再调用PTRACE_CONT使用进程继续执行。

二、在linker找到dlopen,dlsym,dlclose的地址。

搜索/proc/$pid/maps中/system/bin/linker在虚拟进程中的地址,然后在linker的地址范围内搜索分别找到dlopen,dlsym,dlclose的地址,存储在数据结构dl_fl_t中。

三、加载并执行本地SO。

Android中的动态加载与Linux中有些不同,是没有懒加载的,C代码中引用到的.so会在进程启动时加载完成。如果C代码中用到dlopen等显式动态加载方法时,需要在编译时加上libdl.so,但代码实际运行中逻辑实现是在linker中的,所以我们在linker中就可以找到dlopen,dlsym,dlclose的handle。

通过第二步拿到dlopen,dlsym的地址之后,就可以通过ptrace改写远程进程的寄存器的PC寄存器地址(R15)到dlopen的地址,然后ptrace_cont,就可以加载本地的SO库了。

 

你可能感兴趣的:(Android Studio微型技术报告(二))