溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

代碼實(shí)例分析android中inline hook

發(fā)布時(shí)間:2020-09-09 15:13:25 來(lái)源:腳本之家 閱讀:202 作者:mmmmar 欄目:移動(dòng)開(kāi)發(fā)

以下內(nèi)容通過(guò)1、實(shí)現(xiàn)目標(biāo)注入程序,2、實(shí)現(xiàn)主程序,3、實(shí)現(xiàn)注入函數(shù),4、thumb指令集實(shí)現(xiàn)等4個(gè)方面詳細(xì)分析了android中inline hook的用法,以下是全部?jī)?nèi)容:

最近終于沉下心來(lái)對(duì)著書(shū)把hook跟注入方面的代碼敲了一遍,打算寫(xiě)幾個(gè)博客把它們記錄下來(lái)。

第一次介紹一下我感覺(jué)難度最大的inline hook,實(shí)現(xiàn)代碼參考了騰訊GAD的游戲安全入門(mén)。

inline hook的大致流程如下:

代碼實(shí)例分析android中inline hook

首先將目標(biāo)指令替換為跳轉(zhuǎn)指令,跳轉(zhuǎn)地址為一段我們自己編寫(xiě)的匯編代碼,這段匯編代碼先是執(zhí)行用戶指定的代碼,如修改寄存器的值,然后執(zhí)行被替換掉的原指令2,最后再跳轉(zhuǎn)回原指令3處,恢復(fù)程序的正常運(yùn)行。

為了避開(kāi)注入過(guò)程,我們通過(guò)hook自己進(jìn)程加載的動(dòng)態(tài)連接庫(kù)進(jìn)行演示。

1、實(shí)現(xiàn)目標(biāo)注入程序

我們將這個(gè)程序編譯為動(dòng)態(tài)連接庫(kù),然后在主程序中加載,作為hook的目標(biāo)。

target.h
#ifndef TARGET_H_INCLUDED #define TARGET_H_INCLUDED void target_foo(); #endif // TARGET_H_INCLUDED target.c #include "target.h" #include <stdlib.h> #include <stdio.h> #include <unistd.h> void target_foo() { int a = 3; int b = 2; while(a--) { sleep(2); b = a * b; printf("[INFO] b is %d\n", b); } b = b + 2; b = b - 1; printf("[INFO] finally, b is %d\n", b); }
Android.mk
include $(CLEAR_VARS) LOCAL_ARM_MODE := arm LOCAL_MODULE := target LOCAL_CFLAGS += -pie -fPIE -std=c11 LOCAL_LDFLAGS += -pie -fPIE -shared -llog APP_ABI := armeabi-v7a LOCAL_SRC_FILES := target.c include $(BUILD_SHARED_LIBRARY)

注意Android.mkLOCAL_ARM_MODE := arm代表編譯時(shí)使用4字節(jié)的arm指令集,而不是2字節(jié)的thumb指令集。

2、實(shí)現(xiàn)主程序

在主程序中我們首先加載之前編寫(xiě)的動(dòng)態(tài)鏈接庫(kù),進(jìn)行hook之后再對(duì)其中的函數(shù)target_foo進(jìn)行調(diào)用。

main.c
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <stdbool.h>
#include "hook_inline.h"
typedef void (*target_foo)(void);
void my_func(struct hook_reg *reg)
{
 puts("here we go!");
}
void main()
{
 void *handler = dlopen("/data/local/tmp/libtarget.so", RTLD_NOW);
 target_foo foo = (target_foo)dlsym(handler, "target_foo");
 hook_inline_make("/data/local/tmp/libtarget.so", 0xde2, my_func, true);
 foo();
}
hook_inline.h
#ifndef HOOK_INLINE_H_INCLUDED
#define HOOK_INLINE_H_INCLUDED
#include <stdbool.h>
struct hook_reg {
 long ARM_r0; long ARM_r1; long ARM_r2; long ARM_r3;
 long ARM_r4; long ARM_r5; long ARM_r6; long ARM_r7;
 long ARM_r8; long ARM_r9; long ARM_r10;long ARM_r11;
 long ARM_r12;long ARM_sp; long ARM_lr; long ARM_cpsr;
};
typedef void (*hook_func)(struct hook_reg *reg);
bool hook_inline_make(const char *library, long address, hook_func func, bool isArm);
#endif // HOOK_INLINE_H_INCLUDED

這里我們hook功能的實(shí)現(xiàn)函數(shù)為hook_inline_make,4個(gè)參數(shù)分別為動(dòng)態(tài)庫(kù)路徑,目標(biāo)地址,用戶函數(shù),目標(biāo)地址處指令集。

當(dāng)程序執(zhí)行到目標(biāo)地址處時(shí)會(huì)回調(diào)我們傳入的用戶函數(shù),可通過(guò)參數(shù)hook_reg來(lái)更改寄存器的值(不包括寄存器pc)。因?yàn)橹霸趧?dòng)態(tài)鏈接庫(kù)的Android.mk文件指定了使用arm指令集進(jìn)行編譯,所以此處指定最后一個(gè)參數(shù)為true

3、實(shí)現(xiàn)注入函數(shù)

現(xiàn)在到了最為關(guān)鍵的地方,為了實(shí)現(xiàn)這個(gè)功能還需要了解幾個(gè)知識(shí)。

(1)、獲取內(nèi)存中動(dòng)態(tài)鏈接庫(kù)的基址

Linux系統(tǒng)中各個(gè)進(jìn)程的內(nèi)存加載信息可以在/proc/pid/maps文件中到,通過(guò)它我們可以獲取到動(dòng)態(tài)鏈接庫(kù)在內(nèi)存中的加載基址。

long get_module_addr(pid_t pid, const char *module_name)
{
 char file_path[256];
 char file_line[512];
 if (pid < 0) {
  snprintf(file_path, sizeof(file_path), "/proc/self/maps");
 } else {
  snprintf(file_path, sizeof(file_path), "/proc/%d/maps", pid);
 }
 FILE *fp = fopen(file_path, "r");
 if (fp == NULL) {
  return -1;
 }
 long addr_start = -1, addr_end = 0;
 while (fgets(file_line, sizeof(file_line), fp)) {
  if (strstr(file_line, module_name)) {
   if (2 == sscanf(file_line, "%8lx-%8lx", &addr_start, &addr_end)) {
    break;
   }
  }
 }
 fclose(fp);
 printf("library :%s %lx-%lx, pid : %d\n", module_name, addr_start, addr_end, pid);
 return addr_start;
}

(2)、更改內(nèi)存中的二進(jìn)制代碼

現(xiàn)在的計(jì)算機(jī)系統(tǒng)中一般對(duì)內(nèi)存進(jìn)行分段式管理,不同的段有不同的讀、寫(xiě)、執(zhí)行的屬性。一般來(lái)講代碼段只有讀和執(zhí)行的屬性,不允許對(duì)代碼段進(jìn)行寫(xiě)操作。Linux系統(tǒng)中通過(guò)函數(shù)mprotect對(duì)內(nèi)存的屬性進(jìn)行更改,需要注意的一點(diǎn)是需要以內(nèi)存頁(yè)的大小進(jìn)行對(duì)齊。

bool change_addr_writable(long address, bool writable) {
 long page_size = sysconf(_SC_PAGESIZE);
 //align address by page size
 long page_start = (address) & (~(page_size - 1));
 //change memory attribute
 if (writable == true) {
  return mprotect((void*)page_start, page_size, PROT_READ | PROT_WRITE | PROT_EXEC) != -1;
 } else {
  return mprotect((void*)page_start, page_size, PROT_READ | PROT_EXEC) != -1;
 }
}

接下來(lái)就可以著手實(shí)現(xiàn)功能了,inline hook跟指令集密切相關(guān),此處我們先演示arm指令集的情況,之后對(duì)thumb指令集進(jìn)行討論。這里實(shí)現(xiàn)的功能是用戶可在自己注冊(cè)的回調(diào)函數(shù)中對(duì)hook點(diǎn)寄存器的值進(jìn)行修改。

 代碼實(shí)例分析android中inline hook

為了實(shí)現(xiàn)32位地址空間的長(zhǎng)跳轉(zhuǎn),我們需要兩條指令的長(zhǎng)度(8個(gè)字節(jié))來(lái)實(shí)現(xiàn)。一般手機(jī)上的arm處理器為3級(jí)流水,所以pc寄存器的值總是指向當(dāng)前執(zhí)行指令后的第二條指令,因而使用ldr pc, [pc, #-4]來(lái)加載該指令之后的跳轉(zhuǎn)地址。當(dāng)程序跳轉(zhuǎn)到shellcode后,首先對(duì)寄存器組進(jìn)行備份,然后調(diào)用用戶注冊(cè)的回調(diào)函數(shù),用戶可在回調(diào)函數(shù)中修改備份中各個(gè)寄存器(pc寄存器除外)的值,然后從備份中恢復(fù)寄存器組再跳轉(zhuǎn)到stubcode,stubcode的功能是執(zhí)行被hook點(diǎn)的跳轉(zhuǎn)指令替換掉的兩條指令,最后跳回原程序。

ellcode.S 1 .global _shellcode_start_s
 .global _shellcode_end_s
 .global _hook_func_addr_s
 .global _stub_func_addr_s
 .data
 _shellcode_start_s:
     @ 備份各個(gè)寄存器
     push  {r0, r1, r2, r3}
     mrs   r0, cpsr
     str   r0, [sp, #0xc]
     str   r14, [sp, #0x8]
     add   r14, sp, #0x10
     str   r14, [sp, #0x4]
     pop   {r0}
     push  {r0-r12}
     @ 此時(shí)寄存器被備份在棧中,將棧頂?shù)刂纷鳛榛卣{(diào)函數(shù)的參數(shù)(struct hook_reg)
     mov   r0, sp
     ldr   r3, _hook_func_addr_s
     blx   r3
     @ 恢復(fù)寄存器值
     ldr   r0, [sp, #0x3c]
     msr   cpsr, r0
     ldmfd sp!, {r0-r12}
     ldr   r14, [sp, #0x4]
     ldr   sp, [r13]
     ldr   pc, _stub_func_addr_s
 _hook_func_addr_s:
 .word 0x0
 _stub_func_addr_s:
 .word 0x0
 _shellcode_end_s:
 .end

shellcode使用匯編實(shí)現(xiàn),在使用時(shí)需要對(duì)里邊的兩個(gè)地址進(jìn)行修復(fù),用戶回調(diào)函數(shù)地址(_hook_func_addr_s)跟stubcode地址(_stub_func_addr_s)。

接下來(lái)我們可以看一下函數(shù)hook_inline_make的具體實(shí)現(xiàn)了

 void hook_inline_make(const char *library, long address, hook_func func)
 {
     //獲取hook點(diǎn)在內(nèi)存中的地址
     long base_addr = get_module_addr(-1, library);
     long hook_addr = base_addr + address;
     //獲取shellcode中的符號(hào)地址
  extern long _shellcode_start_s;
     extern long _shellcode_end_s;
     extern long _hook_func_addr_s;
     extern long _stub_func_addr_s;
     void *p_shellcode_start = &_shellcode_start_s;
     void *p_shellcdoe_end = &_shellcode_end_s;
     void *p_hook_func = &_hook_func_addr_s;
     void *p_stub_func = &_stub_func_addr_s;
     //計(jì)算shellcode大小
     int shellcode_size = (int)(p_shellcdoe_end - p_shellcode_start);
     //新建shellcode
     void *shellcode = malloc(shellcode_size);
     memcpy(shellcode, p_shellcode_start, shellcode_size);
     //添加執(zhí)行屬性
     change_addr_writable((long)shellcode, true);
     //在32bit的arm指令集中,stubcode中的4條指令占用16個(gè)字節(jié)的空間
     //前兩條指令為hook點(diǎn)被替換的兩條指令
     //后兩條指令跳轉(zhuǎn)回原程序
     void *stubcode = malloc(16);
     memcpy(stubcode, (void*)hook_addr, 8);
     //ldr pc, [pc, #-4]
     //[address]
     //手動(dòng)填充stubcode
     char jump_ins[8] = {0x04, 0xF0, 0x1F, 0xE5};
     uint32_t jmp_address = hook_addr + 8;
     memcpy(jump_ins + 4, &jmp_address, 4);
     memcpy(stubcode + 8, jump_ins, 8);
     //添加執(zhí)行屬性
     change_addr_writable((long)stubcode, true);
     //修復(fù)shellcode中的兩個(gè)地址值
     uint32_t *shell_hook = shellcode + (p_hook_func - p_shellcode_start);
     *shell_hook = (uint32_t)func;
     uint32_t *shell_stub = shellcode + (p_stub_func - p_shellcode_start);
     *shell_stub = (uint32_t)stubcode;
     //為hook點(diǎn)添加寫(xiě)屬性
     change_addr_writable(hook_addr, true);
     //替換hook點(diǎn)指令為跳轉(zhuǎn)指令,跳轉(zhuǎn)至shellcode
     jmp_address = (uint32_t)shellcode;
     memcpy(jump_ins + 4, &jmp_address, 4);
     memcpy((void*)hook_addr, jump_ins, 8);
     change_addr_writable(hook_addr, false);
     //刷新cache
     cacheflush(hook_addr, 8, 0);
 }

注意這里的change_addr_writable函數(shù)無(wú)論傳入false還是true對(duì)應(yīng)地址都會(huì)添加上執(zhí)行屬性。由于處理器采用流水線跟多級(jí)緩存,在更改代碼后我們需要手動(dòng)刷新cache,即函數(shù)cacheflush(第三個(gè)參數(shù)無(wú)意義)。

4、thumb指令集實(shí)現(xiàn)

由于thumb指令集的功能受到限制,雖然思路上跟arm指令集一致,但在實(shí)現(xiàn)上需要用更多條指令,下面是我自己想的一種實(shí)現(xiàn)方式,歡迎交流。

代碼實(shí)例分析android中inline hook

需要注意的是由于每條thumb指令為16bit,所以32位的跳轉(zhuǎn)地址需要占用兩條指令的空間,而且跳轉(zhuǎn)時(shí)會(huì)污染r0寄存器所以要對(duì)其進(jìn)行保護(hù)。我在實(shí)現(xiàn)程序時(shí)將shellcode編譯為了arm指令集,所以在原程序、shellcode、stubcode之間相互跳轉(zhuǎn)時(shí)需要使用bx指令進(jìn)行處理器狀態(tài)切換(需要跳轉(zhuǎn)的地址代碼為thumb指令集時(shí),需要將地址的第1個(gè)bit位置位)。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI