Android apk快速定位、灰色按钮克星--DroidSword
本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80994434
在进行Android应用程序的逆向分析时,经常需要对Android应用程序的按钮事件、Activity界面等类的代码进行定位分析,传统的代码定位方法就是进行按钮或者Activity界面等显示的 字符串信息 进行全局的搜索,然后找他们的id或者类进行代码的定位,比较繁琐,这里介绍一个基于Xposed Hook实现的Android apk快速定位,灰色按钮克星工具DroidSword,当然了亦可以使用我前面的博客中提到的《Xposed框架Hook Android应用的所有类方法打印Log日志》和《查找和定位Android应用的按钮点击事件的代码位置基于Xposed Hook实现》进行Android应用程序的需要分析的代码的定位。
DroidSword工具的功能介绍:
1.快速定位Activity,以及点击View的信息
2.点击悬浮窗口获取Fragment
3.灰色按钮克星
4.文字修改神器
DroidSword工具的github地址:https://github.com/githubwing/DroidSword
DroidSword工具作者的学习博客:http://androidwing.net
DroidSword工具是基于Xposed Hook实现的,但是作者githubwing是使用Kotlin语言实现的,对于Kotlin语言不熟悉,但是对于DroidSword工具的实现思路还是能看明白,下面简要的分析一下。
1.类IHooker是作者编写的xposed hook的接口类,代码如下:
2.类net.androidwing.droidsword.Init是DroidSword工具xposed hook的入口类:
3.类ViewClickedHooker主要用于实现Hook类android.view.View的方法onTouchEvent,获取到View类的名称和View类的id以及View的事件监听类对象的类名称;Hook类android.view.View的方法dispatchTouchEvent,获取到的View类的名称、View类的id、View的事件监听类对象的类名称并在设备的界面上显示出来。
源码文件路径:/frameworks/base/core/java/android/view/View.java
一般View组件情况下,Hook类android.view.View的类方法onTouchEvent函数,View组件通过获取实例对象View中的成员变量mListenerInfo->mOnClickListener所属的类名称,得到响应View按钮单击事件的监听响应类OnClickListener的信息。
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true);
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true);
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
对于AdapterView类型的View组件,通过Hook类android.view.View的方法dispatchTouchEvent,AdapterView组件获取实例对象View中的成员变量mOnItemClickListener的类(事件响应类)的类名称,得到监听和响应用户单击事件的处理类OnItemClickListener的信息。
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
4.类ActivityHooker主要用于实现Hook类android.app.Activity的方法onResume,获取类方法onResume所属类Activity的实例对象的类名称并显示出来。
/**
* Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or
* {@link #onPause}, for your activity to start interacting with the user.
* This is a good place to begin animations, open exclusive-access devices
* (such as the camera), etc.
*
* <p>Keep in mind that onResume is not the best indicator that your activity
* is visible to the user; a system window such as the keyguard may be in
* front. Use {@link #onWindowFocusChanged} to know for certain that your
* activity is visible to the user (for example, to resume a game).
*
* <p><em>Derived classes must call through to the super class's
* implementation of this method. If they do not, an exception will be
* thrown.</em></p>
*
* @see #onRestoreInstanceState
* @see #onRestart
* @see #onPostResume
* @see #onPause
*/
protected void onResume() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
getApplication().dispatchActivityResumed(this);
mCalled = true;
}
5.类FragmentHooker用于实现Hook类"android.support.v4.app.Fragment"的方法onResume和方法setUserVisibleHint,获取类Fragment的类名称并进行显示。
源码文件路径:/frameworks/support/v4/java/android/support/v4/app/Fragment.java
/**
* Called when the fragment is visible to the user and actively running.
* This is generally
* tied to {@link Activity#onResume() Activity.onResume} of the containing
* Activity's lifecycle.
*/
public void onResume() {
mCalled = true;
}
/**
* Set a hint to the system about whether this fragment's UI is currently visible
* to the user. This hint defaults to true and is persistent across fragment instance
* state save and restore.
*
* <p>An app may set this to false to indicate that the fragment's UI is
* scrolled out of visibility or is otherwise not directly visible to the user.
* This may be used by the system to prioritize operations such as fragment lifecycle updates
* or loader ordering behavior.</p>
*
* @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
* false if it is not.
*/
public void setUserVisibleHint(boolean isVisibleToUser) {
if (!mUserVisibleHint && isVisibleToUser && mState < STARTED) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
mDeferStart = !isVisibleToUser;
}
有作者gtict112将DroidSword工具的功能用java代码进行了实现并添加了新的功能构建成工程xposedhook,xposedhook工程的github地址:https://github.com/gtict112/xposedhook,后面有时间我再看下将这部分代码集成到我自己的Xposed模块中。
DroidSword工具的类ViewClickedHooker的代码:
package net.androidwing.droidsword.hooker
import android.app.AlertDialog
import android.app.AndroidAppHelper
import android.app.Dialog
import android.content.DialogInterface
import android.view.MotionEvent
import android.view.View
import android.widget.*
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage
import net.androidwing.droidsword.func.TextViewChanger
import net.androidwing.droidsword.func.ViewEnabler
import net.androidwing.droidsword.utils.LogUtils
/**
* Created on 28/10/2017.
*/
class ViewClickedHooker : IHooker {
override fun hook(lp: XC_LoadPackage.LoadPackageParam) {
// Hook类android.view.View的方法onTouchEvent
// public boolean onTouchEvent(MotionEvent event)
XposedHelpers.findAndHookMethod(View::class.java,
"onTouchEvent",
MotionEvent::class.java, object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) {
super.afterHookedMethod(param)
// 获取类方法onTouchEvent所在的实例对象View
val view = param?.thisObject as View
// 获取类方法onTouchEvent的传入参数MotionEvent实例对象
val event = param.args!![0] as MotionEvent
// 对用户点击屏幕的事件进行判断
if (event.action == MotionEvent.ACTION_UP) {
// 获取实例对象View中的成员变量mListenerInfo->mOnClickListener所属的类名称
val listener = XposedHelpers.getObjectField(
XposedHelpers.getObjectField(view, "mListenerInfo"),
"mOnClickListener").javaClass.name
// 显示获取到的View类的名称、View类的id、View的事件监听类对象的类名称
ActivityHooker.setActionInfoToMenu("",
"${view.javaClass.name} ${view.id} \nListener: $listener")
antiDisable(view)
}
}
})
// Hook类android.view.View的方法dispatchTouchEvent
// public boolean dispatchTouchEvent(MotionEvent event)
XposedHelpers.findAndHookMethod(View::class.java,
"dispatchTouchEvent",
MotionEvent::class.java, object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) {
super.afterHookedMethod(param)
// 获取类方法dispatchTouchEvent所在类View的实例
val view = param?.thisObject as View
// 获取类方法onTouchEvent的传入参数MotionEvent实例对象
val event = param.args!![0] as MotionEvent
// 进行用户点击屏幕的事件类型的判断
if (event.action == MotionEvent.ACTION_DOWN) {
// 进行View类型的判断(AdapterView)
if (view is AdapterView<*>) {
// 获取实例对象View中的成员变量mOnItemClickListener的类(事件响应类)的类名称
val listener = XposedHelpers.getObjectField(view,"mOnItemClickListener").javaClass.name
// 显示获取到的View类的名称、View类的id、View的事件监听类对象的类名称
ActivityHooker.setActionInfoToMenu("",
"${view.javaClass.name} ${view.id} \nListener: $listener")
}
}
}
})
// 文字修改功能的实现
XposedHelpers.findAndHookMethod(View::class.java,
"onTouchEvent",
MotionEvent::class.java, object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) {
super.afterHookedMethod(param)
val targetView = param?.thisObject as View
if (true) {
// ??
showChangeTextDialog(targetView, param)
}
}
})
}
private fun antiDisable(view: View) {
//TODO 默认开启待添加配置文件
if (false) {
ViewEnabler.antiDisable(view)
}
}
/**
* 文本修改神器功能
*/
private fun showChangeTextDialog(targetView: View,
param: XC_MethodHook.MethodHookParam) {
//TODO 默认开启待添加配置文件
val event = param.args!![0] as MotionEvent
if (false) {
TextViewChanger.showChangeDialog(targetView, event)
}
}
DroidSword工具的类ActivityHooker的代码:
package net.androidwing.droidsword.hooker
import android.app.Activity
import android.app.AndroidAppHelper
import android.app.Fragment
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.support.v7.widget.AppCompatImageHelper
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage
import net.androidwing.droidsword.utils.LogUtils
import java.util.ArrayList
/**
* Created on 30/10/2017.
*/
class ActivityHooker : IHooker {
// /frameworks/base/core/java/android/app/Activity.java
override fun hook(lp: XC_LoadPackage.LoadPackageParam) {
// Hook类android.app.Activity的方法onResume
// protected void onResume()
XposedHelpers.findAndHookMethod(Activity::class.java, "onResume", object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) {
super.afterHookedMethod(param)
// 获取类方法onResume所属类Activity的实例对象
val activity = param?.thisObject as Activity
// 显示类对象实例Activity的类名称
addTextView(activity)
// Hook类Fragment的类方法,获取类Fragment实例对象的类名称
FragmentHooker().hookFragment(param)
}
})
}
// 显示类对象实例Activity的类名称
private fun addTextView(activity: Activity) {
// 获取类对象实例Activity的类名称
val className = activity.javaClass.name.toString()
// 构建TextView实例对象
if (sTextView == null) {
genTextView(activity)
}
if (sTextView?.parent != null) {
val parent = sTextView?.parent
if (parent is ViewGroup) {
parent.removeView(sTextView)
}
}
(activity.window.decorView as FrameLayout).addView(sTextView)
// 显示类对象实例Activity的类名称
setActionInfoToMenu(className, "")
sTextView?.bringToFront()
}
// 创建TextView的实例对象
private fun genTextView(activity: Activity) {
sTextView = TextView(activity)
with(sTextView!!) {
textSize = 8f
y = 48 * 2f
setBackgroundColor(Color.parseColor("#cc888888"))
setTextColor(Color.WHITE)
layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT)
}
}
companion object {
var sTextView: TextView? = null
private var sActivityName = ""
private var sViewName = ""
fun setActionInfoToMenu(activityName: String, viewName: String) {
sTextView?.text = getActionInfo(activityName, viewName)
}
public var sFragmentName = ""
private fun getActionInfo(activityName: String, viewName: String): CharSequence? {
if (activityName.isEmpty().not()) {
sActivityName = activityName
}
if (viewName.isEmpty().not()) {
sViewName = viewName
}
val pid = android.os.Process.myPid()
return "Activity: $sActivityName \nPid: $pid \nClick: $sViewName \nFragment:$sFragmentName"
}
}
DroidSword工具的类FragmentHooker的代码:
package net.androidwing.droidsword.hooker
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage
import net.androidwing.droidsword.utils.LogUtils
/**
* Created on 30/10/2017.
*/
class FragmentHooker : IHooker {
override fun hook(lp: XC_LoadPackage.LoadPackageParam) {
}
// /frameworks/support/v4/java/android/support/v4/app/Fragment.java
fun hookFragment(param: XC_MethodHook.MethodHookParam?) {
// Hook类"android.support.v4.app.Fragment"的方法onResume
// public void onResume()
XposedHelpers.findAndHookMethod(
param?.thisObject?.javaClass?.classLoader?.loadClass("android.support.v4.app.Fragment"),
"onResume",
object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) {
super.afterHookedMethod(param)
// 获取类Fragment的类名称
ActivityHooker.sFragmentName = (param?.thisObject?.javaClass?.name!!)
// 进行类Fragment的类名称显示的设置
ActivityHooker.setActionInfoToMenu("","")
}
override fun beforeHookedMethod(param: MethodHookParam?) {
super.beforeHookedMethod(param)
}
})
// Hook类"android.support.v4.app.Fragment"的方法setUserVisibleHint
// public void setUserVisibleHint(boolean isVisibleToUser)
XposedHelpers.findAndHookMethod(
param?.thisObject?.javaClass?.classLoader?.loadClass("android.support.v4.app.Fragment"),
"setUserVisibleHint", Boolean::class.java,
object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) {
super.afterHookedMethod(param)
if (param?.args!![0] == true) {
LogUtils.e("fragment showing:")
LogUtils.e("fragment ${param?.thisObject?.javaClass?.name}")
// 获取类Fragment的类名称
ActivityHooker.sFragmentName = (param?.thisObject?.javaClass?.name!!)
// 进行类Fragment的类名称显示的设置
ActivityHooker.setActionInfoToMenu("","")
}
}
override fun beforeHookedMethod(param: MethodHookParam?) {
super.beforeHookedMethod(param)
}
})
}
}
Android apk快速定位、灰色按钮克星--DroidSword的更多相关文章
- CLRInjection - 通用托管注入(超级灰色按钮克星升级版)
通用托管注入 - CLRInjection CLR软件系列第二发: 通用托管注入 - CLRInjection 软件简介:这款软件可以将任意托管DLL用插件的形式,注入到正在运行中的.net托管程序集 ...
- [Android Studio] Android Studio快速定位当前打开的文件在哪个目录(package)下
转载自:http://blog.csdn.net/hyr83960944/article/details/38067499 在Eclipse中有一个很好的功能,就是比如我打开一个AActivity,左 ...
- [Android Studio] Android Studio快速定位当前打开的文件在哪个目录(package)下
转载自:http://blog.csdn.net/hyr83960944/article/details/38067499 在Eclipse中有一个很好的功能,就是比如我打开一个AActivity,左 ...
- Android ListView快速定位(三)
方法三: android:fastScrollEnabled="true" 这个很简单,只要把属性设置了,就可以起作用了 不过这个滑块比较丑,当然网上也有自定义图片的例子. 参考 ...
- Android ListView快速定位(二)
方法二:android:textFilterEnabled="true" + Filter 这个属性在android.widget.AbsListView下,要求adapter必须 ...
- Android ListView快速定位(四)
方法四: 添加一个EditText,作为搜索框 + Filter 其实这个不算第四个方法,因为与第二个一样,主要是实现Filter. 但是对于EditText的监听,我以前也没有写过,所以也记录一下. ...
- Android ListView快速定位(一)
方法一: SectionIndexer接口 + 索引列表 参考:http://www.apkbus.com/android-69999-1-1.html 所谓section 就是一组有共性的item, ...
- 快速定位 Android APP 当前页面的三种方法(Activity / Fragment)
方法一.通过adb命令打印当前页面: Android 如何快速定位当前页面是哪个Activity or Fragment (1)查看当前Activity :adb shell "dumpsy ...
- Android GIS开发系列-- 入门季(10) MapView快速定位到Geometry
我们知道某个Geometry的坐标,但不知道具体的位置,该如何使地图快速定位呢?这时需要用到MapView.setExtent方法,来看下这个方法的介绍:Zooms the map to the gi ...
随机推荐
- PAT-1043(Is It a Binary Search Tree)JAVA实现
Is It a Binary Search Tree PAT-1043 主要涉及到根据前序遍历序列片段是否是一颗二叉树,这里有一个小tip就是插入序列就是二叉树的前序遍历序列. 第二个是会对排序二叉树 ...
- vue+vuex 修复数据更新页面没有渲染问题
不解: 为什么在关闭开关后,已经将data里的属性和vuex属性初始化后,页面就是不响应??? 问题: 由于切换路由后,获取到vuex的数据在created中赋值到data相对应的属性中,在关闭开关后 ...
- Python基础(2)——循环和分支[xiaoshun]
一.瞎扯 世界上一切的系统都可以被'分支'表示.循环也是分支,只不过又重复之前的'分支'选择罢了.程序如人生,每一次的'分支',每一次的选择,都会有不同的结果: 有的选择止步不前,无限循环: 有的选择 ...
- [LOJ 572] Misaka Network 与求和
一.题目 点此看题 二.解法 直接推柿子吧: \[\sum_{i=1}^n\sum_{j=1}^nf(\gcd(i,j))^k \] \[\sum_{d=1}^nf(d)^k\sum_{i=1}^{n ...
- 「NOIP模拟赛」Round 2
Tag 递推,状压DP,最短路 A. 篮球比赛1 题面 \(Milky\ Way\)的代码 #include <cstdio> const int N = 2000, xzy = 1e9 ...
- ArrayList源码阅读(小白的java进阶)
ArrayList(线程不安全) ArrayList是一个其容量能够动态增长的动态数组 继承关系 构造方法 是符合collection父接口的规范的 //传0则设置为默认容量 public Array ...
- 《数据持久化与鸿蒙的分布式数据管理能力》直播课答疑和PPT分享
问:hi3861开发板支持分布式数据库吗? 目前,分布式数据库仅支持Java接口,因此Hi3861没有现成的API用于操作分布式数据库. 问:分布式数据管理包括搜索吗? 分布式数据管理包括融合搜索能力 ...
- java中的String,StringBuffer与StringBuilder
String类是不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁. StringBuffer对象则代表一个字符序列可变的字符串,当一个Stri ...
- 为科学计算而生的Julia——基于Manjaro Linux的安装与入门
技术背景 Julia是一门为科学计算而生的编程语言,其着重强调了开源.生态与性能.从开源角度来说,相比于Matlab就要友好很多,用户可以免费使用,而且MIT协议应该是最宽松的开源协议之一(截图来自于 ...
- 写个小程序01 | 注册微信小程序
出于兴趣和学习目的,我想自己做一个基于"子弹笔记(Bullet Journal)"的小程序.由于个人开发经验很有限,只在课程作业中写过 web 前端,所以也不知道多久能写出来(逃) ...