adbi学习:so hook实现机制

本篇我们来看看adbi的实现原理,其实里面的知识点前面差不多都有涉及了,没多少新知识。adbi利用hijack程序将libexample.so注入到指定的进程中,并且在进程中加载libexample.so;而libexample.so在加载过程中会执行其.init_array section里的代码,代码中实现函数hook(替换原先的函数为自定义函数)。这样运行hijack就自动实现了函数hook。

  hijack流程图:

    1.得到pid进程下的mprotect()函数地址

    2.得到piid进程下的dlopen()函数地址

    3.利用ptrace attach pid进程,并得到regs并保存

    4.将sc结构体push进pin 进程的栈空间中;sc包含特定指令、regs、mprotect、dlopen和libexample.so的绝对路径

    5.修改regs值并设置到pin进程,接着PTRACE_DETACH释放pid进程,使之得以继续运行;此时hijack已全部执行完毕

    6.此时pid进程会去执行先前压入栈中的特定指令:

  这里涉及到2个知识点:

    1.如何得到pid进程下的指定的fun函数地址:

    假设fun在lib.so中,首先得到本进程lib.so的地址addrLib(如何得到加载的so库地址:/proc/pid/maps存储共享库的内存地址),接着得到本进程fun地址addrFun,再得到pin进程的addrPidLib地址,则pid下的fun地址=addrFun-addrLib+addrPidLib;另外一种方式是根据so中dynamic section得到dynsym和dynstr section,利用dynsym->name作为在dynstr的字符得到字符串str1和fun比较,即可得到fun的地址(具体参考之前的文章:elf格式和linker源码分析)。

    2.特定指令究竟是什么,它干了什么事?指令如下:

    特定指令用mprotect改变page读写权限;dlopen加载libexample.so;利用保存的regs恢复attach 前的pid进程状态。至于这些指令的细节看参考资料1,这里就不展开了。需要提及的是arm_pc这个寄存器,在hijack修改了pid进程的pc寄存器,使pid进程在DETACH后直接执行压入栈中的特定指令。但我们知道pc是取指令的地址,arm架构中在执行指令和取指令中还有译码,怎么会在改变pc值后直接去执行pc所执行地址的指令呢?网上是说在修改pc值后,先前的流水线(取指令、译码、执行)丢弃,然后从pc出开始取指令、译码、执行。没查到arm官方资料,望知情人告知!

  libexample执行流程:

    1.得到要hook函数hookedfun的地址;如何得到函数地址看上面方法1

    2.修改hookedfun函数指令前几个指令为特定指令,使hookedfun函数替换成自定义的函数

    3.在进程执行hookedfun函数时,会去执行自定义函数而不是hookedfun函数;再次修改hookedfun函数指令来卸载函数hook

  这里的知识点主要在于如何替换hookedfun中涉及到的汇编指令。arm分为arm指令和thum指令,在adbi中这么判断的:

if (addr % 4 == 0) {
    arm指令
}else {
     thumb指令
}

   接着arm指令涉及到的替换函数汇编指令:

h->patch = (unsigned int)hook_arm; //自定义函数的地址
h->orig = addr;      //hookedfun函数的地址
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];  //保存hookedfun函数的指令
for (i = 0; i < 3; i++)
    ((int*)h->orig)[i] = h->jump[i];    //修改hookedfun函数的指令,当调用hookedfun时,执行h->jump[0]

  看上面代码,执行hookedfun时其实是执行LDR pc,[pc,#0]。我们知道pc值为当前执行的指令+8,即pc值为h->jump[2] = 自定义函数的值。ok,这相当于就是jump去执行自定义函数了。下面看thumb汇编指令:

if ((unsigned long int)hook_thumb % 4 == 0)
            log("warning hook is not thumb 0x%lx\n", (unsigned long)hook_thumb)
        h->thumb = 1;
        log("THUMB using 0x%lx\n", (unsigned long)hook_thumb)
        h->patch = (unsigned int)hook_thumb;
        h->orig = addr;
        h->jumpt[1] = 0xb4;
        h->jumpt[0] = 0x60; // push {r5,r6}
        h->jumpt[3] = 0xa5;
        h->jumpt[2] = 0x03; // add r5, pc, #12; 这里r5实质是指向jumpt[18]那为什么会说其执行自定义函数地址junpt[16]呢?
        h->jumpt[5] = 0x68;
        h->jumpt[4] = 0x2d; // ldr r5, [r5]
        h->jumpt[7] = 0xb0;
        h->jumpt[6] = 0x02; // add sp,sp,#8
        h->jumpt[9] = 0xb4;
        h->jumpt[8] = 0x20; // push {r5}
        h->jumpt[11] = 0xb0;
        h->jumpt[10] = 0x81; // sub sp,sp,#4
        h->jumpt[13] = 0xbd;
        h->jumpt[12] = 0x20; // pop {r5, pc}
        h->jumpt[15] = 0x46;
        h->jumpt[14] = 0xaf; // mov pc, r5 ; just to pad to 4 byte boundary
        memcpy(&h->jumpt[16], (unsigned char*)&h->patch, sizeof(unsigned int));  //存自定义函数地址到jumpt[16]——jumpt[19]
        unsigned int orig = addr - 1; // sub 1 to get real address        注意这里减1了,thumb的函数被编译后其函数符号地址都会在真正地址+1,这是为了辨别thumb函数还是arm函数,arm函数4字节对齐最低位永远为0
        for (i = 0; i < 20; i++) {
            h->storet[i] = ((unsigned char*)orig)[i];
            //log("%0.2x ", h->storet[i])
        }
        //log("\n")
        for (i = 0; i < 20; i++) {
            ((unsigned char*)orig)[i] = h->jumpt[i];
            //log("%0.2x ", ((unsigned char*)orig)[i])
        }

    利用栈的push和pop将保存自定义函数地址(jump[16])的r5赋值为pc,具体原理看参考资料2。但下面这段话需要注意

这里还有一点需要注意,对于Thumb的“Add Rd, Rp, #expr”指令来说,如果Rp是PC寄存器的话,那么PC寄存器读出的值应该是(当前指令地址+4)& 0xFFFFFFFC,也就是去掉最后两位,算下来正好可以减去2。但这里也有个假设,就是被hook函数的起始地址必须是4字节对齐的,哪怕被hook函数使用Thumb指令集写的。

  也就说当被hook函数4字节对齐时,add r5, pc, #12这条指令的地址刚好是2字节对齐,那么根据上面这段话刚好可以减2即r5指向的是jumpt[16]而不是jumpt[18],所以这对hook函数有要求。替换函数的指令讲解完毕,卸载hook自然就清楚了——把原来改变的指令复原就ok了。我们是改变了函数指令达到替换函数的效果。但处理都包含指令cache,若执行的时候从cache中取呢,那我就再刷新下cache;在这里我们用系统调用号的方式来执行:

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"
        );
}

  r0=begin,r1=end,r7=0xf0002(cacheflush的系统调用号),直接svc来执行系统调用。

  hijack和libexample的流程就这样了,但怎么从hijack到libexample啊?

// this file is going to be compiled into a thumb mode binary
// 这里是重点,当进程第一次打开lib操作时,linker会执行此函数
void __attribute__ ((constructor)) my_init(void);

  还记得hijack执行完毕后,pid进程执行的指令吗;它会去执行dlopen(libexample),此时回去执行libexample中的.init_array中的指令;而加了"__attribute__ ((constructor))"则my_init就是在.init_array中。知道了吧,在dlopen中执行my_init,而在my_init中实现函数的替换即hook。当然my_init的函数可以不同,但必须有在.init_array中的函数去执行函数替换功能(对于adbi来说是调用hook())。

  最后我们看看libinject:

  1.利用ptrace来注入pid进程

  2.得到pid进程中的函数地址,用的是上面第一种方法:fun地址=addrFun-addrLib+addrPidLib

  3.libinject不注入so没实现函数挂钩,它实现了hijack的功能,但是它不仅仅是dlopen还在这里执行了自定义函数(实现方式与adbi相同)

  下篇我们来看看adbi是如何实现dalvik hook

参考资料:

  1 Android平台下hook框架adbi的研究(上)

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

时间: 2024-10-18 06:40:55

adbi学习:so hook实现机制的相关文章

php中的钩子(hook插件机制)

对"钩子"这个概念其实不熟悉,最近看到一个php框架中用到这种机制来扩展项目,所以大概来了解下. hook插件机制的基本思想: 在项目代码中,你认为要扩展(暂时不扩展)的地方放置一个钩子函数,等需要扩展的时候,把需要实现的类和函数挂载到这个钩子上,就可以实现扩展了. 思想就是这样听起来比较笼统,看一个网上的实现的例子. 整个插件机制包含三个部分: 1.hook插件经理类:这个是核心文件,是一个应用程序全局Global对象.它主要有三个职责 1>监听已经注册了的所有插件,并实例化这

Android 进阶学习:事件分发机制全然解析,带你从源代码的角度彻底理解(上)

http://blog.csdn.net/guolin_blog/article/details/9097463 事实上我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客開始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比方:onTouch和onTouchEvent有什么差别,又该怎样使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?

九、Android学习第八天——广播机制与WIFI网络操作(转)

(转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 九.Android学习第八天——广播机制与WIFI网络操作 今天熟悉了Android中的广播机制与WIFI网络的一些基本操作,总结如下: Android的广播机制 我们知道广播机制中,发送方不会关心接收方时候接收到数据或者如何去处理数据. 这里总结下Android中BroadcastReceiver的注册方法: (一)在应用程序中进行注册 (二)在Manifest.xml

linux网络编程学习笔记之五 -----并发机制与线程?

进程线程分配方式 简述下常见的进程和线程分配方式:(好吧,我仅仅是举几个样例作为笔记...并发的水太深了,不敢妄谈...) 1.进程线程预分配 简言之,当I/O开销大于计算开销且并发量较大时,为了节省每次都要创建和销毁进程和线程的开销.能够在请求到达前预先进行分配. 2.进程线程延迟分配 预分配节省了处理时的负担,但操作系统管理这些进程线程也会带来一定的开销.由此,有个折中的方法是,当某个处理须要花费较长时间的时候,我们创建一个并发的进程或线程来处理该请求.实现也非常easy,在主线程中定时,定

linux网络编程学习笔记之五 -----并发机制与线程池

进程线程分配方式 简述下常见的进程和线程分配方式:(好吧,我只是举几个例子作为笔记...并发的水太深了,不敢妄谈...) 1.进程线程预分配 简言之,当I/O开销大于计算开销且并发量较大时,为了节省每次都要创建和销毁进程和线程的开销.可以在请求到达前预先进行分配. 2.进程线程延迟分配 预分配节省了处理时的负担,但操作系统管理这些进程线程也会带来一定的开销.由此,有个折中的方法是,当某个处理需要花费较长时间的时候,我们创建一个并发的进程或线程来处理该请求.实现也很简单,在主线程中定时,定时到期,

学习java虚拟机 - 类加载机制

学习java虚拟机 - 类加载机制  一.是什么 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 在Java语言里面,类型的加载.链接.初始化过程都是在程序运行期间完成的,Java里天生可以动态扩展的语言特性就是依赖运行期间动态加载和动态连接这个特点实现的.例如,如果编写一个面向接口的应用程序,可以等到运行时在制定实际的实现类:用户可以通过Java预定义的和自定义类加载器,让一个本地的应用程序

[转] 深度学习中的注意力机制

from: https://zhuanlan.zhihu.com/p/37601161 注意力模型最近几年在深度学习各个领域被广泛使用,无论是图像处理.语音识别还是自然语言处理的各种不同类型的任务中,都很容易遇到注意力模型的身影.所以,了解注意力机制的工作原理对于关注深度学习技术发展的技术人员来说有很大的必要. 人类的视觉注意力 从注意力模型的命名方式看,很明显其借鉴了人类的注意力机制,因此,我们首先简单介绍人类视觉的选择性注意力机制. 图1 人类的视觉注意力 视觉注意力机制是人类视觉所特有的大

adbi学习一:安装和使用

adbi 是一个android平台(arm 32 )的so注入+挂钩框架,源码开放在github上 :  ADBI 项目 .从github上下载来目录如下: 执行主目录下build.sh编译后目录如下,前提是你有在环境变量下设置ndk-build路径,不知道ndk-build是啥的说明还没下载过NDK哦. 对比编译前的目录很容易看出多了obj目录.so库.静态库和可执行文件,具体为何如此请学习各自目录下的Android.mk hijack下的Android.mk——生成可执行文件hijack L

tornado 学习之 cookie 验证机制详解

本文和大家分享的主要是tornado中cookie 验证机制相关内容,一起来看看吧,希望对大家 学习tornado有所帮助. 处理过程简单来说就是验证密码之后服务器端(tornado) 返回带有  cookie  信息的  Set-Cookie header 给客户端 ,  之后客户端发起请求时会把此  cookie  放入  Cookie header  中发给服务器端. tornado 设置 cookie 首先是对 cookie  的变量进行设置 , Morsel  是含有几个特殊  key