Android Hook Java

什么是Hook

Hook技术是一种逆向技术,源于商业和军工领域的硬件分析,主要的目的是通过对成品的分析,得出产品的设计原理。逆向分析分为动态分析静态分析,静态分析是在不执行程序的情况下对程序的行为进行分析;动态分析是在程序运行时对程序进行调试分析的技术。
Hook属于逆向分析中的动态分析。

Android中的Hook介绍

在Android程序中,程序的调用与结果返回是按照固定的逻辑与顺序进行,如:

调用
返回
A
B

对象A调用对象B对象,B对象处理后将数据返回给A,中间没有任何其他的对象对其产生影响,也就是说A最终得到的结果就是B给A的结果。
那么有Hook技术参与的这段调用流程将会有如下变化:

调用
修改后调用
返回
修改后返回
A
HOOK
B

也就是在A调用B的过程中,Hook对这次调用做了劫持,并且修改了其中的参数或返回值。
无中生有 暗度陈仓 凭空想象 凭空捏造
既然Hook这么牛掰,那么它究竟是什么呢???书上说,它可以是一个方法或一个对象。。。。
我们知道,在Android系统中,应用程序的进程彼此独立,应用进程与系统进程之间也是如此,那么想要修改其他进程的某些行为不能直接实现。那么合理运用Hook技术,就可以实现修改其他进程的行为。

Hook分类

1.根据Hook的API语言分类:Hook Java,Hook Native

·Hook Java 主要通过反射和代理实现,用于在Android SKD 环境修改Java代码。
·Hook Native 应用于在NDK环境修改Native代码。

2.根据Hook的进程,分为应用程序进程Hook, 全局Hook

·应用程序进程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点,并在代理对象中做自定义操作,这是一个典型应用。下面我们来举个简单的例子,来运用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方法,而mInstrumentationActivity的成员变量,所以我们推测到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);

现在屏幕前的你是明白了还是更糊涂了呢
Alt

不如我们再来总结一下

1.Hook技术是一种动态的逆向技术。

2.Hook java 运用反射和代理模式,用于在Android SKD 环境修改Java代码。

3.Hook的步骤

1.首先找到Hook点 通常为不易改变的对象或方法;
2.写代理类并反射替换Hook点:
3.在代理类中实现逻辑修改。

以上,如有错误请指正,我会尽快修改。
欢迎共同学习交流。

demo地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值