Android so注入( inject)和Hook(挂钩)的实现思路讨论
本文博客:http://blog.csdn.net/qq1084283172/article/details/54095995
前面的博客中分析一些Android的so注入和Hook目标函数的代码,它们的实现思路基本是一致的只是在一些细节实现的地方稍有不同。下面的文章中,将前面学习的别人的Android的so注入和目标函数的Hook的实现方法,进行整理一下,对Android的so注入和目标函数的Hook的代码实现的方法进行思考和分析。
Androd so注入和函数Hook(基于got表)的步骤:
1.ptrace附加目标pid进程;
2.在目标pid进程中,查找内存空间(用于存放被注入的so文件的路径和so中被调用的函数的名称或者shellcode);
3.调用目标pid进程中的dlopen、dlsym等函数,用于加载so文件实现Android so的注入和函数的Hook;
4.释放附加的目标pid进程和卸载注入的so文件。
一、在目标pid进程中查找内存空间的方法
方法一:获取目标pid进程加载的"/system/lib/libc.so"动态库文件中mmap函数的调用地址,然后远程调用目标pid进程中的mmap函数,在目pid进程中申请内存空间用于保存被注入加载的so库文件的路径的名称和被加载的so库文件中被调用的导出函数,主要为后面调用dlopen加载so库文件和调用dlsym获取被注入的so文件的中导出函数做准备即为后面进行Android的so注入和函数的Hook做准备(见作者ariesjzj提供的Android的so注入和函数Hook的思路)或者向目标pid进程中写入shellcode操作。
// 获取目标pid进程中"/system/lib/libc.so"中的mmap函数的调用地址
mmap_addr = get_remote_addr(target_pid, libc_path, (void *)mmap);
DEBUG_PRINT("[+] Remote mmap address: %x\n", mmap_addr);
/* call mmap 准备调用目标pid进程中的mmap函数需要的参数 */
parameters[0] = 0; // addr
parameters[1] = 0x4000; // size--在目标pid进程中申请的内存空间的大小
parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; // prot
parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; // flags
parameters[4] = 0; //fd
parameters[5] = 0; //offset
// 调用目标pid进程中的mmap函数,在目标pid进程的内存空间中申请内存空间
if (ptrace_call_wrapper(target_pid, "mmap", mmap_addr, parameters, 6, ®s) == -1)
// 调用失败,跳转
goto exit;
// 获取调用目标pid进程的mmap函数完成的函数返回值即申请的内存空间地址
map_base = ptrace_retval(®s);
// 获取目标pid进程中dlopen函数的调用地址
dlopen_addr = get_remote_addr( target_pid, linker_path, (void *)dlopen );
// 获取目标pid进程中dlsym函数的调用地址
dlsym_addr = get_remote_addr( target_pid, linker_path, (void *)dlsym );
方法二:遍历目标pid进程的/proc/pid/maps文件,找到空闲的内存空间,用于存放调用dlopen、dlsym等函数的参数或者执行的shellcode代码。
// 在目标pid进程的"/system/lib/libc.so"的内存范围内(从内存结束地址往回的方向)查找内存空间
void* find_space_in_maps(int pid, int size) {
char statline[1024];
FILE * fp;
uint32_t* addr = (uint32_t*) 0x40008000;
char *address, *proms, *ptr;
const char* tname = "/system/lib/libc.so";
const char* tproms = "r-xp";
// 获取字符串"/system/lib/libc.so"的长度
int tnaem_size = strlen(tname);
// 获取字符串"r-xp"的长度
int tproms_size = strlen(tproms);
// 内存以4字节对齐
size = ((size / 4) + 1) * 4;
// 格式化得到字符串"/proc/pid/maps"
sprintf(statline, "/proc/%d/maps", pid);
// 打开文件"/proc/pid/maps"
fp = fopen(statline, "r");
if (fp == 0)
return 0;
// 读取文件"/proc/pid/maps"中内容(每次读一行)
while (fgets(statline, sizeof(statline), fp)) {
// 分割字符串
ptr = statline;
// 得到内存模块的起始和结束地址
address = nexttok(&ptr); // skip address
// 内存模块的属性
proms = nexttok(&ptr); // skip proms
nexttok(&ptr); // skip offset
nexttok(&ptr); // skip dev
nexttok(&ptr); // skip inode
// ptr中最终保存的是加载的内存模块的路径字符串
while (*ptr != '\0') {
if (*ptr == ' ')
ptr++;
else
break;
}
// 查找目标so模块
if (ptr && proms && address) {
// 判断是否是"r-xp"属性的模块
if (strncmp(tproms, proms, tproms_size) == 0) {
// 判断是否是"/system/lib/libc.so"模块
if (strncmp(tname, ptr, tnaem_size) == 0) {
// address like afe00000-afe3a000
if (strlen(address) == 17) {
// 获取内存加载模块/system/lib/libc.so的内存范围的结束地址(方便后面查找内存空间)
addr = (uint32_t*) strtoul(address + 9, NULL, 16);
// 在目标pid进程的/system/lib/libc.so的内存范围内查找到size大小内存空间
addr -= size;
printf("proms=%s address=%s name=%s", proms, address, ptr);
break;
}
}
}
}
}
// 关闭文件
fclose(fp);
// 返回在目标进程中查找到的内存空间的地址
return (void*) addr;
}
二、在目标pid进程中加载so实现Android的so注入和函数Hook的方法
方法一:初期Android so的注入版本(古河)的Android的so的注入和函数Hook是由arm汇编的shellcode实现,代码的移植性不好而且不易阅读,具体的实现就是:将dlopen、dlsym等函数调用需要的函数参数以及执行so加载和执行Hook目标pid进程中的函数的shellcode代码指令写入到目标pid进程内存中,然后修改目标pid进程的PC指令计数寄存器跳转到目标pid进程内存的shellcode处执行加载so和Hook目标pid进程的函数(基于got表)。
.global _dlopen_addr_s @全局变量_dlopen_addr_s保存dlopen函数的调用地址
.global _dlopen_param1_s @全局变量_dlopen_param1_s保存函数dlopen的第一个参数-加载库文件的路径
.global _dlopen_param2_s @全局变量_dlopen_param2_s保存函数dlopen的第二个参数-库文件的打开模式
.global _dlsym_addr_s @全局变量_dlsym_addr_s保存函数dlsym的调用地址
.global _dlsym_param2_s @全局变量_dlsym_param2_s保存函数dlsym的第二个参数-获取调用地址的函数的名称
.global _dlclose_addr_s @全局变量_dlclose_addr_s保存函数dlclose的调用地址
.global _inject_start_s @全局变量_inject_start_s保存注入代码的起始地址
.global _inject_end_s @全局变量_inject_end_s保存注入代码的结束地址
.global _inject_function_param_s @全局变量_inject_function_param_s保存Hook函数的参数
.global _saved_cpsr_s @全局变量_saved_cpsr_s保存当前程序状态寄存器CPSR的值
.global _saved_r0_pc_s @全局变量_saved_r0_pc_s保存寄存器环境R0-R15(PC)的值起始地址
@定义数据段.data
.data
@注入代码的起始地址
_inject_start_s:
@ debug loop
3:
@sub r1, r1, #0
@B 3b
@调用dlopen函数
ldr r1, _dlopen_param2_s @库文件的打开模式
ldr r0, _dlopen_param1_s @加载库文件的路径字符串即Hook函数所在的模块
ldr r3, _dlopen_addr_s @dlopen函数的调用地址
blx r3 @调用函数dlopen加载并打开动态库文件
subs r4, r0, #0 @判断函数返回值r0-是否打开动态库文件成功
beq 2f @打开动态库文件失败跳转标签2的地方执行
@r0保存加载库的引用pHandle
@调用dlsym函数
ldr r1, _dlsym_param2_s @获取调用的地址的函数名称字符串
ldr r3, _dlsym_addr_s @dlsym函数的调用地址
blx r3 @调用函数dlsym获取目标函数的调用地址
subs r3, r0, #0 @判断函数的返回值r0
beq 1f @不成功跳转到标签1的地方执行
@r3保存获取到的函数的调用地址
@调用Hook_Api函数
ldr r0, _inject_function_param_s @给Hook函数传入参数r0
blx r3 @调用Hook函数Hook远程目标进程的某系统调用函数
subs r0, r0, #0 @判断函数的返回值r0
beq 2f @r0=0跳转到标签2的地方执行 ??
1:
@调用dlclose函数
mov r0, r4 @参数r0动态库的应用
ldr r3, _dlclose_addr_s @赋值r3为dlclose函数的调用地址
blx r3 @调用dlclose函数关闭库文件的引用pHandle
2:
@恢复目标进程的原来状态
ldr r1, _saved_cpsr_s
msr cpsr_cf, r1 @恢复目标进程寄存器CPSR的值
ldr sp, _saved_r0_pc_s
ldmfd sp, {r0-pc} @恢复目标进程寄存器环境R0-R15(PC)的值且sp不改变
_dlopen_addr_s:
.word 0x11111111 @初始化word型全局变量_dlopen_addr_s
_dlopen_param1_s:
.word 0x11111111 @初始化word型全局变量_dlopen_param1_s
_dlopen_param2_s: @初始化word型全局变量_dlopen_param2_s = 0x2
.word 0x2
_dlsym_addr_s:
.word 0x11111111 @初始化word型全局变量_dlsym_addr_s
_dlsym_param2_s:
.word 0x11111111 @初始化word型全局变量_dlsym_param2_s
_dlclose_addr_s:
.word 0x11111111 @初始化word型全局变量_dlclose_addr_s
_inject_function_param_s:
.word 0x11111111 @初始化word型全局变量_inject_function_param_s
_saved_cpsr_s:
.word 0x11111111 @初始化word型全局变量_saved_cpsr_s
_saved_r0_pc_s:
.word 0x11111111 @初始化word型全局变量_saved_r0_pc_s
@注入代码的结束地址
_inject_end_s:
.space 0x400, 0 @申请的代码段内存空间大小
@数据段.data的结束位置
.end
方法二:其实呢,注入so库文件到目标pid进程以及Hook目标pid进程中got表的函数的实现,不需要用注入shellcode到目标pid进程中的方式来解决。通过获取目标pid进程中dlopen、dlsym等函数的调用地址,远程调用目标pid进程的dlpoen函数可以实现so库文件的注入,远程调用目标pid进程中dlsym函数获取加载的so库文件中的导出函数,调用该导出函数对目标pid进程中基于got表的目标函数进行Hook即可。
// 调用目标pid进程中的mmap函数,在目标pid进程的内存空间中申请内存空间
if (ptrace_call_wrapper(target_pid, "mmap", mmap_addr, parameters, 6, ®s) == -1)
// 调用失败,跳转
goto exit;
// 获取调用目标pid进程的mmap函数完成的函数返回值即申请的内存空间地址
map_base = ptrace_retval(®s);
// 获取目标pid进程中dlopen函数的调用地址
dlopen_addr = get_remote_addr( target_pid, linker_path, (void *)dlopen );
// 获取目标pid进程中dlsym函数的调用地址
dlsym_addr = get_remote_addr( target_pid, linker_path, (void *)dlsym );
// 获取目标pid进程中dlclose函数的调用地址
dlclose_addr = get_remote_addr( target_pid, linker_path, (void *)dlclose );
// 获取目标pid进程中dlerror函数的调用地址
dlerror_addr = get_remote_addr( target_pid, linker_path, (void *)dlerror );
// 打印获取到目标pid进程中dlopen等函数的地址
DEBUG_PRINT("[+] Get imports: dlopen: %x, dlsym: %x, dlclose: %x, dlerror: %x\n",
dlopen_addr, dlsym_addr, dlclose_addr, dlerror_addr);
// 打印即将被注入的so库文件的文件路径
printf("library path = %s\n", library_path);
// 将要被注入到目标pid进程中的so库文件的路径字符串library_path写入到前面mmap申请的内存空间中
ptrace_writedata(target_pid, map_base, library_path, strlen(library_path) + 1);
// 设置调用dlopen函数的函数参数
parameters[0] = map_base; // library_path将被加载到目标pid进程中的so文件路径
parameters[1] = RTLD_NOW| RTLD_GLOBAL;
// 调用目标pid进程中的dlopen函数,加载library_path路径的so文件到目标pid进程中,实现so注入
if (ptrace_call_wrapper(target_pid, "dlopen", dlopen_addr, parameters, 2, ®s) == -1)
// 失败进行跳转
goto exit;
// 获取dlopen函数调用后的返回值即library_path指定的so文件在目标pid进程中的加载基址
void * sohandle = ptrace_retval(®s);
// 设置map_base中保存library_path指定的so文件的导出函数function_name字符串的内存偏移
#define FUNCTION_NAME_ADDR_OFFSET 0x100
// 将library_path指定的so文件的导出函数function_name的函数名称字符串写入到目标pid进程中前面mmap申请的内存空间offset=0x100的位置
ptrace_writedata(target_pid, map_base + FUNCTION_NAME_ADDR_OFFSET, function_name, strlen(function_name) + 1);
// 设置dlsym函数调用的函数参数
parameters[0] = sohandle; // so基址模块句柄
parameters[1] = map_base + FUNCTION_NAME_ADDR_OFFSET; // 将被获取的导出函数的调用地址
// 在目标pid进程中调用dlsym函数,获取上面加载的so文件中的导出函数function_name的调用地址
if (ptrace_call_wrapper(target_pid, "dlsym", dlsym_addr, parameters, 2, ®s) == -1)
// 失败,跳转
goto exit;
// 获取调用dlsym函数后,返回的导出函数function_name的调用地址
void * hook_entry_addr = ptrace_retval(®s);
// 打印获取到的导出函数function_name的调用地址
DEBUG_PRINT("hook_entry_addr = %p\n", hook_entry_addr);
// 设置map_base中保存调用导出函数function_name需要的函数参数的内存偏移
#define FUNCTION_PARAM_ADDR_OFFSET 0x200
// 将调用hook_entry_addr函数需要的函数参数保存到前面在目标pid进程中mmap申请的内存空间offset=0x200的位置
ptrace_writedata(target_pid, map_base + FUNCTION_PARAM_ADDR_OFFSET, param, strlen(param) + 1);
// 设置调用目标pid进程中hook_entry函数的函数参数
parameters[0] = map_base + FUNCTION_PARAM_ADDR_OFFSET;
// 调用注入到目标pid进程中的so库的导出函数hook_entry实现我们自定义的代码,可以是Hook目标pid进程的函数
if (ptrace_call_wrapper(target_pid, "hook_entry", hook_entry_addr, parameters, 1, ®s) == -1)
goto exit;
三、在目标pid进程中注入so以后进行一次函数调用,实现Hook目标函数
方法一:前面的步骤中已经将so库文件注入到目标pid进程中了,只需要远程调用目标pid进程中的dlsym函数获取执行函数Hook的导出函数,然后远程调用目标pid进程中该导出函数即可实现Hook目标pid进程中基于got表的目标函数。
// 设置map_base中保存library_path指定的so文件的导出函数function_name字符串的内存偏移
#define FUNCTION_NAME_ADDR_OFFSET 0x100
// 将library_path指定的so文件的导出函数function_name的函数名称字符串写入到目标pid进程中前面mmap申请的内存空间offset=0x100的位置
ptrace_writedata(target_pid, map_base + FUNCTION_NAME_ADDR_OFFSET, function_name, strlen(function_name) + 1);
// 设置dlsym函数调用的函数参数
parameters[0] = sohandle; // so基址模块句柄
parameters[1] = map_base + FUNCTION_NAME_ADDR_OFFSET; // 将被获取的导出函数的调用地址
// 在目标pid进程中调用dlsym函数,获取上面加载的so文件中的导出函数function_name的调用地址
if (ptrace_call_wrapper(target_pid, "dlsym", dlsym_addr, parameters, 2, ®s) == -1)
// 失败,跳转
goto exit;
// 获取调用dlsym函数后,返回的导出函数function_name的调用地址
void * hook_entry_addr = ptrace_retval(®s);
// 打印获取到的导出函数function_name的调用地址
DEBUG_PRINT("hook_entry_addr = %p\n", hook_entry_addr);
// 设置map_base中保存调用导出函数function_name需要的函数参数的内存偏移
#define FUNCTION_PARAM_ADDR_OFFSET 0x200
// 将调用hook_entry_addr函数需要的函数参数保存到前面在目标pid进程中mmap申请的内存空间offset=0x200的位置
ptrace_writedata(target_pid, map_base + FUNCTION_PARAM_ADDR_OFFSET, param, strlen(param) + 1);
// 设置调用目标pid进程中hook_entry函数的函数参数
parameters[0] = map_base + FUNCTION_PARAM_ADDR_OFFSET;
// 调用注入到目标pid进程中的so库的导出函数hook_entry实现我们自定义的代码,可以是Hook目标pid进程的函数
if (ptrace_call_wrapper(target_pid, "hook_entry", hook_entry_addr, parameters, 1, ®s) == -1)
goto exit;
方法二:不需要调用目标pid进程中dlsym函数获取注入so文件的导出函数,来实现Hook目标pid进程的目标函数。在so库文件加载的时候,会首先执行.init段的构造函数,该构造函数的定义方法为 void __attribute__((constructor)) x_init(void),因此当我们向目标pid进程注入so库文件的时候,执行该x_init函数,可以实现Hook目标pid进程的函数的目的,该x_init函数唯一的不足就是不能传递函数参数。
共享库文件的高级特性:
dlopen函数的调用流程图:
因此,被注入的so库文件的源码文件中实现Hook目标函数的导出函数 hook_entry 的定义为:
void __attribute__((constructor)) hook_entry(void) {
// 执行Hook目标函数的操作
}
方法三:在注入到目标pid进程中的so库文件的源码文件中定义全局变量,在so库文件加载到目标pid进程的内存中,全局变量的初始化的时候,有一次执行Hook目标pid进程的基于got表的目标函数的机会。也就是在注入的so的源码文件中定义一个全局变量(对象),当全局变量初始化时(全局对象变量调用构造函数的时候),有一次执行Hook目标函数的机会即使用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);
}
} object;
参考链接:
《UNIX系统编程手册 下》.((德)Michael Kerrisk )
http://blog.csdn.net/l173864930/article/details/38456313
http://bbs.pediy.com/showthread.php?p=1299179
http://bbs.pediy.com/showthread.php?t=212035
四、在目标pid进程中Hook目标函数的实现方法
方法一:通过解析目标pid进程内存中的需要被Hook的目标so库文件,找到该目标so文件的.got表,然后遍历查找到需要被Hook的目标函数的调用地址进行替换,替换为我们自定义的函数的地址。
// Hook库/system/lib/libsurfaceflinger.so中的eglSwapBuffers函数
#define LIBSF_PATH "/system/lib/libsurfaceflinger.so"
int hook_eglSwapBuffers()
{
// 保存被Hook的目标函数的原始调用地址
old_eglSwapBuffers = eglSwapBuffers;
LOGD("Orig eglSwapBuffers = %p\n", old_eglSwapBuffers);
// 获取目标pid进程中"/system/lib/libsurfaceflinger.so"模块的加载地址
void * base_addr = get_module_base(getpid(), LIBSF_PATH);
LOGD("libsurfaceflinger.so address = %p\n", base_addr);
int fd;
// 打开内存模块文件"/system/lib/libsurfaceflinger.so"
fd = open(LIBSF_PATH, O_RDONLY);
if (-1 == fd) {
LOGD("error\n");
return -1;
}
// elf32文件的文件头结构体Elf32_Ehdr
Elf32_Ehdr ehdr;
// 读取elf32格式的文件"/system/lib/libsurfaceflinger.so"的文件头信息
read(fd, &ehdr, sizeof(Elf32_Ehdr));
// elf32文件中区段表信息结构的文件偏移
unsigned long shdr_addr = ehdr.e_shoff;
// elf32文件中区段表信息结构的数量
int shnum = ehdr.e_shnum;
// elf32文件中每个区段表信息结构中的单个信息结构的大小(描述每个区段的信息的结构体的大小)
int shent_size = ehdr.e_shentsize;
// elf32文件区段表中每个区段的名称存放的字符串区段,在区段表中的序号index
unsigned long stridx = ehdr.e_shstrndx;
// elf32文件中区段表的每个单元信息结构体(描述每个区段的信息的结构体)
Elf32_Shdr shdr;
// elf32文件中定位到存放每个区段名称的字符串表的信息结构体位置.shstrtab
lseek(fd, shdr_addr + stridx * shent_size, SEEK_SET);
// 读取elf32文件中的描述每个区段的信息的结构体(这里是保存elf32文件的每个区段的名称字符串的)
read(fd, &shdr, shent_size);
// 为保存elf32文件的所有的区段的名称字符串申请内存空间
char * string_table = (char *)malloc(shdr.sh_size);
// 定位到具体存放elf32文件的所有的区段的名称字符串的文件偏移处
lseek(fd, shdr.sh_offset, SEEK_SET);
// 从elf32内存文件中读取所有的区段的名称字符串到申请的内存空间中
read(fd, string_table, shdr.sh_size);
// 重新设置elf32文件的文件偏移为区段信息结构的起始文件偏移处
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;
// 循环遍历elf32文件的区段表(描述每个区段的信息的结构体)
for (i = 0; i < shnum; i++) {
// 依次读取区段表中每个描述区段的信息的结构体
read(fd, &shdr, shent_size);
// 判断当前区段描述结构体描述的区段是否是SHT_PROGBITS类型
if (shdr.sh_type == SHT_PROGBITS) {
// 获取区段的名称字符串在保存所有区段的名称字符串段.shstrtab中的序号
int name_idx = shdr.sh_name;
// 判断区段的名称是否为".got.plt"或者".got"
if (strcmp(&(string_table[name_idx]), ".got.plt") == 0
|| strcmp(&(string_table[name_idx]), ".got") == 0) {
// 获取区段".got"或者".got.plt"在内存中实际数据存放地址
out_addr = base_addr + shdr.sh_addr;
// 获取区段".got"或者".got.plt"的大小
out_size = shdr.sh_size;
LOGD("out_addr = %lx, out_size = %lx\n", out_addr, out_size);
// 遍历区段".got"或者".got.plt"获取保存的全局的函数调用地址
for (i = 0; i < out_size; i += 4) {
// 获取区段".got"或者".got.plt"中的单个函数的调用地址
got_item = *(uint32_t *)(out_addr + i);
// 判断区段".got"或者".got.plt"中函数调用地址是否是将要被Hook的目标函数地址
if (got_item == old_eglSwapBuffers) {
LOGD("Found eglSwapBuffers in got\n");
// 查找到要被Hook的目标函数的地址
got_found = 1;
// 获取当前内存分页的大小
uint32_t page_size = getpagesize();
// 获取内存分页的起始地址(需要内存对齐)
uint32_t entry_page_start = (out_addr + i) & (~(page_size - 1));
LOGD("entry_page_start = %lx, entry_page_start = %lx\n", entry_page_start, page_size);
// 修改内存属性为可读可写可执行
if (mprotect((uint32_t *)entry_page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) {
LOGD("mprotect false\n");
return -1;
}
// Hook目标函数之前
LOGD("%s, old_eglSwapBuffers = %lx, new_eglSwapBuffers = %lx\n", "befor hook function", got_item, new_eglSwapBuffers);
// Hook函数为我们自己定义的函数
//*(uint32_t *)(out_addr + i) = new_eglSwapBuffers; // Hook函数的作用,等价的
got_item = new_eglSwapBuffers;
// Hook目标函数之后
LOGD("%s, old_eglSwapBuffers = %lx, new_eglSwapBuffers = %lx\n", "after hook function", got_item, new_eglSwapBuffers);
// 恢复内存属性为可读可执行
if (mprotect((uint32_t *)entry_page_start, page_size, PROT_READ | PROT_EXEC) == -1) {
LOGD("mprotect false\n");
return -1;
}
break;
// 此时,目标函数的调用地址已经被Hook了
} else if (got_item == new_eglSwapBuffers) {
LOGD("Already hooked\n");
break;
}
}
// Hook目标函数成功,跳出循环
if (got_found)
break;
}
}
}
free(string_table);
close(fd);
}
方法二:获取将被Hook的so库文件内存加载后产生的soinfo结构体指针,而该结构体保存着该目标so文件加载到内存中的各种信息,然后也是遍历目标so文件的got表找到将被Hook的目标函数的调用地址进行挂钩Hook替换为我自定义的函数。
// 需要添加的头文件
#include <linux/elf.h>
#include <dlfcn.h>
//#include "linker.h" // Android系统源码的私有头文件
// 这种方法虽然是可行的,但是在soinfo结构体的使用时,暂时遇到了问题
// Hook目标函数
int hook_api() {
// 保存被Hook的目标函数的原始调用地址
old_eglSwapBuffers = eglSwapBuffers;
LOGD("Orig eglSwapBuffers = %p\n", old_eglSwapBuffers);
// 使用打开动态库的方式得到动态库的soinfo结构
soinfo* si = (soinfo*)dlopen(LIBSF_PATH, RTLD_NOW);
if(si == NULL || si->strtab == NULL || si->plt_rel == NULL) {
return -1;
}
for (uint32_t i = 0; i < si->plt_rel_count; i++) {
// 查找重定位表中eglSwapBuffers所在的项
if(strcmp(si->symtab[ELF32_R_SYM(si->plt_rel[i].r_info)].st_name + si->strtab, "eglSwapBuffers") == 0) {
// 计算对应的GOT表项的地址
uint32_t* got = (uint32_t*)(si->base + si->plt_rel[i].r_offset);
if(*(got) != new_eglSwapBuffers) {
// 获取当前内存分页的大小
uint32_t pagesize = sysconf(_SC_PAGE_SIZE);
// 获取当前内存分页的起始地址
void* start = (void*)(((uint32_t)got) / pagesize*pagesize);
// 修改当前分页的内存属性为可读可写可执行
if (mprotect(start, pagesize*2, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) {
return -1;
}
// 替换目标函数为我们自定义的函数
*(got) = new_eglSwapBuffers;
// 修改当前分页的内存属性为可读可执行
if (mprotect(start, pagesize*2, PROT_READ | PROT_EXEC) == -1) {
return -1;
}
}
return 0;
}
}
}
感谢连接:
http://blog.sina.com.cn/s/blog_dae890d10101f00d.html
http://blog.csdn.net/qq1084283172/article/details/46859931
http://anyun.org/a/jishuguanzhu/wangluoanquan/loudongfenxiang/WooYu/2016/0923/6323.html
http://www.paigu.com/a/1822/17648298.html
http://blog.csdn.net/l173864930/article/details/38456313
http://www.cnblogs.com/lanrenxinxin/p/4965149.html
Android so注入( inject)和Hook(挂钩)的实现思路讨论的更多相关文章
- Android so注入(inject)和Hook技术学习(三)——Got表hook之导出表hook
前文介绍了导入表hook,现在来说下导出表的hook.导出表的hook的流程如下.1.获取动态库基值 void* get_module_base(pid_t pid, const char* modu ...
- Android so注入(inject)和Hook技术学习(二)——Got表hook之导入表hook
全局符号表(GOT表)hook实际是通过解析SO文件,将待hook函数在got表的地址替换为自己函数的入口地址,这样目标进程每次调用待hook函数时,实际上是执行了我们自己的函数. GOT表其实包含了 ...
- Android so注入(inject)和Hook技术学习(一)
以前对Android so的注入只是通过现有的框架,并没有去研究so注入原理,趁现在有时间正好拿出来研究一下. 首先来看注入流程.Android so的注入流程如下: attach到远程进程 -> ...
- Android的so注入( inject)和函数Hook(基于got表) - 支持arm和x86
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53942648 前面深入学习了古河的Libinject注入Android进程,下面来 ...
- Android 依赖注入 ButterKnife 基本使用
ButterKnife 是一个快速 Android View 注入框架,开发者是Jake Wharton,简单的来说,ButterKnife 是用注解的方式替代findViewById和setXXXL ...
- Android 依赖注入: Dagger 2 实例解说(一)
本文原创,转载请注明出处:http://blog.csdn.net/zjbpku [Duplicated] link to Dagger on Android - Dagger2具体解释 关于D ...
- [Android]依赖注入框架google的dagger
分享一下Android依赖注入框架--Google升级版Dagger2框架 Google的Dagger2是对上一版squareup的Dagger改版,话不多说直接上项目代码. Dagger2源码 Da ...
- [Android]依赖注入框架squareup的dagger
分享一下Android依赖注入框架--Dagger使用 Dagger源码 Dagger1-Demo 希望能给大家的开发带来帮助.
- Android进程注入
全部代码在这里下载:http://download.csdn.net/detail/a345017062/8133239 里面有两个exe.inj是一个C层进程注入的样例.inj_dalvik是我写的 ...
随机推荐
- 虚拟机测试cobbler,网络安装加载最后出现 dracut:/#
1.cobbler的几个重要概念: distro:发行版系统容,我理解为镜像来源,提供了kernel 和 initrd 文件以及repo源 profile:kickstart文件,用于定制系统,定制安 ...
- CCF(引水入城:60分):最大流+ISAP算法
引水入城 201703-5 这从题目分析来看很像最大流的问题,只需要增加一个超级源点和一个超级汇点就可以按照题意连边再跑最大流算法. 因为数据量太大了,肯定会超时.但是没有想到可行的解决方法. #in ...
- 【转载】Java虚拟机类加载机制与案例分析
出处:https://blog.csdn.net/u013256816/article/details/50829596 https://blog.csdn.net/u013256816/articl ...
- C语言中指针和多维数组
指针和多维数组 数组名是特殊的指针 数组是一个特殊的指针,多维数组也是更为复杂的数组,它们的关系是什么样的呢? 我们通过一个简单的例子来比较形象的了解指针和多维数组: int a[2][3]; 这是一 ...
- Hi3559AV100 NNIE开发(4)mobilefacenet.cfg参数配置挖坑解决与SVP_NNIE_Cnn实现分析
前面随笔给出了NNIE开发的基本知识,下面几篇随笔将着重于Mobilefacenet NNIE开发,实现mobilefacenet.wk的chip版本,并在Hi3559AV100上实现mobilefa ...
- [省选联考 2020 A/B 卷] 冰火战士
一.题目 点此看题 二.解法 其实这道题也不是特别难吧 \(......\) 但树状数组上二分是我第一次见. 我们把冰人和火人都按温度排序,那么考虑一个分界线 \(x\) ,问题就是求冰数组 \(x\ ...
- 【H264】视频编码发展简史
一.常见视频编码格式 编码格式有很多,如下图: 目前比较常用的编码有: H26x系列:由ITU(国际电传视讯联盟)主导,侧重网络传输 MPEG系列:由ISO(国际标准组织机构)下属的MPEG(运动图象 ...
- 【数据库】Redis(2)--Redis的常用数据类型及命令
1.Redis主要数据类型分类 Redis中存储数据常用的数据类型主要有五种:String.List.Set.Sorted Set.Hash,这五种数据结构在Redis中存储数据的命令掌握对于我们后期 ...
- Linux—用户新建目录和文件的默认权限设置:umask详解
关注微信公众号:CodingTechWork,一起学习进步. 引言 我们有没有思考过一个问题,在登录Linux系统后,我们创建的目录或者文件的权限,为什么每次创建都是统一的?我们做以下实验:新建一 ...
- TypeError: 'str' object does not support item assignment Python常见错误
1.string是一种不可变的数据类型 2.尝试使用 range()创建整数列 有时你想要得到一个有序的整数列表,所以 range() 看上去是生成此列表的不错方式. 需要记住 range() 返回的 ...