天问

  • Blog
  • 热修复与动态加载
  • python爬虫
  • 专栏
    • 热修复与动态加载
    • NDK开发
    • 设计模式与架构
    • React Native
  • 好文转载
    • 计算机技术
    • 投资理财
    • 摄影与拍照技巧
  • 投稿
  • 关于本站及隐私政策
  • Github
StevenQ
为今天工作,为明天投资,为未来孵化一些东西!
  1. 首页
  2. 计算机技术
  3. 安卓安全
  4. 正文

安卓上使用Xposed进行hook

2017年5月29日 2880点热度 8人点赞 0条评论

什么是hook

hook技术本质上是一种动态注入技术,我们知道任何一个App都是按照程序代码既定的流程一步一步的执行的,那么如果我们想要动态的去更改一个程序的执行流程或者截取某个流程中我们感兴趣的数据怎么办?如登陆劫持,盗取QQ账号。有2种思路:

  • 修改原程序代码。比如通过逆向分析出QQ账号登陆的逻辑位于哪个函数。然后通过AOP框架插入打log的字节码或者smali插桩方式。但是这种方式修改了原apk后不一定能够成功二次打包成功。
  • 不修改原程序,但是让原程序在执行到某个地方时流程能够跳到我们自己写的程序中,然后在我们自己写的程序中打印log。类似《水浒传》占山为王,等你路过我地盘时再下手。既然程序的代码是他人已经写好的,那我们唯一能做的就是添加我们自己的代码,也就是说在原程序的某个函数的执行过程中添加我们的代码(这里的添加是从执行流程的结果上来说的,不是真的添加),让程序能够重新走我们的代码流程,这样在我们的流程中我们就可以掌控一切。能够达到这个目的的框架就是XPosed。

不过Xposed需要在root手机上才能使用,而且开发的Xposed插件需要依赖Xposed框架才能运行。所以这个基本上无实际应用价值,但是可以作为逆向研究分析。

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;


    @Override
    protected 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">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

       <!--添加以下meta-data信息-->
        <meta-data
            android:name="xposedmodule"
            android:value="true" />  <!--必须设置为true-->
        <meta-data
            android:name="xposeddescription"
            android:value="Easy example which makes the status bar clock red and adds a smiley" />
        <meta-data
            android:name="xposedminversion"
            android:value="53" />  <!--Xposed API版本-->

    </application>

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

然后创建一个assets/xposed_init文件,文件的内容为包名+类名,如本例为:

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. className 顾名思义就是我们要hook的函数所在的类的名称,包括包名+类名
  2. classLoader 类加载器,因为findAndHookMethod函数是在前面讲到的handleLoadPackage函数中调用的,因此该值通常为loadPackageParam.classLoader
  3. methodName 我们要hook的函数名
  4. 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

登陆劫持的一个demo

基本的原理讲解完了,下面就开始写个登陆劫持的小demo进行应用了,主要就是使用前面提到的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 {

            }
        });

    }
}

然后安装运行该模块到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之后回调此函数
    @Override
    protected 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;

    @Override
    public 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));
            }
        });
    }

    @Override
    public void initZygote(StartupParam startupParam) throws Throwable {
        MODULE_PATH = startupParam.modulePath;
    }
}

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

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

layoutInflatedParam.view.findViewById

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

 

 

注:本文首次发表于huqi.tech,谢绝转载,如需转载,请注明出处:huqi.tech

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: hook Xposed 安卓安全
最后更新:2018年9月21日

天问

追梦追自由,穷则独善其身,达则兼济天下

打赏 点赞
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2022 天问 ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang