Hook技术是一种逆向技术,源于商业和军工领域的硬件分析,主要的目的是通过对成品的分析,得出产品的设计原理。逆向分析分为动态分析和静态分析,静态分析是在不执行程序的情况下对程序的行为进行分析;动态分析是在程序运行时对程序进行调试分析的技术。
Hook属于逆向分析中的动态分析。
在Android程序中,程序的调用与结果返回是按照固定的逻辑与顺序进行,如:
对象A调用对象B对象,B对象处理后将数据返回给A,中间没有任何其他的对象对其产生影响,也就是说A最终得到的结果就是B给A的结果。
那么有Hook技术参与的这段调用流程将会有如下变化:
也就是在A调用B的过程中,Hook对这次调用做了劫持,并且修改了其中的参数或返回值。
无中生有 暗度陈仓 凭空想象 凭空捏造
既然Hook这么牛掰,那么它究竟是什么呢???书上说,它可以是一个方法或一个对象。。。。
我们知道,在Android系统中,应用程序的进程彼此独立,应用进程与系统进程之间也是如此,那么想要修改其他进程的某些行为不能直接实现。那么合理运用Hook技术,就可以实现修改其他进程的行为。
·Hook Java 主要通过反射和代理实现,用于在Android SKD 环境修改Java代码。
·Hook Native 应用于在NDK环境修改Native代码。
·应用程序进程Hook只Hook当前应用程序进程。
·全局Hook是通过对Zygote进行Hook,来Hook系统中所有的应用程序进程。为什么呢?因为应用程序进 程由Zygote fork 出来的。可通过框架实现比如Xposed(需要root)。
so 可能我们要先了解一下 代理模式
代理模式是Hook技术的基础之一。代理模式也叫委托模式,是结构型设计模式的一种。试想一下在生活中我们经常会通过代购去买一些物品,包包,口红,化妆品,加特林……其实这就是代理模式。
代理模式是指:为其他对象提供一种代理以控制对这个对象的访问。
翻译一下,就是我作为一个客户,想买一个国外的包,但是又因为穷,不能因为买个包出国,所以我找了代购去帮我买,我只负责交钱拿货就行了,而国外的商店是和代购进行交易的。这个代购就是我的代理,是隔离我与商店的对象。
代理模式的实现:声明一个功能接口,代理类和真实类都实现这个功能接口,在代理类中持有真实类的对象,当调用代理类执行方法时,代理类内部最终调用真实类的对象方法。
翻译一下,我与代购都继承了购物这个功能,“我”是代购的客户,代购持有“我”这个真实买家的引用(可能把我记在了小本本上)去麦包包,当调用代购的购物功能时,代购最终会调用“我”的购物功能(没错花的是我的钱)。
静态代理:
RealBuyer realBuyer = new RealBuyer(); //创建真实购买对象
BuyerProxy buyerProxy = new BuyerProxy(realBuyer); //创建代理购买对象并代理真实对象
buyerProxy.buy(); //代理购买对象执行代理方法
public interface IShop {
void buy(String product);
}
/**
* 客户
*/
public class RealBuyer implements IShop {
private static final String TAG = "真实购买者";
@Override
public void buy() {
System.out.println(TAG + "买了个包");
}
}
/**
* 代购
*/
public class BuyerProxy implements IShop {
private static final String TAG = "代购";
private IShop mRealBuyer;
public BuyerProxy(IShop realBuyer) {
this.mRealBuyer = realBuyer;
}
@Override
public void buy(String product) {
System.out.println(TAG + "去商店买包");
mRealBuyer.buy(product);
}
}
它运行的结果是这样的:
I/System.out: 代购去商店买包
I/System.out: 真实购买者买了个包
好了,到此为止代购已经为我成功的购买了一次。
我们来分析一下,由于我与商店之间是通过代购来执行购买行为,所以在这个购买的过程中代购是可以随意对商品进行操作的,比如替换一下:
@Override
public void buy() {
System.out.println("代购去商店买假包");
mRealBuyer.buy();
}
它运行的结果是这样的:
I/System.out: 代购去商店买假包
I/System.out: 真实购买者买了个包
以上代理模式分析,在代码运行前已经创建了代理类的class文件,这种叫做静态代理。
动态代理:编码时不指定代理对象,在执行时动态生成一个代理对象,这种方式由Java接口InvocationHandler提供支持。动态代理实现该接口并重写invoke方法,如下所示:
IShop realBuyer = new RealBuyer();//创建真实购买者
DynamicBuyerProxy dynamicBuyerProxy = new DynamicBuyerProxy(realBuyer); //创建动态处理器
ClassLoader loader = realBuyer.getClass().getClassLoader();
IShop buyerProxy = (IShop) Proxy.newProxyInstance(loader
, new Class[]{
IShop.class}, dynamicBuyerProxy);//生成动态代理类
buyerProxy.buy(); //调用代理类的buy方法会调用动态代理的invoke方法。
public interface IShop {
void buy();
}
/**
* 客户
*/
public class RealBuyer implements IShop {
private static final String TAG = "真实购买者";
@Override
public void buy() {
System.out.println(TAG + "买了个包");
}
}
public class DynamicBuyerProxy implements InvocationHandler {
private Object obj;
public DynamicBuyerProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("buy")) {
System.out.println("代购去买包");
}
Object result = method.invoke(obj, args);
return result;
}
}
输出是这样的
I/System.out: 代购去买包
I/System.out: 真实购买者买了个包
en,可以看出,动态代理和静态代理都可以拦截源程序的执行。
这是不是就很尴尬,所以,一定要找靠谱的代购^^ 不对,跑题了, 所以我们可以通过代理类来对原程序的逻辑进行拦截和修改,这里的原程序包括系统的框架。
以上介绍了代理模式,那么Hook java技术主要通过代理和放射方式实现,如果我们想要一个稳定的Hook就需要找到一个固定且必定会执行的点,即Hook点,Hook点要选取容易找到并且不易变化的对象。静态变量和单例是符合条件的选择。
Hook的本质就是劫持并替换,被劫持的对象叫Hook点,用代理对象去替换Hook点,并在代理对象中做自定义操作,这是一个典型应用。下面我们来举个简单的例子,来运用Hook技术来实现通过startActivity Activity A来启动Activity B。
根据前面的阐述,我们知道,Hook技术的关键点在于找到Hook点,那么我们首先来找到替换目标Activity的Hook点。
so,让我们来分析一下Activity的启动过程:
上代码:
/**
* Same as {@link #startActivity(Intent, Bundle)} with no options
* specified.
*
* @param intent The intent to start.
*
* @throws android.content.ActivityNotFoundException
*
* @see #startActivity(Intent, Bundle)
* @see #startActivityForResult
*/
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
当我们在调用Activity的startActivity方法时,该方法只是调用了 this.startActivity(intent, null)方法,继续查看该方法:
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
最终会调用:
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
// If this start is requesting a result, we can avoid making
// the activity visible until the result is received. Setting
// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
// activity hidden during this time, to avoid flickering.
// This can only be done when a result is requested because
// that guarantees we will get information back when the
// activity is finished, no matter what happens to it.
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
// TODO Consider clearing/flushing other event sources and events for child windows.
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
// Note we want to go through this method for compatibility with
// existing applications that may have overridden it.
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
其关键代码在于这句:
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
我们可以推测真正执行启动Activity的是mInstrumentation对象的execStartActivity方法,而mInstrumentation是Activity的成员变量,所以我们推测到Hook点为mInstrumentation对象。
现在找到了Hook点,那么接下来我们就创建它的代理类,并代理execStartActivity方法:
public class InstrumentationProxy extends Instrumentation {
private static final String TAG = "InstrumentationProxy";
Instrumentation mInstrumentation;
public InstrumentationProxy(Instrumentation instrumentation) {
this.mInstrumentation = instrumentation;
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
System.out.println(TAG + " Hook 调用成功");
try {
Method execStartActivityMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",
Context.class,
IBinder.class,
IBinder.class,
Activity.class,
Intent.class,
int.class,
Bundle.class);
ComponentName componentName = intent.getComponent();
String className = componentName.getClassName();
if (className.equals("com.test.threadexcoter.Main2Activity")) {
intent.setClassName(who, Main3Activity.class.getCanonicalName());
}
return (ActivityResult) execStartActivityMethod.invoke(mInstrumentation,
who,
contextThread,
token,
target,
intent,
requestCode,
options);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
因为execStartActivity方法不提供外部调用所以只能通过反射来调用,在调用真实的mInstrumentation 对象的execStartActivity方法前对intent做了修改,把跳转的Main2Activity替换成Main3Activity。
代理类有了下面我们来用反射把Activity中的mInstrumentation对象替换成代理对象,并把真实的mInstrumentation传入代理类:
private void hookActivityInstrumentation(Activity activity) {
Field field = null;
try {
field = Activity.class.getDeclaredField("mInstrumentation");
field.setAccessible(true);
Instrumentation instrumentation = (Instrumentation)field.get(activity);
Instrumentation instrumentationProxy = new InstrumentationProxy(instrumentation);
field.set(activity, instrumentationProxy);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
那么,在我们跳转Main2Activity之前,对mInstrumentation进行Hook
hookActivityInstrumentation(this);
Intent intent = new Intent(this, Main2Activity.class);
startActivity(intent);
到此为止,当调用 startActivity(intent);打开Main2Activity时,实际打开的是Main3Activity。
以上案例的前提是所涉及Activity都在AndroidManifest.xml中注册。如果启动一个未注册的Activity会跑出异常,那么可不可以打开没有注册过的Activity呢?
顺便复习一下Context的startActivity流程
我们知道Context的实现类是ContextImpl,其startActivity方法最终调用ActivityThread 的 getInstrumentation 方法得到 Instrumentation,在Android中一个进程只有一个ActivityThread,所以ActivityThread中的Instrumentation对象是符合Hook点要求的,于是有了下列代码:
private void hookActivityInstrumentation2(Activity activity) {
try {
Class<?> classes = Class.forName("android.app.ActivityThread");
Method activityThread = classes.getDeclaredMethod("currentActivityThread");
activityThread.setAccessible(true);
Object currentThread = activityThread.invoke(null);
Field instrumentationField = null;
instrumentationField = classes.getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);
Instrumentation instrumentationInfo = (Instrumentation) instrumentationField.get(currentThread);
InstrumentationProxy2 fixInstrumentation = new InstrumentationProxy2(instrumentationInfo);
instrumentationField.set(currentThread, fixInstrumentation);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
同样用反射和代理的方式替换了ActivityThread中的“mInstrumentation”,下面我们来看一下代理类中是如何偷天换日的:
public class InstrumentationProxy2 extends Instrumentation {
private static final String TAG = "InstrumentationProxy2";
Instrumentation mInstrumentation;
public InstrumentationProxy2(Instrumentation instrumentation) {
this.mInstrumentation = instrumentation;
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
System.out.println(TAG + " Hook 调用成功");
try {
Method execStartActivityMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",
Context.class,
IBinder.class,
IBinder.class,
Activity.class,
Intent.class,
int.class,
Bundle.class);
ComponentName componentName = intent.getComponent();
String className = componentName.getClassName();
if (className.equals("com.test.threadexcoter.Main4Activity")) {
intent.setClassName(who, Main2Activity.class.getCanonicalName());
}
return (ActivityResult) execStartActivityMethod.invoke(mInstrumentation,
who,
contextThread,
token,
target,
intent,
requestCode,
options);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Activity newActivity(ClassLoader cl, String className,
Intent intent) throws InstantiationException,
IllegalAccessException {
String packageName = intent.getComponent().getPackageName();
if (className.equals(Main2Activity.class.getCanonicalName())) {
ComponentName componentName = new ComponentName(packageName, Main4Activity.class.getCanonicalName());
intent.setComponent(componentName);
className = Main4Activity.class.getCanonicalName();
}
try {
@SuppressLint("PrivateApi")
Method method = mInstrumentation.getClass().getDeclaredMethod("newActivity",
ClassLoader.class, String.class, Intent.class);
if (!Modifier.isPublic(method.getModifiers())) {
method.setAccessible(true);
}
return (Activity) method.invoke(mInstrumentation, cl, className, intent); // 执行原来的创建方法
} catch (Exception e) {
throw new RuntimeException(e);
}
}
原理是通过Main2Activity进行占位,欺骗系统的检查,当创建Activity实例时再替换回真实的Main4Activity,从而实现打开未注册的Main4Activity
hookActivityInstrumentation2(this);
Intent intent = new Intent(this, Main4Activity.class);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
现在屏幕前的你是明白了还是更糊涂了呢
1.首先找到Hook点 通常为不易改变的对象或方法;
2.写代理类并反射替换Hook点:
3.在代理类中实现逻辑修改。
以上,如有错误请指正,我会尽快修改。
欢迎共同学习交流。
demo地址