android inject so,android hook 框架 libinject 如何实现so注入

本文对比分析了libinject和libinject2在Android进程注入中的实现方式。libinject通过汇编构造shellcode一次性写入目标进程内存,而libinject2则是分步设置内存和寄存器。尽管两者原理相同,但libinject2的代码可读性更强。文章详细介绍了注入过程,包括目标进程的mmap、函数地址获取、参数设置和shellcode注入步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面两篇

今天分析一下古河大神原始的 libinject 的源码,libinject2 与 原始的 libinject 大部分代码是一致的,各种 ptrace 的封装函数基本照抄,但有一点很不一样,古河的 libinject 在执行so注入及执行注入的so内的hook函数时,是将一连串的函数构造成一块统一的 shellcode ,然后整块shellcode 写入目标进程 mmap 出来的一块地址,设置目标进程寄存器,一次性调用。 而 libinject2 是分开多次设置目标进程的内存和寄存器,并多次调用实现同样的效果。从代码可读性看,libinject2 更清晰易懂。这一篇文章只分析两者不同的部分。

首先,shellcode 代码写在一个单独的汇编文件里, shellcode.s  , 将需要在目标进程执行的 dlopen,dlsym,dlclose, 及hook函数都先用汇编写好,各个函数的真实地址和参数的真实值由 inject.c 里的函数动态指定,这份汇编会与 inject.c 编译后的汇编链接在一起

.global_dlopen_addr_s

.global_dlopen_param1_s

.global_dlopen_param2_s

.global_dlsym_addr_s

.global_dlsym_param2_s

.global_dlclose_addr_s

.global_inject_start_s

.global_inject_end_s

.global_inject_function_param_s

.global_saved_cpsr_s

.global_saved_r0_pc_s

.data

_inject_start_s:

@ debug loop3:

@sub r1, r1, #0@B 3b

@ dlopen

ldr r1, _dlopen_param2_s

ldr r0, _dlopen_param1_s

ldr r3, _dlopen_addr_s

blx r3

subs r4, r0, #0beq 2f

@dlsym

ldr r1, _dlsym_param2_s

ldr r3, _dlsym_addr_s

blx r3

subs r3, r0, #0beq 1f

@call our function

ldr r0, _inject_function_param_s

blx r3

subs r0, r0, #0beq 2f1:

@dlclose

mov r0, r4

ldr r3, _dlclose_addr_s

blx r32:

@restore context

ldr r1, _saved_cpsr_s

msr cpsr_cf, r1

ldr sp, _saved_r0_pc_s

ldmfd sp, {r0-pc}

_dlopen_addr_s:

.word0x11111111_dlopen_param1_s:

.word0x11111111_dlopen_param2_s:

.word0x2_dlsym_addr_s:

.word0x11111111_dlsym_param2_s:

.word0x11111111_dlclose_addr_s:

.word0x11111111_inject_function_param_s:

.word0x11111111_saved_cpsr_s:

.word0x11111111_saved_r0_pc_s:

.word0x11111111_inject_end_s:

.space0x400, 0.end

这里插入一下,怎么在android里使用这份代码呢,仍然是新建一个module,然后 Android.mk 如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE :=inject

LOCAL_SRC_FILES := ../inject.c ../shellcode.s

LOCAL_ARM_MODE :=arm

LOCAL_CFLAGS := -g

include $(BUILD_EXECUTABLE)

在Android.mk文件目录下执行 ndk-build 即可

libinject 的注入过程如下:

int inject_remote_process( pid_t target_pid, const char *library_path, const char *function_name, void *param, size_t param_size )

{int ret = -1;void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr;void *local_handle, *remote_handle, *dlhandle;

uint8_t*map_base;structpt_regs regs, original_regs;externuint32_t _dlopen_addr_s, _dlopen_param1_s, _dlopen_param2_s, _dlsym_addr_s,

_saved_cpsr_s, _saved_r0_pc_s; // shellcode.s 汇编代码定义的变量long parameters[10];

DEBUG_PRINT("[+] Injecting process: %d", target_pid );

if ( ptrace_attach( target_pid ) == -1 )

return EXIT_SUCCESS;

if ( ptrace_getregs( target_pid, &regs ) == -1)gotoexit;/*save original registers*/memcpy(&original_regs, &regs, sizeof(regs) ); // 第一步,attach目标进程,并保留注入前的寄存器状态

mmap_addr= get_remote_addr( target_pid, "/system/lib/libc.so", (void *)mmap );/*call mmap*/parameters[0] = 0; //addr

parameters[1] = 0x4000; //size

parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; //prot

parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; //flags

parameters[4] = 0; //fd

parameters[5] = 0; //offset

DEBUG_PRINT("[+] Calling mmap in target process.");

if ( ptrace_call( target_pid, (uint32_t)mmap_addr, parameters, 6, &regs ) == -1 )

goto exit;

if ( ptrace_getregs( target_pid, &regs ) == -1)gotoexit;

map_base= (uint8_t *)regs.ARM_r0; // 第二步,在目标进程执行 mmap 分配一块内存

dlopen_addr= get_remote_addr( target_pid, linker_path, (void *)dlopen );

dlsym_addr= get_remote_addr( target_pid, linker_path, (void *)dlsym );

dlclose_addr= get_remote_addr( target_pid, linker_path, (void *)dlclose );

remote_code_ptr= map_base + 0x3C00;

local_code_ptr= (uint8_t *)&_inject_start_s; // 本进程 shellcode 的起始地址

_dlopen_addr_s=(uint32_t)dlopen_addr;

_dlsym_addr_s=(uint32_t)dlsym_addr;

_dlclose_addr_s=(uint32_t)dlclose_addr; //第三步,获取目标进程 dlopen,dlsym,dlclose函数地址并赋值给汇编文件定义的变量

code_length= (uint32_t)&_inject_end_s - (uint32_t)&_inject_start_s;

dlopen_param1_ptr= local_code_ptr + code_length + 0x20;

dlsym_param2_ptr= dlopen_param1_ptr +MAX_PATH;

saved_r0_pc_ptr= dlsym_param2_ptr +MAX_PATH;

inject_param_ptr= saved_r0_pc_ptr +MAX_PATH; // 设置dlopen,dlsym等函数的参数相对于汇编代码起始地址的地址

strcpy( dlopen_param1_ptr, library_path );

_dlopen_param1_s = REMOTE_ADDR( dlopen_param1_ptr, local_code_ptr, remote_code_ptr );

/*dlopen parameter 1: library name*/DEBUG_PRINT("[+] _dlopen_param1_s: %x", _dlopen_param1_s );/*dlsym parameter 2: function name*/strcpy( dlsym_param2_ptr, function_name );

_dlsym_param2_s=REMOTE_ADDR( dlsym_param2_ptr, local_code_ptr, remote_code_ptr );

DEBUG_PRINT("[+] _dlsym_param2_s: %x", _dlsym_param2_s );/*saved cpsr*/_saved_cpsr_s=original_regs.ARM_cpsr;/*saved r0-pc*/memcpy( saved_r0_pc_ptr,&(original_regs.ARM_r0), 16 * 4 ); //r0 ~ r15

_saved_r0_pc_s =REMOTE_ADDR( saved_r0_pc_ptr, local_code_ptr, remote_code_ptr );

DEBUG_PRINT("[+] _saved_r0_pc_s: %x", _saved_r0_pc_s );/*Inject function parameter*/memcpy( inject_param_ptr, param, param_size );

_inject_function_param_s=REMOTE_ADDR( inject_param_ptr, local_code_ptr, remote_code_ptr );

DEBUG_PRINT("[+] _inject_function_param_s: %x", _inject_function_param_s );//第四步,计算要执行的 dlopen,dlsym函数及参数在目标进程内相对于前面mmap出来的地址的地址

DEBUG_PRINT("[+] Remote shellcode address: %x", remote_code_ptr );

ptrace_writedata( target_pid, remote_code_ptr, local_code_ptr,0x400);//第五步: 将整块shellcode代码写入目标进程的内存

memcpy(&regs, &original_regs, sizeof(regs) );

regs.ARM_sp= (long)remote_code_ptr;

regs.ARM_pc= (long)remote_code_ptr;

ptrace_setregs( target_pid,&regs ); // 第六步,修改目标进程的栈顶指针 sp 执行shellcode 的初始位置,且设置pc寄存器也指向这个位置

ptrace_detach( target_pid ); // 第七步,detach目标进程,目标进程接下去会开始执行shellcode//inject succeeded

ret = 0;

exit:returnret;

}

我们看到,libinject 没有将 original_regs 恢复到目标进程的ptrace 操作,其实shellcode里执行完hook函数后,最后会执行  @restore context   的代码,这几句就是恢复寄存器状态的

到目前为止,一共分析了3份so注入代码,前两份为:

对比一下,

首先,3份注入代码原理是一样的,都是利用ptrace系统调用,获取目标进程寄存器,在目标进程调用dlopen/dlsym等动态库函数实现在目标进程加载指定的so, 加载之后执行挂钩函数时,hijack 利用  __attribute__ ((constructor)) 函数属性实现自动执行,libinject/libinject2 还是通过寄存器ptrace操作执行函数。

其次,古河的libinject和adbi的hijack都是事先构造一片‘shellcode’,再块拷贝到目标进程地址空间(先操作目标进程寄存器调用mmap分配一块),然后再执行这块预先构造好的‘shellcode’,不同是libinject 是用汇编代码构造,hijack是写在一个数组里。libinject2在实现注入时,将上述shellcode分成多次的 ptrace 调用,代码可读性更高,逻辑更清晰

最后,libinject/libinject2只是提高了注入的实现,没有挂钩的实现。 adbi 项目还提供了挂钩框架的实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值