Android PMS HOOK

前言

PackageManagerService(簡(jiǎn)稱PMS),是Android系統(tǒng)核心服務(wù)之一货矮,處理包管理相關(guān)的工作补箍,常見的比如安裝、卸載應(yīng)用等赦邻。

使用PMS獲取包簽名信息

許多時(shí)候我們會(huì)使用到PMS髓棋,來(lái)獲取apk的簽名值。一般獲取方式如下:

private void getSignature() {
        try {
            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
            Log.i(SHARK, "len:"+packageInfo.signatures.length);
            if (packageInfo.signatures != null) {
                Log.i(SHARK, "sig:"+packageInfo.signatures[0].toCharsString());
            }
        } catch (Exception e) {
        }
    }

源碼分析

我們進(jìn)入到getPackageManager()中

//ContextWrapper.java
 @Override
    public PackageManager getPackageManager() {
        return mBase.getPackageManager();
    }

這里是調(diào)用了mBase的getPackageManager方法惶洲,可是mBase是Context引用這個(gè)是一個(gè)抽象類按声,那么mBase是在什么時(shí)候設(shè)置進(jìn)去的呢?

//ContextWrapper.java
 protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

通過(guò)上面的代碼可以知道m(xù)Base 是在attachBaseContext方法中設(shè)置進(jìn)去的


image.png

查看方法的調(diào)用處可以看到是在我們的MainActivity的attachBaseContext方法中

//MainActivity.java
 protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
    }

這個(gè)attachBaseContext方法是什么時(shí)候調(diào)用的呢?

//Activity.java
@UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        //調(diào)用attachBaseContext
        attachBaseContext(context);
...
}

這個(gè)就是在attach方法中調(diào)用的了恬吕,設(shè)置給mBase的值就是這上面?zhèn)鬟^(guò)來(lái)的

調(diào)用attach的地方是在ActivityThread的performLaunchActivity方法中

 /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
 ContextImpl appContext = createBaseContextForActivity(r);
...
 activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
...
}

有上面的代碼可以知道最終調(diào)用mBase.getPackageManager()的mBase是Context的子類ContextImpl
getPackageManager()的實(shí)現(xiàn)如下

//ContextImpl .java
@Override
    public PackageManager getPackageManager() {
        if (mPackageManager != null) {
            return mPackageManager;
        }

        final IPackageManager pm = ActivityThread.getPackageManager();
        final IPermissionManager permissionManager = ActivityThread.getPermissionManager();
        if (pm != null && permissionManager != null) {
            // Doesn't matter if we make more than one instance.
            return (mPackageManager = new ApplicationPackageManager(this, pm, permissionManager));
        }

        return null;
    }

因?yàn)镻ackageManager也是一個(gè)抽象類签则,所以這里使用的是他的繼承類ApplicationPackageManager,我們?cè)趹?yīng)用層獲得的其實(shí)就是它铐料。

可以看看getPackageInfo的具體實(shí)現(xiàn)

//ApplicationPackageManager.java
...
private final IPackageManager mPM;
...
@Override
    public PackageInfo getPackageInfo(String packageName, int flags)
            throws NameNotFoundException {
        return getPackageInfoAsUser(packageName, flags, getUserId());
    }
...
@Override
    public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
            throws NameNotFoundException {
        try {
            PackageInfo pi = mPM.getPackageInfo(packageName, flags, userId);
            if (pi != null) {
                return pi;
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        throw new NameNotFoundException(packageName);
    }

可以知道其實(shí)最后是交給mPM去獲得包的信息
所以我們需要將mPM替換掉就可以達(dá)到Hook的效果
那么mPM是什么時(shí)候設(shè)置進(jìn)去的呢渐裂?

這就要回到ContextImpl的getPackageManager中了

這里是通過(guò)ActivityThread.getPackageManager獲取到IPackageManager 構(gòu)造出來(lái)的ApplicationPackageManager
所以我們關(guān)注一下這個(gè)方法

//ActivityThread.java
...
static volatile IPackageManager sPackageManager;
...
 public static IPackageManager getPackageManager() {
        if (sPackageManager != null) {
            return sPackageManager;
        }
        final IBinder b = ServiceManager.getService("package");
        sPackageManager = IPackageManager.Stub.asInterface(b);
        return sPackageManager;
    }

上面就是返回sPackageManager屬性

而ApplicationPackageManager構(gòu)造方法如下

//ApplicationPackageManager.java
...
private final IPackageManager mPM;
...
protected ApplicationPackageManager(ContextImpl context, IPackageManager pm,
            IPermissionManager permissionManager) {
        mContext = context;
        mPM = pm;
        mPermissionManager = permissionManager;
    }

可以看到我們?cè)谧钌蠈討?yīng)用中調(diào)用的就是這個(gè)IPackageManager的方法豺旬,并且在ActivityThread的sPackageManager屬性中有這樣一個(gè)對(duì)象,在ApplicationPackageManager的mPM也有一個(gè)這個(gè)對(duì)象柒凉。

最后知道我們要使用動(dòng)態(tài)代理的方式替換掉這里的兩個(gè)屬性

  • ActivityThread的靜態(tài)變量sPackageManager
  • ApplicationPackageManager對(duì)象里面的mPM變量

HOOK PMS

這里我用了一個(gè)修改簽名值為例子
Hook的方式主要使用了JDK動(dòng)態(tài)代理族阅,不了解的可以看看動(dòng)態(tài)代理模式

//ServiceManagerWraper.java
package com.shark.hookpms;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Log;

public class ServiceManagerWraper {

    public final static String SHARK = "Shark";

    public static void hookPMS(Context context, String signed, String appPkgName, int hashCode) {
        try {
            // 獲取全局的ActivityThread對(duì)象
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod =
                    activityThreadClass.getDeclaredMethod("currentActivityThread");
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);
            // 獲取ActivityThread里面原始的sPackageManager
            Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            Object sPackageManager = sPackageManagerField.get(currentActivityThread);
            // 準(zhǔn)備好代理對(duì)象, 用來(lái)替換原始的對(duì)象
            Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
            Object proxy = Proxy.newProxyInstance(
                    iPackageManagerInterface.getClassLoader(),
                    new Class<?>[]{iPackageManagerInterface},
                    new PmsHookBinderInvocationHandler(sPackageManager, signed, appPkgName, 0));
            // 1. 替換掉ActivityThread里面的 sPackageManager 字段
            sPackageManagerField.set(currentActivityThread, proxy);
            // 2. 替換 ApplicationPackageManager里面的 mPM對(duì)象
            PackageManager pm = context.getPackageManager();
            Field mPmField = pm.getClass().getDeclaredField("mPM");
            mPmField.setAccessible(true);
            mPmField.set(pm, proxy);
        } catch (Exception e) {
            Log.d(SHARK, "hook pms error:" + Log.getStackTraceString(e));
        }
    }

    public static void hookPMS(Context context) {
        String qqSign = "30820253308201bca00302010202044bbb0361300d06092a864886f70d0101050500306d310e300c060355040613054368696e61310f300d06035504080c06e58c97e4baac310f300d06035504070c06e58c97e4baac310f300d060355040a0c06e885bee8aeaf311b3019060355040b0c12e697a0e7babfe4b89ae58aa1e7b3bbe7bb9f310b30090603550403130251513020170d3130303430363039343831375a180f32323834303132303039343831375a306d310e300c060355040613054368696e61310f300d06035504080c06e58c97e4baac310f300d06035504070c06e58c97e4baac310f300d060355040a0c06e885bee8aeaf311b3019060355040b0c12e697a0e7babfe4b89ae58aa1e7b3bbe7bb9f310b300906035504031302515130819f300d06092a864886f70d010101050003818d0030818902818100a15e9756216f694c5915e0b529095254367c4e64faeff07ae13488d946615a58ddc31a415f717d019edc6d30b9603d3e2a7b3de0ab7e0cf52dfee39373bc472fa997027d798d59f81d525a69ecf156e885fd1e2790924386b2230cc90e3b7adc95603ddcf4c40bdc72f22db0f216a99c371d3bf89cba6578c60699e8a0d536950203010001300d06092a864886f70d01010505000381810094a9b80e80691645dd42d6611775a855f71bcd4d77cb60a8e29404035a5e00b21bcc5d4a562482126bd91b6b0e50709377ceb9ef8c2efd12cc8b16afd9a159f350bb270b14204ff065d843832720702e28b41491fbc3a205f5f2f42526d67f17614d8a974de6487b2c866efede3b4e49a0f916baa3c1336fd2ee1b1629652049";
        hookPMS(context, qqSign, "com.shark.hookpms", 0);
    }
}

要想替換ActivityThread的靜態(tài)變量sPackageManager膝捞,需要先調(diào)用ActivityThread的靜態(tài)方法currentActivityThread獲得當(dāng)前的ActivityThread
替換ApplicationPackageManager對(duì)象里面的mPM變量坦刀,可以使用context.getPackageManager得到這個(gè)外層對(duì)象

這里我們得到了真正的sPackageManager,我們只需要使用JDK動(dòng)態(tài)代理構(gòu)建一個(gè)代理類就可以了

最后兩者使用反射將屬性替換成了我們構(gòu)造出來(lái)的代理類

//PmsHookBinderInvocationHandler.java
package com.shark.hookpms;

import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.util.Log;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class PmsHookBinderInvocationHandler implements InvocationHandler {
    private Object base;

    public final static String SHARK = "Shark";

    //應(yīng)用正確的簽名信息
    private String SIGN;
    private String appPkgName = "";

    public PmsHookBinderInvocationHandler(Object base, String sign, String appPkgName, int hashCode) {
        try {
            this.base = base;
            this.SIGN = sign;
            this.appPkgName = appPkgName;
        } catch (Exception e) {
            Log.d(SHARK, "error:"+Log.getStackTraceString(e));
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.i(SHARK, method.getName());
        //查看是否是getPackageInfo方法
        if("getPackageInfo".equals(method.getName())){
            String pkgName = (String)args[0];
            Integer flag = (Integer)args[1];
            //是否是獲取我們需要hook apk的簽名
            if(flag == PackageManager.GET_SIGNATURES && appPkgName.equals(pkgName)){
                //將構(gòu)造方法中傳進(jìn)來(lái)的新的簽名覆蓋掉原來(lái)的簽名
                Signature sign = new Signature(SIGN);
                PackageInfo info = (PackageInfo) method.invoke(base, args);
                info.signatures[0] = sign;
                return info;
            }
        }
        return method.invoke(base, args);
    }
}

上面的代碼主要是過(guò)濾到是否是我們需要hook apk調(diào)用了pms的getPackageInfo蔬咬,并且獲取簽名信息
調(diào)用處

    protected void attachBaseContext(Context newBase) {
        ServiceManagerWraper.hookPMS(newBase);
        super.attachBaseContext(newBase);
    }

運(yùn)行

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鲤遥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子计盒,更是在濱河造成了極大的恐慌渴频,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件北启,死亡現(xiàn)場(chǎng)離奇詭異卜朗,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)咕村,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門场钉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人懈涛,你說(shuō)我怎么就攤上這事逛万。” “怎么了批钠?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵宇植,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我埋心,道長(zhǎng)指郁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任拷呆,我火速辦了婚禮闲坎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茬斧。我一直安慰自己腰懂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布项秉。 她就那樣靜靜地躺著绣溜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪娄蔼。 梳的紋絲不亂的頭發(fā)上涮毫,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天瞬欧,我揣著相機(jī)與錄音,去河邊找鬼罢防。 笑死艘虎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咒吐。 我是一名探鬼主播野建,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼恬叹!你這毒婦竟也來(lái)了候生?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤绽昼,失蹤者是張志新(化名)和其女友劉穎唯鸭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硅确,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡目溉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了菱农。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缭付。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖循未,靈堂內(nèi)的尸體忽然破棺而出陷猫,到底是詐尸還是另有隱情,我是刑警寧澤的妖,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布绣檬,位于F島的核電站,受9級(jí)特大地震影響嫂粟,放射性物質(zhì)發(fā)生泄漏河咽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一赋元、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧飒房,春花似錦搁凸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至嚼松,卻和暖如春嫡良,著一層夾襖步出監(jiān)牢的瞬間锰扶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工寝受, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坷牛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓很澄,卻偏偏與公主長(zhǎng)得像京闰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子甩苛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容