Android Hook框架adbi的分析(3)---编译和inline Hook实践
阅读原文时间:2021年09月06日阅读:2

本文博客地址: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
      *
    • Elf parsing code slightly modified for this project
    • (c) Collin Mulliner
      *
    • License: LGPL v2.1
      *
    • Termios code taken from glibc with slight modifications for this project
      *
      */
      #define _XOPEN_SOURCE 500
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include

    #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 &gt; rv) {
        //perror("read");
        return -1;
    }
    // 判断内存布局信息是否读取完了
    if (0 == rv)
        break;
    
    // 修改指向内存缓冲区raw中的指针偏移p
    p += rv;
    
    // 判断是否超过内存缓冲区范围
    if (p-raw &gt;= 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", &amp;start, &amp;end, name);
    
    // 继续分割字符串
    p = strtok(NULL, "\n");
    
    // sscanf函数前两个字段start、end匹配成功的情况即没有名称的情况
    if (rv == 2) {
    
        m = &amp;mm[nmm++];
        // 内存布局起始地址
        m-&gt;start = start;
        // 内存布局结束地址
        m-&gt;end = end;
        // 设置默认内存布局名称为"[memory]"
        strcpy(m-&gt;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 &gt;= 0; i--) {
    
        m = &amp;mm[i];
        if (!strcmp(m-&gt;name, name))
            break;
    }
    
    // 进行相同名称的内存布局起始地址和结束地址的合并
    if (i &gt;= 0) {
    
        if (start &lt; m-&gt;start)
            m-&gt;start = start;
        if (end &gt; m-&gt;end)
            m-&gt;end = end;
    
    } else {
    
        // 内存起始地址、内存结束地址、内存布局名称
        m = &amp;mm[nmm++];
    
        // 内存起始地址
        m-&gt;start = start;
        // 内存结束地址
        m-&gt;end = end;
        // 内存布局名称
        strcpy(m-&gt;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-&gt;name, MEMORY_ONLY))
        continue;
    
    // 从右开始搜索'/'符号,获取内存布局的名称
    // 例如/system/lib/libdl.so,获取名称libdl.so
    p = strrchr(m-&gt;name, '/');
    // 跳过不符合要求的情况
    if (!p)
        continue;
    
    // 判断获取到的lib库名称是否是要查找的目标lib库名称libn
    p++;
    if (strncmp(libn, p, strlen(libn)))
        continue;
    
    // 获取查找的例如:"libc."的长度
    p += strlen(libn);
    
    /* here comes our crude test -&gt; 'libc.so' or 'libc-[0-9]' */
    // 作者并没有使用
    if (!strncmp("so", p, 2) || 1) // || (p[0] == '-' &amp;&amp; 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-&gt;c_iflag &amp; ~IBAUD0;
    k_termios.c_oflag = termios_p-&gt;c_oflag;
    k_termios.c_cflag = termios_p-&gt;c_cflag;
    k_termios.c_lflag = termios_p-&gt;c_lflag;
    k_termios.c_line = termios_p-&gt;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, &amp;k_termios);
    
    if (retval == 0 &amp;&amp; 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, &amp;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-&gt;c_cflag &amp; (PARENB | CREAD))
        != (k_termios.c_cflag &amp; (PARENB | CREAD))
        || ((termios_p-&gt;c_cflag &amp; CSIZE)
            &amp;&amp; ((termios_p-&gt;c_cflag &amp; CSIZE)
            != (k_termios.c_cflag &amp; 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;</code></pre>

    }

    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 (&amp;termios_p-&gt;c_cc[0], &amp;k_termios.c_cc[0],
                __KERNEL_NCCS * sizeof (cc_t)),
            _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * sizeof (cc_t));
        #endif
        memset ( (memcpy (&amp;termios_p-&gt;c_cc[0], &amp;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 (&amp;termios_p-&gt;c_cc[0], &amp;k_termios.c_cc[0],
            __KERNEL_NCCS * sizeof (cc_t));
    
        for (cnt = __KERNEL_NCCS; cnt &lt; NCCS; ++cnt)
            termios_p-&gt;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
    • http://www.mulliner.org/android/
      *
    • (c) 2012,2013
      *
    • License: LGPL v2.1
      *
      */
      #define _GNU_SOURCE
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include

    #include

    #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-&gt;thumb = 0;
    // 自己实现的Hook函数地址
    h-&gt;patch = (unsigned int)hook_arm;
    // 被Hook目标函数的原函数地址
    h-&gt;orig = addr;
    
    // 用于Hook目标函数的调用地址为新地址hook_arm
    h-&gt;jump[0] = 0xe59ff000; // LDR pc, [pc, #0]
    h-&gt;jump[1] = h-&gt;patch;
    // pc寄存器读出的值实际上是当前指令地址加8
    // 把jump[2]的值加载进pc寄存器
    h-&gt;jump[2] = h-&gt;patch;
    
    // 保存原目标函数的12字节指令,用于函数的恢复
    for (i = 0; i &lt; 3; i++)
        h-&gt;store[i] = ((int*)h-&gt;orig)[i];
    
    // 覆盖目标函数的12字节指令为Hook函数指令,实现对目标函数的Hook
    for (i = 0; i &lt; 3; i++)
        ((int*)h-&gt;orig)[i] = h-&gt;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-&gt;thumb = 1;
    log("THUMB using 0x%lx\n", (unsigned long)hook_thumb)
    
    // 保存用于Hook目标函数的调用地址为新地址hook_thumb
    h-&gt;patch = (unsigned int)hook_thumb;
    // 保存被Hook目标函数的原函数地址
    h-&gt;orig = addr; 
    
    // 保存寄存器r5,r6的值用于恢复环境
    h-&gt;jumpt[1] = 0xb4;
    h-&gt;jumpt[0] = 0x60; // push {r5,r6}</code></pre>

    // 将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 &lt; 20; i++) {
    
        h-&gt;storet[i] = ((unsigned char*)orig)[i];
        //log("%0.2x ", h-&gt;storet[i])
    }
    //log("\n")
    
    // 覆盖被Hook目标函数的指令为自定义的Hook函数指令
    for (i = 0; i &lt; 20; i++) {
    
        ((unsigned char*)orig)[i] = h-&gt;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-&gt;orig - 1;
    // 进行thumb指令模式被Hook指令的恢复
    for (i = 0; i &lt; 20; i++) {
    
        ((unsigned char*)orig)[i] = h-&gt;storet[i];
    }
    } else {
    // 进行arm指令模式被Hook指令的恢复
    for (i = 0; i &lt; 3; i++){
    
        ((int*)h-&gt;orig)[i] = h-&gt;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-&gt;orig - 1;
    // 进行thumb指令模式Hook目标函数指令的覆盖
    for (i = 0; i &lt; 20; i++)
        ((unsigned char*)orig)[i] = h-&gt;jumpt[i];
    } else {
    // 进行arm指令模式Hook目标函数指令的覆盖
    for (i = 0; i &lt; 3; i++)
        ((int*)h-&gt;orig)[i] = h-&gt;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
    • http://www.mulliner.org/android/
      *
    • (c) 2012,2013
      *
    • License: LGPL v2.1
      *
      */
      #define _GNU_SOURCE
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include
      #include

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

    #undef log

    // 打印日志消息到"/data/local/tmp/adbi_example.log"文件的宏
    #define log(…) <br /> {FILE *fp = fopen("/data/local/tmp/adbi_example.log", "a+"); if (fp) {<br /> fprintf(fp, VA_ARGS);<br /> 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(&amp;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指令模式的函数。

    /*

    #include
    #include

    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日志打印的问题。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器