本文博客地址: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.sop = raw;while (1) {// 分行读取目标pid进程的内存布局信息rv = read(fd, p, sizeof(raw)-(p-raw));if (0 > rv) {//perror("read");return -1;}// 判断内存布局信息是否读取完了if (0 == rv)break;// 修改指向内存缓冲区raw中的指针偏移pp += 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.sofor (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 ofits name in NAME and set START to its startingaddress.  If libc cannot be found return -1 andleave NAME and START untouched.  Otherwise return 0and 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.sop = strrchr(m->name, '/');// 跳过不符合要求的情况if (!p)continue;// 判断获取到的lib库名称是否是要查找的目标lib库名称libnp++;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库libnif (i >= nmm)/* not found */return -1;// 获取指定lib库文件的内存的起始地址*start = m->start;// 保存查找到的目标lib库文件的路径字符串m->namestrncpy(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)
{// 在动态系统符号表中查找获取目标函数的RVAif (s->dyn && !lookup2(s->dyn, type, name, val))return 0;// 在静态系统符号表中查找获取目标函数的RVAif (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_ISPEEDk_termios.c_ispeed = termios_p->c_ispeed;#endif#ifdef _HAVE_C_OSPEEDk_termios.c_ospeed = termios_p->c_ospeed;#endifmemcpy (&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 invalidc_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 returnan 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 thePARENB/CREAD/CSIZE bits in c_cflag. Report it as anerror. *///__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_ISPEEDtermios_p->c_ispeed = k_termios.c_ispeed;#endif#ifdef _HAVE_C_OSPEEDtermios_p->c_ospeed = k_termios.c_ospeed;#endifif (sizeof (cc_t) == 1 || _POSIX_VDISABLE == 0|| (unsigned char) _POSIX_VDISABLE == (unsigned char) -1){#if 0memset (mempcpy (&termios_p->c_cc[0], &k_termios.c_cc[0],__KERNEL_NCCS * sizeof (cc_t)),_POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * sizeof (cc_t));#endifmemset ( (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字节Hookunsigned int jump[3];   /* 要修改的hook指令(Arm) */unsigned int store[3]; /* 被修改的原指令(Arm) */// thumb指令模式的20字节Hookunsigned 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即addrif (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_armh->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函数指令,实现对目标函数的Hookfor (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_thumbh->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 addressunsigned 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_waitint res = orig_epoll_wait(epfd, events, maxevents, timeout);// 再次恢复函数的Hookif (counter) {// 再次恢复目标函数的Hookhook_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日志打印的问题。

Android Hook框架adbi的分析(3)---编译和inline Hook实践相关推荐

  1. Android平台dalvik模式下java Hook框架ddi的分析(2)--dex文件的注入和调用

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/77942585 前面的博客<Android平台dalvik模式下java Ho ...

  2. Android平台dalvik模式下java Hook框架ddi的分析(1)

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/75710411 一.前 言 在前面的博客中已经学习了作者crmulliner编写的, ...

  3. java上传ddi_Android平台dalvik模式下java Hook框架ddi的分析(2)--dex文件的注入和调用...

    前面的博客<Android平台dalvik模式下java Hook框架 ddi 的分析(1)>中,已经分析了dalvik模式下 ddi 框架Hook java方法的原理和流程,这里来学习一 ...

  4. Android平台下hook框架adbi的研究(下)

    上篇中,我大致介绍了一下如何将一个dlopen()的调用插入到指定进程的执行序列中去. 但是,光插入这个没用,还没有具体解决如何hook进程中指定函数的问题.这个任务就要交给dlopen()函数加载进 ...

  5. Android免Root环境下Hook框架Legend原理分析

    0x1 应用场景 现如今,免Root环境下的逆向分析已经成为一种潮流! 在2015年之前的iOS软件逆向工程领域,要想对iOS平台上的软件进行逆向工程分析,越狱iOS设备与安装Cydia是必须的!几乎 ...

  6. android图形框架之surfaceflinger分析(一)

    1. 概念 surfaceflinger作用是接受多个来源的图形显示数据,将它们合成,然后发送到显示设备.比如打开应用,常见的有三层显示,顶部的statusbar底部或者侧面的导航栏以及应用的界面,每 ...

  7. Android 输入法框架源码分析总结(1)

    1 IMF主要包含三个部分 1.InputmethodMethodManager(IMM)运行于客户端进程 - -Input Method Manager(IMM)是负责管理其他部分交互的中心,以cl ...

  8. android hook 第三方app_基于 VirtualApp 结合 whale hook框架实现hook第三方应用

    要点 1. whale hook framework 使用示例: 2. 参考项目:VirtualHook: 3. 按照 VirtualHook 修改 VirtualApp: 4. 编写 hook pl ...

  9. Android平台下Dalvik层hook框架ddi的研究

    通过adbi,可以对native层的所有代码进行hook.但对于Android系统来说,这还远远不够,因为很多应用都还是在Dalvik虚拟机中运行的. 那么,有没有什么办法可以对Dalvik虚拟机中跑 ...

  10. Android以太网框架情景分析之NetworkFactory与NetworkAgent深入分析

    Android以太网框架情景分析之NetworkFactory与NetworkAgent深入分析 Android网络框架分析系列文章目录: Android P适配以太网功能开发指南 Android以太 ...

最新文章

  1. mongo报错:not authorized on bb to execute command { create: \“xxx\“...}
  2. weka分类器怎么设置样本类别_自步对比学习: 充分挖掘无监督学习样本
  3. Request_获取请求行数据_方法介绍
  4. 测试回收站测试回收站测试回收站测试回收站测试回收站测试回收站测试回收站测试回收站
  5. NET 4.0 System.Threading.Tasks学习笔记
  6. 杂:(4)NE555
  7. 软件部署——nvidia-docker的学习笔记
  8. linux usb 同步传输,从设备到主机的用户模式USB等时传输
  9. 人工智能兼职讲师内训讲师叶梓对sony技术部进行CV计算机视觉培训
  10. 排名前十的天使投资机构
  11. 系统之家启动维护光盘v3.0[小盘·贺岁篇]
  12. ES6_1.块级作用域绑定_临时死区TDZ
  13. Avalonia UI 简介
  14. BZOJ 1066 POJ 2711 [SCOI2007]蜥蜴
  15. Postgresql - 查询表引用或被引用的外键
  16. 国家药监局打印不了(打印插件一直弹框)
  17. Dubbo项目消费者调用提供者报cannot be cast to com.baomidou.mybatisplus.core.metadata.IPage
  18. python谁是卧底游戏流程图_谁是卧底游戏题目
  19. oracle商品当日销售排行,Oracle零售 (Retek)品类管理.ppt
  20. 如何查看AD域账号的删除记录

热门文章

  1. 【华为交换机】STP生成树协议端口选举详解
  2. jsoup 网页抓取简介详解
  3. 英特尔hd630驱动_intel UHD graphics 620/630 -win7 驱动
  4. Android studio配置Google play服务
  5. IIS 漏洞工具解析
  6. python数据分析与应用-Python数据分析与应用 PDF 内部全资料版
  7. 狂团KtAdmin框架正式免费开源发布,助力独立版SAAS系统快速开发
  8. 当您尝试加入域时,出现“Network Location Cannot be Reached”(不能访问网络位置)错误信息...
  9. CDN技术详解(电子书)下载链接
  10. 大腿神经网络解剖图片,大腿神经网络解剖图谱