(Android) 如何使用HOOK实现动态注入以及自动化操作

为什么会有这边博文?

最近一直在搞一些apk破解以及自动化方面的东西.觉得有必要记录一下.也是为了修改下自己懒的毛病(很久很久很久没更新博客了)

一. 项目需求

项目功能:

本文将使用QQ为例,利用Hook实现自动登录,自动退出登录功能

项目流程:

流程比较简单

二. 资源准备

大致需要准备以下东西:

  1. 一台空闲的安卓手机,能root最好.
  2. 安装VirtualXposed(传送门),安装QQ(传送门,提取码: ty58)(注意QQ是8.0.0.4000版本,不同版本可能会影响hook点),对于不懂怎么使用hook的请参考
  3. 将QQ安装到VirtualXposed
  4. QQ脱壳获取源代码(不懂脱壳操作请点这里)

三. 功能实现

3.1 实现自动从QQ新用户页跳转到登陆页

第一次启动qq的时候,qq会停留在,显然要实现登陆的话我们需要让其自动进入登陆页面,如图:

3.1.1 获取新用户页的堆栈信息(手机需要连接电脑)
adb shell dumpsys activity >>输出路径

下面是导出的堆栈信息:

我们可以得到登陆界面的RegisterGuideView.以及登陆按钮的16进制值7f0b0da9

3.1.2 获取登陆按钮逻辑的Hook点

通过PowerGREP工具对脱壳后的QQdex源码(脱壳后可得到十几个dex文件)进行字符串搜索找到RegisterGuideView所在的dex,然后通过jeb工具反编译该dex, 以下是RegisterGuideView类的源码

package com.tencent.mobileqq.activity.registerGuideLogin;import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View$OnClickListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import com.tencent.mobileqq.activity.RegisterPhoneNumActivity;
import com.tencent.mobileqq.app.QQAppInterface;
import com.tencent.mobileqq.statistics.ReportController;
import com.tencent.qphone.base.util.QLog;public class RegisterGuideView extends GuideBaseFragment implements View$OnClickListener {private View a;private Button a;private Button b;public RegisterGuideView() {super();}@SuppressLint(value={"ValidFragment"}) public RegisterGuideView(QQAppInterface arg1) {super(arg1);}public void onClick(View arg14) {Intent v0;switch(arg14.getId()) {case 2131430824: {ReportController.b(this.a, "CliOper", "", "", "0X8007576", "0X8007576", 0, 0, "", "", "", "");v0 = new Intent(this.a, RegisterPhoneNumActivity.class);v0.putExtra("key_register_from", 2);v0.putExtra("leftViewText", this.a.getString(2131499116));v0.addFlags(67108864);this.a.startActivity(v0);break;}case 2131430825: {ReportController.b(this.a, "CliOper", "", "", "0X8007575", "0X8007575", 0, 0, "", "", "", "");v0 = this.a.getIntent();v0.putExtra("from_register_guide", true);v0.putExtra("is_need_show_logo_animation", true);GuideBaseFragment v0_1 = GuideHandler.a(this.a, this.a);if(this.a == null) {return;}this.a.a(v0_1);break;}}}public View onCreateView(LayoutInflater arg7, ViewGroup arg8, Bundle arg9) {View v1 = arg7.inflate(2130903607, arg8, false);this.a = v1.findViewById(2131428759);this.a.setVisibility(0);this.a = v1.findViewById(2131430825);this.b = v1.findViewById(2131430824);this.a.setOnClickListener(((View$OnClickListener)this));this.b.setOnClickListener(((View$OnClickListener)this));View v0 = v1.findViewById(2131430823);try {((ImageView)v0).setImageDrawable(new BitmapDrawable(this.getResources(), this.getActivity().getAssets().open("splash.jpg")));}catch(Throwable v0_1) {QLog.e("LoginActivity.RegisterGuideView", 1, "onCreateView error:" + v0_1.getMessage());}return v1;}
}

通过我们得到的登陆按钮的16进制值7f0b0da9可以确定10进制值2131430825,从而确定变量a是登陆按钮.

3.1.3 通过Hook 实现登陆按钮点击

由于我们已经找到了登陆按钮相关的类和按钮变量对象,接下来我们只需要通过hook拿到该按钮对象,实现点击即可自动进入登陆页面
以下是核心代码:

/*** hook新用户页,拿到登陆按钮,模拟点击登陆*/public static void hookRegisterGuideLogin(){XposedHelpers.findAndHookMethod("com.tencent.mobileqq.activity.registerGuideLogin.RegisterGuideView", context.getClassLoader(), "onCreateView", LayoutInflater.class, ViewGroup.class, Bundle.class, new XC_MethodHook(){@Overrideprotected void afterHookedMethod(MethodHookParam param) throws Throwable{super.afterHookedMethod(param);final Object obj = param.thisObject;doMainLooper(context, 1000, new Runnable(){@Overridepublic void run(){//得到按钮对象Object viewObj = ReflectUtils.getFieldValueForFiledClsNm(obj, "a", "android.widget.Button");if (viewObj instanceof Button){//点击按钮((Button) viewObj).performClick();}}});}});}

目前为止,我们就已经完成了长征的第一步,从新用户页自动点击到登陆页. 下面是成果演示:

可以看到我们已经实现了从新用户页点击到登陆页了.

3.2 实现登陆页自动填充登陆账号和密码并自动登陆

3.2.1 打印登陆页的堆栈信息

如下图,我们可以找到登陆页面所在的类名,以及按钮输入框等信息

3.2.2 找到源码中登陆相关hook点

LoginView的重要源码如下,我们基本可以定位到账号输入框,密码输入框,登陆按钮等对象

private void a(View arg12) {int v0_6;ImageView v1_1;int v3;int v1;int v10 = -16578534;int v9 = 2;int v8 = 8;this.i = arg12.findViewById(2131430866);arg12.findViewById(2131438684).setVisibility(v8);this.a = arg12.findViewById(2131430867);this.a = arg12.findViewById(2131430735);this.a.setHeadBorder(2130839513);this.a = this.a.a();this.a.a = ((NewStyleDropdownView$DropdownCallback)this);this.a.setContentDescription(this.a.getString(2131492973));Bundle v0 = this.a.getInputExtras(true);if(v0 != null) {v0.putInt("INPUT_TYPE_ON_START", 1);}this.a = arg12.findViewById(2131430736);this.a.setCustomClearButtonCallback(new abgm(this, arg12.findViewById(2131430871), ((int)(43f * this.a + 0.5f))));this.a.setContentDescription(this.a.getString(2131492974));SpannableString v0_1 = new SpannableString("输入密码");v0_1.setSpan(new AbsoluteSizeSpan(17, true), 0, v0_1.length(), 33);this.a.setHint(((CharSequence)v0_1));if(Build$VERSION.SDK_INT >= 26) {try {View.class.getMethod("setImportantForAutofill", Integer.TYPE).invoke(this.a, Integer.valueOf(8));}catch(Exception v0_2) {QLog.w("LoginActivity.LoginView", v9, "disable auto fill error", ((Throwable)v0_2));}}this.a = arg12.findViewById(2131430150);this.a.setContentDescription(this.a.getString(2131492976));arg12.findViewById(2131430876).setOnClickListener(((View$OnClickListener)this));View v0_3 = arg12.findViewById(2131430875);ViewCompat.setImportantForAccessibility(v0_3, v9);v0_3.setContentDescription(this.a.getString(2131498620) + this.a.getString(2131500954));this.a.setOnClickListener(((View$OnClickListener)this));this.a.a();this.g();this.a = arg12.findViewById(2131430740);this.a.setContentDescription(this.a.getString(2131492978));this.a.setOnClickListener(((View$OnClickListener)this));this.a = arg12.findViewById(2131430864);this.f = arg12.findViewById(2131430874);this.g = arg12.findViewById(2131430733);this.a.setOnSizeChangedListenner(((InputMethodRelativeLayout$onSizeChangedListenner)this));this.a.setOnTouchListener(((View$OnTouchListener)this));this.e = arg12.findViewById(2131430865);this.e.setOnTouchListener(new abgn(this));this.a = arg12.findViewById(2131430868);if(this.j) {this.a.setVisibility(4);}this.b = arg12.findViewById(2131430738);this.b = arg12.findViewById(2131430739);this.b.setContentDescription(this.a.getString(2131498674));arg12.findViewById(2131438685).setOnClickListener(((View$OnClickListener)this));this.a = this.a.getSystemService("input_method");this.b = this.a.a();this.b.setOnClickListener(this.a);if(this.a == null) {this.a = new ArrayList();goto label_183;}try {this.a.clear();}catch(Exception v0_2) {QLog.d("LoginActivity.LoginView", 1, "initViews crash: ", ((Throwable)v0_2));this.a = new ArrayList();}label_183:List v0_4 = BaseApplicationImpl.sApplication.getAllAccounts();if(v0_4 != null) {this.a.addAll(((Collection)v0_4));}if(this.a != null) {if(this.a.size() <= 0) {goto label_470;}while(this.a.size() > v8) {this.a.remove(this.a.size() - 1);}this.a.setAdapter(new abgo(this, this.a));if((this.g) && !this.f) {goto label_273;}if(this.i) {goto label_273;}if(this.k) {goto label_273;}String v4 = this.a.getIntent().getStringExtra("uin");String v5 = this.a.getIntent().getStringExtra("befault_uin");if((this.f) && v4 != null && v4.length() > 0) {v1 = 0;v3 = -1;}else {this.a(this.a.get(0));this.a = 0;goto label_273;}while(v1 < this.a.size()) {Object v0_5 = this.a.get(v1);if(v0_5 != null && ((SimpleAccount)v0_5).getUin() != null) {if(v5 != null && (v5.equals(((SimpleAccount)v0_5).getUin()))) {v3 = v1;}if(!v4.equals(((SimpleAccount)v0_5).getUin())) {goto label_255;}this.a(((SimpleAccount)v0_5));this.a = v1;}label_255:++v1;}if(v3 == -1) {goto label_273;}this.a.remove(v3);}else {label_470:this.a.b().setVisibility(v8);}label_273:this.a.addTextChangedListener(this.a);this.a.addTextChangedListener(this.b);this.a.setOnFocusChangeListener(this.a);this.a.setOnFocusChangeListener(this.a);this.a.setLongClickable(false);this.c = arg12.findViewById(2131430872);this.c.setOnClickListener(((View$OnClickListener)this));if(this.a) {this.a.setTransformationMethod(PasswordTransformationMethod.getInstance());v1_1 = this.c;v0_6 = (this.g) || (this.f) || (this.i) ? 2130844987 : 2130842965;v1_1.setImageResource(v0_6);this.c.setContentDescription("隐藏密码");}else {this.a.setTransformationMethod(HideReturnsTransformationMethod.getInstance());v1_1 = this.c;v0_6 = (this.g) || (this.f) || (this.i) ? 2130844988 : 2130842968;v1_1.setImageResource(v0_6);this.c.setContentDescription("显示密码");}this.c.setVisibility(v8);this.b.setOnClickListener(((View$OnClickListener)this));if(this.a.mSystemBarComp != null && ImmersiveUtils.isSupporImmersive() == 1) {this.a.mSystemBarComp.init();}this.c = arg12.findViewById(2131430734);this.d = arg12.findViewById(2131430869);this.a.clearFocus();this.a.clearFocus();this.a.setClearButtonVisible(false);this.a.setTextClearedListener(((ConfigClearableEditText$OnTextClearedListener)this));this.a.addTextChangedListener(this.c);this.b(arg12);if(this.a.getIntent().getBooleanExtra("reason_for_upgrade", false)) {this.a.showDialog(v9);}if((this.a.getIntent().getBooleanExtra("key_req_by_contact_sync", false)) && (this.a.getIntent().getBooleanExtra("IS_ADD_ACCOUNT", false))) {this.a.setText(this.a.getIntent().getStringExtra("key_uin_to_login"));}this.a.setVisibility(0);this.i.setVisibility(v8);this.f.setVisibility(0);this.b.setVisibility(0);this.a.a(false, null);this.a.setTextColor(v10);this.a.setHintTextColor(-5196865);this.a.setFocusable(true);this.a.setFocusableInTouchMode(true);this.a.setTextColor(v10);this.a.setHintTextColor(-5196865);this.a.setVisibility(0);this.b.findViewById(2131430877).setVisibility(0);this.a.setVisibility(0);this.b.setVisibility(0);this.d(this.a.isInMultiWindow());if((this.g) || (this.f) || (this.i)) {this.c.setVisibility(0);this.a.setVisibility(v8);this.i.setVisibility(0);this.i.setOnClickListener(((View$OnClickListener)this));if(this.i) {String v0_7 = this.a.getIntent().getStringExtra("uin");if(!TextUtils.isEmpty(((CharSequence)v0_7))) {v1 = v0_7.length();this.a.setText(v0_7.substring(0, v9) + "****" + v0_7.substring(v1 - 2, v1));this.b(v0_7);this.a.setFocusable(false);this.a.setFocusableInTouchMode(false);}this.b.findViewById(2131430877).setVisibility(v8);this.a.setVisibility(v8);RelativeLayout$LayoutParams v0_8 = new RelativeLayout$LayoutParams(-2, -2);v0_8.addRule(13);this.b.setLayoutParams(((ViewGroup$LayoutParams)v0_8));goto label_454;}this.a.a(false, null);this.a.setFocusable(true);this.a.setFocusableInTouchMode(true);}else {if(!this.l && !this.a.isInMultiWindow()) {goto label_454;}this.c.setVisibility(0);}label_454:this.a = this.a.getLayoutParams();this.b = this.g.getLayoutParams();this.c = this.a.getLayoutParams();this.e();}

3.2.3 使用hook实现自动登陆

以下是核心代码

private static void hookAutoLogin(){String loginviewClsNm = "com.tencent.mobileqq.activity.registerGuideLogin.LoginView";final String loginBtnClsNm = "com.tencent.mobileqq.activity.registerGuideLogin.LoginAnimBtnView"; //登陆按钮类名final String loginBtnFiledNm = "a"; //登陆按钮变量名final String pwdEdTextClsNm = "com.tencent.mobileqq.widget.CustomSafeEditText"; //密码输入框类名final String pwdEdTextFiledNm = "a"; //密码输入框变量名final String newStyleDropdownViewClsNm = "com.tencent.mobileqq.widget.NewStyleDropdownView"; //输入框外view类名final String newStyleDropdownViewFiledNm = "a"; //输入框外view类名final String actInputViewClsNm = "aqsw"; //输入框类名final String actInputViewFiledNm = "a"; //输入框的变量名//hook登录页节点XposedHelpers.findAndHookMethod(loginviewClsNm, context.getClassLoader(), "a", View.class, new XC_MethodHook(){@Overrideprotected void afterHookedMethod(MethodHookParam param) throws Throwable{super.afterHookedMethod(param);//得到登陆所需对象Object loginViewObj = param.thisObject;Object newStyleDropdownViewOBj = ReflectUtils.getFieldValueForFiledClsNm(loginViewObj, newStyleDropdownViewFiledNm, newStyleDropdownViewClsNm);final Object loginBtnObj = ReflectUtils.getFieldValueForFiledClsNm(loginViewObj, loginBtnFiledNm, loginBtnClsNm);final Object pwdEdTextObj = ReflectUtils.getFieldValueForFiledClsNm(loginViewObj, pwdEdTextFiledNm, pwdEdTextClsNm);final Object actInputViewobj = ReflectUtils.getFieldValueForFiledClsNm(newStyleDropdownViewOBj, actInputViewFiledNm, actInputViewClsNm);// 开始输入账号密码登陆final String account="";final String pwd="";doMainLooper(context, 3000, new Runnable(){@Overridepublic void run(){if (actInputViewobj instanceof AutoCompleteTextView){((AutoCompleteTextView) actInputViewobj).setText(account);}if (pwdEdTextObj instanceof EditText){((EditText) pwdEdTextObj).setText(pwd);}if (loginBtnObj instanceof View){((View) loginBtnObj).performClick();}}});}});}

至此我们的自动登陆功能就算是做好了,下面是演示:

3.3 实现退出登录

和上面步骤一样,我们需要从账号管理界面界面入手

3.3.1 获取堆栈信息

拿到堆栈信息如下,找到所在的类AccountManageActivity

3.3.2 查找源码中退出登录的相关hook点
public void a(int arg11, boolean arg12) {String v9 = null;int v5 = 7000;int v6 = 2;Object v0 = this.a.get(arg11);if(v0 == null) {this.a.dismiss();if(QLog.isColorLevel()) {QLog.w("Switch_Account", v6, "onItemLongClick simple account = null");}}else {String v2 = ((SimpleAccount)v0).getUin();String v3 = this.app.getCurrentAccountUin();this.a = v2;if(v2.equals(v3)) {AccountManageActivity.a(((Activity)this), this.app);}if(QLog.isColorLevel()) {QLog.d("hunter", v6, "++++++++++");}this.a(this.a, arg12);HistoryChatMsgSearchKeyUtil.a(v2);CrmUtils.a(this.getBaseContext(), v3);this.a.remove(v0);Manager v1 = this.app.getManager(60);if(v1 != null && (((SubAccountManager)v1).a(v2))) {SubAccountControll.a(this.app, 0, v2);((SubAccountManager)v1).e(v2);((SubAccountManager)v1).a(v2, v9, true);((SubAccountManager)v1).a(v2, v6);SubAccountControll.a(this.app, v2, 7);int v1_1 = 1 - this.app.a().a(v2, v5);if(v1_1 != 0) {this.app.a().c(v2, v5, v1_1);}if(!QLog.isColorLevel()) {goto label_68;}QLog.d("SUB_ACCOUNT", v6, "deleteAccount() hint need to verify,msg num=1, subUin=" + v2);}label_68:GesturePWDUtils.clearGestureData(this.getActivity(), ((SimpleAccount)v0).getUin());if(v2.equals(v3)) {this.app.getApplication().refreAccountList();List v0_1 = this.getAppRuntime().getApplication().getAllAccounts();if(v0_1 != null && v0_1.size() > 0) {v0 = v0_1.get(0);if(((SimpleAccount)v0).isLogined()) {this.getAppRuntime().startPCActivePolling(((SimpleAccount)v0).getUin(), "delAccount");}}}ThreadManager.post(new unx(this, v2, arg12, arg11), 8, ((ThreadExcutor$IThreadListener)v9), true);}}
3.3.3 hook注入退出方法,实现自动退出登录

以下是核心代码:

 private static void loginOut(){//hook 账号管理页XposedHelpers.findAndHookMethod(accountManageActivityClsNm, context.getClassLoader(), "a", Bundle.class, new XC_MethodHook(){@Overrideprotected void afterHookedMethod(MethodHookParam param) throws Throwable{super.afterHookedMethod(param);final Object accountManageActivityObj = param.thisObject;doMainLooper(context, 3000, new Runnable(){@Overridepublic void run(){ReflectUtils.invokeMethod(accountManageActivityObj, "a", new Class[]{int.class, boolean.class}, 0, true);}});}});}

四. 最终效果

已经实现了完全的自动登录,自动退出登录操作,完整展示:

由于篇幅原因,部分代码已省略,完整代码已上传github(传送门)

实际上,利用hook动态注入可以无所不能,大家可以发挥想象.(不过希望用在正途哦,手动滑稽)

(Android) 如何使用HOOK实现动态注入以及自动化操作相关推荐

  1. Android组件化专题-路由动态注入跳转参数以及获取其他模块的fragment

    上几篇组件化专题文章,我们对路由框架基本实现了,Activity之间当跳转和模块间当通信. 现在思考一个问题: 不管是在Activity或者Fragment,跳转都会传递一些参数,然后在对应对Acti ...

  2. Android 插件之Hook机制动态代理

    代理是什么 为什么需要代理呢?其实这个代理与日常生活中的"代理","中介"差不多:比如你想海淘买东西,总不可能亲自飞到国外去购物吧,这时候我们使用第三方海淘服务 ...

  3. linux qt getpid,[QTA] Android 动态注入原理分析

    一.前言 Android 的 UI 自动化测试可以通过注入式和非注入式分别实现,通过注入式可以更加方便地与应用进行交互.QTA 团队提供的 Android UI 自动化测试框架QT4A, 是通过动态注 ...

  4. 【Android 逆向】Android 进程代码注入原理 ( 注入本质 | 静态注入和动态注入 | 静态注入两种方式 | 修改动态库重打包 | 修改 /data/app/xx/libs 动态库 )

    文章目录 一.注入本质 二.静态注入和动态注入 三.静态注入两种方式 ( 修改动态库重打包 | 修改 /data/app/packageName/libs/ 下的动态库 ) 一.注入本质 进程注入本质 ...

  5. 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 创建 事件监听器 对应的 动态代理 | 动态代理的数据准备 | 创建调用处理程序 | 创建动态代理实例对象 )

    文章目录 前言 一.创建 事件监听器 对应的 动态代理 二.动态代理 数据准备 三.动态代理 调用处理程序 四.动态代理 实例对象创建 前言 Android 依赖注入的核心就是通过反射获取 类 / 方 ...

  6. Android插件化开发之动态加载技术简单易懂的介绍方式

    转载地方:https://segmentfault.com/a/1190000004062866 基本信息 Author:kaedea GitHub:android-dynamical-loading ...

  7. android hook 实例,代码实例分析android中inline hook

    以下内容通过1.实现目标注入程序,2.实现主程序,3.实现注入函数,4.thumb指令集实现等4个方面详细分析了android中inline hook的用法,以下是全部内容: 最近终于沉下心来对着书把 ...

  8. 如何快速检测app动态注入漏洞

    介绍一个快速检测app动态注入的方法 使用friada检测 一.环境配置 adb: Android Debug Bridge,安卓渗透测试强大的工具 frida:是一个轻量级的hook框架 frida ...

  9. 安装使用Frida在Android上进行hook

    前言 我们对Android应用进行hook最常用的就是Xposed,它相对来说更加完善,而且有强大的社区和丰富的插件.而Frida则于Xposed不同,它是一款轻量级的Hook框架,可用于多平台,相同 ...

  10. Android安全:Hook技术

    原址 一.Hook技术  1.Hook英文翻译为"钩子",而钩子就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时,处理一些自己特定的事件:  ...

最新文章

  1. BERT拿下最佳长论文奖!NAACL 2019最佳论文奖公布
  2. Python 计算机视觉(一) —— 数字图像处理基础
  3. 多线程并发 (二) 了解 Thread
  4. nodejs中使用worker_threads来创建新的线程
  5. 昨天日志 今天日志的获取方法
  6. Mongodb 安装和启动
  7. spring 多线程 事务 源码解析(一)
  8. 【洛谷1527】 [国家集训队]矩阵乘法(整体二分)
  9. Spring AOP 性能监控器
  10. 图之Dijkstra算法
  11. nmea怎么转wgs84坐标c语言源码,NMEA-0813数据格式说明
  12. 高等数学常用极限求法总结(无详解)
  13. element ui 前台模板_简单了解Vue + ElementUI后台管理模板
  14. Linux之nslookup命令使用详解—网络故障定位(一)
  15. 数据结构之队列(链式队列)的基本操作与实现
  16. LOGO特训营 第五节 字体结构与设计常用技法
  17. 硬盘开盘证明怎么写,开盘证明范本
  18. MISC机制编写字符驱动程序
  19. c++类和对象(类的概念)
  20. 威尔逊云室的一些知识

热门文章

  1. 第六次团队作业——Alpha冲刺之事后诸葛亮
  2. 吴恩达深度学习笔记(五)
  3. “Vidalia tor privoxy”配置自动启动
  4. 如何在命令行状态清除SEP隔离区(Quarantine)
  5. hdu5773 2016年多校4 nlogn求LIS
  6. 2013年01月06日
  7. 汇编基础2:看懂汇编
  8. 给爱车安装一台GPS,贴身“保镖”保护爱车
  9. 2022-04-13 分布式任务调度Quartz
  10. Win10 全屏截图快捷键