欢迎使用CSDN-markdown编辑器

来源:互联网 发布:python命令行运行文件 编辑:程序博客网 时间:2024/06/08 13:19

首先解释下什么是hook技术以及为何需要hook技术,我在学些一种新技术的时候特别喜欢思考为何需要这种技术,也就是这种技术能够解决什么问题,如果想不明白这个问题,那么我是不会去学习这门技术,因为没意义,在我看来技术和产品一样都是需求推动的,只不过技术面对的是开发者,而产品面对的是普通小白用户。

什么是hook技术/为何需要hook技术
hook技术本质上是一种动态注入技术,我们知道任何一个App都是按照程序代码既定的流程一步一步的执行的,那么如果我们想要动态的去更改一个程序的执行流程或者截取某个流程中我们感兴趣的数据怎么办?如登陆劫持,盗取QQ账号,既然程序的代码是他人已经写好的,那我们唯一能做的就是添加我们自己的代码,也就是说在原程序的某个函数的执行过程中添加我们的代码,让程序能够重新走我们的代码流程,这样在我们的流程中我们就可以掌控一切,这就是hook技术,hook英译过来即为“用钩挂”,类似于用一个hook挂在原来的代码流程的开始与结束之间,从而能够执行我们的hook插入的代码

XPosed框架下载与安装
xposed是一款开源的hook框架工具,官网下载地址:http://repo.xposed.info/module/de.robv.android.xposed.installer 。由于Android系统5.0以上默认采用了ART模式,Android系统5.0 以下默认采用Davik模式,所以xposed installer主要包含两个版本,主要区别大家打开上述网站上面介绍的很清楚。这里本人以安卓5.0以下的系统为例进行讲解。下载完成de.robv.android.xposed.installer_v33_36570c.apk之后安装Xposed管理器到手机或者模拟器上。手机或者模拟器需要root。

Xposed基本使用
XPosed本质上是一个hook模块管理工具,当我们要hook某一个函数实现自己的功能的时候,需要我们自己写一个hook模块,然后让XPosed来加载管理该模块。我们以登陆劫持为例进行讲解。

首先写一个简单的模仿QQ登陆的demo,代码如下:

public class LoginActivity extends AppCompatActivity {

private final String ACCOUNT="123";private final String PASSWORD="123";private EditText etAccount, etPassword;private Button btnLogin;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_login);    etAccount=(EditText)findViewById(R.id.account);    etPassword=(EditText)findViewById(R.id.password);    btnLogin=(Button)findViewById(R.id.login);    btnLogin.setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View v) {            if (login(etAccount.getText().toString(), etPassword.getText().toString())) {                Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();            } else {                Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();            }        }    });}private boolean login(String account, String password){    return account.equals(ACCOUNT) && password.equals(PASSWORD);}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class LoginActivity extends AppCompatActivity {

private final String ACCOUNT="123";private final String PASSWORD="123";private EditText etAccount, etPassword;private Button btnLogin;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_login);    etAccount=(EditText)findViewById(R.id.account);    etPassword=(EditText)findViewById(R.id.password);    btnLogin=(Button)findViewById(R.id.login);    btnLogin.setOnClickListener(new View.OnClickListener() {        @Override        public void onClick(View v) {            if (login(etAccount.getText().toString(), etPassword.getText().toString())) {                Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();            } else {                Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();            }        }    });}private boolean login(String account, String password){    return account.equals(ACCOUNT) && password.equals(PASSWORD);}

}
代码很简单,大家应该都看的懂,大概的解释下当用户点击登陆按钮的时候从用户名和密码这两个EditText控件中获取用户输入的字符串,然后将其传递给login接口进行验证,此处只是简单的做个了判断,如果账号和密码同为123则认为合法,登陆验证成功,否则登陆验证失败。既然是登陆劫持,我们要做的就是截获用户输入的用户名和密码,类似于盗取用户QQ号和密码。那么很显然我们要hook的函数即为login()函数。

因为hook的目的是劫持QQ登陆的账号与密码,因此我们的安卓工程不需要activity界面,仅仅需要处理劫持逻辑即可,打开Android Studio创建一个空工程,命令为hook_login。然后在app/build.gradle文件中添加依赖项,

如图,注意此时不要使用compile,而要使用provided(IMPORTANT: Never compile this into your APK, use type “provided” instead of “compile”!)原因是不需要将API类compile到我们的apk中,只需要provided相关API的引用,API类的真正实现在Xposed FramWork中。安卓5.0以前的版本推荐使用API 53,即provided ‘de.robv.android.xposed:api:53’。依赖项添加好了之后,接下来在AndroidManifest.xml 文件中添加meta信息。

<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.htq.baidu.hook_login”>

&lt;application    android:allowBackup="true"    android:icon="@mipmap/ic_launcher"    android:label="@string/app_name"    android:supportsRtl="true"    android:theme="@style/AppTheme"&gt;   &lt;!--添加以下meta-data信息--&gt;    &lt;meta-data        android:name="xposedmodule"        android:value="true" /&gt;  &lt;!--必须设置为true--&gt;    &lt;meta-data        android:name="xposeddescription"        android:value="Easy example which makes the status bar clock red and adds a smiley" /&gt;    &lt;meta-data        android:name="xposedminversion"        android:value="53" /&gt;  &lt;!--Xposed API版本--&gt;&lt;/application&gt;

</manifest>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.htq.baidu.hook_login”>

&lt;application    android:allowBackup="true"    android:icon="@mipmap/ic_launcher"    android:label="@string/app_name"    android:supportsRtl="true"    android:theme="@style/AppTheme"&gt;   &lt;!--添加以下meta-data信息--&gt;    &lt;meta-data        android:name="xposedmodule"        android:value="true" /&gt;  &lt;!--必须设置为true--&gt;    &lt;meta-data        android:name="xposeddescription"        android:value="Easy example which makes the status bar clock red and adds a smiley" /&gt;    &lt;meta-data        android:name="xposedminversion"        android:value="53" /&gt;  &lt;!--Xposed API版本--&gt;&lt;/application&gt;

</manifest>
然后创建一个类HookLogin,implements接口IXposedHookLoadPackage,然后IDE会自动提示要重写其抽象方法handleLoadPackage,我们先尝试把加载的模块信息打印出来看下,代码如下:

public class HookLogin implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
XposedBridge.log(“Loaded app: ” + loadPackageParam.packageName);
}
}
1
2
3
4
5
6
public class HookLogin implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
XposedBridge.log(“Loaded app: ” + loadPackageParam.packageName);
}
}
然后创建一个assets/xposed_init文件,文件的内容为包名+类名,如本例为:

com.htq.baidu.hook_login.HookLogin
1
com.htq.baidu.hook_login.HookLogin
然后运行整个工程,将apk安装到已经安装了Xposed安装器的模拟器或者手机上,安装好了之后,我们点击Xposed中的模块选项,如图

点击模块选项之后就可以看到我们的hook_login模块已将显示出来了,然后需要将其勾选,如图:

然后需要重启该设备,点击Xposed主界面的框架选项,然后选择软重启设备就会自动重启了。重启之后我们点击Xposed主界面的日志选项,即可看到模块加载信息的日志,如图:

这就说明整个模块的功能是正确的,那么接下来我们就需要hook login()函数进行登陆劫持。此时需要使用到Xposed框架的findAndHookMethod函数,该函数的定义如下:

findAndHookMethod (String className, ClassLoader classLoader, String methodName, Object… parameterTypesAndCallback)
1
findAndHookMethod (String className, ClassLoader classLoader, String methodName, Object… parameterTypesAndCallback)
该函数包含的四个参数及意义如下:

className 顾名思义就是我们要hook的函数所在的类的名称,包括包名+类名
classLoader 类加载器,因为findAndHookMethod函数是在前面讲到的handleLoadPackage函数中调用的,因此该值通常为loadPackageParam.classLoader
methodName 我们要hook的函数名
parameterTypesAndCallback Object类型的变长参数,包括要hook的函数的参数,以及hook回调接口。具体要根据要hook的函数来确定
更多详细解释请参考官方API文档:http://api.xposed.info/reference/de/robv/android/xposed/XposedHelpers.html#findAndHookMethod(java.lang.String, java.lang.ClassLoader, java.lang.String, java.lang.Object…)

xposed源码地址:https://github.com/rovo89

xposed官方文档地址:https://github.com/rovo89/XposedBridge/wiki/Development-tutorial

实战登陆劫持
基本的原理讲解完了,下面就开始登陆劫持的实战了,主要就是使用前面提到的findAndHookMethod函数,代码如下:

package com.htq.baidu.hook_login;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;

public class HookLogin implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
XposedBridge.log(“Loaded app: ” + loadPackageParam.packageName);
if (!loadPackageParam.packageName.equals(“com.htq.baidu.qq”))//过滤掉不是我们要hook的App
return;

    findAndHookMethod("com.htq.baidu.qq.LoginActivity", loadPackageParam.classLoader, "login", String.class,String.class, new XC_MethodHook() {        @Override        //hook之前回调此函数        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {            //在Xposed的log中印出信息            XposedBridge.log("账号:"+(String)param.args[0]+"   密码:"+(String)param.args[1]);        }        //hook之后回调此函数        @Override        protected void afterHookedMethod(MethodHookParam param) throws Throwable {        }    });}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.htq.baidu.hook_login;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;

public class HookLogin implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
XposedBridge.log(“Loaded app: ” + loadPackageParam.packageName);
if (!loadPackageParam.packageName.equals(“com.htq.baidu.qq”))//过滤掉不是我们要hook的App
return;

    findAndHookMethod("com.htq.baidu.qq.LoginActivity", loadPackageParam.classLoader, "login", String.class,String.class, new XC_MethodHook() {        @Override        //hook之前回调此函数        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {            //在Xposed的log中印出信息            XposedBridge.log("账号:"+(String)param.args[0]+"   密码:"+(String)param.args[1]);        }        //hook之后回调此函数        @Override        protected void afterHookedMethod(MethodHookParam param) throws Throwable {        }    });}

}
然后安装运行该模块到Xposed模块管理中,重启模拟器,打开我们前面写的仿QQ登陆的demo,输入账号有然后安装运行该模块到Xposed模块管理中,重启模拟器,打开我们前面写的仿QQ登陆的demo,输入账号与密码,点击登陆按钮,如图:

然后回到Xposed主界面,点击日志,可以看到账号与密码已经被劫持下来了,如图:

这样就完成了登陆劫持的功能,类似于盗取QQ号,是不是感觉很厉害的样子,哈哈!除了获取参数以外,还可以更改参数,如我们可以让用户输入的账号与密码恒为123,这样不论用户输入的账号与密码是多少都可以登陆成功,只需要在hook之前的回调函数中设置参数为某个固定值即可,代码如下:

findAndHookMethod(“com.htq.baidu.qq.LoginActivity”, loadPackageParam.classLoader, “login”, String.class,String.class, new XC_MethodHook() {
@Override
//hook之前回调此函数
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//将传入的参数修改为某固定的值,这样无论用户输入的账号与密码是多少登陆时都为以下固定值
param.args[0]=”123”;
param.args[1]=”123”;
//在Xposed的log中印出信息
//XposedBridge.log(“账号:”+(String)param.args[0]+” 密码:”+(String)param.args[1]);

}//hook之后回调此函数@Overrideprotected void afterHookedMethod(MethodHookParam param) throws Throwable {}

});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
findAndHookMethod(“com.htq.baidu.qq.LoginActivity”, loadPackageParam.classLoader, “login”, String.class,String.class, new XC_MethodHook() {
@Override
//hook之前回调此函数
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//将传入的参数修改为某固定的值,这样无论用户输入的账号与密码是多少登陆时都为以下固定值
param.args[0]=”123”;
param.args[1]=”123”;
//在Xposed的log中印出信息
//XposedBridge.log(“账号:”+(String)param.args[0]+” 密码:”+(String)param.args[1]);

}//hook之后回调此函数@Overrideprotected void afterHookedMethod(MethodHookParam param) throws Throwable {}

});
其余部分的代码都是一样的,只需要在beforeHookedMethod中修改相应的参数即可,这样就达到了无论用户输入的参数是多少都可以成功登陆,这种修改参数的功能可以用在游戏中作弊使用,如修改游戏中的金币数量或者欢乐豆的数量,感兴趣的同学可以去试试哦!

hook资源文件
除了hook函数外,还可以通过hook资源文件的方式来获取布局文件中的控件,然后对其某些资源进行修改,如修改文本的颜色,替换资源的图标等,hook布局文件使用到的函数为hookLayout,其定义如下:

可以看到该函数包括三个重载函数,通常我们使用第一个,其各个参数的含义如下:

pkg,要hook的布局文件所在的包名
type:要hook的资源的类型,如layout,drawable,id等
name:要hook的资源的名称,如布局文件名称,图片drawable的名称,控件id名称
callback:hook回调函数
更详细的介绍请参考官网文档:http://api.xposed.info/reference/android/content/res/XResources.html#hookLayout(java.lang.String, java.lang.String, java.lang.String, de.robv.android.xposed.callbacks.XC_LayoutInflated)

如前面的仿QQ登陆的demo中登陆头像是企鹅,腾讯QQ也是企鹅大家可能看腻了,想把登陆的默认头像改为一个美女的头像,这就需要使用到hook资源文件了。此时需要实现的是IXposedHookInitPackageResources接口了,代码如下:

package com.htq.baidu.hook_login;

import android.content.res.XModuleResources;
import android.widget.EditText;
import android.widget.ImageView;

import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.IXposedHookZygoteInit;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_LayoutInflated;

public class HookLogin implements IXposedHookZygoteInit,IXposedHookInitPackageResources {

private static String MODULE_PATH = null;@Overridepublic void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam initPackageResourcesParam) throws Throwable {    if (!initPackageResourcesParam.packageName.equals("com.htq.baidu.qq"))        return;    initPackageResourcesParam.res.hookLayout("com.htq.baidu.qq","layout","activity_login", new XC_LayoutInflated() {        @Override        public void handleLayoutInflated(XC_LayoutInflated.LayoutInflatedParam layoutInflatedParam) throws Throwable {            EditText account = (EditText) layoutInflatedParam.view.findViewById(                    layoutInflatedParam.res.getIdentifier("account", "id", "com.htq.baidu.qq"));            EditText password = (EditText) layoutInflatedParam.view.findViewById(                    layoutInflatedParam.res.getIdentifier("password", "id", "com.htq.baidu.qq"));            /*ImageView defaultImg=(ImageView)layoutInflatedParam.view.findViewById(                    layoutInflatedParam.res.getIdentifier("login_picture", "id", "com.htq.baidu.qq"));*/            //替换drawable资源            XModuleResources modRes = XModuleResources.createInstance(MODULE_PATH, layoutInflatedParam.res);            layoutInflatedParam.res.setReplacement("com.htq.baidu.qq", "drawable", "login_default_avatar",                    modRes.fwd(R.drawable.beauty));        }    });}@Overridepublic void initZygote(StartupParam startupParam) throws Throwable {    MODULE_PATH = startupParam.modulePath;}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.htq.baidu.hook_login;

import android.content.res.XModuleResources;
import android.widget.EditText;
import android.widget.ImageView;

import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.IXposedHookZygoteInit;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_LayoutInflated;

public class HookLogin implements IXposedHookZygoteInit,IXposedHookInitPackageResources {

private static String MODULE_PATH = null;@Overridepublic void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam initPackageResourcesParam) throws Throwable {    if (!initPackageResourcesParam.packageName.equals("com.htq.baidu.qq"))        return;    initPackageResourcesParam.res.hookLayout("com.htq.baidu.qq","layout","activity_login", new XC_LayoutInflated() {        @Override        public void handleLayoutInflated(XC_LayoutInflated.LayoutInflatedParam layoutInflatedParam) throws Throwable {            EditText account = (EditText) layoutInflatedParam.view.findViewById(                    layoutInflatedParam.res.getIdentifier("account", "id", "com.htq.baidu.qq"));            EditText password = (EditText) layoutInflatedParam.view.findViewById(                    layoutInflatedParam.res.getIdentifier("password", "id", "com.htq.baidu.qq"));            /*ImageView defaultImg=(ImageView)layoutInflatedParam.view.findViewById(                    layoutInflatedParam.res.getIdentifier("login_picture", "id", "com.htq.baidu.qq"));*/            //替换drawable资源            XModuleResources modRes = XModuleResources.createInstance(MODULE_PATH, layoutInflatedParam.res);            layoutInflatedParam.res.setReplacement("com.htq.baidu.qq", "drawable", "login_default_avatar",                    modRes.fwd(R.drawable.beauty));        }    });}@Overridepublic void initZygote(StartupParam startupParam) throws Throwable {    MODULE_PATH = startupParam.modulePath;}

}
然后运行安装该模块到Xposed模块管理中,重启模拟器,打开仿QQ登陆demo,可以看到QQ登陆默认的憨态可掬的企鹅消失了,取而代之的是长相甜美的美女哦,如图:

这样就达到了替换apk资源的目的哦!当然在上面的代码中除了替换资源文件外,还可以通过

layoutInflatedParam.view.findViewById
1
layoutInflatedParam.view.findViewById
查找控件,然后修改控件的一些属性。具体的大家可以仔细看上面的代码哦!

原创粉丝点击