如何获取系统服务?

1.AlarmManager系统服务

AlarmManager是一个典型的系统服务,意为“警告”,会定时执行动作。典型用法为

    void set(int type, long triggerAtMillis, PendingIntent operation)

到达设定时间后服务会发出广播,执行PendingIntent中定义的动作。动作完成后系统重启后alarm会被清除。如果期间设备进入休眠了,动作将保持等待设备苏醒。

AlarmManager也可以在广播执行期间持有唤醒锁,保证设备不被关闭。广播执行完成后再释放唤醒锁。

AlarmManager可以以精确时间执行

setExact(int, long, PendingIntent) 

也可以指定一个时间窗口,由系统OS调整调整执行的精确时间

setWindow(int, long, long, PendingIntent)

还可以重复执行动作

void setRepeating(int type, long triggerAtMillis,
        long intervalMillis, PendingIntent operation)

使用 AlarmManager 的优势是比Hanlder更方便和效率,能够重复执行,且 AlarmManager 的执行不用保持 AP,相比之下,Timer 的执行则依赖 AP,因此更为耗电,应该避免使用 Timer。

2.如何启动系统服务?

AlarmManager服务的获取方式如下

AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

2.1从缓存中读取服务获取器

context的实现类是ContextImpl

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

SystemServiceRegistry类是系统服务的注册管理类,其内部有两个字典记录缓存服务

//1.记录服务类与名称的键值对
HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES
//2.记录ServiceFetcher与服务类名称的键值对
HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS 

SystemServiceRegistry类读取服务就是从集合中读取ServiceFetcher,进而获取服务类AlarmManager

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

ServiceFetcher是一个接口,只有一个方法用于获取具体服务

static abstract interface ServiceFetcher<T> {
    T getService(ContextImpl ctx);
}

ServiceFetcher的实现类是CachedServiceFetcher该类是获取服务的核心类,ServiceFetcher 实现了对服务的缓存,如果没有则使用createService()方法创建服务。这种设计是因为缓存机制是通用的,而具体服务是独特的。

@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
    final Object[] cache = ctx.mServiceCache;
    synchronized (cache) {
        // Fetch or create the service.
        Object service = cache[mCacheIndex];
        if (service == null) {
            service = createService(ctx);
            cache[mCacheIndex] = service;
        }
        return (T)service;
    }
}

SystemServiceRegistry类中的静态代码块完成系统服务的注册,填充两个集合。

static{
    ...
    registerService(Context.ALARM_SERVICE, AlarmManager.class,CachedServiceFetcher
}    

registerService方法会将某个服务的相关信息写入到内部集合中。

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);
}

2.3系统服务的创建

到目前为止 ServiceFetcher 还仅仅是一层包装,服务还没有真正创建,CachedServiceFetcher类负责创建服务,其实现如下,主要定义如何创建系统服务类的实例

public abstract T createService(ContextImpl ctx);

    return new CachedServiceFetcher<AlarmManager>() {

      @Override
          public AlarmManager createService(ContextImpl ctx) {
        IBinder b = ServiceManager.getService(Context.ALARM_SERVICE);
        IAlarmManager service = IAlarmManager.Stub.asInterface(b);
        return new AlarmManager(service, ctx);
      }
};

可见AlarmManager类的创建依靠从ServiceManager获取的IBinder 对象,这个IBinder对象是Alarm服务所使用的Binder接口,而后将被下转型为业务接口 IAlarmManager 。

ServiceManager本身就是一个服务类,负责管理系统服务。它的内部有一个IServiceManager 对象,负责从远端获取各种服务的Binder对象,定义如下

public interface IServiceManager extends IInterface{
    public IBinder getService(String name);
}

ServiceManager使用这个IServiceManager 对象获取具体服务,如AlarmManager

HashMap<String, IBinder> sCache;
public static IBinder getService(String name) {

    IBinder service = sCache.get(name);
    if (service != null) {
        return service;
    } else {
        return iServiceManager.getService(name);
    }
}

可见ServiceManager内部还有一个集合缓存所获取的各种 Binder 对象,如果没有就使用IServiceManager 对象获取服务所需的Binder接口。

IServiceManager 对象本身就是一个远程Binder对象

public final class ServiceManager {
    private static IServiceManager sServiceManager;
    private static IServiceManager getIServiceManager() {
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }
}

其中BinderInternal.getContextObject()返回一个全局Context类,该对象同时是一个IBinder类,即IServiceManager,由其构建ServiceManager服务。

ServiceManagerNative是其服务端实现,定义如下

class ServiceManagerNative extends Binder implements IServiceManager

本地实际使用的是远端的代理类ServiceManagerProxy,获取服务的方法为

public IBinder getService(String name) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IServiceManager.descriptor);
    data.writeString(name);
    mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
    IBinder binder = reply.readStrongBinder();
    reply.recycle();
    data.recycle();
    return binder;
}

远端的onTransact方法响应如下

switch (code) {
    case IServiceManager.GET_SERVICE_TRANSACTION: {
        data.enforceInterface(IServiceManager.descriptor);
        String name = data.readString();
        IBinder service = getService(name);
        reply.writeStrongBinder(service);
        return true;
    }
}

3.结论与扩展

总结一下加载系统服务的过程ServiceManager类通过问答式机制(即Binder机制)获取到某个服务的 IBinder 对象,而后将其下转型为服务特定接口,最后构建服务对象。在此过程中,存在一些缓存行为以提高性能

使用 Hook 技术可以替换和伪造系统服务 AlarmManager 。在 ApplicationattachBaseContext 中替换被 HookIBinder 对象。

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    Class serviceManagerClass = Class.forName("android.os.ServiceManager");
    Method getServiceMethod = serviceManagerClass.getDeclaredMethod("getService", String.class);
    IBinder rawBinder = (IBinder) getServiceMethod.invoke(null, Context.ALARM_SERVICE);

    IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManagerClass.getClassLoader(),
            new Class<?>[] { IBinder.class },
            new BinderInvocationHandler(rawBinder));

    Field cacheField = serviceManagerClass.getDeclaredField("sCache");
    cacheField.setAccessible(true);
    Map<String, IBinder> cache = (Map) cacheField.get(null);
    cache.put(Context.ALARM_SERVICE, hookedBinder);
}

我们往serviceManager的缓存列表中加入 Hook 过的的 IBinder 对象,这样等于阉割了其 getService方法,使得该方法只能从缓存中获取对象,而不能自己创建对象。

但只 Hook 掉 IBinder 对象没有意义,我们需要继续 Hook 掉其下转型的具体接口 IAlarmManager ,这样才能改变服务的功能。为此需要改写 IBinder 对象的 queryLocalInterface 方法,使其返回一个 Hook 过的IAlarmManager对象。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if ("queryLocalInterface".equals(method.getName())) {
        Class iAlarmManager = Class.forName("android.app.IAlarmManager");
        Class stub = Class.forName("android.app.IAlarmManager$Stub");
        return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{IBinder.class, IInterface.class, iAlarmManager},new BinderHookInvocationHandler(iBinder, stub));
    }
    return  method.invoke(iBinder, args);
}

BinderHookInvocationHandler类中可以改写服务的各种方法实现。注意这里需要使用Stub类的asInterface向下转型为IAlarmManager接口。

private class IAlarmManagerHookHandler implements InvocationHandler{

    IBinder iBinder;

    Object iAlarmManager;

    public IAlarmManagerHookHandler(IBinder rawBinder) {
        this.iBinder = rawBinder;
        Class stubClass = Class.forName("android.app.IAlarmManager$Stub");
        Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface",IBinder.class);
        iAlarmManager = asInterfaceMethod.invoke(null, rawBinder);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("getNextAlarmClock".equals(method.getName())){
            Log.i(TAG, "new getNextAlarmClock: " + method.toString());
        }
        return method.invoke(iAlarmManager, args);
    }
}

总结一下 Hook 系统服务就意味着 Hook 服务的 Binder 对象,包括 IBinder 接口和 IAlarmManager接口两层。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容