Andrdoid适当的执行行为拦截的应用----从底部C截距

前一个概要文章称这项研究我的一些主要细节。这里就不在说。但还需要指出的是。关于三大感谢上帝愿意分享知识(在我看来,人们懂得分享和慎重考虑之神,奥地利不一定是技术牛~~)


第一篇:http://blog.csdn.net/jinzhuojun/article/details/9900105

第二篇:http://bbs.pediy.com/showthread.php?

t=186880

第三篇:http://bbs.pediy.com/showthread.php?t=157419


最重要的还是第一篇。所以这里我就不多介绍了。当然假设要看这篇blog的话,最好是先仔细阅读一下上面的三篇文章。

当然我对第一篇文章做了改动了详细的描写叙述了:http://blog.csdn.net/jiangwei0910410003/article/details/39293635

这篇文章最好是看懂了,并且是必须真的懂了,同一时候将demo自己执行一边,流程走通了,不然以下也是会遇到问题的。


当然这样的拦截方式的前提是:手机必须root,同一时候须要获取su权限


以下開始进入正题


当然在之前的文章中的摘要中我们能够看到我这次主要拦截的是能够获取系统信息的进程。所以我们要搞清楚拦截的对象,这里就不做介绍了。我们拦截的进程是system_server(关于这个进程能够google一下,当然我们能够使用adb shell以及ps命令查看这个进程的相关信息)


关于inject源代码这里就不做太多的解释了,主要来看一下他的main函数:

int main(int argc, char** argv) {    
    pid_t target_pid;    
    target_pid = find_pid_of("system_server");    
    if (-1 == target_pid) {  
        printf("Can't find the process
");  
        return -1;  
    }  
    printf("target_id:%d
",target_pid);  
    inject_remote_process(target_pid, "/data/libsys.so", "hook_entry",  "I'm parameter!", strlen("I'm parameter!"));    
    return 0;  
}
这里的一个基本的方法就是inject_remote_process(...)

第一个參数:须要注入的进程id

第二个參数:须要注入的动态库(事实上这个库中就是包括我们须要替换的函数地址)

第三个參数:动态库入口的函数名称

第四个參数:动态库入口的函数所须要的參数

第五个參数:动态库入口的函数所须要的參数的长度

当然这里我们另一个是通过进程名称获取到进程的id的函数find_pid_of(...)


既然如今我们须要注入system_server进程中,那么我们就要须要将我们的代码注入到libbiner.so文件里


在sys.c代码中改动几个地方:

第一个改动的地方就是so文件路径:

#define LIBSF_PATH  "/system/lib/libbinder.so"


然后就是注入的函数:我们记得在注入surfaceflinger进程的时候。拦截的是eglSwapBuffers函数,我们注入到system_server的话,就是拦截ioctl函数,由于我们知道想使用一些系统服务都是调用这种方法的。以下就对这个函数进行替换:

int (*old_ioctl) (int __fd, unsigned long int __request, void * arg) = 0;

// 欲接替ioctl的新函数地址,当中内部调用了老的ioctl
int new_ioctl (int __fd, unsigned long int __request, void * arg)
{
        if ( __request == BINDER_WRITE_READ )
        {
            call_count++;
	    LOGD("call_count:%d",call_count);
        }
        int res = (*old_ioctl)(__fd, __request, arg);
        return res;
}

在这个函数中。我们会推断一下请求状态_request,假设是BINDER_WRITE_READ,说明上层有请求服务了。这里就是做一个简单的推断,通过一个int值,然后用log将其值打印出来。

int hook_eglSwapBuffers()    
{    
	old_ioctl = ioctl;   
	void * base_addr = get_module_base(getpid(), LIBSF_PATH);    
	LOGD("libsurfaceflinger.so address = %p
", base_addr);    
	int fd;    
	fd = open(LIBSF_PATH, O_RDONLY);    
	if (-1 == fd) {    
		LOGD("error
");    
		return -1;    
	}    

	Elf32_Ehdr ehdr;    
	read(fd, &ehdr, sizeof(Elf32_Ehdr));    

	unsigned long shdr_addr = ehdr.e_shoff;      
	int shnum = ehdr.e_shnum;      
	int shent_size = ehdr.e_shentsize;      
	unsigned long stridx = ehdr.e_shstrndx;      

	Elf32_Shdr shdr;    
	lseek(fd, shdr_addr + stridx * shent_size, SEEK_SET);      
	read(fd, &shdr, shent_size);      

	char * string_table = (char *)malloc(shdr.sh_size);      
	lseek(fd, shdr.sh_offset, SEEK_SET);      
	read(fd, string_table, shdr.sh_size);    
	lseek(fd, shdr_addr, SEEK_SET);      

	int i;      
	uint32_t out_addr = 0;    
	uint32_t out_size = 0;    
	uint32_t got_item = 0;  
	int32_t got_found = 0;    

	for (i = 0; i < shnum; i++) {      
		read(fd, &shdr, shent_size);      
		if (shdr.sh_type == SHT_PROGBITS) {    
			int name_idx = shdr.sh_name;      
			if (strcmp(&(string_table[name_idx]), ".got.plt") == 0 || strcmp(&(string_table[name_idx]), ".got") == 0) {      
				out_addr = base_addr + shdr.sh_addr;      
				out_size = shdr.sh_size;    
				LOGD("out_addr = %lx, out_size = %lx
", out_addr, out_size);

				for (i = 0; i < out_size; i += 4) {      
					got_item = *(uint32_t *)(out_addr + i);  
					if (got_item  == old_ioctl) {      
						LOGD("Found eglSwapBuffers in got
");    
						got_found = 1;  
						uint32_t page_size = getpagesize();  
						uint32_t entry_page_start = (out_addr + i) & (~(page_size - 1));  
						mprotect((uint32_t *)entry_page_start, page_size, PROT_READ | PROT_WRITE);  
						*(uint32_t *)(out_addr + i) = new_ioctl;    
						break;      
					} else if (got_item == new_ioctl) {      
						LOGD("Already hooked
");    
						break;      
					}      
				}     
				if (got_found)   
					break;  
			}     
		}      
	}      

	free(string_table);      
	close(fd);    
}

还有就是hook_eglSwapBuffers函数(函数名都难得改了~~)


好的,改动差点儿相同了,以下我们来编译吧~~

编译会出错的。由于会提示找不到ioctl的定义以及一些常量值,所以我们得找到这个函数的定义,百度一下之后会发现这个函数的定义是在binder.h中,当然这个头文件我们是能够在Android源代码中找到的

(关于源代码下载和编译的问题:http://blog.csdn.net/jiangwei0910410003/article/details/37988637)。
然后将这个binder.h复制到我们编译的文件夹中,然后再代码中引用一下就可以。

再次编译,擦,还是提示错误。说这个函数未定义。

原因非常easy,我们仅仅是引用了头文件。并没有将函数的详细实现引用进来,所以还须要去找到这个函数的详细定义了。

这个过程就是有点麻烦了。纠结了非常长时间呀~~,幸好最后搞定了


详细步骤:

从设备的system/lib/ 文件夹中找到libbinder.so文件。将其拷贝出来,这是一个动态库文件

然后将其放到我们之前的NDK配置文件夹中的:详细文件夹例如以下:



最后我们在Android.mk文件进行引用:

LOCAL_LDLIBS := -llog -lbinder -lutils -landroid_runtime

当然,这里我们会看到-lXXX是通用的格式,相同的,我们假设要用到JVM中的函数的话,会用到libandroid_runtime.so文件,头文件:android_runtime.h,也是能够在源代码中找到的(后面会提及到)

(注:这里就介绍了我们怎样在使用Android系统中底层的一些函数,同一时候编译的时候引用到了动态链接库文件)


扩展:

这里在扩展一下:还有另外的一种方式引用so文件:

操作步骤:

首先在我们编译文件夹中新建一个文件夹:prebuilt

在这个文件夹中存放两个文件:

1.Android.mk:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := binder
LOCAL_SRC_FILES := libbinder.so 
include $(PREBUILT_SHARED_LIBRARY)

2. libbinder.so(这个动态链接库文件就是我们须要用到的)


然后在回到我们编译的文件夹中,改动一下我们的Android.mk文件:

LOCAL_PATH := $(call my-dir)        
include $(CLEAR_VARS)
LOCAL_LDLIBS := -llog
LOCAL_MODULE := sys  
LOCAL_SRC_FILES := sys.c
include $(BUILD_SHARED_LIBRARY) 
include $(LOCAL_PATH)/prebuilt/Android.mk 

最重要的一行就是在最后。

所以这也是一种方法。

当然我们会看到这样的方法有一个弊端。就是一次仅仅能引用到一个so文件。

所以使用范围比較窄~~


在回到主题上来。我们通过引用libbinder.so文件之后。编译能够通过了,然后将编译之后的libsys.so文件复制到/data/文件夹中,这个和之前的libsuf.so步骤差点儿相同,以及将inject复制到/data/文件夹中。


然后进入到设备的data文件夹中,执行inject

=> adb shell

=> cd data

=> ./inject

同一时候检測一下log信息:

adb logcat -s PERMISSIONINTERCEPTER


随便动动手机,call_count就刷刷的变~~


至此。我们看到了,hook进程和拦截ioctl函数成功~~

Demo下载地址:

http://download.csdn.net/detail/jiangwei0910410003/7930603


以下再来深入看一下。怎样拦截哪些应用在使用系统服务呢?

首先,我们还是须要对上面我们自己定义的new_ioctl函数中的逻辑进行改动一下。

事实上这里面就有一个非常大的难度了(这个也正是底层C进行拦截的一个弊端),须要分析数据格式。然后才干正确的进行拦截。

以下来看一下详细的代码吧:

// 欲接替ioctl的新函数地址,当中内部调用了老的ioctl
int new_ioctl (int __fd, unsigned long int __request, void * arg)
{  

	if ( __request == BINDER_WRITE_READ )
	{
		int dir  =  _IOC_DIR(__request);   //依据命令获取传输方向  
		int type =  _IOC_TYPE(__request);  //依据命令获取类型  
		int nr   =  _IOC_NR(__request);    //依据命令获取类型命令  
		int size =  _IOC_SIZE(__request);  //依据命令获取数据传输大小

		struct binder_write_read* tmp = (struct binder_write_read*) arg;
		signed long write_size = tmp->write_size;
		signed long read_size = tmp->read_size;

		if(write_size > 0)//该命令将write_buffer中的数据写入到binder
		{
			//LOGD("binder_write_read----->write size: %d,write_consumed :%d", tmp->write_size, tmp->write_consumed);
			int already_got_size = 0;
			unsigned long *pcmd = 0;

			//LOGD("=================write_buffer process start!");
			while(already_got_size < write_size)//循环处理buffer中的每个命令
			{
				pcmd = (unsigned long *)(tmp->write_buffer + already_got_size);    //指针后移
				int code = pcmd[0];
				//LOGD("pcmd: %x, already_got_size: %d", pcmd, already_got_size);

				int dir  =  _IOC_DIR(code);   //依据命令获取传输方向  
				int type =  _IOC_TYPE(code);  //依据命令获取类型  
				int nr   =  _IOC_NR(code);    //依据命令获取类型命令  
				int size =  _IOC_SIZE(code);  //依据命令获取数据传输大小
				//LOGD("cmdcode:%d, dir:%d, type:%c, nr:%d, size:%d
", code, dir, type, nr, size);

				struct binder_transaction_data* pdata = (struct binder_transaction_data*)(&pcmd[1]);
				switch (code)
				{
				case BC_TRANSACTION:
					if(pdata->sender_pid>5000){
						//LOGD("code:%d",pdata->code);
						//LOGD("name:%x",pdata->data.ptr.buffer);
						//LOGD("pid: %d",pdata->sender_pid);
						//char *pname = (char*)malloc(50*sizeof(char));
						//cfgmng_get_taskname(pdata->sender_pid,pname);
						//LOGD("pname: %s",pname);
						//free(pname);
						//hexdump(pdata->data.ptr.buffer, pdata->data_size);
					}
					parse_binder(pdata, 1);
					break;

				case BC_REPLY:
					//LOGD("pid: %d, BC_REPLY, dir:%d, type:%c, nr:%d, size:%d
", pdata->sender_pid, dir, type, nr, size);
					parse_binder(pdata, 1);
					break;

				default:
					break;  
				}
				already_got_size += (size+4);
			}
			//LOGD("=================write_buffer process end!");
		}
		if(read_size > 0)//从binder中读取数据写入到read_buffer
		{
			//LOGD("binder_write_read----->read size: %d, read_consumed: %d", tmp->read_size, tmp->read_consumed);
			int already_got_size = 0;
			unsigned long *pret = 0;

			//LOGD("=================read_buffer process start!");
			while(already_got_size < read_size)//循环处理buffer中的每个命令
			{
				pret = (unsigned long *)(tmp->read_buffer + already_got_size);    //指针后移
				int code = pret[0];
				//LOGD("pret: %x, already_got_size: %d", pret, already_got_size);

				int dir  =  _IOC_DIR(code);   //依据命令获取传输方向  
				int type =  _IOC_TYPE(code);  //依据命令获取类型  
				int nr   =  _IOC_NR(code);    //依据命令获取类型命令  
				int size =  _IOC_SIZE(code);  //依据命令获取数据传输大小
				//LOGD("retcode:%d, dir:%d, type:%c, nr:%d, size:%d
", code, dir, type, nr, size);

				struct binder_transaction_data* pdata = (struct binder_transaction_data*)(&pret[1]);
				switch (code)
				{
				case BR_TRANSACTION:
					if(pdata->sender_pid>5000){
						//LOGD("code:%d",pdata->code);
						//LOGD("name:%s",pdata->data.ptr.buffer->flag);
						//LOGD("pid: %d",pdata->sender_pid);
						//char *pname = (char*)malloc(50*sizeof(char));
						//getNameByPid(pdata->sender_pid,pname);
						//LOGD("pname: %s",pname);
						//free(pname);
						char * pname = hexdump(pdata->data.ptr.buffer, pdata->data_size);
						if(isStub(pname,STUB) == 1 && pid!=pdata->sender_pid)
						{
							pid = pdata->sender_pid;

							//指定一下log的输出格式:服务的名称&&应用的进程id
							LOGD("%s&&%d",pname,pdata->sender_pid);

							//在一个线程中开启socket
							/*pthread_t tid;
							int status = pthread_create(&tid,NULL,&new_socket,(void*)&data);
							if(status != 0){
								LOGD("can't create thread");
							}else{
								LOGD("create thread success");			
							}*/

							//开一个socket
							//new_socket(pid,pname);

							//採用动态调用so文件里的函数f()
							/*void * dp = dlopen("libmiddle.so",RTLD_NOW);
							LOGD("dp is %p",dp);
							int (*f)() = dlsym(dp,"f");
							LOGD("func pointer==%p",f);
							if(f == NULL)
							{
								LOGD("not find func");	
							}
							else
							{
								(*f)();
							}*/
						}
						free(pname);
					}
					parse_binder(pdata, 2);
					break;

				case BR_REPLY:
					//LOGD("pid: %d, BR_REPLY, dir:%d, type:%c, nr:%d, size:%d
", pdata->sender_pid, dir, type, nr, size);
					parse_binder(pdata, 2);
					break;

				default:
					break;  
				}
				already_got_size += (size+4);//数据内容加上命令码
			}
			//LOGD("=================read_buffer process end!");

		}
	}
	if (old_ioctl == -1)  
	{
		//LOGD("error
");  
		return;
	}  

	int res = (*old_ioctl)(__fd, __request, arg);
	return res;
}


这个函数改动的逻辑就比較多了~~

首先来了解一下Binder在数据传输中比較关键的一个数据结构:binder_transaction_data

了解了这个数据结构之后,我们就能够从这个数据结构中提取出我们想要的服务的名称(这一步真的非常重要,也是网上非常多人咨询的一个问题,关于这个问题,我是阅读了一本比較好的资料《Android框架揭秘》是棒子写的一本书。里面有一些错误,可是不影响大体的知识方向)、同一时候也是能够获取到进程id,Android中一个应用一般就是一个进程,所以我们能够粗略的通过进程id来来获取应用的一些详细信息(能够获取近期正在执行的应用信息列表中进行过滤)


获取服务的名称的主要操作:

char * pname = hexdump(pdata->data.ptr.buffer, pdata->data_size);

然后就是data.ptr.buffer的数据结构:



那么如今问题就是怎样能将这个服务的名称正确的取出来呢?那么这个就要看hexdump函数了

从字面意义上看应该是将hex文本转化一下:

char* hexdump(void *_data, unsigned len)
{
    unsigned char *data = _data;
    char *dataAry = (char*)malloc(len*(sizeof(char)));
    char *dataTmp = dataAry;
    unsigned count;
    for (count = 0; count < len; count++) 
    {
        if ((count & 15) == 0)
            LOGD(stderr,"%04x:", count);
	//only show charset and '.'
	if(((*data >= 65) && (*data <= 90)) || ((*data >= 97) && (*data <= 122)) || (*data == 46))
	{
		*dataAry = *data;
		dataAry++;	
	}
        data++;
        if ((count & 15) == 15)
            LOGD(stderr,"
");
    }
    *dataAry = '';
    return dataTmp;
    if ((count & 15) != 0)
        LOGD(stderr,"
");
}

看到中间的一个核心的if推断,那个就是用来过滤服务的包名的:大写和小写字母+点号,这样就能从buffer中提取出服务的包名了,然后返回就可以(说实话,这个问题也是纠结了我好长时间,攻克了还是非常开心的)


在函数new_ioctl中最基本的核心代码:

switch (code)
{
case BR_TRANSACTION:
	if(pdata->sender_pid>5000){
		//LOGD("pid: %d, BR_TRANSACTION, dir:%d, type:%c, nr:%d, size:%d
", pdata->sender_pid, dir, type, nr, size);
		//LOGD("code:%d",pdata->code);
		//LOGD("name:%s",pdata->data.ptr.buffer->flag);
		//LOGD("pid: %d",pdata->sender_pid);
		//char *pname = (char*)malloc(50*sizeof(char));
		//getNameByPid(pdata->sender_pid,pname);
		//LOGD("pname: %s",pname);
		//free(pname);
		char * pname = hexdump(pdata->data.ptr.buffer, pdata->data_size);
		if(isStub(pname,STUB) == 1 && pid!=pdata->sender_pid)
		{
			pid = pdata->sender_pid;
			//指定一下log的输出格式:服务的名称&&应用的进程id
			LOGD("%s&&%d",pname,pdata->sender_pid);
			//在一个线程中开启socket
			/*pthread_t tid;
					int status = pthread_create(&tid,NULL,&new_socket,(void*)&data);
					if(status != 0){
						LOGD("can't create thread");
					}else{
						LOGD("create thread success");			
					}*/

			//开一个socket
			//new_socket(pid,pname);

			//採用动态调用so文件里的函数f()
			/*void * dp = dlopen("libmiddle.so",RTLD_NOW);
					LOGD("dp is %p",dp);
					int (*f)() = dlsym(dp,"f");
					LOGD("func pointer==%p",f);
					if(f == NULL)
					{
						LOGD("not find func");	
					}
					else
					{
						(*f)();
					}*/
		}
		free(pname);
	}
	parse_binder(pdata, 2);
	break;

case BR_REPLY:
	//LOGD("pid: %d, BR_REPLY, dir:%d, type:%c, nr:%d, size:%d
", pdata->sender_pid, dir, type, nr, size);
	parse_binder(pdata, 2);
	break;

default:
	break;  
}


这个函数中就是推断请求的方式

当code等于BR_TRANSACTION

表示開始读取数据,所以我们这时候就能够在这里进行数据的拦截分析。这里在获取进程id的时候,我做了一次推断就是推断进程id大于5000的,这个5000仅仅是我随便取的一个值。由于假设这里不做推断的话。在后面打印log信息的时候非常不方便,由于系统有非常多应用都可能在获取服务,log信息有点多吧。这里就相当于做个过滤。

同一时候这里另一个过滤条件,就是服务名称中有ILocationManager的,由于開始的时候仅仅是想验证一下这个不做是否能成功。所以先用位置信息来做实验。

效果图:


Demo下载地址:http://download.csdn.net/detail/jiangwei0910410003/7932293


上面的拦截操作算是完毕了,那么以下我们还得来做一件事(这件事也是网上好多同学纠结的一个问题)

就是怎样将我们这里拦截到的信息包括是哪个应用获取服务的进程id以及获取服务的名称,怎样将其这些信息传递到上层然后进行显示(比方360弹出的对话框,或者是在通知栏中:XXX应用正在获取你的XXX信息,拒绝还是同意),例如以下图:



我也花了一个礼拜的时间去解决的,找到三种方式:

第一种方式:通过上层(就是我们的app。建立一个SockeServer),然后在底层拦截到的详细信息通过socket传递到server中。

底层的代码例如以下:

void new_socket(int pid,char *pname){
	
	//将进程id转化成字符串然后和服务名称进行拼接
	pname = strcat(int_to_str(pid),pname);
	LOGD("start trasact data ...%s",pname);
	
	int sockfd,sendbytes;
	char buf[MAXDATASIZE];

	struct hostent *host = gethostbyname(SERVIP);

	struct sockaddr_in serv_addr;

	if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
		LOGD("socket fail");
		exit(1);
	}

	serv_addr.sin_family=AF_INET;
	serv_addr.sin_port=htons(SERVPORT);
	serv_addr.sin_addr.s_addr = inet_addr(SERVIP);
	bzero(&(serv_addr.sin_zero),8);
	if(connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))==-1){
		LOGD("connect fail");
		//exit(1);
	}

	if((sendbytes=send(sockfd,pname,strlen(pname),0))==-1){
		LOGD("send fail");
		//exit(1);
	}
	free(pname);
	close(sockfd);
} 

这个是在Linux中建立一个Socket,详细的操作这里就不做解释了,自行能够去搜索一下Linux中怎样建立socket。

以下在来看一下上层的app中建立一个SocketServer:

package com.sohu.intercepter.demo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {
	
	private static final String TAG = "PERMISSIONINTERCEPTER";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		new Thread(){
			@Override
			public void run(){
				java.net.ServerSocket sc = null;
				try {
					sc = new java.net.ServerSocket(3333);
					Log.i(TAG,"socket create success");
					while (true) {
						java.net.Socket client = sc.accept();
						String content = streamToString(client.getInputStream());
						Log.i(TAG, "accept ==> "+content);
					}
				} catch (Exception ef) {
					Log.i(TAG, "exception===>"+ef.getMessage());
				}finally{
					Log.i(TAG, "stop connection");			
					if (sc != null){
						try {
							sc.close();
						} catch (IOException e) {
							Log.i(TAG, "exception===>"+e.getMessage());
						}
						sc = null;
					}
				}
			}
		}.start();
		
	}
	
	public String streamToString(InputStream is){
		if(is == null)
			return "";
		ByteArrayOutputStream bos = null;
		try{
			byte[] buffer = new byte[1024];
			int len = 0;
			bos = new ByteArrayOutputStream();
			while((len=is.read(buffer)) != -1){
				bos.write(buffer, 0, len);
			}
			return new String(bos.toByteArray());
		}catch(Exception e){
			Log.i("TTT", "exception--->"+e.getMessage());
		}finally{
			try {
				is.close();
				bos.close();
			} catch (IOException e) {
				Log.i("TTT", "exception===>"+e.getMessage());
			}
			
		}
		return "";
	}

}

这段代码通常是放在Service中的。由于要始终监听。同一时候这里开启的端口是:3333.为了防止和其它应用的端口冲突,一边最好起个比較有个性的端口,当然这里没有做更好的优化。比方当建立端口的时候发现这个端口已经被占用了怎么办?做的好的应该做一下端口的推断。然后找到没被占用的端口(能够用Socket探针技术)。

详细步骤:

1.改动一下sys.c文件里,使用socket进行数据传输(将相应的凝视代码打开就可以),编译之后,将libsys.so复制到/data/文件夹中

2.执行上层代码,安装到手机中。执行应用(打开SocketServer监听)

3.进入到/data/文件夹。执行inject

4.执行命令:

=>  adb shell

=> cd data

=> su

=> ./inject

5.同一时候检測log信息:adb logcat -s PERMISSIONINTERCEPTER

结果图:



另外一种方式:有点复杂了就是使用JNI技术了。我们须要在底层中调用上层的Java代码。这个技术就是能够在C++代码中使用类载入机制+反射技术调用Java中的指定方法。这里有一个问题就是怎样获取JVM对象以及JNIEnv对象。

众所周知,Android的应用进程,都是由Zygote孵化的子进程。每个进程都执行在独立的JVM中(详细的知识能够查看老罗的blog)。

那么JVM对象能够获取到,那么JNIEnv对象呢?我们知道,在JVM进程中,JavaVM是全局唯一的,而JNIEnv则是按线程分配。另外,Dalvik的线程跟Linux线程是一一相应的。因此我们能够把自身所在的线程Attatch到JavaVM,JavaVM就会为我们分配JNIEnv对象了。通过阅读Dalvik源代码。从AndroidRuntime类中我们能够得到JavaVm的地址。再通过JavaVm所提供的AttachCurrentThead和DetachCurrentThread两个函数,就可以完毕JNIEnv的获取。


详细代码例如以下:

importdex.cpp代码

#include <stdio.h>
#include <stddef.h>
#include <jni.h>
#include <android_runtime/AndroidRuntime.h>

#include "log.h"

using namespace android;

static const char JSTRING[] = "Ljava/lang/String;";
static const char JCLASS_LOADER[] = "Ljava/lang/ClassLoader;";
static const char JCLASS[] = "Ljava/lang/Class;";

static JNIEnv* jni_env;
static char sig_buffer[512];

//ClassLoader.getSystemClassLoader()
static jobject getSystemClassLoader(){
	jclass class_loader_claxx = jni_env->FindClass("java/lang/ClassLoader");
	snprintf(sig_buffer, 512, "()%s", JCLASS_LOADER);
	jmethodID getSystemClassLoader_method = jni_env->GetStaticMethodID(class_loader_claxx, "getSystemClassLoader", sig_buffer);
	return jni_env->CallStaticObjectMethod(class_loader_claxx, getSystemClassLoader_method);
}

void Main() {

	JavaVM* jvm = AndroidRuntime::getJavaVM();
	jvm->AttachCurrentThread(&jni_env, NULL);
	//TODO 使用JNIEnv
	//jvm->DetachCurrentThread();
	jstring apk_path = jni_env->NewStringUTF("/data/local/tmp/DemoInject2.apk");
	jstring dex_out_path = jni_env->NewStringUTF("/data/data/");
	jclass dexloader_claxx = jni_env->FindClass("dalvik/system/DexClassLoader");
	snprintf(sig_buffer, 512, "(%s%s%s%s)V", JSTRING, JSTRING, JSTRING, JCLASS_LOADER);
	jmethodID dexloader_init_method = jni_env->GetMethodID(dexloader_claxx, "<init>", sig_buffer);
	snprintf(sig_buffer, 512, "(%s)%s", JSTRING, JCLASS);
	jmethodID loadClass_method = jni_env->GetMethodID(dexloader_claxx, "loadClass", sig_buffer);

	jobject class_loader = getSystemClassLoader();
	check_value(class_loader);

	jobject dex_loader_obj = jni_env->NewObject(
			dexloader_claxx, dexloader_init_method, apk_path, dex_out_path, NULL, class_loader);
	jstring class_name = jni_env->NewStringUTF("com.demo.inject2.EntryClass");
	jclass entry_class = static_cast<jclass>(jni_env->CallObjectMethod(dex_loader_obj, loadClass_method, class_name));

	jmethodID invoke_method = jni_env->GetStaticMethodID(entry_class, "invoke", "(I)[Ljava/lang/Object;");
	check_value(invoke_method);

	jobjectArray objectarray = (jobjectArray) jni_env->CallStaticObjectMethod(entry_class, invoke_method, 0);

	jvm->DetachCurrentThread();
}


这里会遇到一个问题就是AndroidRuntime类找不到的问题,能够google一下,他的定义在android_runtime.h头文件里,这个相同也是能够从Android源代码中找到的,同一时候这个AndroidRuntime类的实现能够从设备的/system/lib/文件夹中找到动态库文件libandroid_runtime.so。

和之前使用libbinder.so差点儿相同的步骤,这里就不做说明了。

编译项目下载地址:

http://download.csdn.net/detail/jiangwei0910410003/7932355

编译之后得到importdex.so文件。这个在后面会用到。


以下来详细看一下代码。在看代码之前须要先了解一下Android中的类载入机制。请看以下的一篇文章:

http://blog.csdn.net/jiangwei0910410003/article/details/17679823
假设这篇文章看懂了,事实上上面的代码就没有不论什么难度了。事实上就是C++中实现这样的方式。和Java中的反射机制非常类似,

在代码中还能够看到DexClassLoader这个类的一个參数是dex文件的输出文件夹。这里直接放到了/data/data文件夹中。

同一时候我们须要将开发一个DemoInject2.apk,这里面要有一个类:com.demo.inject2.EntryClass,在这个类中须要定义一个静态的invoke方法:

package com.demo.inject2;

import android.content.Context;
import android.util.Log;
import android.widget.Toast;

public final class EntryClass {

	public static Object[] invoke(int i) {

		try {
			Log.i("TTT", ">>>>>>>>>>>>>I am in, I am a bad boy 2!!!!<<<<<<<<<<<<<<");
			Context context = ContexHunter.getContext();
			Toast.makeText(context, "Success", Toast.LENGTH_LONG).show();
			/*Class<?> MainActivity_class = context.getClassLoader().loadClass("com.demo.host.MainActivity");
			Method setA_method = MainActivity_class.getDeclaredMethod("setA", int.class);
			setA_method.invoke(null, 1);*/
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}
}
然后我们就能够通过底层调用这种方法,将进程id和服务的名称作为參数传递过去。可是在使用这样的方法的时候遇到一个问题:

那就是我们看到sys.c是C程序,可是上面的那个程序是C++的。所以这里面会牵扯到一个问题就是怎么在C程序中调用C++的代码,这个能够看一下以下的一篇文章是怎么操作:

http://blog.csdn.net/jiangwei0910410003/article/details/39312947


以下看看我们这里是怎样做到的

这里会用到一个libimportdex.so文件(上面编译之后的文件)。还须要在弄一个编译项目middle.

middle文件夹:

middle.c:

#include "importdex.h"

extern "C"{
	int f()
	{
		callback();
		return 0;	
	}
}

Android.mk:

LOCAL_PATH := $(call my-dir)        
include $(CLEAR_VARS)        
LOCAL_LDLIBS := -llog -lbinder -lutils -limportdex
LOCAL_MODULE := middle  
LOCAL_SRC_FILES := middle.cpp
include $(BUILD_SHARED_LIBRARY) 
这里我们还须要将之前的编译得到的libimportdex.so文件复制到NDK的配置文件夹中
编译项目下载地址:

http://download.csdn.net/detail/jiangwei0910410003/7932369


底层的运行so文件里指定的函数的核心代码:

void * dp = dlopen("libmiddle.so",RTLD_NOW);
LOGD("dp is %p",dp);
int (*f)() = dlsym(dp,"f");
LOGD("func pointer==%p",f);
if(f == NULL)
{
	LOGD("not find func");	
}
else
{
	(*f)();
}

最后假设想成功的话,还须要将importdex.so文件以及libmiddle.so文件复制到设备的/system/lib/文件夹以下,由于你须要引用。


第三种方式:使用Log日志来传递数据(这个是终于的解决方法)

这个实现原理非常easy。就是在底层C中。我们拿到的了进程的id和服务的名称然后使用特定的规则将其进行拼接,然后在上层的app进行log的拦截,然后获取指定的值。这里会遇到一个问题就是怎样能在本应用中获取其它进程中的log信息,假设我们在本应用中获取log信息非常easy的,可是获取其它进程的log信息遇到点问题,可是最后靠着自己摸索,发如今su权限的情况下,能够获取到全部进程的log信息。所以我们要获取su权限。


在上层代码中取指定log信息代码:

package com.isoft.log;

import java.io.DataInputStream;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

public class LogObserverService extends Service implements Runnable{
	private String TAG = "LOG";
	private StringBuffer logContent = null;
	
	private final static String[] CMDS = new String[]{"/system/bin/su","-c","logcat -s PERMISSIONINTERCEPTER"};
	
	@SuppressLint("HandlerLeak")
	private Handler handler = new Handler(){
		@Override
		public void dispatchMessage(Message msg) {
			if(msg.what == 0){
				String content = (String)msg.obj;
				Toast.makeText(getApplicationContext(), "conent:"+content, Toast.LENGTH_LONG).show();
			}
			super.dispatchMessage(msg);
		}
		
	};
	
	@Override
	public void onCreate() {
		super.onCreate();
		Log.i(TAG,"onCreate");
		logContent = new StringBuffer();
		new Thread(this).start();
	}
	
	
	@Override
	public void run() {
		Process pro = null;
		try {
			pro = Runtime.getRuntime().exec(CMDS);
		}catch (Exception e) {
			Log.i(TAG, "异常:"+e.getMessage());
			e.printStackTrace();
		}

		DataInputStream dis = new DataInputStream(pro.getInputStream());
		String line = null;
		
		while (true) {
			try {
				while ((line = dis.readLine()) != null) {
					if(line != null){
						String[] lineAry = line.split("&&");
						if(lineAry.length == 2){
							//检測一下获取有位置信息的log信息
							if(lineAry[0].contains("ILocationManager")){
								Message msg = new Message();
								msg.what = 0;
								msg.obj = "进程id:"+lineAry[1] + "
获取位置信息";
								handler.sendMessage(msg);
							}
						}
					}
					String temp = logContent.toString();
					logContent.delete(0, logContent.length());
					logContent.append(line);
					logContent.append("
");
					logContent.append(temp);
					Thread.yield();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

}

基本的运行命令:

private final static String[] CMDS = new String[]{"/system/bin/su","-c","logcat -s PERMISSIONINTERCEPTER"};

这样就能够获取到指定的Tag的log信息,然后对其进行解析。获取进程id和服务的名称。


这三种方式的实现的代码的Demo下载地址(前两种方式我在代码中凝视了,假设想用的话。能够打开凝视)


总结一下,为什么会有这三种方式的产生呢?首先在有这样的需求,想到的解决方式就是第一种。

可是我在实现第一种方式的时候,出现了一个问题。就是运行一段时间总是会死机。总是须要reboot命令重新启动或者扣电池了~~。如今还没找到解决方式。所以就延伸出另外一种方式了。可是另外一种方式能够是能够。没先到也会死机和第一种方式的问题一样。如今还没有找到解决方式。

所以就延伸出第三种方式了。由于第三种方式真的非常easy。

可是第三种方式有一个非常大的问题,就是不能和底层进行交互,由于当我们在上层app中会点击拒绝的话,就是不给某个应用获取此服务,所以我们须要得到用户的选择状态值,这样才干在底层做详细的操作。

回忆一下,第一种和另外一种方式都是能够实现的。可是会出现死机的情况。所以这个在兴许是一定要解决的。


最后在来感受一下效果吧:



(ps:关于这个注入和拦截的问题,网上有非常多同学都在研究,可能有的同学已经成功了。可是有的同学没有。所以我就将这篇文章分享了一下,由于在这个过程中,我懂得那种遇到问题无助的感触,所以假设你看完了这个文章,假设遇到不论什么问题。请给我留言,我能帮你解决的尽量解决一下)








版权声明:本文博客原创文章。博客,未经同意,不得转载。

原文地址:https://www.cnblogs.com/gcczhongduan/p/4726961.html