From ec2b14c171520781b4c587c2a2fa6ca1d411489e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Tajchert?= Date: Wed, 29 Apr 2026 01:26:40 +0200 Subject: [PATCH] feat: detect Koin DI and HMAC request-signing schemes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two gaps in the previous coverage: 1. Koin was not mentioned anywhere — Hilt/Dagger got a full section in call-flow-analysis.md but Koin (the dominant DI in KMP and a large share of Kotlin-only Android apps) had zero patterns. Add a Koin subsection with the runtime-DSL patterns (module {}, single<>, factory<>, viewModel<>, by inject, by viewModel) plus the practical trick for resolving an interface to its impl after R8 obfuscation: intersect "files that import org.koin.core.module" with "files that reference the interface name". 2. The --auth mode caught Bearer / API-key / OAuth header patterns but missed HMAC and other request-signing schemes. A hardcoded HMAC secret embedded in an APK is a security finding worth surfacing — the same kind of authority the user gets is the same authority a decompiler grants to anyone. Add patterns for: * JCA primitives: HmacSHA{1,256,512}, Mac.getInstance(...), SecretKeySpec(...), Signature.getInstance(...) * Header conventions: X-Signature, X-Hmac, X-Amz-Signature, X-Client-Authorization, AWS4-HMAC, signRequest(), signaturev2/3 * Likely secret-bearing identifiers: app_secret, client_secret, signing_key, hmac_secret, consumer_secret, private_key * Ktor BearerTokens / loadTokens / refreshTokens DSL These survive R8 because the JCA and Ktor APIs are public and not shrunk. On a real-world app with a homegrown HMAC scheme they pinpoint the signing class and its hardcoded key directly. --- .../references/call-flow-analysis.md | 45 ++++++++++++++++--- .../scripts/find-api-calls.sh | 20 ++++++++- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/plugins/android-reverse-engineering/skills/android-reverse-engineering/references/call-flow-analysis.md b/plugins/android-reverse-engineering/skills/android-reverse-engineering/references/call-flow-analysis.md index 0e26df1..fa8be66 100644 --- a/plugins/android-reverse-engineering/skills/android-reverse-engineering/references/call-flow-analysis.md +++ b/plugins/android-reverse-engineering/skills/android-reverse-engineering/references/call-flow-analysis.md @@ -84,9 +84,9 @@ Look for: - Firebase/analytics initialization - Base URL configuration -## 5. Dependency Injection (Dagger / Hilt) +## 5. Dependency Injection -Modern Android apps use DI. Trace bindings to find implementations: +### Dagger / Hilt ```bash # Hilt modules @@ -102,10 +102,43 @@ grep -rn '@Component\|@Subcomponent' sources/ grep -rn '@Inject' sources/ ``` -To trace a call flow through DI: -1. Find where an interface is used (e.g., `ApiService` injected into a repository) -2. Find the `@Provides` or `@Binds` method that creates the implementation -3. Follow the implementation to the actual HTTP call +### Koin + +Koin is the dominant DI framework in Kotlin Multiplatform and a large +share of Kotlin-only Android apps. It uses a runtime DSL rather than +compile-time generated factories, so the search patterns are different: + +```bash +# Confirm Koin is actually wired up +grep -rn 'org\.koin\.' sources/ + +# DI module declarations +grep -rn 'fun [A-Za-z]\+Module\|module\s*{\|module(' sources/ + +# Bindings inside a module DSL +grep -rn 'single\s*[<{(]\|factory\s*[<{(]\|viewModel\s*[<{(]\|scoped\s*[<{(]\|singleOf\|factoryOf' sources/ + +# Resolution call-sites (where a binding is consumed) +grep -rn '\bget\s*<\|\binject\s*<\|by\s\+inject\b\|by\s\+viewModel\b\|getKoin' sources/ +``` + +After R8, every binding lambda becomes an anonymous +`Function2` impl. To find the binding for an +interface `Foo`, look for files that contain both a Koin import / module +DSL marker and a reference to `Foo`: + +```bash +grep -rln 'org\.koin\.core\.module' sources/ | xargs grep -l 'Foo' +``` + +### Trace through DI + +1. Find where an interface is used (e.g. `ApiService` injected into a + repository). +2. Find the `@Provides` / `@Binds` method (Hilt) **or** the + `single { ... }` / `factory { ... }` block (Koin) that creates the + implementation. +3. Follow the implementation to the actual HTTP call. ## 6. Find Constants and Configuration diff --git a/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/find-api-calls.sh b/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/find-api-calls.sh index d557d15..c0243a0 100755 --- a/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/find-api-calls.sh +++ b/plugins/android-reverse-engineering/skills/android-reverse-engineering/scripts/find-api-calls.sh @@ -226,9 +226,27 @@ fi # --- Auth patterns --- if [[ "$SEARCH_ALL" == true || "$SEARCH_AUTH" == true ]]; then section "Authentication & API Keys" - run_grep -i '(api[_-]?key|auth[_-]?token|bearer|authorization|x-api-key|client[_-]?secret|access[_-]?token)' + run_grep -i '(api[_-]?key|auth[_-]?token|bearer|authorization|x-api-key|client[_-]?secret|access[_-]?token|refresh[_-]?token)' + + # Request-signing schemes: a hardcoded HMAC / RSA secret in an APK is a + # security finding worth surfacing prominently. These patterns catch the + # common shapes of homegrown / SDK-issued request signers. + section "Request Signing (HMAC / signature schemes)" + run_grep '(HmacSHA(1|256|512)|Mac\.getInstance\("Hmac|SecretKeySpec\(|Signature\.getInstance\()' + run_grep -i '(x-signature|x-client-authorization|x-amz-signature|x-hmac|aws4-hmac|signRequest|signatureFor|computeSignature|signaturev[0-9])' + + # Hardcoded high-entropy strings adjacent to "secret"/"key" assignments + # are the canonical leaked-credential pattern. + section "Possible Hardcoded Secrets / Keys" + run_grep -i '(app[_-]?secret|client[_-]?secret|signing[_-]?key|hmac[_-]?secret|consumer[_-]?secret|private[_-]?key)' + section "Base URLs and Constants" run_grep -i '(BASE_URL|API_URL|SERVER_URL|ENDPOINT|API_BASE|HOST_NAME)' + + # Ktor BearerTokens / refresh DSL — common on Kotlin apps and lives on + # Ktor's public API, so it survives R8 unchanged. + section "Ktor Auth (Bearer + Refresh)" + run_grep '(BearerTokens|loadTokens\s*\{|refreshTokens\s*\{|\bbearer\s*\{)' fi echo