编程中老有人问hook是什么

HOOK就是传说中的钩子,用于劫持消息,在windows中是这样的,因为win32程序是以消息机制为基础的,比如你点击鼠标,会给窗口传递一个消息,移动鼠标,会给窗口一个消息,用钩子可以比你的窗口先检测到这个消息,从而得到这个消息进行处理,你的窗口可能就处理不到这个消息了,要看你的钩子处理程序是否把这个消息传给窗口,具体看WIN32应用程序开发吧。

随着Android设备上的隐私安全问题越来越被公众重视,恶意软件对用户隐私,尤其是对电话、短信等私密信息的威胁日益突出,各大主流安全软件均推出了自己的隐私行为监控功能,在root情况下能有效防止恶意软件对用户隐私的窃取,那么这背后的技术原理是什么?我带着疑问开始一步步探索,如果要拦截恶意软件对电话、短信等API的调用,在Java或者Dalvik层面是不好进行的,因为这些层面都没有提供Hook的手段,而在Native层面,我认为可行的方案是对电话、短信的运行库so进行Hook(比如系统运行库\system\lib\libreference-ril.so或\system\lib\libril.so),如果注入自己的so到上述进程后,并通过dlopen()和dlsym()获取原有API地址,替换原有API地址为自己so中的API地址就可以达到Hook的目的。

  Hook的前提是进程注入,而Linux下最便捷的进程注入手段——ptrace,是大名鼎鼎的调试工具GDB的关键技术点;本文参考自Pradeep Padala于2002年的博文http://www.linuxjournal.com/article/6100(国内很多博客有这篇文章的译文,不过本着获取“一手”知识的想法,还是细读了原版英文,确实发现了一些翻译得不够到位的地方,在此还是推荐各位能读原文就不要读译文),由于02年时还是ia32(32位Intel Architecture)时代,时至今日,在我ia64也就是x64的机器已经无法运行了,所以自己动手实现了x64版本。代码主要功能是注入子进程的地址空间,Hook住子进程执行系统调用时的参数,并反转其参数,从而逆序输出ls命令的结果。

  代码如下:

  1 /*
  2   ptrace3.c
  3   author: pengyiming
  4   description:
  5   1, child process need be traced by father process
  6   2, father process reserve the result of "ls" command which executed by child process
  7 */
  8
  9 #include <stdio.h>
 10 #include <stdlib.h>
 11 #include <string.h>
 12 #include <sys/ptrace.h>
 13 #include <sys/types.h>
 14 #include <sys/wait.h>
 15 #include <sys/reg.h>
 16 #include <sys/user.h>
 17 #include <sys/syscall.h>
 18 #include <unistd.h>
 19
 20 #ifdef __x86_64__
 21
 22   #define OFFSET_UNIT 8
 23
 24 #else
 25
 26   #define OFFSET_UNIT 4
 27
 28 #endif
 29
 30 // converter long to char[]
 31 union
 32 {
 33   long rawData;
 34   char strData[sizeof(long)];
 35 } converter;
 36
 37 void getData(pid_t child, unsigned long long dataAddr, unsigned long long dataLen, char * const p_data)
 38 {
 39   // PEEKDATA counter
 40   int counter = 0;
 41   // PEEKDATA max count
 42   int maxCount = dataLen / sizeof(long);
 43   if (dataLen % sizeof(long) != 0)
 44   {
 45     maxCount++;
 46   }
 47   // moving pointer
 48   void * p_moving = p_data;
 49
 50   while (counter < maxCount)
 51   {
 52     memset(&converter, 0, sizeof(long));
 53     converter.rawData = ptrace(PTRACE_PEEKDATA, child, dataAddr + counter * sizeof(long), NULL);
 54     if (converter.rawData < 0)
 55     {
 56       perror("ptrace peek data error : ");
 57     }
 58
 59     memcpy(p_moving, converter.strData, sizeof(long));
 60     p_moving += sizeof(long);
 61     counter++;
 62   }
 63   p_data[dataLen] = ‘\0‘;
 64 }
 65
 66 void setData(pid_t child, unsigned long long dataAddr, unsigned long long dataLen, char * const p_data)
 67 {
 68   // POKEDATA counter
 69   int counter = 0;
 70   // POKEDATA max count
 71   int maxCount = dataLen / sizeof(long);
 72   // data left length (prevent out of range in memory when written)
 73   int dataLeftLen = dataLen % sizeof(long);
 74   // moving pointer
 75   void * p_moving = p_data;
 76
 77   // write part of data which align to sizeof(long)
 78   int ret;
 79   while (counter < maxCount)
 80   {
 81     memset(&converter, 0, sizeof(long));
 82     memcpy(converter.strData, p_moving, sizeof(long));
 83     ret = ptrace(PTRACE_POKEDATA, child, dataAddr + counter * sizeof(long), converter.rawData);
 84     if (ret < 0)
 85     {
 86       perror("ptrace poke data error : ");
 87     }
 88
 89     p_moving += sizeof(long);
 90     counter++;
 91   }
 92
 93   // write data left
 94   if (dataLeftLen != 0)
 95   {
 96     memset(&converter, 0, sizeof(long));
 97     memcpy(converter.strData, p_moving, dataLeftLen);
 98     ret = ptrace(PTRACE_POKEDATA, child, dataAddr + counter * sizeof(long), converter.rawData);
 99     if (ret < 0)
100     {
101       perror("ptrace poke data error : ");
102     }
103   }
104 }
105
106 void reverseStr(char * p_str)
107 {
108   int strLen = strlen(p_str);
109   char * p_head = p_str;
110   char * p_tail = p_str + strLen - 1;
111   char tempCh;
112
113   // skip ‘\n‘
114   if (*p_tail == ‘\n‘)
115   {
116     p_tail--;
117   }
118
119   //exchange char
120   while (p_head < p_tail)
121   {
122     tempCh = *p_head;
123     *p_head = *p_tail;
124     *p_tail = tempCh;
125
126     p_head++;
127     p_tail--;
128   }
129 }
130
131 void debugRegs(struct user_regs_struct * p_regs )
132 {
133   printf("syscall param DS = %llu\n", p_regs->ds);
134   printf("syscall param RSI = %llu\n", p_regs->rsi);
135   printf("syscall param ES = %llu\n", p_regs->es);
136   printf("syscall param RDI = %llu\n", p_regs->rdi);
137
138   printf("syscall return RAX = %llu\n", p_regs->rax);
139   printf("syscall param RBX = %llu\n", p_regs->rbx);
140   printf("syscall param RCX = %llu\n", p_regs->rcx);
141   printf("syscall param RDX = %llu\n", p_regs->rdx);
142 }
143
144 int main()
145 {
146   pid_t child = fork();
147   if(child == 0)
148   {
149     ptrace(PTRACE_TRACEME, 0, NULL, NULL);
150
151     // make a syscall(SYS_write)
152     execl("/bin/ls", "ls", NULL);
153   }
154   else
155   {
156     int status;
157     // SYS_write will be called twice, one is entry, another is exit, so we mark it
158     unsigned int calledCount = 0;
159
160     while (1)
161     {
162       wait(&status);
163       if (WIFEXITED(status))
164       {
165         break;
166       }
167
168       // PEEK regs to find the syscall(SYS_execve)
169       struct user_regs_struct regs;
170       ptrace(PTRACE_GETREGS, child, NULL, &regs);
171
172       // catch it!
173       if (regs.orig_rax == SYS_write)
174       {
175           if (calledCount == 0)
176           {
177             calledCount = 1;
178
179             // debugRegs(&regs);
180
181             char * p_dataStr = (char *) malloc((regs.rdx + 1) * sizeof(char));
182             if (p_dataStr == NULL)
183         {
184               return;
185         }
186
187             getData(child, regs.ds * OFFSET_UNIT + regs.rsi, regs.rdx, p_dataStr);
188             reverseStr(p_dataStr);
189             setData(child, regs.ds * OFFSET_UNIT + regs.rsi, regs.rdx, p_dataStr);
190           }
191           else if (calledCount == 1)
192           {
193             // debugRegs(&regs);
194           }
195       }
196
197       ptrace(PTRACE_SYSCALL, child, NULL, NULL);
198     }
199   }
200
201   return 0;
202 }

  代码执行结果:

  可以看到工程目录下的文件名均被反转输出,已达到想要的效果,那么接下来解释代码中的几个关键点:

  1,SYSCALL与orig_rax寄存器

  不论是ia32还是ia64,orig_rax寄存器都存放着每一次系统调用的ID,为了方便开发和调试,我们可以在/usr/include/x86_64-linux-gnu/sys/syscall.h中找到系统调用的定义,比如#define SYS_write __NR_write,但是我们无法得知__NR_write具体代表的ID,进一步搜索,可以在/usr/include/x86_64-linux-gnu/asm/unistd_64.h中找到ia64下对__NR_write的定义,#define __NR_write
1,这样一来我们打印出orig_rax寄存器中的值就可以判断此时子进程正在进行何种操作了。

  2,PTRACE_PEEKDATA与PTRACE_PEEKTEXT参数的选取

  Linux进程的地址空间不存在独立的数据段和代码段(或叫正文段),二者位于同一空间,所以上述两个参数并无实际意义上的区别,不过为了标识我们是在读取数据段中的数据,还是使用PTRACE_PEEKDATA比较好,同理对应于PTRACE_POKEDATA和PTRACE_POKETEXT。

  3,联合体converter

  由于执行PTRACE_PEEKDATA操作时,返回值的二进制代表内存中的实际数据,我们可以利用“联合体中的变量有相同的初始地址”这一特性来帮助我们完成从二进制到字符串的转换。(这是一个做过嵌入式开发的人基本都知道的小技巧,考虑到做Android开发对这段代码可能会有疑惑,容我啰嗦两句)

  4,数据段寻址

  这是在实现x64版本时遇到的最大的困难,在getData()与setData()函数中,第二个参数表示数据在数据段中的地址,由于和ia32时寻址方式不一致,苦苦搜索几天,发现国内很多博客上的说法并不一致,最终在Intel官网上下载了Intel处理器开发手册《64-ia-32-architectures-software-developer-vol-1-manual.pdf》方才解答我的问题,寻址方式涉及两个寄存器,DS和RSI,参考手册的说法,DS表示数据段的selector,其实这个selector就是index索引的意思,由于x64下字长64bit,即8个字节,索引乘以8即得数据段在内存中的基址,RSI表示数据段内偏移地址,与基址相加即可得出数据的绝对地址,使用此地址直接访问内存就可以取出数据。

Xposed 之Android hook框架入门

Xposed Xposed原理 Xposed入门 xposed基础 xposed教程

目录[-]

  • 原理
  • 安装XposedInstaller
  • 开发Xposed项目
  • 1. Xposed module基础
  • 2. 一个简单的示例
  • 3. 运行module
  • 4. 运行结果
  • 参考链接
  • 原理

    Xposed替换了/system/bin/app_process可执行文件,在启动Zygote时加载额外的jar文件(/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar),并执行一些初始化操作(执行XposedBridge的main方法)。然后我们就可以在这个Zygote上下文中进行某些hook操作。

    安装XposedInstaller

    1. 下载XposedInstaller
    2. 安装。安装完会提示重启手机。如果是虚拟机要选择软重启,真实手机要选择硬重启,千万不要搞反。
      注:手机启动会比较慢,但如果手机重启时卡在欢迎界面,可以通过连续按电源键来跳过Xposed加载。

    开发Xposed项目

    1. Xposed module基础

    Xposed程序称为module,它是包含一些特殊元数据和文件的android项目。建议使用android SDK 4.0.3 (API 15)进行开发。 一个Xposed module项目结构如下:

    其中/assets/xposed_init文件指定了module的入口类,开发者要在这个类中实现需要的hook代码。这个类要实现特定的XposedBridge接口。 /assets/xposed_init内容:

    com.example.xmodule.car.XModule
    

    AndroidMannifest.xml内容:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.example.Xmodule" android:versionName="1.0" android:versionCode="1">
        <uses-sdk android:minSdkVersion="15"/>
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <meta-data
                    android:name="xposedmodule"
                    android:value="true"/>
            <meta-data
                    android:name="xposeddescription"
                    android:value="Xposed模块示例"/>
            <meta-data
                    android:name="xposedminversion"
                    android:value="54"/><!-- 对应的XposedBridge版本号 -->
        </application>
    </manifest>
    

    注意: Xposed module项目在开发时导入了XposedBridge jar文件,但编译时要去掉,否则因为在Zygote中已经导入该jar文件而导致类冲突。

    2. 一个简单的示例

    开发一个xposed module来修改手机imei,imsi。

    1. 新建一个普通的android项目,添加依赖XposedBridge.jar(源码下载),scope为Provided。
    2. 在AndroidManifest.xml中添加Xposed元数据
    3. 编写 /assets/xposed_init文件
    4. 编写hook代码
    public class XModule implements IXposedHookLoadPackage {
        @Override
        public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
            //只hook测试app
            if (lpparam.packageName.equals("com.example.test")) {
                XposedHelpers.findAndHookMethod(TelephonyManager.class, "getDeviceId", new XC_MethodReplacement() {
                    @Override
                    protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
                        return "this is imei";
                    }
                });
                XposedHelpers.findAndHookMethod(TelephonyManager.class, "getSubscriberId", new XC_MethodReplacement() {
                    @Override
                    protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
                        return "this is imsi";
                    }
                });
            }
        }
    }
    

    3. 运行module

    module的安装与普通app一样,如果没有编写Activity,模块不会显示在launcher中。但可以在XposedInstaller应用中看到安装的module;另外module的启用与停止都需要在XposedInstaller中进行配置,配置完还需要重启才能生效。

    4. 运行结果

    test app的Activity代码

     @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            final TextView tv1 = (TextView) findViewById(R.id.tv1);
            final TextView tv2 = (TextView) findViewById(R.id.tv2);
            TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
            tv1.setText("imei: "+tm.getDeviceId());
            tv2.setText("imsi: "+tm.getSubscriberId());
        }
    

    运行结果如下:

    参考链接

    时间: 2024-08-30 03:36:41

    编程中老有人问hook是什么的相关文章

    PHP 编程中 10 个最常见的错误,你犯过几个?

    错误1:foreach循环后留下悬挂指针 在foreach循环中,如果我们需要更改迭代的元素或是为了提高效率,运用引用是一个好办法: $arr = array(1,2,3,4); foreach($arr as&$value){    $value = $value *2; } // $arr is now array(2, 4, 6, 8) 这里有个问题很多人会迷糊. 错误1:foreach循环后留下悬挂指针 在foreach循环中,如果我们需要更改迭代的元素或是为了提高效率,运用引用是一个好

    【转载】如果有人问你数据库的原理,叫他看这篇文章

    原文:如果有人问你数据库的原理,叫他看这篇文章 本文由 伯乐在线 - Panblack 翻译,黄利民 校稿.未经许可,禁止转载!英文出处:Christophe Kalenzaga.欢迎加入翻译组. 一提到关系型数据库,我禁不住想:有些东西被忽视了.关系型数据库无处不在,而且种类繁多,从小巧实用的 SQLite 到强大的 Teradata .但很少有文章讲解数据库是如何工作的.你可以自己谷歌/百度一下『关系型数据库原理』,看看结果多么的稀少[译者注:百度为您找到相关结果约1,850,000个…] 

    [转]如果有人问你数据库的原理,叫他看这篇文章

    推荐一篇文章:http://blog.jobbole.com/100349/  --原文出处 一提到关系型数据库,我禁不住想:有些东西被忽视了.关系型数据库无处不在,而且种类繁多,从小巧实用的 SQLite 到强大的 Teradata .但很少有文章讲解数据库是如何工作的.你可以自己谷歌/百度一下『关系型数据库原理』,看看结果多么的稀少[译者注:百度为您找到相关结果约1,850,000个…] ,而且找到的那些文章都很短.现在如果你查找最近时髦的技术(大数据.NoSQL或JavaScript),你

    码农-如果当初学习编程时能有人给我这些忠告该多好

    在你学习编程之前思考一下你的目标 要知道编程大多时候就是在创造,当你有最终目标感时道路会更加的清晰.如果你的目标是"学习编程"而不是更具体的学习哪种程序及如何让你的生活更好,那么你可能会发现这不过是一次令人沮丧的实践. 我有点惭愧地承认我学习计算机科学的部分动机是为了证明我聪明,及我想干"聪明人"的工作.我也喜欢思考数学和理论(<哥德尔.艾舍尔.巴赫:集异璧之大成 >这本书在我易受影响的年纪进入了我的脑海),编程是一个不错的选择.当然这并不足以使我坚持这

    编程中最没用的东西是源代码,最有用的东西是算法和数据结构(转载)

    重要的不是你用什么开发,而是你在开发什么. 程序=算法+数据结构 过程=对象+属性+方法+事件 程序员的秘诀是:编程.编程.再编程. 编程的秘诀是:思索.思索.再思索. 自由固不是钱所能买到的,但能够为编程而卖掉. 编程为了生活,生活为了编程. 不要认为编程是一项任务,其实是一次让人羡慕的机会! 编程之乐何处寻,数点梅花天地心. 假如编程易懂得,那么程序员就不会热情地写出注释,也不会有得到编程的快乐. 编程中最没用的东西是源代码,最有用的东西是算法和数据结构. 编程之久除了算法和数据结构,什么也

    链接脚本在编程中的高级运用之一:可变长数组

    作为嵌入式软件工程师,应该要清楚程序的每一条指令在哪里,什么时候会被加载到内存,什么时候会被执行.链接脚本会明确告诉你程序的代码和数据在内存中的分布.精确控制代码和数据在内存中的分布是高效利用内存资源的前提.自定义链接脚本是资深嵌入式软件工程师的必备技能,更是嵌入式架构师的最基本要求.此外,灵活定制链接脚本在编程方面有更高级的应用. 一.编译链接原理 简单讲述编译链接的基本原理有助于后面内容的理解. a. 简单点说,一个可执行程序包括文件头.代码段(.text).数据段(.bss).符号段等信息

    【转】游戏编程中的人工智能技术--神经网络

    原文:http://blog.csdn.net/ecitnet/article/details/1799444 游戏编程中的人工智能技术. > .  (连载之一) 用平常语言介绍神经网络(Neural Networks in Plain English) 因为我们没有很好了解大脑,我们经常试图用最新的技术作为一种模型来解释它.在我童年的时候,我们都坚信大脑是一部电话交换机.(否 则它还能是什么呢?)我当时还看到英国著名神经学家谢林顿把大脑的工作挺有趣地比作一部电报机.更早些时候,弗罗伊德经常把大

    作为一个程序员编程中经常碰到且觉得难的事是什么?

    作为一个程序员编程中经常碰到且觉得难的事是什么?有人说,感觉最难的是 trade-off:也有人说,给函数和变量起一个不用写注释的名字:架构师说,预测需求的变化比较难:一线码农说,写出可被长期维护并持续产生价值的代码是最难的.你觉得编程中经常碰到且觉得难的事是什么? 比如用新技术做项目,编写到80%-90%左右,发现了更优的方案,然后在重构和继续之间取舍......变量命名难:编码进入超凡状态时被打断 ? 还是双方对接,涉及到加解密算法,对方又不给你提供明确说明的,比如RSA吧,虽然都叫RSA,

    在windows下的QT编程中的_TCHAR与QString之间的转换

    由于在windows下的QT编程中,如果涉及到使用微软的API,那么不可避免使用_TCHAR这些类型,因此在网上查了一下,其中一个老外的论坛有人给出了这个转换,因此在这里做一下笔记 : )#ifdef UNICODE #define QStringToTCHAR(x)     (wchar_t*) x.utf16() #define PQStringToTCHAR(x)    (wchar_t*) x->utf16() #define TCHARToQString(x)     QString: