【Android 逆向】函数拦截实例 ( ③ 刷新 CPU 高速缓存 | ④ 处理拦截函数 | ⑤ 返回特定结果 )

举报
韩曙亮 发表于 2022/01/11 00:54:04 2022/01/11
【摘要】 文章目录 前言一、刷新 CPU 高速缓存二、处理拦截函数1、桩函数2、处理拦截函数 三、返回特定结果四、相关完整代码 前言 【Android 逆向】函数拦截实例 ( 函数拦截流程 | ...

前言

【Android 逆向】函数拦截实例 ( 函数拦截流程 | ① 定位动态库及函数位置 ) 博客中简单介绍了 hook 函数 ( 函数拦截 ) 的流程 , 本系列博客介绍函数拦截实例 ;

拦截 clock_gettime 函数 ;

#include <time.h>
int clock_gettime(clockid_t clk_id,struct timespec *tp);

  
 
  • 1
  • 2

【Android 逆向】函数拦截实例 ( ② 插桩操作 | 保存实际函数入口 6 字节数据 | 在插桩的函数入口写入跳转指令 | 构造拼接桩函数 ) 博客中进行了插桩操作 ,





一、刷新 CPU 高速缓存



执行 cache_flush 系统调用函数 刷新 CPU 的高速缓存 ; 该步骤 只在 ARM 架构的 CPU 中执行 , x86 架构的 CPU 不需要刷新缓存 ;

x86 不需要执行刷新缓存操作 , 但也可以执行系统调用操作 syscall 来刷新缓存 ;


刷新 CPU 高速缓存 代码示例 : pApi 是实际调用的函数指针 , size 是 6 字节 , 也就是说刷新 (int)pApi 地址到 (int)pApi + size 之间 6 6 6 字节对应的 CPU 高速缓存即可 ;

	/* 清空 CPU 高速缓存 */
#if !defined(__i386__)
	/* 在 arm 架构中必须刷新 CPU 高速缓存 , x86 不需要执行 */
	cacheflush((int)pApi, (int)pApi + size, ICACHE|DCACHE);
#else
	/* x86 下可以执行该系统调用 */
	syscall(0xF002, (int)pApi,(int)pApi + sizeE);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7




二、处理拦截函数




1、桩函数


【Android 逆向】函数拦截实例 ( ② 插桩操作 | 保存实际函数入口 6 字节数据 | 在插桩的函数入口写入跳转指令 | 构造拼接桩函数 ) 三、在插桩的函数入口写入跳转指令 | 构造拼接桩函数 博客章节 , 介绍了拼接装函数 do_clock_gettime 函数 , 实现了调用 do_clock_gettime 函数 与调用 clock_gettime 函数相同的效果 ;


构造拼接桩函数 : 前 6 字节是保存下来的 clock_gettime 函数的前 6 字节指令 , 执行到第 6 字节时 , 直接跳转到 clock_gettime 函数 执行 , 这样执行拼接的函数 等同于执行 clock_gettime 函数 ;


do_clock_gettime 函数构造成 clock_gettime 函数流程 : 执行 do_clock_gettime 方法的第 6 字节的指令时 , 跳转到 clock_gettime 函数的第 6 字节指令位置 , do_clock_gettime 的 0 ~ 6 字节指令是 clock_gettime 实际函数的前 6 字节 , 之所以这么定义 , 是因为 clock_gettime 的前 6 个字节被覆盖为 跳转指令了 ;


2、处理拦截函数


处理拦截函数 :

当函数执行到 clock_gettime 之后 , 就会执行插入的跳转指令 , 跳转到 dn_clock_gettime 函数中 ;

在该函数中 , 可以调用 do_clock_gettime 函数 , 执行原有的指令 ;

do_clock_gettime 函数执行前后 , 都可以插入自己的业务逻辑 , 监控或修改都可以 ;


处理拦截函数 代码示例 :

/* 拦截函数 , 拦截 clock_gettime 函数后 , 跳转到此处 */
int dn_clock_gettime(clockid_t id, struct timespec* ts) {
	/*if (ts == NULL) {
		return -1;
	}*/

	/* 如果设备实现了系统调用 , 可以通过该代码调用原有的 clock_gettime 函数 */
	//int ret = syscall(__NR_clock_gettime, id, ts);

	/* 
		此处实际上调用的是原有的 clock_gettime 函数 
		如果设备上没有实现系统调用 , 使用如下方法可以调用原有的 clock_gettime 函数 
	*/
	do_clock_gettime(id, ts);
	/* clock_gettime 函数执行完后 , 继续执行一些自己实现的部分 */
	/* 
		上面的代码是 hook 住的真实代码 
		我们可以在真实代码 前面 / 后面 执行一些自定义内容 
	*/

	if (id > CLOCK_MONOTONIC)return 0;
	if (clock_base[id] == 0.0) {
		clock_base[id] = ts->tv_sec * 1000000000.0 + ts->tv_nsec;
		clock_new[id] = clock_base[id];
	}
	else {
		//mutex.lock();
		double tick = ts->tv_sec * 1000000000.0 + ts->tv_nsec;
		//printf("tick : %f base: %f delta: %f\n", tick, clock_base[id], tick - clock_base[id]);
		if (tick > clock_base[id]) {
			clock_new[id] += (tick - clock_base[id]) * time_scale;
			ts->tv_sec = (time_t)(clock_new[id] / 1000000000.0);
			ts->tv_nsec = (long)(fmod(clock_new[id], 1000000000.0));
			clock_base[id] = tick;
		}
		//mutex.unlock();
	}
	return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39




三、返回特定结果



执行上述 dn_clock_gettime 函数的返回值 , 就是最终的返回结果 ;





四、相关完整代码



下面是相关代码 , 只是逆向代码中的函数拦截部分代码 :

调用代码 :

/* 这是 hook 标准库中的 clock_gettime 函数的入口方法 , 跳转到自定义的 dn_clock_gettime 方法中 */
hook_func((uint8_t*)clock_gettime, (uint8_t*)dn_clock_gettime, (uint8_t*)do_clock_gettime, 6);

  
 
  • 1
  • 2

函数拦截代码 :

/* hook 函数的完整流程 , 跳转指令 size 是 6 字节*/
/* 这是 hook 标准库中的 clock_gettime 函数的入口方法 , 跳转到自定义的 dn_clock_gettime 方法中 */
/* hook_func((uint8_t*)clock_gettime, (uint8_t*)dn_clock_gettime, (uint8_t*)do_clock_gettime, 6); */
void hook_func(uint8_t* pApi, uint8_t* pUser, uint8_t* pStub, size_t size)
{
	unsigned char code[64] = { 0 };
	/* 插桩前先保存函数的入口 6 字节数据 , 因为之后插桩 , 
	 * 会使用跳转代码 0xE9,0,0,0,0 覆盖函数入口内存
	 * 该函数最终还是要执行 , 需要拷贝一下 , 供之后实际函数调用使用 
	 */
	memcpy(code, pApi, size);

	/* 函数插桩 , pApi 是实际函数 , pUser 是插桩后跳转到的拦截函数 */
	write_code(pApi, pUser);

	/* 
	   执行 size + pStub 位置的指令时 , 直接跳转到 size + pApi 位置
	   如 : 执行 do_clock_gettime 方法的第 6 字节的指令时 , 跳转到 clock_gettime 函数的第 6 字节指令位置	
	   do_clock_gettime 的 0 ~ 6 字节指令是 clock_gettime 实际函数的前 6 字节 , 
	   之所以这么定义 , 是因为 clock_gettime 的前 6 个字节被覆盖为 跳转指令了

	   调用 do_clock_gettime 方法 , 就相当于调用了
	*/
	write_code(size + pStub, size + pApi);
	/* 将复制的 6 字节 代码存放到 pStub 函数中的 0 ~ 6 字节位置 */
	memcpy(pStub, code, size);

	/* 清空 CPU 高速缓存 */
#if !defined(__i386__)
	/* 在 arm 架构中必须刷新 CPU 高速缓存 , x86 不需要执行 */
	cacheflush((int)pApi, (int)pApi + size, ICACHE|DCACHE);
#else
	/* x86 下可以执行该系统调用 */
	syscall(0xF002, (int)pApi,(int)pApi + sizeE);
#endif
}

/*
 * unsigned char* pFunc
 * unsigned char* pStub
 * 上述两个参数分别是两个函数指针
 * 
 * 注意 : 写完之后要刷新 CPU 高速缓存 , 调用 cache_flush 系统调用函数
 */
int write_code(unsigned char* pFunc, unsigned char* pStub) {
	/* 获取 pFunc 函数入口 , 先获取该函数所在内存页地址 */
	void* pBase = (void*)(0xFFFFF000 & (int)pFunc);
	/* 修改整个内存页属性 , 修改为 可读 | 可写 | 可执行 , 
	 * 避免因为内存访问权限问题导致操作失败
	 * mprotect 函数只能对整个页内存的属性进行修改 
	 * 每个 内存页 大小都是 4KB 
	 */
	int ret = mprotect(pBase, 0x1000, PROT_WRITE | PROT_READ | PROT_EXEC);
	/* 修改内存页属性失败的情况 */
	if (ret == -1) {
		perror("mprotect:");
		return -1;
	}
#if defined(__i386__) // arm 情况处理
	/* E9 是 JMP 无条件跳转指令 , 后面 4 字节是跳转的地址 */
	unsigned char code[] = { 0xE9,0,0,0,0 };
	/* 计算 pStub 函数跳转地址 , 目标函数 pStub 地址 - 当前函数 pFunc 地址 - 5 
	 * 跳转指令 跳转的是 偏移量 , 不是绝对地址值
	 */
	*(unsigned*)(code + 1) = pStub - pFunc - 5;
	/* 将跳转代码拷贝到 pFunc 地址处 , 这是 pFunc 函数的入口地址 */
	memcpy(pFunc, code, sizeof(code));
#else // arm 情况处理
	/* B 无条件跳转指令 */
	unsigned char code[] = { 0x04,0xF0,0x1F,0xE5,0x00,0x00,0x00,0x00 };
	/* arm 的跳转是绝对地址跳转 , 传入 pStub 函数指针即可 */
	*(unsigned*)(code + 4) = (unsigned)pStub;
	/* 将机器码复制到函数开始位置 */
	memcpy(pFunc, code, sizeof(code));
#endif
	return 0;
}

/* 拦截函数 , 拦截 clock_gettime 函数后 , 跳转到此处 */
int dn_clock_gettime(clockid_t id, struct timespec* ts) {
	/*if (ts == NULL) {
		return -1;
	}*/

	/* 如果设备实现了系统调用 , 可以通过该代码调用原有的 clock_gettime 函数 */
	//int ret = syscall(__NR_clock_gettime, id, ts);

	/* 
		此处实际上调用的是原有的 clock_gettime 函数 
		如果设备上没有实现系统调用 , 使用如下方法可以调用原有的 clock_gettime 函数 
	*/
	do_clock_gettime(id, ts);
	/* clock_gettime 函数执行完后 , 继续执行一些自己实现的部分 */
	/* 
		上面的代码是 hook 住的真实代码 
		我们可以在真实代码 前面 / 后面 执行一些自定义内容 
	*/

	if (id > CLOCK_MONOTONIC)return 0;
	if (clock_base[id] == 0.0) {
		clock_base[id] = ts->tv_sec * 1000000000.0 + ts->tv_nsec;
		clock_new[id] = clock_base[id];
	}
	else {
		//mutex.lock();
		double tick = ts->tv_sec * 1000000000.0 + ts->tv_nsec;
		//printf("tick : %f base: %f delta: %f\n", tick, clock_base[id], tick - clock_base[id]);
		if (tick > clock_base[id]) {
			clock_new[id] += (tick - clock_base[id]) * time_scale;
			ts->tv_sec = (time_t)(clock_new[id] / 1000000000.0);
			ts->tv_nsec = (long)(fmod(clock_new[id], 1000000000.0));
			clock_base[id] = tick;
		}
		//mutex.unlock();
	}
	return 0;
}

int do_clock_gettime(clockid_t which_clock, struct timespec* tp)
{//未使用,内核中该函数实际的代码,反汇编libc.so得到
	/*
		这些指令都不重要 
		都会被覆盖调 , 写的这些指令主要是占坑用的 
		实际上调用的是 clock_gettime 函数 
		下面的汇编代码都会被覆盖为 跳转代码 , 跳转到 clock_gettime 函数 , 
		注意 , clock_gettime 函数 的前 6 字节的指令会被拷贝到函数入口 , 
		执行第 6 字节位置时 , 跳转到 clock_gettime 函数 的第 6 字节位置
	*/
	//return syscall(__NR_clock_gettime, which_clock, tp);
	__asm__ __volatile__("push %%ebx"::: "ebx");
	__asm__ __volatile__("push %%ecx\n"::: "ecx");
	__asm__ __volatile__("mov 12(%%esp),%%ebx"::: "ebx");
	__asm__ __volatile__("mov 16(%%esp),%%ecx"::: "ecx");
	__asm__ __volatile__("mov $0x109,%%eax"::: "eax");
	__asm__ __volatile__("int $0x80");
	__asm__ __volatile__("pop %%ecx":::"ecx");
	__asm__ __volatile__("pop %%ebx":::"ebx");
	__asm__ __volatile__("retn");
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139

文章来源: hanshuliang.blog.csdn.net,作者:韩曙亮,版权归原作者所有,如需转载,请联系作者。

原文链接:hanshuliang.blog.csdn.net/article/details/121247905

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。