• 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
    ");
            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
    ");
            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
    ");
            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
    ")
            goto out;
        }
        if (rv != sizeof(ehdr)) {
            log("elf error 1
    ")
            goto out;
        }
        if (strncmp(ELFMAG, ehdr.e_ident, SELFMAG)) { /* sanity */
            log("not an elf
    ")
            goto out;
        }
        if (sizeof(Elf32_Shdr) != ehdr.e_shentsize) { /* sanity */
            log("elf error 2
    ")
            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
    ")
            goto out;
        }
        if (rv != size) {
            log("elf error 3 %d %d
    ", 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
    ")
            goto out;
        }
        if (rv != size) {
            log("elf error 4 %d %d
    ", 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
    ")
                    goto out;
                }
                symh = p;
            } else if (SHT_DYNSYM == p->sh_type) {
                if (dynsymh) {
                    log("too many symbol tables
    ")
                    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
    ")
                    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
    ")
                    goto out;
                }
                dynstrh = p;
            }
        /* sanity checks */
        if ((!dynsymh && dynstrh) || (dynsymh && !dynstrh)) {
            log("bad dynamic symbol table
    ")
            goto out;
        }
        if ((!symh && strh) || (symh && !strh)) {
            log("bad symbol table
    ")
            goto out;
        }
        if (!dynsymh && !symh) {
            log("no symbol table
    ")
            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
    ", __func__);
            return NULL;
        }
    
        if (0 > do_load(fd, symtab)) {
    
            log("Error ELF parsing %s
    ", 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
    ", 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
    ");
                return -1;
            }
        }
    
        // 关闭文件
        close(fd);
    
        // 分割字符串
        p = strtok(raw, "
    ");
        m = mm;
        while (p) {
    
            // 根据格式解析每一行内存布局信息
            // rv = sscanf函数都将返回成功转换并分配的字段数
            rv = sscanf(p, "%08lx-%08lx %*s %*s %*s %*s %s
    ", &start, &end, name);
    
            // 继续分割字符串
            p = strtok(NULL, "
    ");
    
            // 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] = '';
    
        // 修改指定内存区域内存属性为可读可写可执行
        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
    ", 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
    ")
            return -1;
        }
    
        // 获取需要查找的目标lib库libn的内存基地址libcaddr并获取保存libn的全路径字符串
        if (0 > find_libname(libn, libc, sizeof(libc), &libcaddr, mm, nmm)) {
    
            log("cannot find lib: %s
    ", libn)
            return -1;
        }
        //log("lib: >%s<
    ", libc)
    
        // 打开查找到的lib目标库文件(路径字符串libc)解析该Elf文件
        // 获取该lib库文件的静态库和动态库的符号表信息".symtab"或者".dynsym".
        s = load_symtab(libc);
        if (!s) {
    
            log("cannot read symbol table
    ");
            return -1;
        }
    
        // 在目标lib库libn的静态库和动态库的符号表查找被Hook的目标函数的RVA即相对地址偏移
        if (0 > lookup_func_sym(s, name, addr)) {
    
            log("cannot find function: %s
    ", 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
    ")
            return -1;
        }
        if (0 > find_libname(libn, libc, sizeof(libc), &libcaddr, mm, nmm)) {
            log("cannot find lib
    ");
            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
    "          
            "mov     r1, %1
    "
            "mov     r7, %2
    "
            "mov     r2, #0x0
    "
            "svc     0x00000000
    "
            :
            :   "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
    ", addr)
        log("hookf = %lx
    ", (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
     don't match!
    ", addr, (unsigned long)hookf)
    
        //log("ARM
    ")
        h->thumb = 0;
        h->patch = (unsigned int)hookf;
        h->orig = addr;
        log("orig = %x
    ", 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
    ", 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
    ", (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
    ", (unsigned long)hook_thumb)
    
            // thumb指令模式
            h->thumb = 1;
            log("THUMB using 0x%lx
    ", (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("
    ")
    
            // 覆盖被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!
    ")
            return 0;
        }
    
        *coms = open("/dev/ptmx", O_RDWR|O_NOCTTY);
        if (*coms <= 0) {
            log("posix_openpt failed
    ")
            return 0;
        }
        //else
        //  log("pty created
    ")
        if (unlockpt(*coms) < 0) {
            log("unlockpt failed
    ")
            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);}}
    
    
    // 在adbiinstrumentsexampleepoll.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
    ");
    
            counter--;
            // 当counter=0,说明执行一次函数Hook之后,Hook即将被移除
            if (!counter)
                log("removing hook for epoll_wait()
    ");
        }
    
        return res;
    }
    
    
    // 在lib库文件被加载注入的时候会执行,用以实现对目标pid进程目标函数的inline Hook,对所有进程都起作用
    void my_init(void)
    {
        // 设置被Hook目标函数的Hook次数
        counter = 3;
    
        log("%s started
    ", __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实现的基础工具instrumentsase的编译和生成libbase.a静态库文件。

    • adbi的inline Hook工具instrumentsase的编译配置文件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工具instrumentsase的编译步骤
    cd instruments
    cd base
    cd jni
    ndk-build

    adbi的inline Hook工具instrumentsase编译成功的示意图:

    这里写图片描述

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

  • 相关阅读:
    定义类和类的实例化
    python学习第二天-文件的读写
    python学习第二天 pyhon字符串操作
    PYTHON的元组和字典丶深拷贝丶浅拷贝丶集合的常用操作方法
    python 数组的常识操作和切片
    Python字符串、集合练习_密码校验
    Python字典练习_注册校验
    获取随机的字符串
    PHP 阿拉伯数字转中文表述
    小程序发送验证码倒计时
  • 原文地址:https://www.cnblogs.com/csnd/p/11800610.html
Copyright © 2020-2023  润新知