Android逆向之玩转Xposed模块以劫持登录为例(Demo篇)

原创 cheny  2019-05-08 14:00  阅读 81 views 次 评论 0 条
摘要:

为帮助童鞋们更有节奏感地学习,本文分为Demo篇和实战篇来作叙述。
Xposed是一个很强大的android平台上的hook工具,其可以在不修改APK文件的情况下影响程序运行的框架服务,且在功能不冲突的情况下同时运作。

为帮助童鞋们更有节奏感地学习,本文分为Demo篇和实战篇来作叙述。

先简单介绍Xposed框架(下文简称:Xposed)

关于Xposed

简介

Xposed是一个很强大的android平台上的hook工具,其可以在不修改APK文件的情况下影响程序运行的框架服务,且在功能不冲突的情况下同时运作。开发者可以基于Xposed制作出很多功能强大的模块,用于android的逆向破解,应用美化等方面,现在市面上很多抢红包,消息防撤回,应用破解收费模块应用也是基于此。

原理

刷入Xposed时会向系统写入并替换掉系统一些关键文件,下面是安装包文件结构:

  META-INF/    里面有flash-script.sh脚本文件,用于配置各个文件安装位置
  system/bin/  主要为替换系统的Zygote进程执行文件对应的xposed版文件,如app_process
  system/framework/XposedBridge.jar jar包位置
  system/lib     so文件所在位置
  system/lib64   so(适用于64架构)文件所在位置
  system/xposed.prop xposed版本说明文件
  1. 我们都知道Zygote进程是Android的核心,所有的应用以及系统服务进程都是由Zygote进程fork出来的。
  2. 系统启动时会开启此进程,对应的执行文件是/system/bin/app_process,由于app_process文件被替换,所以启动了Xposed版的Zygote进程。
  3. 该进程会加载Xposed相关类库和函数,其中包括XposedBridge.jar库。
  4. XposedBridge.jar中的XposedBridge.main函数完成对系统的一些关键函数进行hook以及Xposed模块的初始化。
  5. Xposed在进行hook java方法时,利用修改过的虚拟机将方法注册为native方法,JVM调用java函数时,如果该函数为native的,就调用它的nativeFunc,这样方法在调用时,就会调用到这个native方法。
  6. 在这个native方法中,Xposed直接调用了一个java方法,这个java方法里面对原方法进行了调用,并在调用前后插入了钩子,于是就hook住了这个方法。

简要流程如下:

你可能没有完全看懂,没有关系,不是本文重点,简单了解下有助于对下文的理解。

逼逼完不是重点

劫持登录Demo

编写Xposed模块

  1. Android Studio(下文简称:AS)新建Android项目工程,本文名为“XposedDemo”,然后新建module(模块),本文名为“HookLoginModule”。
  2. 在HookLoginModule下的build.gradle中添加依赖(更多版本依赖点此访问):
    compileOnly 'de.robv.android.xposed:api:82'

==注意: 一定为“compileOnly”,老版本AS请用“provided”,不能为“implementation”或“compile”==

  1. 在模块中的AndroidManifest.xml下的application标签里添加如下内容:
        <!--模块申明,true表示申明为xposed模块-->
        <meta-data
            android:name="xposedmodule"
            android:value="true" />

        <!--模块说明,一般为模块的功能描述-->
        <meta-data
            android:name="xposeddescription"
            android:value="这个模块是用来劫持登录的" />

        <!--模块兼容版本-->
        <meta-data
            android:name="xposedminversion"
            android:value="54" />
  1. 新建Hook入口类HookLogin实现xposed的接口IXposedHookLoadPackage并重写方法handleLoadPackage:
public class HookLogin implements IXposedHookLoadPackage {

    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {

    }
}
  1. 在模块的src/main/assets(若没有assets目录请新建个)下新建文件xposed_init并将HookLogin类的完整类名写进去以完成Hook入口类的注册:
    cn.icheny.xposed.HookLogin

好了,Xposed模块已完成了基本的编写,接下来写Demo项目

编写Demo项目

  1. 本文为了简化直接使用XposedDemo下的“app”模块,更名为“LoginDemo”
  2. 新建登录Activity为“LoginActivity”并编写如下代码:
public class LoginActivity extends AppCompatActivity {
    EditText mTvUsername, mTvPassword;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        mTvUsername = findViewById(R.id.tv_username);
        mTvPassword = findViewById(R.id.tv_password);
    }

    /**
     * 登录
     */
    public void login(View view) {

        String username = mTvUsername.getText().toString();
        String password = mTvPassword.getText().toString();

        if (TextUtils.isEmpty(username)) {
            Toast.makeText(this, "请输入用户名", Toast.LENGTH_SHORT).show();
            return;
        } else if (TextUtils.isEmpty(password)) {
            Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show();
            return;
        }
        if (checkLogin(username, password)) {
            startActivity(new Intent(this, LoginSuccessActivity.class));
        }
    }

    /**
     * 模拟后台服务器校验登录
     *
     * @param username 用户名
     * @param password 密码
     */
    public boolean checkLogin(String username, String password) {

        if ("admin".equals(username) && "admin123".equals(password)) {
            return true;
        }
        Toast.makeText(this, "用户名或密码不正确!", Toast.LENGTH_SHORT).show();
        return false;
    }
}
  1. 在对应的布局中编辑登录界面代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="30dp">

    <EditText
        android:id="@+id/tv_username"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:hint="请输入用户名..."
        android:imeOptions="actionNext"
        android:textSize="18dp" />

    <EditText
        android:id="@+id/tv_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:hint="请输入密码..."
        android:imeOptions="actionDone"
        android:inputType="textPassword"
        android:textSize="18dp" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:onClick="login"
        android:text="登录"
        android:textSize="18dp" />

</LinearLayout>

通过LoginActivity中的代码可以了解用户名必须为“admin”密码为“admin123”才能完成登录。那么我们运行LoginDemo验证下:

before_hook_demo

演示图成功验证了代码逻辑。

Hook劫持

  1. 编辑HookLogin下的handleLoadPackage方法:
    ......
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {

        if (lpparam == null) {
            return;
        }

        Log.e(TAG, "Load app packageName:" + lpparam.packageName);

        /**
         * 过滤非目标应用,本文目标应用即LoginDemo,包名为:cn.icheny.logindemo
         */
        if (!"cn.icheny.logindemo".equals(lpparam.packageName)) {
            return;
        }

        //固定格式
        XposedHelpers.findAndHookMethod(
                "cn.icheny.logindemo.LoginActivity", // 需要hook的方法所在类的完整类名
                lpparam.classLoader,                 // 类加载器,固定这么写就行了
                "checkLogin",                        // 需要hook的方法名,checkLogin(username,password)
                String.class,                        // 第一个参数,用户名
                String.class,                        // 第二个参数,密码
                // Hook回调
                new XC_MethodHook() {
                    @Override
                    /**
                     * checkLogin被hook前执行下面的方法
                     */
                    protected void beforeHookedMethod(MethodHookParam param) {
                        Log.e(TAG, "劫持开始了↓↓↓↓↓↓");
                    }

                    /**
                     * checkLogin被hook后执行下面的方法
                     */
                    protected void afterHookedMethod(MethodHookParam param) {

                        // hook 用户名和密码
                        String username = (String) param.args[0];
                        String password = (String) param.args[1];
                        Log.e(TAG, "用户名:" + username + "     密码:" + password);

                        // 被hook后返回自己指定的值(true,表示方法checkLogin调用返回值为true)
                        param.setResult(true);

                        Log.e(TAG, "劫持结束了↑↑↑↑↑↑");
                    }
                }
        );
    }
    ......

代码中我们对LoginActivity下的“checkLogin(String username, String password)”方法进行hook,获取用户名和密码并做log打印,最后让其返回结果为true,即输入任意字符的用户名和密码都能登录成功。beforeHookedMethod和afterHookedMethod即前文所述“前后插钩”的实现方法。

  1. 代码已经注释的很清晰,如果你还没看懂,可以下方评论帮助我优化文章,接下我们将写好的Xposed模块HookLoginModule编译apk安装到手机中,在Xposed Installer中勾选使用该模块,然后重启手机。

hook_login_module
3. 接下来再次打开LoginDemo应用来验证Hook代码:

after_hook_demo

演示图成功验证了Hook代码逻辑,这里贴下Hook日志:

2019-05-08 22:08:06.614 24617-24617/? E/HookLogin: 劫持开始了↓↓↓↓↓↓
2019-05-08 22:08:06.621 24617-24617/? E/HookLogin: 用户名:11     密码:111
2019-05-08 22:08:06.621 24617-24617/? E/HookLogin: 劫持结束了↑↑↑↑↑↑
---------------------------------------------------------------------------
2019-05-08 22:08:14.855 24617-24617/? E/HookLogin: 劫持开始了↓↓↓↓↓↓
2019-05-08 22:08:14.863 24617-24617/? E/HookLogin: 用户名:114554     密码:1114545
2019-05-08 22:08:14.863 24617-24617/? E/HookLogin: 劫持结束了↑↑↑↑↑↑

成功获取到了用户名和密码,这样我们的Xposed模块就大工造成了,Demo篇到此结束!

XposedDemo下载:https://github.com/ausboyue/XposedDemo

本文地址:https://www.icheny.cn/archives/1907
关注我们:请关注一下微信公众号:扫描二维码乘月网的公众号,公众号:it_place
版权声明:本文为原创文章,版权归 cheny 所有,欢迎分享本文,转载请保留出处!

发表评论


表情