前面兩篇
android hook 框架 libinject2 簡介、編譯、運行
android hook 框架 libinject2 如何實現so注入
實際運行並分析了 Android中的so注入(inject)和掛鈎(hook) - For both x86 and arm 這個博客給出了 libinject 改進版的代碼。
今天分析一下古河大神原始的 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 loop 3: @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, #0 beq 2f @dlsym ldr r1, _dlsym_param2_s ldr r3, _dlsym_addr_s blx r3 subs r3, r0, #0 beq 1f @call our function ldr r0, _inject_function_param_s blx r3 subs r0, r0, #0 beq 2f 1: @dlclose mov r0, r4 ldr r3, _dlclose_addr_s blx r3 2: @restore context ldr r1, _saved_cpsr_s msr cpsr_cf, r1 ldr sp, _saved_r0_pc_s ldmfd sp, {r0-pc} _dlopen_addr_s: .word 0x11111111 _dlopen_param1_s: .word 0x11111111 _dlopen_param2_s: .word 0x2 _dlsym_addr_s: .word 0x11111111 _dlsym_param2_s: .word 0x11111111 _dlclose_addr_s: .word 0x11111111 _inject_function_param_s: .word 0x11111111 _saved_cpsr_s: .word 0x11111111 _saved_r0_pc_s: .word 0x11111111 _inject_end_s: .space 0x400, 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; struct pt_regs regs, original_regs; extern uint32_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\n", target_pid );
if ( ptrace_attach( target_pid ) == -1 )
return EXIT_SUCCESS;
if ( ptrace_getregs( target_pid, ®s ) == -1 ) goto exit; /* save original registers */ memcpy( &original_regs, ®s, 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.\n" );
if ( ptrace_call( target_pid, (uint32_t)mmap_addr, parameters, 6, ®s ) == -1 )
goto exit;
if ( ptrace_getregs( target_pid, ®s ) == -1 ) goto exit; 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\n", _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\n", _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\n", _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\n", _inject_function_param_s );//第四步,計算要執行的 dlopen,dlsym函數及參數在目標進程內相對於前面mmap出來的地址的地址 DEBUG_PRINT( "[+] Remote shellcode address: %x\n", remote_code_ptr ); ptrace_writedata( target_pid, remote_code_ptr, local_code_ptr, 0x400 );//第五步: 將整塊shellcode代碼寫入目標進程的內存 memcpy( ®s, &original_regs, sizeof(regs) ); regs.ARM_sp = (long)remote_code_ptr; regs.ARM_pc = (long)remote_code_ptr; ptrace_setregs( target_pid, ®s ); // 第六步,修改目標進程的棧頂指針 sp 執行shellcode 的初始位置,且設置pc寄存器也指向這個位置 ptrace_detach( target_pid ); // 第七步,detach目標進程,目標進程接下去會開始執行shellcode // inject succeeded ret = 0; exit: return ret; }
我們看到,libinject 沒有將 original_regs 恢復到目標進程的ptrace 操作,其實shellcode里執行完hook函數后,最后會執行 @restore context 的代碼,這幾句就是恢復寄存器狀態的
到目前為止,一共分析了3份so注入代碼,前兩份為:
android hook 框架 libinject2 如何實現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 項目還提供了掛鈎框架的實現
