前言
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)去的
查看方法的調(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);
}