Android Hook框架adbi的分析(3)---編譯和inline Hook實踐

本文博客地址:http://blog.csdn.net/qq1084283172/article/details/75200800

一、序言

在前面的博客中,已經分析過了Android Hook框架adbi源碼的具體實現,Android Hook框架adbi的實現主要分爲兩部分,一部分是root權限下的Android跨進程的so注入,一部分是基於Android系統的inline Hook。只要搞清楚了Android系統的跨進程so注入和基於Android系統的inline Hook這兩個知識點,理解adbi等Android的Hook框架就不是問題了。Android系統的跨進程so注入和Android的各種Hook非常重要而且它們應用的範圍也非常廣,Android加固中的反調試對抗、反內存dump對抗,基於ClassLoader的VirtualApp的Hook等等。前面的博客中已經學習了adbi的實現原理,但是僅僅理解原理還不夠,實踐一下證明adbi的inline Hook是有效的才ok,在接下來的博文將着重記錄一下adbi的源碼的編譯和inlineHook操作實踐。


二、Android Hook框架adbi的inline Hook代碼的簡析

Android Hook框架adbi的inline Hook部分主要代碼的簡要解析和說明。

  • 帶有註釋分析的Android Hook框架adbi源碼下載地址:http://download.csdn.net/detail/qq1084283172/9893002
  • util.c文件,只要是用於inline Hook中目標函數所在so庫文件的文件路徑和內存加載基地址的獲取以及解析該so庫文件獲取被inline Hook目標函數的內存調用地址的實現。雖然代碼量有點大,但是作者解析指定so庫文件,獲取該so庫文件的靜態庫或動態庫的符號表即”.symtab”或者”.dynsym”信息和獲取目標函數的調用地址的方法還是值得去學習的,與前面提到基於Android的.got表的Hook還是有區別的。下面貼的代碼中,有些函數是沒有使用的,爲了閱讀的方便和尊重原作者的編碼還是加上了。
/*
 * Elf parsing code taken from: hijack.c (for x86)
 * by Victor Zandy <zandy[at]cs.wisc.edu>
 *
 * Elf parsing code slightly modified for this project
 * (c) Collin Mulliner <collin[at]mulliner.org>
 *
 * License: LGPL v2.1
 *  
 * Termios code taken from glibc with slight modifications for this project
 * 
 */
#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <dlfcn.h>
#include <elf.h>
#include <unistd.h>
#include <errno.h>       
#include <sys/mman.h>
#include <termios.h>
#include <sys/ioctl.h>

#include "hook.h"

/* memory map for libraries */
#define MAX_NAME_LEN 256
#define MEMORY_ONLY  "[memory]"

struct mm {

    // 內存佈局的名稱
    char name[MAX_NAME_LEN];
    // 內存佈局的起始地址和結束地址
    unsigned long start, end;
};

typedef struct symtab *symtab_t;
struct symlist {
    Elf32_Sym *sym;       /* symbols */
    char *str;            /* symbol strings */
    unsigned num;         /* number of symbols */
};

struct symtab {
    struct symlist *st;    /* "static" symbols */
    struct symlist *dyn;   /* dynamic symbols */
};


static void* xmalloc(size_t size)
{
    void *p;
    p = malloc(size);
    if (!p) {
        printf("Out of memory\n");
        exit(1);
    }
    return p;
}


static int my_pread(int fd, void *buf, size_t count, off_t offset)
{
    lseek(fd, offset, SEEK_SET);
    return read(fd, buf, count);
}


static struct symlist* get_syms(int fd, Elf32_Shdr *symh, Elf32_Shdr *strh)
{
    struct symlist *sl, *ret;
    int rv;

    ret = NULL;
    sl = (struct symlist *) xmalloc(sizeof(struct symlist));
    sl->str = NULL;
    sl->sym = NULL;

    /* sanity */
    if (symh->sh_size % sizeof(Elf32_Sym)) { 
        //printf("elf_error\n");
        goto out;
    }

    /* symbol table */
    sl->num = symh->sh_size / sizeof(Elf32_Sym);
    sl->sym = (Elf32_Sym *) xmalloc(symh->sh_size);
    rv = my_pread(fd, sl->sym, symh->sh_size, symh->sh_offset);
    if (0 > rv) {
        //perror("read");
        goto out;
    }
    if (rv != symh->sh_size) {
        //printf("elf error\n");
        goto out;
    }

    /* string table */
    sl->str = (char *) xmalloc(strh->sh_size);
    rv = my_pread(fd, sl->str, strh->sh_size, strh->sh_offset);
    if (0 > rv) {
        //perror("read");
        goto out;
    }
    if (rv != strh->sh_size) {
        //printf("elf error");
        goto out;
    }

    ret = sl;
out:
    return ret;
}


static int do_load(int fd, symtab_t symtab)
{
    int rv;
    size_t size;
    Elf32_Ehdr ehdr;
    Elf32_Shdr *shdr = NULL, *p;
    Elf32_Shdr *dynsymh, *dynstrh;
    Elf32_Shdr *symh, *strh;
    char *shstrtab = NULL;
    int i;
    int ret = -1;

    /* elf header */
    rv = read(fd, &ehdr, sizeof(ehdr));
    if (0 > rv) {
        log("read\n")
        goto out;
    }
    if (rv != sizeof(ehdr)) {
        log("elf error 1\n")
        goto out;
    }
    if (strncmp(ELFMAG, ehdr.e_ident, SELFMAG)) { /* sanity */
        log("not an elf\n")
        goto out;
    }
    if (sizeof(Elf32_Shdr) != ehdr.e_shentsize) { /* sanity */
        log("elf error 2\n")
        goto out;
    }

    /* section header table */
    size = ehdr.e_shentsize * ehdr.e_shnum;
    shdr = (Elf32_Shdr *) xmalloc(size);
    rv = my_pread(fd, shdr, size, ehdr.e_shoff);
    if (0 > rv) {
        log("read\n")
        goto out;
    }
    if (rv != size) {
        log("elf error 3 %d %d\n", rv, size)
        goto out;
    }

    /* section header string table */
    size = shdr[ehdr.e_shstrndx].sh_size;
    shstrtab = (char *) xmalloc(size);
    rv = my_pread(fd, shstrtab, size, shdr[ehdr.e_shstrndx].sh_offset);
    if (0 > rv) {
        log("read\n")
        goto out;
    }
    if (rv != size) {
        log("elf error 4 %d %d\n", rv, size)
        goto out;
    }

    /* symbol table headers */
    symh = dynsymh = NULL;
    strh = dynstrh = NULL;
    for (i = 0, p = shdr; i < ehdr.e_shnum; i++, p++)
        if (SHT_SYMTAB == p->sh_type) {
            if (symh) {
                log("too many symbol tables\n")
                goto out;
            }
            symh = p;
        } else if (SHT_DYNSYM == p->sh_type) {
            if (dynsymh) {
                log("too many symbol tables\n")
                goto out;
            }
            dynsymh = p;
        } else if (SHT_STRTAB == p->sh_type
               && !strncmp(shstrtab+p->sh_name, ".strtab", 7)) {
            if (strh) {
                log("too many string tables\n")
                goto out;
            }
            strh = p;
        } else if (SHT_STRTAB == p->sh_type
               && !strncmp(shstrtab+p->sh_name, ".dynstr", 7)) {
            if (dynstrh) {
                log("too many string tables\n")
                goto out;
            }
            dynstrh = p;
        }
    /* sanity checks */
    if ((!dynsymh && dynstrh) || (dynsymh && !dynstrh)) {
        log("bad dynamic symbol table\n")
        goto out;
    }
    if ((!symh && strh) || (symh && !strh)) {
        log("bad symbol table\n")
        goto out;
    }
    if (!dynsymh && !symh) {
        log("no symbol table\n")
        goto out;
    }

    /* symbol tables */
    if (dynsymh)
        symtab->dyn = get_syms(fd, dynsymh, dynstrh);
    if (symh)
        symtab->st = get_syms(fd, symh, strh);
    ret = 0;
out:
    free(shstrtab);
    free(shdr);
    return ret;
}


static symtab_t load_symtab(char *filename)
{
    int fd;
    symtab_t symtab;

    symtab = (symtab_t) xmalloc(sizeof(*symtab));
    memset(symtab, 0, sizeof(*symtab));

    fd = open(filename, O_RDONLY);
    if (0 > fd) {
        log("%s open\n", __func__);
        return NULL;
    }

    if (0 > do_load(fd, symtab)) {

        log("Error ELF parsing %s\n", filename)
        free(symtab);
        symtab = NULL;
    }

    close(fd);

    return symtab;
}


// 獲取指定pid進程的內存佈局的信息
static int load_memmap(pid_t pid, struct mm *mm, int *nmmp)
{
    char raw[80000]; // increase this if needed for larger "maps"
    char name[MAX_NAME_LEN];
    char *p;
    unsigned long start, end;
    struct mm *m;
    int nmm = 0;
    int fd, rv;
    int i;

    // 格式字符串"/proc/pid/maps"
    sprintf(raw, "/proc/%d/maps", pid);
    // 獲取目標pid進程的內存佈局信息
    fd = open(raw, O_RDONLY);
    if (0 > fd) {

        //printf("Can't open %s for reading\n", raw);
        return -1;
    }

    // 數組清零
    memset(raw, 0, sizeof(raw));

    // 格式:400c2000-400da000 r-xp 00000000 b3:19 949        /system/lib/libm.so
    p = raw;
    while (1) {

        // 分行讀取目標pid進程的內存佈局信息
        rv = read(fd, p, sizeof(raw)-(p-raw));
        if (0 > rv) {
            //perror("read");
            return -1;
        }
        // 判斷內存佈局信息是否讀取完了
        if (0 == rv)
            break;

        // 修改指向內存緩衝區raw中的指針偏移p
        p += rv;

        // 判斷是否超過內存緩衝區範圍
        if (p-raw >= sizeof(raw)) {

            //printf("Too many memory mapping\n");
            return -1;
        }
    }

    // 關閉文件
    close(fd);

    // 分割字符串
    p = strtok(raw, "\n");
    m = mm;
    while (p) {

        // 根據格式解析每一行內存佈局信息
        // rv = sscanf函數都將返回成功轉換並分配的字段數
        rv = sscanf(p, "%08lx-%08lx %*s %*s %*s %*s %s\n", &start, &end, name);

        // 繼續分割字符串
        p = strtok(NULL, "\n");

        // sscanf函數前兩個字段start、end匹配成功的情況即沒有名稱的情況
        if (rv == 2) {

            m = &mm[nmm++];
            // 內存佈局起始地址
            m->start = start;
            // 內存佈局結束地址
            m->end = end;
            // 設置默認內存佈局名稱爲"[memory]"
            strcpy(m->name, MEMORY_ONLY);

            continue;
        }

        /* search backward for other mapping with same name */
        // 在前面保存的內存佈局行信息中查找相同名稱的內存佈局
        // 例如:
        // 7739a000-7739c000 r-xp 00000000 b3:19 795        /system/lib/libOpenSLES.so
        // 7739c000-7739d000 r--p 00001000 b3:19 795        /system/lib/libOpenSLES.so
        // 7739d000-7739e000 rw-p 00002000 b3:19 795        /system/lib/libOpenSLES.so
        for (i = nmm-1; i >= 0; i--) {

            m = &mm[i];
            if (!strcmp(m->name, name))
                break;
        }

        // 進行相同名稱的內存佈局起始地址和結束地址的合併
        if (i >= 0) {

            if (start < m->start)
                m->start = start;
            if (end > m->end)
                m->end = end;

        } else {

            // 內存起始地址、內存結束地址、內存佈局名稱
            m = &mm[nmm++];

            // 內存起始地址
            m->start = start;
            // 內存結束地址
            m->end = end;
            // 內存佈局名稱
            strcpy(m->name, name);
        }
    }

    // 保存合併後內存佈局的個數
    *nmmp = nmm;

    return 0;
}


/* Find libc in MM, storing no more than LEN-1 chars of
   its name in NAME and set START to its starting
   address.  If libc cannot be found return -1 and
   leave NAME and START untouched.  Otherwise return 0
   and null-terminated NAME. */
// libn爲要查找的lib庫文件的名稱字符串,如:"libc."
static int find_libname(char *libn, char *name, int len, unsigned long *start, struct mm *mm, int nmm)
{
    int i;
    struct mm *m;
    char *p;

    // 遍歷獲取到的目標pid進程的內存佈局的信息
    for (i = 0, m = mm; i < nmm; i++, m++) {

        // 直接跳過內存佈局名稱爲"[memory]"的情況
        if (!strcmp(m->name, MEMORY_ONLY))
            continue;

        // 從右開始搜索'/'符號,獲取內存佈局的名稱
        // 例如/system/lib/libdl.so,獲取名稱libdl.so
        p = strrchr(m->name, '/');
        // 跳過不符合要求的情況
        if (!p)
            continue;

        // 判斷獲取到的lib庫名稱是否是要查找的目標lib庫名稱libn
        p++;
        if (strncmp(libn, p, strlen(libn)))
            continue;

        // 獲取查找的例如:"libc."的長度
        p += strlen(libn);

        /* here comes our crude test -> 'libc.so' or 'libc-[0-9]' */
        // 作者並沒有使用
        if (!strncmp("so", p, 2) || 1) // || (p[0] == '-' && isdigit(p[1])))
            break;
    }

    // 判斷是否查找到目標lib庫libn
    if (i >= nmm)
        /* not found */
        return -1;

    // 獲取指定lib庫文件的內存的起始地址
    *start = m->start;

    // 保存查找到的目標lib庫文件的路徑字符串m->name
    strncpy(name, m->name, len);
    // 判斷lib庫文件的路徑字符串是否超過內存數組的長度
    if (strlen(m->name) >= len)
        // 進行截取
        name[len-1] = '\0';

    // 修改指定內存區域內存屬性爲可讀可寫可執行
    mprotect((void*)m->start, m->end - m->start, PROT_READ|PROT_WRITE|PROT_EXEC);

    return 0;
}

static int lookup2(struct symlist *sl, unsigned char type,
    char *name, unsigned long *val)
{
    Elf32_Sym *p;
    int len;
    int i;

    len = strlen(name);
    for (i = 0, p = sl->sym; i < sl->num; i++, p++) {
        //log("name: %s %x\n", sl->str+p->st_name, p->st_value)
        if (!strncmp(sl->str+p->st_name, name, len) && *(sl->str+p->st_name+len) == 0
            && ELF32_ST_TYPE(p->st_info) == type) {
            //if (p->st_value != 0) {
            *val = p->st_value;
            return 0;
            //}
        }
    }
    return -1;
}

//struct symtab {
//  struct symlist *st;    /* "static" symbols */
//  struct symlist *dyn;   /* dynamic symbols */
//};

static int lookup_sym(symtab_t s, unsigned char type,
       char *name, unsigned long *val)
{
    // 在動態系統符號表中查找獲取目標函數的RVA
    if (s->dyn && !lookup2(s->dyn, type, name, val))
        return 0;

    // 在靜態系統符號表中查找獲取目標函數的RVA
    if (s->st && !lookup2(s->st, type, name, val))
        return 0;

    return -1;
}

static int lookup_func_sym(symtab_t s, char *name, unsigned long *val)
{
    return lookup_sym(s, STT_FUNC, name, val);
}


// 在指定pid進程的指定lib庫中查找將被Hook的目標函數的地址
int find_name(pid_t pid, char *name, char *libn, unsigned long *addr)
{
    struct mm mm[1000];
    unsigned long libcaddr;
    int nmm;
    char libc[1024];
    symtab_t s;

    // 獲取指定pid進程的內存佈局的信息並保存到mm數組中
    if (0 > load_memmap(pid, mm, &nmm)) {

        log("cannot read memory map\n")
        return -1;
    }

    // 獲取需要查找的目標lib庫libn的內存基地址libcaddr並獲取保存libn的全路徑字符串
    if (0 > find_libname(libn, libc, sizeof(libc), &libcaddr, mm, nmm)) {

        log("cannot find lib: %s\n", libn)
        return -1;
    }
    //log("lib: >%s<\n", libc)

    // 打開查找到的lib目標庫文件(路徑字符串libc)解析該Elf文件
    // 獲取該lib庫文件的靜態庫和動態庫的符號表信息".symtab"或者".dynsym".
    s = load_symtab(libc);
    if (!s) {

        log("cannot read symbol table\n");
        return -1;
    }

    // 在目標lib庫libn的靜態庫和動態庫的符號表查找被Hook的目標函數的RVA即相對地址偏移
    if (0 > lookup_func_sym(s, name, addr)) {

        log("cannot find function: %s\n", name);
        return -1;
    }

    // 獲取到目標pid進程中被Hook的目標函數的VA即虛擬地址偏移(有效的函數調用地址)
    *addr += libcaddr;

    return 0;
}

// 獲取指定so庫文件的內存加載地址
int find_libbase(pid_t pid, char *libn, unsigned long *addr)
{
    struct mm mm[1000];
    unsigned long libcaddr;
    int nmm;
    char libc[1024];
    symtab_t s;

    if (0 > load_memmap(pid, mm, &nmm)) {
        log("cannot read memory map\n")
        return -1;
    }
    if (0 > find_libname(libn, libc, sizeof(libc), &libcaddr, mm, nmm)) {
        log("cannot find lib\n");
        return -1;
    }
    *addr = libcaddr;
    return 0;
}


// --------------------------------------------------------------
#if 0

# define IBAUD0 0

/* Set *T to indicate raw mode. */
void cfmakeraw (struct termios *t)
    {
      t->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
      t->c_oflag &= ~OPOST;
      t->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
      t->c_cflag &= ~(CSIZE|PARENB);
      t->c_cflag |= CS8;
      t->c_cc[VMIN] = 1; /* read returns when one char is available. */
      t->c_cc[VTIME] = 0;
    }
#define __KERNEL_NCCS 19
struct __kernel_termios
    {
        tcflag_t c_iflag; /* input mode flags */
        tcflag_t c_oflag; /* output mode flags */
        tcflag_t c_cflag; /* control mode flags */
        tcflag_t c_lflag; /* local mode flags */
        cc_t c_line; /* line discipline */
    cc_t c_cc[__KERNEL_NCCS]; /* control characters */
    };


/* Set the state of FD to *TERMIOS_P. */
int tcsetattr (int fd, int optional_actions, const struct termios *termios_p)
    {
        struct __kernel_termios k_termios;
        unsigned long int cmd;
        int retval;

        switch (optional_actions)
        {
        case TCSANOW:
            cmd = TCSETS;
            break;
        case TCSADRAIN:
            cmd = TCSETSW;
            break;
        case TCSAFLUSH:
            cmd = TCSETSF;
            break;
        default:
            //__set_errno (EINVAL);
            return -1;
        }

        k_termios.c_iflag = termios_p->c_iflag & ~IBAUD0;
        k_termios.c_oflag = termios_p->c_oflag;
        k_termios.c_cflag = termios_p->c_cflag;
        k_termios.c_lflag = termios_p->c_lflag;
        k_termios.c_line = termios_p->c_line;
    #ifdef _HAVE_C_ISPEED
    k_termios.c_ispeed = termios_p->c_ispeed;
    #endif
    #ifdef _HAVE_C_OSPEED
        k_termios.c_ospeed = termios_p->c_ospeed;
    #endif
        memcpy (&k_termios.c_cc[0], &termios_p->c_cc[0],
            __KERNEL_NCCS * sizeof (cc_t));

        retval = ioctl (fd, cmd, &k_termios);

        if (retval == 0 && cmd == TCSETS)
        {
        /* The Linux kernel has a bug which silently ignore the invalid
           c_cflag on pty. We have to check it here. */
        int save = 0; //errno;
        retval = ioctl (fd, TCGETS, &k_termios);
        if (retval)
        {
            /* We cannot verify if the setting is ok. We don't return
               an error (?). */
            //__set_errno (save);
            retval = 0;
        }
        else if ((termios_p->c_cflag & (PARENB | CREAD))
            != (k_termios.c_cflag & (PARENB | CREAD))
            || ((termios_p->c_cflag & CSIZE)
                && ((termios_p->c_cflag & CSIZE)
                != (k_termios.c_cflag & CSIZE))))
        {
            /* It looks like the Linux kernel silently changed the
               PARENB/CREAD/CSIZE bits in c_cflag. Report it as an
               error. */
            //__set_errno (EINVAL);
            retval = -1;
        }
        }

        return retval;
}

int tcgetattr (int fd, struct termios *termios_p)
    {
        struct __kernel_termios k_termios;
        int retval;

        retval = ioctl (fd, TCGETS, &k_termios);
        if(retval == 0) {
            termios_p->c_iflag = k_termios.c_iflag;
            termios_p->c_oflag = k_termios.c_oflag;
            termios_p->c_cflag = k_termios.c_cflag;
            termios_p->c_lflag = k_termios.c_lflag;
            termios_p->c_line = k_termios.c_line;
    #ifdef _HAVE_C_ISPEED
            termios_p->c_ispeed = k_termios.c_ispeed;
    #endif
    #ifdef _HAVE_C_OSPEED
            termios_p->c_ospeed = k_termios.c_ospeed;
    #endif


            if (sizeof (cc_t) == 1 || _POSIX_VDISABLE == 0
                || (unsigned char) _POSIX_VDISABLE == (unsigned char) -1)
            {
            #if 0
            memset (mempcpy (&termios_p->c_cc[0], &k_termios.c_cc[0],
                    __KERNEL_NCCS * sizeof (cc_t)),
                _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * sizeof (cc_t));
            #endif
            memset ( (memcpy (&termios_p->c_cc[0], &k_termios.c_cc[0],
                    __KERNEL_NCCS * sizeof (cc_t)) + (__KERNEL_NCCS * sizeof (cc_t))) ,
                _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * sizeof (cc_t));

            } else {
            size_t cnt;

            memcpy (&termios_p->c_cc[0], &k_termios.c_cc[0],
                __KERNEL_NCCS * sizeof (cc_t));

            for (cnt = __KERNEL_NCCS; cnt < NCCS; ++cnt)
                termios_p->c_cc[cnt] = _POSIX_VDISABLE;
            }
        }

        return retval;
    }
#endif

  • hook.c文件,adbi源碼的inline Hook的主要實現部分也是整個adbi框架的精華部分;hook函數實現了20個字節Thumb指令模式和12字節Arm指令模式的inline Hook,hook_precall函數實現Thumb或者Arm模式被inline Hook目標函數指令的恢復即實現函數inline Hook的恢復還原;hook_postcall函數實現Thumb或者Arm指令模式inline Hook目標函數的指令覆蓋即實現目標函數的再次inline Hook,hook_cacheflush函數調用Android系統的私有系統調用__ARM_NR_cacheflush實現緩存指令的刷新,現代的很多處理器爲了提高指令的運行效率都有指令緩存機制,因此爲了inline Hook的生效和被執行,需要進行inline Hook操作之後的指令刷新。
struct hook_t {

    // arm指令模式的12字節Hook
    unsigned int jump[3];   /* 要修改的hook指令(Arm) */
    unsigned int store[3]; /* 被修改的原指令(Arm) */

    // thumb指令模式的20字節Hook
    unsigned char jumpt[20]; /* 要修改的hook指令(Thumb) */
    unsigned char storet[20]; /* 被修改的源指令(Thumb) */

    unsigned int orig; /* 被hook的目標函數地址 */
    unsigned int patch; /* hook的自定義函數地址 */

    unsigned char thumb; /* 表明被hook函數使用的指令集,1爲Thumb,0爲Arm */
    unsigned char name[128]; /* 被hook的函數名 */

    // 用於存放其他的數據(未使用)
    void *data;
};
 *  Collin's Binary Instrumentation Tool/Framework for Android
 *  Collin Mulliner <collin[at]mulliner.org>
 *  http://www.mulliner.org/android/
 *
 *  (c) 2012,2013
 *
 *  License: LGPL v2.1
 *
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <string.h>
#include <termios.h>
#include <pthread.h>
#include <sys/epoll.h>

#include <jni.h>

#include "util.h"
#include "hook.h"

//void __attribute__ ((constructor)) my_init(void);

// 調用Android系統的私有系統調用__ARM_NR_cacheflush實現緩存指令的刷新
void inline hook_cacheflush(unsigned int begin, unsigned int end)
{   
    const int syscall = 0xf0002;

    // 禁止編譯器對彙編指令進行指令優化
    __asm __volatile (
        "mov     r0, %0\n"          
        "mov     r1, %1\n"
        "mov     r7, %2\n"
        "mov     r2, #0x0\n"
        "svc     0x00000000\n"
        :
        :   "r" (begin), "r" (end), "r" (syscall) // 輸入列表
        :   "r0", "r1", "r7"                      // 修改寄存器列表
        );
}

// 未使用
int hook_direct(struct hook_t *h, unsigned int addr, void *hookf)
{
    int i;

    log("addr  = %x\n", addr)
    log("hookf = %lx\n", (unsigned long)hookf)

    if ((addr % 4 == 0 && (unsigned int)hookf % 4 != 0) || (addr % 4 != 0 && (unsigned int)hookf % 4 == 0))
        log("addr 0x%x and hook 0x%lx\n don't match!\n", addr, (unsigned long)hookf)

    //log("ARM\n")
    h->thumb = 0;
    h->patch = (unsigned int)hookf;
    h->orig = addr;
    log("orig = %x\n", h->orig)
    h->jump[0] = 0xe59ff000; // LDR pc, [pc, #0]
    h->jump[1] = h->patch;
    h->jump[2] = h->patch;
    for (i = 0; i < 3; i++)
        h->store[i] = ((int*)h->orig)[i];
    for (i = 0; i < 3; i++)
        ((int*)h->orig)[i] = h->jump[i];

    hook_cacheflush((unsigned int)h->orig, (unsigned int)h->orig+sizeof(h->jumpt));
    return 1;
}


// 對目標pid進程的指定函數進行Hook處理

// h爲記錄Hook信息的靜態變量的指針,pid爲被Hook的目標進程的pid,libname爲被Hook函數所在的so庫文件名稱,
// funcname爲被Hook的目標函數,hook_arm爲被Hook的函數的arm指令模式的替換函數,hook_thumb爲被Hook的函數的thumb指令模式的替換函數
int hook(struct hook_t *h, int pid, char *libname, char *funcname, void *hook_arm, void *hook_thumb)
{
    unsigned long int addr;
    int i;

    // 在指定pid進程的指定so庫中查找將被Hook的目標函數funcname的調用地址VA即addr
    if (find_name(pid, funcname, libname, &addr) < 0) {

        log("can't find funcname: %s\n", funcname)
        return 0;
    }

    log("hooking:   %s = 0x%lx ", funcname, addr)
    // 保存被Hook的目標函數的名稱
    strncpy(h->name, funcname, sizeof(h->name)-1);


    // 通過判斷函數跳轉地址的最後兩位是不是全0,來判斷指令的運行模式,
    // 如果後兩位全是的0,那就一定是用Arm指令,如果後兩位不全爲0,那一定是用Thumb指令集

    // Arm指令模式的HooK目標函數的處理
    if (addr % 4 == 0) {

        log("ARM using 0x%lx\n", (unsigned long)hook_arm)

        // arm指令模式
        h->thumb = 0;
        // 自己實現的Hook函數地址
        h->patch = (unsigned int)hook_arm;
        // 被Hook目標函數的原函數地址
        h->orig = addr;

        // 用於Hook目標函數的調用地址爲新地址hook_arm
        h->jump[0] = 0xe59ff000; // LDR pc, [pc, #0]
        h->jump[1] = h->patch;
        // pc寄存器讀出的值實際上是當前指令地址加8
        // 把jump[2]的值加載進pc寄存器
        h->jump[2] = h->patch;

        // 保存原目標函數的12字節指令,用於函數的恢復
        for (i = 0; i < 3; i++)
            h->store[i] = ((int*)h->orig)[i];

        // 覆蓋目標函數的12字節指令爲Hook函數指令,實現對目標函數的Hook
        for (i = 0; i < 3; i++)
            ((int*)h->orig)[i] = h->jump[i];

    }

    // Thumb指令模式的Hook目標函數的處理
    else {

        // 對自定義Hook函數的調用地址進行指令模式的判斷
        if ((unsigned long int)hook_thumb % 4 == 0)
            log("warning hook is not thumb 0x%lx\n", (unsigned long)hook_thumb)

        // thumb指令模式
        h->thumb = 1;
        log("THUMB using 0x%lx\n", (unsigned long)hook_thumb)

        // 保存用於Hook目標函數的調用地址爲新地址hook_thumb
        h->patch = (unsigned int)hook_thumb;
        // 保存被Hook目標函數的原函數地址
        h->orig = addr; 

        // 保存寄存器r5,r6的值用於恢復環境
        h->jumpt[1] = 0xb4;
        h->jumpt[0] = 0x60; // push {r5,r6}
//      將PC寄存器的值加上12賦值給r5。加上的立即數必須是4的倍數,而加上8又不夠,只能加12。
//      這樣的話,讀出的PC寄存器的值是當前指令地址加上4,再加上12的話,那麼可以算出來r5寄存器的值實際指向的是jumpt[18],而不是jumpt[16]了。
//      這裏還有一點需要注意,對於Thumb的“Add Rd, Rp, #expr”指令來說,如果Rp是PC寄存器的話,那麼PC寄存器讀出的值應該是(當前指令地址+4)& 0xFFFFFFFC,
//      也就是去掉最後兩位,算下來正好可以減去2。但這裏也有個假設,就是被hook函數的起始地址必須是4字節對齊的,哪怕被hook函數使用Thumb指令集寫的。
        h->jumpt[3] = 0xa5;
        h->jumpt[2] = 0x03; // add r5, pc, #12 (比較難理解)
        // 將保存在jumpt[16]處的hook函數地址加載到r5寄存器中
        h->jumpt[5] = 0x68;
        h->jumpt[4] = 0x2d; // ldr r5, [r5]
        // 降低棧頂,恢復到初始的狀態,釋放內存空間
        h->jumpt[7] = 0xb0;
        h->jumpt[6] = 0x02; // add sp,sp,#8
        // 用保存的自定義hook函數地址覆蓋原來壓入的r6的值,r5的值暫時不受影響
        h->jumpt[9] = 0xb4;
        h->jumpt[8] = 0x20; // push {r5}
        // 擡高棧頂,r5的值被保護
        h->jumpt[11] = 0xb0;
        h->jumpt[10] = 0x81; // sub sp,sp,#4
        // 進行出棧操作,pc寄存器得到自定義的Hook函數的地址,r5的值還是原來的
        h->jumpt[13] = 0xbd;
        h->jumpt[12] = 0x20; // pop {r5, pc}
        // 僅僅用於4字節對齊的填充,只是因爲前面的add指令只能加4的倍數
        h->jumpt[15] = 0x46;
        h->jumpt[14] = 0xaf; // mov pc, r5 ; just to pad to 4 byte boundary

        // 用於存放自定義Hook函數的調用地址(4字節)
        memcpy(&h->jumpt[16], (unsigned char*)&h->patch, sizeof(unsigned int));

        // sub 1 to get real address
        unsigned int orig = addr - 1;
        // 保存被Hook目標函數的原始thumb指令
        for (i = 0; i < 20; i++) {

            h->storet[i] = ((unsigned char*)orig)[i];
            //log("%0.2x ", h->storet[i])
        }
        //log("\n")

        // 覆蓋被Hook目標函數的指令爲自定義的Hook函數指令
        for (i = 0; i < 20; i++) {

            ((unsigned char*)orig)[i] = h->jumpt[i];
            //log("%0.2x ", ((unsigned char*)orig)[i])
        }

    }

    // 刷新指令緩存(被修改的這段字節數的指令)
    hook_cacheflush((unsigned int)h->orig, (unsigned int)h->orig+sizeof(h->jumpt));

    return 1;
}


// 進行thumb或者arm模式被Hook目標函數指令的恢復即實現函數Hook的恢復
void hook_precall(struct hook_t *h)
{
    int i;

    // thumb指令模式被Hook目標函數的指令的恢復
    if (h->thumb) {

        // 獲取被Hook目標函數的真實調用地址
        unsigned int orig = h->orig - 1;
        // 進行thumb指令模式被Hook指令的恢復
        for (i = 0; i < 20; i++) {

            ((unsigned char*)orig)[i] = h->storet[i];
        }

    } else {

        // 進行arm指令模式被Hook指令的恢復
        for (i = 0; i < 3; i++){

            ((int*)h->orig)[i] = h->store[i];
        }
    }   

    // 刷新指令緩存
    hook_cacheflush((unsigned int)h->orig, (unsigned int)h->orig+sizeof(h->jumpt));
}


// 進行thumb或者arm指令模式Hook目標函數的指令覆蓋即實現函數的Hook
void hook_postcall(struct hook_t *h)
{
    int i;

    if (h->thumb) {

        // 獲取thumb指令模式函數真實的調用地址
        unsigned int orig = h->orig - 1;
        // 進行thumb指令模式Hook目標函數指令的覆蓋
        for (i = 0; i < 20; i++)
            ((unsigned char*)orig)[i] = h->jumpt[i];

    } else {

        // 進行arm指令模式Hook目標函數指令的覆蓋
        for (i = 0; i < 3; i++)
            ((int*)h->orig)[i] = h->jump[i];
    }

    // 刷新指令緩存
    hook_cacheflush((unsigned int)h->orig, (unsigned int)h->orig+sizeof(h->jumpt)); 
}


// 取消函數的Hook
void unhook(struct hook_t *h)
{
    log("unhooking %s = %x  hook = %x ", h->name, h->orig, h->patch)
    // 進行被Hook目標函數的恢復
    hook_precall(h);
}

/*
 *  workaround for blocked socket API when process does not have network
 *  permissions
 *
 *  this code simply opens a pseudo terminal (pty) which gives us a
 *  file descriptor. the pty then can be used by another process to
 *  communicate with our instrumentation code. an example program
 *  would be a simple socket-to-pty-bridge
 *  
 *  this function just creates and configures the pty
 *  communication (read, write, poll/select) has to be implemented by hand
 *
 */
int start_coms(int *coms, char *ptsn)
{
    if (!coms) {
        log("coms == null!\n")
        return 0;
    }

    *coms = open("/dev/ptmx", O_RDWR|O_NOCTTY);
    if (*coms <= 0) {
        log("posix_openpt failed\n")
        return 0;
    }
    //else
    //  log("pty created\n")
    if (unlockpt(*coms) < 0) {
        log("unlockpt failed\n")
        return 0;
    }

    if (ptsn)
        strcpy(ptsn, (char*)ptsname(*coms));

    struct termios  ios;
    tcgetattr(*coms, &ios);
    ios.c_lflag = 0;  // disable ECHO, ICANON, etc...
    tcsetattr(*coms, TCSANOW, &ios);

    return 1;
}

  • epoll.c文件,adbi框架的inline Hook的實踐,實現了對Android系統的libc.so庫文件的”epoll_wait”函數的inline Hook,my_init函數在so庫文件被加載注入到目標進程中的時候會執行,用以實現對目標進程目標函數的inline Hook對所有進程都起作用,my_epoll_wait函數爲Thumb指令模式下”epoll_wait”函數被inline Hook的自定義替換函數。
/*
 *  Collin's Binary Instrumentation Tool/Framework for Android
 *  Collin Mulliner <collin[at]mulliner.org>
 *  http://www.mulliner.org/android/
 *
 *  (c) 2012,2013
 *
 *  License: LGPL v2.1
 *
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <string.h>
#include <termios.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <jni.h>
#include <stdlib.h>

#include "../base/hook.h"
#include "../base/base.h"

#undef log

// 打印日誌消息到"/data/local/tmp/adbi_example.log"文件的宏
#define log(...) \
        {FILE *fp = fopen("/data/local/tmp/adbi_example.log", "a+"); if (fp) {\
        fprintf(fp, __VA_ARGS__);\
        fclose(fp);}}


// 在adbi\instruments\example\epoll.c中定義.init段的構造函數
// this file is going to be compiled into a thumb mode binary
// 當so庫文件被加載的時候,會執行的構造函數
void __attribute__ ((constructor)) my_init(void);

// 靜態數據
static struct hook_t eph;

// 用於設置被Hook目標函數有效的Hook次數
static int counter;

// 全局導出arm指令模式的自定義Hook函數
extern int my_epoll_wait_arm(int epfd, struct epoll_event *events, int maxevents, int timeout);

/*  
 *  log function to pass to the hooking library to implement central loggin
 *
 *  see: set_logfunction() in base.h
 */
// 將日誌消息打印到"/data/local/tmp/adbi_example.log"文件中
static void my_log(char *msg)
{
    // 調用打印日誌消息到"/data/local/tmp/adbi_example.log"文件的宏
    log("%s", msg)
}


// 自定義Hook函數(默認編譯成thumb指令模式)
int my_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
{
    // 聲明epoll_wait函數的函數指針
    int (*orig_epoll_wait)(int epfd, struct epoll_event *events, int maxevents, int timeout);

    // 獲取被Hook目標函數的原始函數調用地址
    orig_epoll_wait = (void*)eph.orig;
    // 恢復被Hook的epoll_wait函數
    hook_precall(&eph);

    // 調用被Hook目標函數的原始函數epoll_wait
    int res = orig_epoll_wait(epfd, events, maxevents, timeout);

    // 再次恢復函數的Hook
    if (counter) {

        // 再次恢復目標函數的Hook
        hook_postcall(&eph);
        log("epoll_wait() called\n");

        counter--;
        // 當counter=0,說明執行一次函數Hook之後,Hook即將被移除
        if (!counter)
            log("removing hook for epoll_wait()\n");
    }

    return res;
}


// 在lib庫文件被加載注入的時候會執行,用以實現對目標pid進程目標函數的inline Hook,對所有進程都起作用
void my_init(void)
{
    // 設置被Hook目標函數的Hook次數
    counter = 3;

    log("%s started\n", __FILE__)

    // 設置消息日誌打印到的日誌文件
    set_logfunction(my_log);

    // 實現對目標pid進程的指定庫文件的目標函數進行Hook處理。
    // arm指令模式的Hook函數--my_epoll_wait_arm
    // thumb指令模式的Hook函數--my_epoll_wait
    // eph存放Hook函數的Hook信息結構體
    hook(&eph, getpid(), "libc.", "epoll_wait", my_epoll_wait_arm, my_epoll_wait);
}

  • epoll_arm.c文件,主要用於Arm指令模式下被inline Hook的”epoll_wait”函數的自定義替換函數,因爲ndk默認編譯方式的函數爲Thumb指令模式的函數。
/*
 *  Collin's Binary Instrumentation Tool/Framework for Android
 *  Collin Mulliner <collin[at]mulliner.org>
 *  http://www.mulliner.org/android/
 *
 *  (c) 2012,2013
 *
 *  License: LGPL v2.1
 *
 */

#include <sys/types.h>
#include <sys/epoll.h>

extern int my_epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

// arm指令模式的Hook函數的執行
int my_epoll_wait_arm(int epfd, struct epoll_event *events, int maxevents, int timeout)
{
    return my_epoll_wait(epfd, events, maxevents, timeout);
}


三、adbi源碼的完整編譯

前面的部分一直在分析adbi源碼的實現,下面就來學習一下adbi源碼的編譯和運行。有關adbi源碼的編譯和運行可以參考adbi源碼官方提供的使用說明,參考地址:https://github.com/crmulliner/adbi/blob/master/README.md。

1.root權限下,Android跨進程so注入工具 hijack的編譯。

  • Android跨進程so注入工具 hijack的編譯配置文件Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# 編譯生成的模塊的名稱
LOCAL_MODULE    := hijack 
LOCAL_SRC_FILES := ../hijack.c 

# 將源碼編譯成arm指令集模式
LOCAL_ARM_MODE := arm
# 在編譯生成的可執行程序中包含標準調試信息
LOCAL_CFLAGS := -g

# 將源碼編譯成ELF可執行文件
include $(BUILD_EXECUTABLE)
  • Android跨進程so注入工具 hijack的編譯步驟
cd hijack
cd jni
ndk-build

Android跨進程so注入工具 hijack編譯成功的示意圖:

這裏寫圖片描述

2.adbi的inline Hook實現的基礎工具instruments\base的編譯和生成libbase.a靜態庫文件。

  • adbi的inline Hook工具instruments\base的編譯配置文件Android.mk和Application.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# 編譯生成的模塊的名稱
LOCAL_MODULE    := base
LOCAL_SRC_FILES := ../util.c ../hook.c ../base.c

# 將源碼編譯成arm指令集模式
LOCAL_ARM_MODE := arm

# 編譯生成靜態庫文件
include $(BUILD_STATIC_LIBRARY)
# Application.mk
APP_MODULES := base
  • adbi的inline Hook工具instruments\base的編譯步驟
cd instruments
cd base
cd jni
ndk-build

adbi的inline Hook工具instruments\base編譯成功的示意圖:

這裏寫圖片描述

3.adbi的inline Hook實踐,實現Hook掉Android系統的libc.so庫文件的epoll_wait函數用以被注入到目標進程中加載的so庫文件libexample.so的編譯。

  • 被注入到目標進程中實現inline Hook掉android系統的epoll_wait函數的動態庫文件libexample.so的編譯配置文件Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := base
LOCAL_SRC_FILES := ../../base/obj/local/armeabi/libbase.a
# 導出當前模塊的頭文件所在路徑,供其他模塊使用
LOCAL_EXPORT_C_INCLUDES := ../../base

# 生成預編譯靜態庫文件
include $(PREBUILT_STATIC_LIBRARY)

# 清除宏變量的信息
include $(CLEAR_VARS)
#由原來的libexample生成模塊名稱改爲example
LOCAL_MODULE    := example
# 編譯生成arm、thumb模式的函數調用
LOCAL_SRC_FILES := ../epoll.c  ../epoll_arm.c.arm
# 編譯生成的模塊帶有標準的調試信息
LOCAL_CFLAGS := -g
# 需要依賴加載的動態庫libdl.so
LOCAL_SHARED_LIBRARIES := dl
# 需要依賴加載的靜態庫libbase.a
LOCAL_STATIC_LIBRARIES := base

# 編譯生成動態庫文件
include $(BUILD_SHARED_LIBRARY)
  • 被注入到目標進程中實現inline Hook掉android系統的epoll_wait函數的動態庫文件libexample.so的編譯步驟
cd example
cd jni
ndk-build

被注入到目標進程中實現inline Hook掉android系統的epoll_wait函數的動態庫文件libexample.so編譯成功的示意圖:

這裏寫圖片描述

四、adbi的inline Hook的運行

有關adbi源碼的編譯和執行可以參考官方的文檔:https://github.com/crmulliner/adbi/blob/master/README.md

在上面的步驟中已經完成了對adbi源碼的完整編譯,再結合adbi官方文檔描述的運行參考,下面給出adbi運行的完整步驟:

$ adb push ./hijack /data/local/tmp/
$ adb shell chmod 0777 /data/local/tmp/hijack

$ adb push ./libexample.so /data/local/tmp/
$ adb shell chmod 0777 /data/local/tmp/libexample.so

$ pause

$ adb shell
$ su
$ cd /data/local/tmp

# log日誌打印的重定位
$ > /data/local/tmp/adbi_example.log

# GET PID from com.android.phone(用com.android.phone作爲目標進程進行so注入)
$ ps | grep com.android.phone

# inject so and inline Hook
$ ./hijack -d -p PID -l /data/local/tmp/libexample.so

# 查看自定義打印的log日誌
$ cat ./adbi_example.log

# 查看被so注入的目標進程的內存佈局
$ cat /pro/pid/maps

在LG G3手機(Android 4.4.2系統)上進行adbi的root權限下的跨進程so庫注入和inline Hook操作,選取LG G3手機設備上的 com.android.phone 作爲目標進程進行so庫文件的注入和inline Hook,具體的結果如下圖所示:

這裏寫圖片描述
這裏寫圖片描述

從adbi工具在LG G3上的運行結果來分析,hijack注入工具注入libexample.so動態庫文件到目標進程com.android.phone中是成功的,但是inline Hook代碼執行的log日誌卻沒有打印出來,測試了幾次也還是沒有打印出來,後來換了一種打印log日誌的方法看到了hook函數被執行的log日誌了,有時間再研究一下log日誌打印的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章