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

(Android) 如何使用HOOK实现动态注入以及自动化操作Android 如何使用 HOOK 实现动态注入以及自动化操作为什么会有这边博文 最近一直在搞一些 apk 激活成功教程以及自动化方面的东西 觉得有必要记录一下 也是为了修改下自己懒的毛病 很久很久很久没更新博客了 一 项目需求项目功能 本文将使用为例 利用 Hook 实现自动登录 自动退出登录功能项目流程 流程比较简单二 资源准备大致需要准备以下东西 一台空闲的安卓手机 能 roo

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

为什么会有这边博文?

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

一. 项目需求

项目功能:

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

项目流程:

二. 资源准备

大致需要准备以下东西:

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

三. 功能实现

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

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

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

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

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

package com.tencent.mobile.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.mobile.activity.RegisterPhoneNumActivity; import com.tencent.mobile.app.AppInterface; import com.tencent.mobile.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(AppInterface arg1) { 
    super(arg1); } public void onClick(View arg14) { 
    Intent v0; switch(arg14.getId()) { 
    case : { 
    ReportController.b(this.a, "CliOper", "", "", "0X", "0X", 0, 0, "", "", "", ""); v0 = new Intent(this.a, RegisterPhoneNumActivity.class); v0.putExtra("key_register_from", 2); v0.putExtra("leftViewText", this.a.getString()); v0.addFlags(); this.a.startActivity(v0); break; } case : { 
    ReportController.b(this.a, "CliOper", "", "", "0X", "0X", 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(, arg8, false); this.a = v1.findViewById(); this.a.setVisibility(0); this.a = v1.findViewById(); this.b = v1.findViewById(); this.a.setOnClickListener(((View$OnClickListener)this)); this.b.setOnClickListener(((View$OnClickListener)this)); View v0 = v1.findViewById(); 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进制值,从而确定变量a是登陆按钮.

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

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

前往登陆界面.gif

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

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 = -; int v9 = 2; int v8 = 8; this.i = arg12.findViewById(); arg12.findViewById().setVisibility(v8); this.a = arg12.findViewById(); this.a = arg12.findViewById(); this.a.setHeadBorder(); this.a = this.a.a(); this.a.a = ((NewStyleDropdownView$DropdownCallback)this); this.a.setContentDescription(this.a.getString()); Bundle v0 = this.a.getInputExtras(true); if(v0 != null) { 
    v0.putInt("INPUT_TYPE_ON_START", 1); } this.a = arg12.findViewById(); this.a.setCustomClearButtonCallback(new abgm(this, arg12.findViewById(), ((int)(43f * this.a + 0.5f)))); this.a.setContentDescription(this.a.getString()); 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(); this.a.setContentDescription(this.a.getString()); arg12.findViewById().setOnClickListener(((View$OnClickListener)this)); View v0_3 = arg12.findViewById(); ViewCompat.setImportantForAccessibility(v0_3, v9); v0_3.setContentDescription(this.a.getString() + this.a.getString()); this.a.setOnClickListener(((View$OnClickListener)this)); this.a.a(); this.g(); this.a = arg12.findViewById(); this.a.setContentDescription(this.a.getString()); this.a.setOnClickListener(((View$OnClickListener)this)); this.a = arg12.findViewById(); this.f = arg12.findViewById(); this.g = arg12.findViewById(); this.a.setOnSizeChangedListenner(((InputMethodRelativeLayout$onSizeChangedListenner)this)); this.a.setOnTouchListener(((View$OnTouchListener)this)); this.e = arg12.findViewById(); this.e.setOnTouchListener(new abgn(this)); this.a = arg12.findViewById(); if(this.j) { 
    this.a.setVisibility(4); } this.b = arg12.findViewById(); this.b = arg12.findViewById(); this.b.setContentDescription(this.a.getString()); arg12.findViewById().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(); 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) ?  : ; 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) ?  : ; 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(); this.d = arg12.findViewById(); 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(-); this.a.setFocusable(true); this.a.setFocusableInTouchMode(true); this.a.setTextColor(v10); this.a.setHintTextColor(-); this.a.setVisibility(0); this.b.findViewById().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().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.mobile.activity.registerGuideLogin.LoginView"; final String loginBtnClsNm = "com.tencent.mobile.activity.registerGuideLogin.LoginAnimBtnView"; //登陆按钮类名 final String loginBtnFiledNm = "a"; //登陆按钮变量名 final String pwdEdTextClsNm = "com.tencent.mobile.widget.CustomSafeEditText"; //密码输入框类名 final String pwdEdTextFiledNm = "a"; //密码输入框变量名 final String newStyleDropdownViewClsNm = "com.tencent.mobile.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() { 
    @Override protected 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() { 
    @Override public 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(); } } }); } }); } 

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

自动登陆.gif

3.3 实现退出登录

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

账号管理.png

3.3.1 获取堆栈信息

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

账号管理堆栈信息.png

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() { 
    @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { 
    super.afterHookedMethod(param); final Object accountManageActivityObj = param.thisObject; doMainLooper(context, 3000, new Runnable() { 
    @Override public void run() { 
    ReflectUtils.invokeMethod(accountManageActivityObj, "a", new Class[]{ 
   int.class, boolean.class}, 0, true); } }); } }); } 

四. 最终效果

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

退出登录.gif

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/223891.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午1:12
下一篇 2026年3月17日 下午1:12


相关推荐

  • Html背景图片_html背景图片平铺

    Html背景图片_html背景图片平铺这里面有许多简洁的html背景界面,对,重要的是简洁,而且很好看,verynice:https://www.toptal.com/designers/subtlepatterns/可能需要梯子。网站提供一键预览:   …

    2022年10月5日
    4
  • Latex 参考文献格式

    Latex 参考文献格式在Latex中,一般使用.bib文件,维护一个参考文献库,对于中英文要求文后的参考文献显示格式不同,我们仅需要修改.tex文件中的引用格式即可。一.对于中文文章,参考文献格式一般要求按照下面的格式进行排版显示格式一般为(举个例子):在Latex中,我们仅需要修改两处:1.在\begin{document}前面加上\usepackage[numbers]{gbt7714}2.在后面参考文献处写上:{\small \bibliographystyle{gbt7714-nume

    2025年10月13日
    11
  • html的display属性

    html的display属性block 标签 比如 div 默认独占一行显示 撑满一行 高度为 0 支持所有 css 命令设置了宽度后 后面剩余的宽度还是会占一行 只是不显示属性 display 可以改变标签的标签的属性 inline 内嵌 标签 比如 span 不占一行显示 后面可以继续跟同类型标签内容撑开宽度不支持宽高不支持上下的 margin 和 padding 代码换行被解析属性 display 可以改变标签的标签的属性

    2026年3月19日
    2
  • Docker镜像自动执行脚本.sh

    Docker镜像自动执行脚本.sh由于博客现在在docker中部署的,每次打包部署,需要停掉旧容器->删除旧容器->删除镜像->将新的jar包打包成镜像->再次运行,每次发布都需要重复的执行此操作,于是想将所有命令写成脚本,jar包上传之后,运行脚本即可。命令:app_name=’java-blog’app_port=’8081’# 停止正在运行的容器echo ‘……stop container……’docker stop ${app_name}# 删除容器echo ‘…..

    2022年6月13日
    198
  • SplitContainer控件设置固定比例

    SplitContainer控件设置固定比例SplitContainer控件2个panel如何设置均等大小.(竖直拆分)先随意设置空间高的大小,然后如果想要均等显示,则直接设置SplitterDistance为高的一半,即可!随后任意改变控件大小,都不会改变均等显示比例。同理3:7,4:6…

    2022年7月18日
    15
  • java海伦公式求三角形面积_海伦公式求三角形面积出错求教

    java海伦公式求三角形面积_海伦公式求三角形面积出错求教该楼层疑似违规已被系统折叠隐藏此楼查看此楼就下面这个程序输入其他的数字都可以算出面积但是当输入 3 4 6 时计算出面积为零求吧友指出错误在哪 packagejavaa importjava util Scanner publicclassJ publicstatic String ar

    2026年3月20日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号