Android 系统服务安全

系列 - SystemSecurity
注意
本文最后更新于 2022-09-19,文中内容可能已过时。

ServiceManager解析

我们在Android开发过程中经常会用到各种各样的系统管理服务,如进行窗口相关的操作会用到窗口管理服务WindowManager,进行电源相关的操作会用到电源管理服务PowerManager,还有很多其他的系统管理服务,如通知管理服务NotifacationManager、振动管理服务Vibrator、电池管理服务BatteryManager…… 这些Manager提供了很多对系统层的控制接口。

在挖漏洞时,也可以针对系统服务进行挖掘,可确认系统服务是在Framework中实现的,且大部分的系统服务都通过AIDL方式进行实现(Google也提倡使用该方式实现自定义服务),但无论如何,系统服务都必须在ServiceManager中进行注册,当App客户端需要使用某个Service时,也需要向ServiceManager查询该Service是否注册过了。

我们可以先通过ServiceManager的启动源码来了解启动过程,源码位置 frameworks/native/cmds/servicemanager/servicemanager.rc,可见其为须要在系统初始化时就启动或退出时自己主动重新启动的程序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 服务名:servicemanager,服务可执行文件路径/system/bin/servicemanager
service servicemanager /system/bin/servicemanager 
    class core animation # 服务的类属:core animation
    user system # 执行前切换用户名为 system
    group system readproc # 切换组名
    critical # 设备相关的关键服务,如果在4分钟内,此服务重复启动了4次,那么设备将会重启进入还原模式
    onrestart restart apexd # 服务重启时执行重启apexd服务
    onrestart restart audioserver # 同理
    onrestart restart gatekeeperd # 同理
    onrestart class_restart --only-enabled main # 重启指定类属的全部服务
    onrestart class_restart --only-enabled hal
    onrestart class_restart --only-enabled early_hal
    task_profiles ServiceCapacityLow
    shutdown critical

ServiceManager进程启动后,会执行main函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//frameworks/native/cmds/servicemanager/main.cpp
int main(int argc, char** argv) {
    if (argc > 2) {
        LOG(FATAL) << "usage: " << argv[0] << " [binder driver]";
    }
    const char* driver = argc == 2 ? argv[1] : "/dev/binder";
    sp<ProcessState> ps = ProcessState::initWithDriver(driver); //启动Binder线程池
    ps->setThreadPoolMaxThreadCount(0);
    ps->setCallRestriction(ProcessState::CallRestriction::FATAL_IF_NOT_ONEWAY);
    sp<ServiceManager> manager = sp<ServiceManager>::make(std::make_unique<Access>()); //启动ServiceManager服务并获取ServiceManager服务Binder代理对象
    if (!manager->addService("manager", manager, false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {
        LOG(ERROR) << "Could not self register servicemanager";
    } //将ServiceManager服务的Binder代理对象注册到ServiceManager服务中
    IPCThreadState::self()->setTheContextObject(manager);
    ps->becomeContextManager();//将ServiceMager服务自己注册为上下文管理者
    sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
    BinderCallback::setupTo(looper);
    ClientCallbackCallback::setupTo(looper, manager);
    while(true) {
        looper->pollAll(-1);
    }//启动Binder线程池Looper并接收处理消息
    // should not be reached
    return EXIT_FAILURE;
}

上面的代码主要进行了如下的启动流程:

  1. 启动Binder线程池
  2. 启动ServiceManager服务并获取ServiceManager服务Binder代理对象
  3. 将ServiceManager服务的Binder代理对象注册到ServiceManager服务中
  4. 将ServiceMager服务自己注册为上下文管理者
  5. 启动Binder线程池Looper并接收处理消息

我们可以通过ServiceManager的源码来获取ServiceManager的作用

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/**
 * Manage binder services as registered with the binder context manager. These services must be
 * declared statically on an Android device (SELinux access_vector service_manager, w/ service
 * names in service_contexts files), and they do not follow the activity lifecycle. When
 * building applications, android.app.Service should be preferred.
 *
 * @hide
 **/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class ServiceManager {
    private static final String TAG = "ServiceManager";
    private static final Object sLock = new Object();
    @UnsupportedAppUsage
    private static IServiceManager sServiceManager;
    ...
    /** @hide */
    @UnsupportedAppUsage
    public ServiceManager() {
    }
    @UnsupportedAppUsage
    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }
        // Find the service manager
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }
    /**
     * Returns a reference to a service with the given name.
     *
     * @param name the name of the service to get
     * @return a reference to the service, or <code>null</code> if the service doesn't exist
     * @hide
     */
    @UnsupportedAppUsage
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return Binder.allowBlocking(rawGetService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }
    ...
    /**
     * Place a new @a service called @a name into the service
     * manager.
     *
     * @param name the name of the new service
     * @param service the service object
     * @hide
     */
    @UnsupportedAppUsage
    public static void addService(String name, IBinder service) {
        addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
    }
    ...
    /**
     * Retrieve an existing service called @a name from the
     * service manager.  Non-blocking.
     * @hide
     */
    @UnsupportedAppUsage
    public static IBinder checkService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return Binder.allowBlocking(getIServiceManager().checkService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in checkService", e);
            return null;
        }
    }
    /**
     * Returns whether the specified service is declared.
     *
     * @return true if the service is declared somewhere (eg. VINTF manifest) and
     * waitForService should always be able to return the service.
     */
    public static boolean isDeclared(@NonNull String name) {
        try {
            return getIServiceManager().isDeclared(name);
        } catch (RemoteException e) {
            Log.e(TAG, "error in isDeclared", e);
            return false;
        }
    }
    /**
     * Returns the list of declared instances for an interface.
     *
     * @return true if the service is declared somewhere (eg. VINTF manifest) and
     * waitForService should always be able to return the service.
     * @hide
     */
    public static String[] getDeclaredInstances(@NonNull String iface) {
        try {
            return getIServiceManager().getDeclaredInstances(iface);
        } catch (RemoteException e) {
            Log.e(TAG, "error in getDeclaredInstances", e);
            return null;
        }
    }
    /**
     * Returns the specified service from the service manager.
     *
     * If the service is not running, servicemanager will attempt to start it, and this function
     * will wait for it to be ready.
     *
     * @return {@code null} only if there are permission problems or fatal errors.
     * @hide
     */
    public static IBinder waitForService(@NonNull String name) {
        return Binder.allowBlocking(waitForServiceNative(name));
    }
    private static native IBinder waitForServiceNative(@NonNull String name);
    /**
     * Returns the specified service from the service manager, if declared.
     *
     * If the service is not running, servicemanager will attempt to start it, and this function
     * will wait for it to be ready.
     *
     * @return {@code null} if the service is not declared in the manifest, or if there are
     * permission problems, or if there are fatal errors.
     * @hide
     */
    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
    @Nullable public static IBinder waitForDeclaredService(@NonNull String name) {
        return isDeclared(name) ? waitForService(name) : null;
    }
    /**
     * Register callback for service registration notifications.
     *
     * @throws RemoteException for underlying error.
     * @hide
     */
    public static void registerForNotifications(
            @NonNull String name, @NonNull IServiceCallback callback) throws RemoteException {
        getIServiceManager().registerForNotifications(name, callback);
    }
    /**
     * Return a list of all currently running services.
     * @return an array of all currently running services, or <code>null</code> in
     * case of an exception
     * @hide
     */
    @UnsupportedAppUsage
    public static String[] listServices() {
        try {
            return getIServiceManager().listServices(IServiceManager.DUMP_FLAG_PRIORITY_ALL);
        } catch (RemoteException e) {
            Log.e(TAG, "error in listServices", e);
            return null;
        }
    }
    /**
     * Get service debug info.
     * @return an array of information for each service (like listServices, but with PIDs)
     * @hide
     */
    public static ServiceDebugInfo[] getServiceDebugInfo() {
        try {
            return getIServiceManager().getServiceDebugInfo();
        } catch (RemoteException e) {
            Log.e(TAG, "error in getServiceDebugInfo", e);
            return null;
        }
    }
    /**
     * This is only intended to be called when the process is first being brought
     * up and bound by the activity manager. There is only one thread in the process
     * at that time, so no locking is done.
     *
     * @param cache the cache of service references
     * @hide
     */
    public static void initServiceCache(Map<String, IBinder> cache) {
        if (sCache.size() != 0) {
            throw new IllegalStateException("setServiceCache may only be called once");
        }
        sCache.putAll(cache);
    }
    ...
}

通过上述的源码,我们可以看见在ServiceManager中存在获取服务,添加服务,获取服务列表等函数,对于几个关键函数解析如下

通过ServiceManager的Binder代理对象,调用getService(String name)即可获取到指定名称服务的Binder接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
     * Returns a reference to a service with the given name.
     *
     * @param name the name of the service to get
     * @return a reference to the service, or <code>null</code> if the service doesn't exist
     * @hide
     */
    @UnsupportedAppUsage
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return Binder.allowBlocking(rawGetService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

这里的sCache是一个Map,可以通过key值,即service名称获取对应的IBinder对象

通过listServices函数能够获取到当前在ServiceManager中注册的所有service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
     * Return a list of all currently running services.
     * @return an array of all currently running services, or <code>null</code> in
     * case of an exception
     * @hide
     */
@UnsupportedAppUsage
public static String[] listServices() {
    try {
        return getIServiceManager().listServices(IServiceManager.DUMP_FLAG_PRIORITY_ALL);
    } catch (RemoteException e) {
        Log.e(TAG, "error in listServices", e);
        return null;
    }
}

Android框架层Fuzz测试

最近在浏览Android源码时看见了Google针对性地添加了binder fuzz的代码,使用libFuzzer进行调用,那么我们也可以使用这一套,将其搬至App层面进行测试,当然此处最好还要绕过HiddenApi的限制。

关注/frameworks/native/libs/binder/tests/parcel_fuzzer中的代码,这一块是Google新增的对于Parcel数据的Fuzz测试代码,现在我们可以把它尝试迁移至App中

/images/android_system_service_security/image-20220918235207175.png

首先我们看一下主程序 main.cpp,核心代码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
    static constexpr size_t kMemLimit = 1 * 1024 * 1024; // 1MB
    size_t hardLimit = getHardMemoryLimit();
    setMemoryLimit(std::min(kMemLimit, hardLimit), hardLimit); // 设置内存上限

    if (size <= 1) return 0;  // no use

    // avoid timeouts, see b/142617274, b/142473153
    if (size > 50000) return 0;

    FuzzedDataProvider provider = FuzzedDataProvider(data, size);

    const std::function<void(FuzzedDataProvider &&)> fuzzBackend[3] = {
            [](FuzzedDataProvider&& provider) {
                doFuzz<::android::hardware::Parcel>("hwbinder", HWBINDER_PARCEL_READ_FUNCTIONS,
                                                    std::move(provider));
            },
            [](FuzzedDataProvider&& provider) {
                doFuzz<::android::Parcel>("binder", BINDER_PARCEL_READ_FUNCTIONS,
                                          std::move(provider));
            },
            [](FuzzedDataProvider&& provider) {
                doFuzz<NdkParcelAdapter>("binder_ndk", BINDER_NDK_PARCEL_READ_FUNCTIONS,
                                         std::move(provider));
            },
    };

    provider.PickValueInArray(fuzzBackend)(std::move(provider)); // 针对不同的binder类型执行Fuzz

    setMemoryLimit(hardLimit, hardLimit); 

    return 0;
}

这里重点关注provider中的doFuzz函数,这实际上是一个模板函数,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
template <typename P>
void doFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
            FuzzedDataProvider&& provider) {
    // Allow some majority of the bytes to be dedicated to telling us what to
    // do. The fixed value added here represents that we want to test doing a
    // lot of 'instructions' even on really short parcels.
    size_t maxInstructions = 20 + (provider.remaining_bytes() * 2 / 3);
    // but don't always use that many instructions. We want to allow the fuzzer
    // to explore large parcels with few instructions if it wants to.
    std::vector<uint8_t> instructions = provider.ConsumeBytes<uint8_t>(
            provider.ConsumeIntegralInRange<size_t>(0, maxInstructions));

    P p;
    if constexpr (std::is_same_v<P, android::Parcel>) { //根据不同的P,判断该填入何种数据
        if (provider.ConsumeBool()) {
            auto session = sp<RpcSession>::make();
            CHECK(session->addNullDebuggingClient());
            p.markForRpc(session);
            fillRandomParcelData(&p, std::move(provider));
        } else {
            fillRandomParcel(&p, std::move(provider));
        }
    } else {
        fillRandomParcel(&p, std::move(provider));
    }

    // since we are only using a byte to index
    CHECK(reads.size() <= 255) << reads.size();

    FUZZ_LOG() << "backend: " << backend;
    FUZZ_LOG() << "input: " << hexString(p.data(), p.dataSize());
    FUZZ_LOG() << "instructions: " << hexString(instructions);	// 打印前置信息

    for (size_t i = 0; i + 1 < instructions.size(); i += 2) {
        uint8_t a = instructions[i];
        uint8_t readIdx = a % reads.size();

        uint8_t b = instructions[i + 1];

        FUZZ_LOG() << "Instruction: " << (i / 2) + 1 << "/" << instructions.size() / 2
                   << " cmd: " << static_cast<size_t>(a) << " (" << static_cast<size_t>(readIdx)
                   << ") arg: " << static_cast<size_t>(b) << " size: " << p.dataSize()
                   << " avail: " << p.dataAvail() << " pos: " << p.dataPosition()
                   << " cap: " << p.dataCapacity();

        reads[readIdx](p, b);
    }
}

此处最重要的函数,当然就是 fillRandomParcel了,我们也重点关注该函数,可见针对不同的P,目前的处理方法基本是一致的,都是从FuzzedDataProvider中取出相应的数据并填充,

1
2
3
4
5
6
7
8
9
void fillRandomParcel(::android::hardware::Parcel* p, FuzzedDataProvider&& provider) {
    // TODO: functionality to create random parcels for libhwbinder parcels
    std::vector<uint8_t> input = provider.ConsumeRemainingBytes<uint8_t>();
    p->setData(input.data(), input.size());
}
static void fillRandomParcel(NdkParcelAdapter* p, FuzzedDataProvider&& provider) {
    // fill underlying parcel using functions to fill random libbinder parcel
    fillRandomParcel(p->parcel(), std::move(provider));
}

并由binder进行通信,那么针对binder,可以通过Frida注入dex的方式获取到ServiceManager中注册的服务名,再通过反射获取binder或者hook system_server的方式获取其中的方法,这样就可以进行Fuzz测试了。

获取系统服务信息

  • 先定义一个服务需要包含的信息:服务名,函数的code值,函数参数类型和返回值类型
1
2
3
4
5
6
7
8
/**
 * 定义服务结构
 * 包含了服务名和接口结构,methodInfo中包括了接口的Code值,接口的名称,接口参数类型数组,返回值类型
 */
public class ServiceInfo {
    private String serviceName;
    private Map<Integer, List<String>> methodInfo;
	...
  • 反射获取系统服务列表
1
2
3
4
5
6
7
8
9
public static String[] getServiceNameList(){
    String[] serviceNameList = {};
    try{
        serviceNameList = (String[]) serviceManager.getDeclaredMethod("listServices").invoke(null);
    }catch (Exception e){
        e.printStackTrace();
    }
    return serviceNameList;
}
  • 根据服务名获取服务的接口描述符
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public static String getInterfaceDescriptor(String serviceName){
    try{
        Method getService = serviceManager.getMethod("getService", String.class);
        IBinder serviceBinder = (IBinder) getService.invoke(serviceManager.newInstance(), serviceName);
        if(serviceBinder == null){
            Log.e(TAG,  "Service " + serviceName + " Binder is null");
            return null;
        }
        String interfaceDescriptor = serviceBinder.getInterfaceDescriptor();
        return interfaceDescriptor.length() != 0 ? interfaceDescriptor : null;
    }catch (Exception e){
        Log.e(TAG, "Service " + serviceName + " get InterfaceDecriptor failed!");
        return null;
    }
}
  • 获取函数和code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
private static HashMap<String,Integer> getMethodMap(String interfaceDescriptor) {
    HashMap<String, Integer> codes = new HashMap<>();
    if (interfaceDescriptor == null)
        return codes;
    try {
        Class<?> cStub = Class.forName(interfaceDescriptor + "$Stub");
        Field[] f = cStub.getDeclaredFields();
        for (Field field : f) {
            field.setAccessible(true);
            String k= field.toString().split("\\$Stub\\.")[1];
            if (k.contains("TRANSACTION_") && field.getInt(null) > 0)
                codes.put(k, field.getInt(null));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return codes;
}
  • 获取函数参数类型
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private static String getParamterTypeStr(Method method){
    method.setAccessible(true);
    Class[] params = method.getParameterTypes();
    String returnStr = "";
    for(Class i : params){
        returnStr += i.getName() + ";";
    }
    return returnStr.length() > 0 && ";".equals(returnStr.substring(returnStr.length() - 1)) ?
        returnStr.substring(0, returnStr.length() - 1):returnStr;
}
  • 获取函数返回值类型
1
2
3
4
private static String getReturnType(Method method){
    method.setAccessible(true);
    return method.getReturnType().getName();
}
  • 结合上述函数,获取完整服务信息
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public static ServiceInfo getServiceInfo(String serviceName){
    if(serviceName == null){
        return null;
    }
    Map<Integer, List<String>> methodInfo = new HashMap<>();
    String interfaceDescriptor = getInterfaceDescriptor(serviceName);
    ServiceInfo serviceInfo = new ServiceInfo();
    if(interfaceDescriptor == null){
        serviceInfo.setServiceName(serviceName);
        serviceInfo.setMethodInfo(null);
        return serviceInfo;
    }
    try{
        Class<?> classStub = Class.forName(interfaceDescriptor + "$Stub$Proxy");
        Method[] methods = classStub.getDeclaredMethods();
        HashMap<String, Integer> tempMethodMap = getMethodMap(interfaceDescriptor);
        for(Method method : methods){
            method.setAccessible(true);
            String methodName = method.getName();
            int methodCode = 0;
            for(String key : tempMethodMap.keySet()){
                if(methodName.contains(key.substring("TRANSACTION_".length()))){
                    methodCode = tempMethodMap.get(key);
                }
            }
            if(methodCode == 0)
                continue;
            String parameterTypeStr = getParamterTypeStr(method);
            String returnType = getReturnType(method);
            List<String> methodSignature = Arrays.asList(methodName, parameterTypeStr, returnType);
            methodInfo.put(methodCode, methodSignature);
        }
        return serviceInfo.setServiceInfo(serviceName, methodInfo);
    }catch (ClassNotFoundException e) {
        Log.e(TAG, "Serivce " + serviceName + "$Stub$Proxy ClassNotFoundException");
        serviceInfo.setServiceName(serviceName);
        serviceInfo.setMethodInfo(null);
    }catch (Exception e){
        e.printStackTrace();
    }
    return serviceInfo;
}

当获取一个服务的完整信息后,结合前面获取整个服务列表的函数,就能够获取整机框架服务信息了,当然,在执行程序前,需要解除Google对非SDK接口的限制,即执行 adb shell settings put global hidden_api_policy 1

通过以上代码获取Service

通过动态获取系统服务信息存在缺点,会存在部分服务无法获取到接口与函数的定义点,针对这类问题,我们也可以使用静态匹配的方法来解决。相比App直接获取相关信息,使用静态需要经过反编译并匹配的过程,运行速度会比动态获取慢很多,但是相对的也能匹配到更多的信息。

参考文献