天天看点

进击的Android注入术《二》继续注入示例代码

继续

在《一》里,我把基本思路描述了一遍,接下为我们先从注入开始入手。

注入

分类

我们平时所说的代码注入,主要静态和动态两种方式

  • 静态注入,针对是可执行文件,比如平时我们修改ELF,DEX文件等等,相关的辅助工具也很多,比如IDA、JEB、ApkTool等等;
  • 动态注入,针对是进程,比如修改进程的寄存器、内存值等等;

动态跟静态最大的区别是,动态不需要改动源文件,但需要高权限(通常是root权限),而且所需的技术含量更高。

本质

动态注入技术,本质上就是一种调度技术。想想平时我们调试一个进程时,可以做哪些功能? 一般有下列几项:

  • 查看变量值
  • 修改变量值
  • 跟踪进程跳转
  • 查看进程调用堆栈
  • 等等

动态注入相比于普通的调试,最大的区别就是动态注入是一个 ”自动化调试并达到加载自定义动态链接库“的过程。所谓自动化,其实就是通过代码实现,在Linux上通过Ptrace就可以完成上面所有功能,当然Ptrace功能是比较原始的,平时调试中的功能还需要很多高层逻辑封装才可以实现。 在阅读下面章节之前,强烈建议阅读一下man文档,见 这里。

目的

一般而言,我们要对一个进程进行注入,主要有以下几方面目的:

  • 增强目标进程的功能;
  • 修复目标进程缺陷;
  • 劫持目标进程函数;
  • 窃取目标进程数据;
  • 篡改目标进程数据;
进击的Android注入术《二》继续注入示例代码

过程

如上图所示,进程A注入到进程B后,通过修改寄存器和内存,让进程B加载自定义的动态库a,当a被加载后,a会尝试加载其他模块,比如加载dex文件等等,具体的注入过程如下:

  • ATTATCH,指定目标进程,开始调试;
  • GETREGS,获取目标进程的寄存器,保存现场;
  • SETREGS,修改PC等相关寄存器,使其指向mmap;
  • POPETEXT,把so path写入mmap申请的地址空间;
  • SETRESG,修改PC等相关寄存器,使其指向dlopen;
  • SETREGS,恢复现场;
  • DETACH,解除调试,使其恢复;

上述是一个简化的过程,整个注入的代码,我已经上传到github,地址https://github.com/boyliang/Poison 当so被dlopen加载到目标进程后,我们需要让so中的逻辑被执行,比较复杂的做法是同样使用ptrace修改寄存器的办法,让目标进程调用dlsym找到我们函数的地址。而比较简单的做法有两种,如下

  • 使用gcc的预编译指令__attribute__ ((__constructor__)),作用是让so被加载后,函数被自动执行;
__attribute__ ((__constructor__))
void Main() {
 LOGI(">>>>>>>>>>>>>I am in, I am a bad boy 1!!!!<<<<<<<<<<<<<<");

 void* handle = dlopen("libinso.so", RTLD_NOW);
 void (*setA_func)(int) = (void (*)(int))dlsym(handle, "setA");

 if (setA_func) {
   setA_func(999);
 }
}
           
  • 使用c++全局对象初始化,其构造函数会被自动执行;
void Main();

static void* _main(void*){
	Main();
	return NULL;
}

class EntryClass {
public:

	EntryClass() {
		pthread_t tid;
		pthread_create(&tid, NULL, _main, NULL);
		pthread_detach(tid);
	}

} boy;
           

示例一

下面示例一个通过ptrace注入的示例,涉及到两部分代码,一部分是目标进程代码记作host,另一部分是被我们注入的so代码记作libmyso.so

Host代码

包含三个源文件,分别是demo1.c,inso.h, inso.c

/*
 * inso.h
 *
 *  Created on: 2014年6月24日
 *      Author: boyliang
 */


__attribute__ ((visibility ("default"))) void setA(int i);

__attribute__ ((visibility ("default"))) int getA();
           
/*
 * inso.c
 *
 *  Created on: 2014年6月24日
 *      Author: boyliang
 */

#include <stdio.h>
#include "inso.h"

static int gA = 1;

void setA(int i){
	gA = i;
}

int getA(){
	return gA;
}
           
/*
 * demo1.c
 *
 *  Created on: 2014年6月24日
 *      Author: boyliang
 */

#include <stdio.h>
#include <unistd.h>

#include "inso.h"
#include "log.h"

int main(){

	LOGI("DEMO1 start.");

	while(1){
		LOGI("%d", getA());
		setA(getA() + 1);
		sleep(2);
	}

	return 0;
}
           

libmyso.so代码

/*
 * myso.c
 *
 *  Created on: 2014年6月24日
 *      Author: boyliang
 */

#include <stdio.h>
#include <stddef.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stddef.h>

#include  "log.h"

__attribute__ ((__constructor__))
void Main() {
	LOGI(">>>>>>>>>>>>>I am in, I am a bad boy 1!!!!<<<<<<<<<<<<<<");

	void* handle = dlopen("libinso.so", RTLD_NOW);
	void (*setA_func)(int) = (void (*)(int))dlsym(handle, "setA");

	if (setA_func) {
		setA_func(999);
	}
}
           

调用

注入程序,我将其命名为poison,使用方法是poison <so_path> <target_pit>。下面是示例的输出显示:

I/TTT     (  594): DEMO1 start.
I/TTT     (  594): 1
I/TTT     (  594): 2
I/TTT     (  594): 3
I/TTT     (  594): 4
I/TTT     (  594): 5
I/TTT     (  594): 6
I/TTT     (  594): 7
I/TTT     (  594): >>>>>>>>>>>>>I am in, I am a bad boy 1!!!!<<<<<<<<<<<<<<
I/TTT     (  594): 999
I/TTT     (  594): 1000
I/TTT     (  594): 1001
           

当执行./poison /data/local/tmp/libmyso.so 594后,输出中马上出现了特定字符串,并且打印的数据一下子变成了999,证明我们注入成功了。

示例代码

上述示例所涉及到代码,我都放发布到github上了,大家如果想研究代码,可以到https://github.com/boyliang/injection_by_ptrace 在 《三》,我会再介绍一种Android上特有的注入技术,敬请期待。