你想成为Android高级工程师你还得学习Hook

Posted 风雨田

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你想成为Android高级工程师你还得学习Hook相关的知识,希望对你有一定的参考价值。

目的

这篇文章是让大家体会hook技术大概是什么样子,在那些地方可以应用到。后面文章将深入hook技术,以及解析市面上现有的hook开源框架原理。

在前面的文章中我们已经说过什么是代理模式,那我们就先使用最简单的静态代理模式应用在我们的hook技术中。我们先复习一下静态代理模式

/** 
 * 定义Demo接口 
 */  
public interface Demo     
    public void save();  
/** 
 * DemoImpl实现Demo接口并覆写save()方法 
 * 真实主题,执行具体业务 
 */  
public class DemoImpl implements Demo     
    public void save()   
        System.out.println("调用save()方法");  
      
  
/** 
 * DemoImplProxy 也实现了Demo接口,并覆写了save()方法,增加了自己的业务  
 * 代理主题,负责其他业务的处理 
 */  
public class DemoImplProxy implements Demo     
    Demo demoImpl = new DemoImpl();  

    public void save()   
        System.out.println("开始记录日志");  
        demoImpl.save();  
        System.out.println("开始结束日志");  
      
  

我们看出来,代理是对原有功能的拓展,而且很多时候我们在开发的过程中并不能改变我们原有的功能,比如系统打电话,startActivity等等。这时候我们就想,如果我们对他们进行拓展,让他们添加我们自己的一些元素,是不是就可以达到意想不到的效果。所以我们就引出了Hook的概念。其实Hook就是干偷梁换柱之事。

项目代码

++分析在下面模块++

我先把项目写上去,然后我们在分析,这样我觉得比先一大堆理论要好。我们目的很简单就是让我们在startActivity的时候我们拓展一下我们的功能(打印一些信息)

首先实现自己的Application子类,原因是这里执行比较早

public class APP extends Application 
    @Override
    public void onCreate() 
        super.onCreate();
        setEvilInstrumentation();
    

    public void setEvilInstrumentation() 
        try 
            //先获取到当前的ActivityThread对象
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");//得到这个方法
            currentActivityThreadMethod.setAccessible(true);
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);//此时得到的就是ActivityThread(因为在attach的时候调用赋值)

            Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");//得到ActvitiyThread中的mInstrumentation字段
            mInstrumentationField.setAccessible(true);
            Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);

            //创建代理对象
            EvilInstrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation,APP.this);
            mInstrumentationField.set(currentActivityThread, evilInstrumentation);//将系统中mInstrumentation换成自己的EvilInstrumentation
         catch (Exception e) 
            e.printStackTrace();
        
    

我们要替换的类EvilInstrumentation

public class EvilInstrumentation extends Instrumentation 

    private static final String TAG = "EvilInstrumentation";
    // ActivityThread中原始的对象, 保存起来
    Instrumentation mBase;
    private Context mContext = null;

    public EvilInstrumentation(Instrumentation base) 
        mBase = base;
    

    public EvilInstrumentation(Instrumentation mInstrumentation, APP app) 
        this(mInstrumentation);
        mContext = app.getApplicationContext();
    

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) 

        Toast.makeText(mContext, "\\n执行了startActivity, 参数如下: \\n" + "who = [" + who + "], " +
                "\\ncontextThread = [" + contextThread + "], \\ntoken = [" + token + "], " +
                "\\ntarget = [" + target + "], \\nintent = [" + intent +
                "], \\nrequestCode = [" + requestCode + "], \\noptions = [" + options + "]", Toast.LENGTH_LONG).show();

        // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
        try 
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mBase, who,
                    contextThread, token, target, intent, requestCode, options);
         catch (Exception e) 
            throw new RuntimeException("需要重新适配了,framework层代码被谷歌改了");
        
    

MainActivity

public class MainActivity extends AppCompatActivity 
    private Button btn ;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn = findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                startActivity(new Intent(MainActivity.this,Activity2.class));
            
        );
    
public class Activity2 extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_2);
    

代码分析

原本如果我们要处理一件事物,我们就需要如下使用

graph LR
A-->B.Method

A直接调用B的方法,当我们Hook的时候就需要多一层调用。

将B完全封装到C中,A直接和C进行通信即可,在我们项目中如何体现呢?

我们项目中,就是通过Hook将原本Android framework中startActivity过程的一个处理方法拦截下来,从而执行我们的方法。具体做法如下:

这个就是我们的核心方法

public void setEvilInstrumentation() 
    try 
        //先获取到当前的ActivityThread对象
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");//得到这个方法
        currentActivityThreadMethod.setAccessible(true);
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);//此时得到的就是ActivityThread(因为在attach的时候调用赋值)

        Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");//得到ActvitiyThread中的mInstrumentation字段
        mInstrumentationField.setAccessible(true);
        Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);

        //创建代理对象
        EvilInstrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation,APP.this);
        mInstrumentationField.set(currentActivityThread, evilInstrumentation);//将系统中mInstrumentation换成自己的EvilInstrumentation
     catch (Exception e) 
        e.printStackTrace();
    

我们首先要熟悉startActivity的大概流程,要知道从哪里拦截比较好,这样我们心中就有一个目的,从而通过反射达到那个目的。

小结过程
- 获取ActivityThread类的Class(这样就能得到其中所有的方法和变量)
- 获取ActivityThread的currentActivityThread方法
- 执行currentActivityThread方法就会返回ActivityThread实例
- 通过ActivityThread实例我们拿到mInstrumentation成员变量
- 通过mInstrumentation成员变量我们就可以对这个变量进行代理,因为他是启动Activity的核心
- 将mInstrumentation对象包装到EvilInstrumentation实例中
- 将我们的系统中启动Activity的核心类mInstrumentation替换成EvilInstrumentation
- 然后startActivity就会执行我们代理类的逻辑也就是(execStartActivity)方法中的内容

但是有坑,有的委托类不让继承,我们就没办法控制其他类调用,怎么办?

  • 第一种办法就是将Instrumentation类中所有方法进行代理(好处就是对于每个方法有不同的处理逻辑)
  • 使用动态代理(不管执行什么方法都会执行我们动态代理里面写的逻辑)

通过上面来做个小结,Hook就是用我们的代理去替换原本的服务,然后达到拓展的目的,甚至改变原有服务的目的。那我们今天就尝试改变系统剪贴板。要想改变系统剪贴板,我们就得了解剪贴板服务最基本的流程,从而我们好控制系统剪贴板的Hook点在哪里。

剪贴板服务的基本流程

我们知道系统服务有个大管家,就是ServiceManager这个类,它管理着我们所有的系统服务。并且我们通过如下方法进行系统服务的获取

eg:得到ActivityManager服务

ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

这个服务的作用是和ActivityManagerService进行通信,从而达到系统控制当前App的进程,进程中包含四大组件也得到控制。

ContextImpl.java

@Override
public Object getSystemService(String name) 
    return SystemServiceRegistry.getSystemService(this, name);

SystemServiceRegistry.java

private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
        new HashMap<String, ServiceFetcher<?>>();

得到服务

public static Object getSystemService(ContextImpl ctx, String name) 
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;

注册服务

private static <T> void registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) 
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);

这里可以知道,ContextImpl对象利用SystemServiceRegistry管理我们的服务。

我们裁剪服务的存储

registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class,
        new CachedServiceFetcher<ClipboardManager>() 
    @Override
    public ClipboardManager createService(ContextImpl ctx) throws ServiceNotFoundException 
        return new ClipboardManager(ctx.getOuterContext(),
                ctx.mMainThread.getHandler());
    );

看到ClipboardManager就是这个服务的管理者

ClipboardManager.java

public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException 
    mContext = context;
    mService = IClipboard.Stub.asInterface(
            ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));

ServiceManager.java

private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException 
    final IBinder binder = getService(name);
    if (binder != null) 
        return binder;
     else 
        throw new ServiceNotFoundException(name);
    
public static IBinder getService(String name) 
    try 
        IBinder service = sCache.get(name);
        if (service != null) 
            return service;
         else 
            return Binder.allowBlocking(getIServiceManager().getService(name));
        
     catch (RemoteException e) 
        Log.e(TAG, "error in getService", e);
    
    return null;
public static void initServiceCache(Map<String, IBinder> cache) 
    if (sCache.size() != 0) 
        throw new IllegalStateException("setServiceCache may only be called once");
    
    sCache.putAll(cache);

这里我们可以转化成两句核心代码为:

IBinder service = sCache.get(name);
IClipboard.Stub.asInterface(service);

那我们的思路就可以写成j将sCache中的服务换成我们hook过的服务。然后hookasInterface方法使用我们自己的服务。

接下来我们就分析hook项目的代码

解析项目代码

public class Main2Activity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        try 
            setBinderHook();//方法主入口
         catch (Exception e) 
            e.printStackTrace();
        
        EditText editText = new EditText(this);
        setContentView(editText);
        this.getSystemService("");
    

    private void setBinderHook() throws Exception 
        final String CLIPBOARD_SERVICE = "clipboard";
        //对通过ServiceManager得到的clipboard服务进行hook
        Class<?> serviceManager = Class.forName("android.os.ServiceManager");
        Method getService = serviceManager.getDeclaredMethod("getService", String.class);
        IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE);
        //用BinderProxyHookHandler进行处理原始裁剪服务的IBinder对象
        IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
                new Class<?>[]IBinder.class,
                new BinderProxyHookHandler(rawBinder));

        Field cacheField = serviceManager.getDeclaredField("sCache");
        cacheField.setAccessible(true);
        Map<String, IBinder> cache = (Map) cacheField.get(null);
        //将替换过的IBinder对象存入ServiceManager的缓存中替换掉原来的对象
        cache.put(CLIPBOARD_SERVICE, hookedBinder);
    

下面这个代码可以看出来,将IBinder中的queryLocalInterface方法进行hook,让此方法返回我们让他返回的对象,这个对象是我们要替换掉系统服务的对象。要使得我们hook的对象听话,也就是一些方法达到我们的目的,我们必须继续使用动态代理模式对其进行代理。也就有了BinderHookHandler

public class BinderProxyHookHandler implements InvocationHandler 
    private static final String TAG = "BinderProxyHookHandler";
    IBinder base;
    Class<?> stub;
    Class<?> iinterface;

    public BinderProxyHookHandler(IBinder base) 
        this.base = base;
        try 
            this.stub = Class.forName("android.content.IClipboard$Stub");
            this.iinterface = Class.forName("android.content.IClipboard");
         catch (ClassNotFoundException e) 
            e.printStackTrace();
        
    

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        if ("queryLocalInterface".equals(method.getName())) 
            Log.d(TAG, "hook queryLocalInterface");
            return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
                    new Class[]  IBinder.class, IInterface.class, this.iinterface ,
                    new BinderHookHandler(base, stub));
        
        Log.d(TAG, "method:" + method.getName());
        return method.invoke(base, args);
    

此时我们已经进入到我们裁剪服务的代理对象中,其中base是我们原Binder,我们通过android.content.IClipboard$Stub对其进行执行asInterface()然后除了我们需要改变的其余方法我们都可以用这个对象处理。其余我们进行拦截

public class BinderHookHandler implements InvocationHandler 

    private static final String TAG = "BinderHookHandler";

    // 原始的Service对象 (IInterface)
    Object base;
    public BinderHookHandler(IBinder base, Class<?> stubClass) 
        try 
            Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);
            // IClipboard.Stub.asInterface(base);
            this.base = asInterfaceMethod.invoke(null, base);
         catch (Exception e) 
            throw new RuntimeException("hooked failed!");
        
    

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        // 把剪切版的内容替换为 "I am wang cai niao"
        if ("getPrimaryClip".equals(method.getName())) 
            Log.d(TAG, "hook getPrimaryClip");
            return ClipData.newPlainText(null, "you are hooked");
        
        // 欺骗系统,使之认为剪切版上一直有内容
        if ("hasPrimaryClip".equals(method.getName())) 
            return true;
        
        return method.invoke(base, args);
    

上面我们已经熟悉了Hook技术,现在我们基于Android系统源码8.0版本进行hook

抛出问题

  • AMS是什么?
  • 从启动Activity的角度看app如何和AMS交互
  • hook点在哪里?
AMS是什么?

AMS是Android框架层用来管理四大组件的管家类并且负责进程的启动消亡和优先级的调度,是Android系统的核心服务。

从启动Activity的角度看app如何和AMS交互

我们知道activity的启动是从startActivity开始的,startActivity调用的是ComtextImpl.startActivity()

ContextImpl


final @NonNull ActivityThread mMainThread;

@Override
public void startActivity(Intent intent, Bundle options) 
    warnIfCallingFromSystemProcess();
    if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
            && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) 
        throw new AndroidRuntimeException(
                "Calling startActivity() from outside of an Activity "
                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                + " Is this really what you want?");
    
    //下面ActivityThread & Instrumentation
    mMainThread.getInstrumentation().execStartActivity(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity) null, intent, -1, options);

ActivityThread

public Instrumentation getInstrumentation()

    return mInstrumentation;

Instrumentation

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) 
    ...
    try 
        ...
        int result = ActivityManager.getService()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, target != null ? target.mEmbeddedID : null,
                    requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
     catch (RemoteException e) 
        throw new RuntimeException("Failure from system", e);
    
    return null;

通过以上了解到使用ActivityManager.getService()的startActivity方法

android.app.ActivityManagerNative

static public IActivityManager getDefault() 
    return ActivityManager.getService();

android.app.ActivityManager

/**
 * @hide
 */
public static IActivityManager getService() 
    return IActivityManagerSingleton.get();


private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() 
            @Override
            protected IActivityManager create() 
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            
        ;

所以最后焦点就落到了IActivityManagerSingleton这个单例对象身上。因为它保存着和AMS交互的Binder代理对象IActivityManager。其中Binder代理对象保存到IActivityManagerSingleton对象的mInstance变量中。

android.util.Singleton

package android.util;
/**
 * @hide
 */
public abstract class Singleton<T> 
    private T mInstance;

    protected abstract T create();

    public final T get() 
        synchronized (this) 
            if (mInstance == null) 
                mInstance = create();
            
            return mInstance;
        
    
hook点在哪里?

通过以上分析,我们就可以将hook点锁定到ActivityManager中mInstance变量上。因为它是与AMS交互在app进程中的Binder代理对象。

项目代码

public class MainActivity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        hookAMS();
    

    private void hookAMS() 
        try 
            Class activityManagerNativeClass = Class.forName("android.app.ActivityManager");
            Field getDefault = activityManagerNativeClass.getDeclaredField("IActivityManagerSingleton");
            getDefault.setAccessible(true);
            Object IActivityManagerSingleton = getDefault.get(null);
            Class<?> singleton = Class.forName("android.util.Singleton");
            Field mInstanceField = singleton.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            Object rawIActivityManager = mInstanceField.get(IActivityManagerSingleton);//gDefault单例对象里面的mInstance值

            //由于IActivityManager.class是隐藏interface所以使用Class.forName("");
            Class IActivityManagerClass = Class.forName("android.app.IActivityManager");
            Object hookOActivityManager = Proxy.newProxyInstance(IActivityManagerClass.getClassLoader(), new Class[]IActivityManagerClass, new HookHandler(rawIActivityManager));
            mInstanceField.set(IActivityManagerSingleton,hookOActivityManager);
         catch (Exception e) 
            e.printStackTrace();
        
    
class HookHandler implements InvocationHandler 

    private static final String TAG = "HookHandler";

    private Object mBase;

    public HookHandler(Object base) 
        mBase = base;
    
    /**
     * @param proxy     代理本身(由于返回this并不是代理本身,所以需要传入proxy)
     * @param method    使用代理执行方法的Method对象
     * @param args      使用代理执行方法传入的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        Log.d(TAG, "method:" + method.getName() + " called with args:" + Arrays.toString(args));
        return method.invoke(mBase, args);
    

PMS的hook

ContextImpl.java

@Override
public PackageManager getPackageManager() 
    if (mPackageManager != null) 
        return mPackageManager;
    
    IPackageManager pm = ActivityThread.getPackageManager();
    if (pm != null) 
        return (mPackageManager = new ApplicationPackageManager(this, pm));
    
    return null;

由于系统的执行肯定在我们代码之前,所以系统先生成了一个pm,这个是原生的pm然后保存在ApplicationPackageManager中,使得以后使用ContextImp.getPackageManager()都返回这个IPackageManager 对象。就算我们后来替换了ActivityThread.getPackageManager(),但是也不影响mPackageManager 里面之前包装好的。所以我们还需要改变mPackageManager 里面的原来的pm对象。

protected ApplicationPackageManager(ContextImpl context,IPackageManager pm) 
    mContext = context;
    mPM = pm;

变更ActivityThread中的sPackageManager引用,然后看代码

ActivityThread.java

PMS代理流程获取源码如下:

static volatile IPackageManager sPackageManager;

private void attach(boolean system) 
    ...
    sCurrentActivityThread = this;
    ...


public static ActivityThread currentActivityThread() 
    return sCurrentActivityThread;


public static IPackageManager getPackageManager() 
    if (sPackageManager != null) 
        return sPackageManager;
    
    IBinder b = ServiceManager.getService("package");
    sPackageManager = IPackageManager.Stub.asInterface(b);
    return sPackageManager;

Hook PMS项目代码

private void hookPMS() 
    try 
        // 获取全局的ActivityThread对象
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);//得到ActivityThread对象

        // 获取ActivityThread里面原始的 sPackageManager
        Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");

        sPackageManagerField.setAccessible(true);
        Object sPackageManager = sPackageManagerField.get(currentActivityThread);

        // 准备好代理对象, 用来替换原始的对象
        Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
        Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),
                new Class<?>[]iPackageManagerInterface,
                new HookHandler(sPackageManager));

        // 1. 替换掉ActivityThread里面的 sPackageManager 字段
        sPackageManagerField.set(currentActivityThread, proxy);

        // 2. 替换 ApplicationPackageManager里面的 mPM对象
        PackageManager pm = this.getPackageManager();
        Field mPmField = pm.getClass().getDeclaredField("mPM");
        mPmField.setAccessible(true);
        mPmField.set(pm, proxy);
     catch (Exception e) 
        e.printStackTrace();
    

最后我们思考一个问题:

我们如果要启动一个activity,我们的做法是1. 在AndroidManifest.xml中声明一个Activity 2. startActivity,如果不在AndroidManifest.xml中声明,启动activity会报错(android.content.ActivityNotFoundException)。但是我们想,我们使用插件化,按照正常的思维是不是要将插件化中的所有activity都要声明到清单文件中才可以。所以我们要怎么做才能hook系统启动activity。通过hook我们对AndroidManifest.xml文件中的activity进行偷梁换柱,换成我们没有注册在清单文件中的activity?
下次文章我们为大家详细分析

写博客不容易,我们需要你的支持!
请关注公众号:码老板

以上是关于你想成为Android高级工程师你还得学习Hook的主要内容,如果未能解决你的问题,请参考以下文章

菜鸟带你Hook技术实战

菜鸟带你Hook技术实战

爬虫工程师分享:三步就搞定 Android 逆向

Android基础知识——你还应该掌握的高级技巧

2019 “钱”途光明的 8 大前端开发技术,你还差几个?

怎么成为Android高级开发工程师