To begin with, Piggy is a family finance management app, which is used to track the in/outcome or transaction between many accounts saved in family. This app is written in flutter with many permissions, which is a perfect example for exploiting flutter.
# I. Tool used
## reFlutter
- Purpose: Lightweight APK “injection” to unpack Flutter assets & stub in custom native libraries.
- Why Chosen:
- Preserves original Flutter runtime structure (so Dart/Flutter compiler offsets remain valid).
- Minimal rebuild: no need to recompile the entire Dart AOT blob.
- Used in Method 1
## apktool & Uber-APK-Signer
- Purpose: decode XML/resources, smali ↔ dex transforms. Then from the decoded apk file, I can directly inject into smali files. After modification, Uber-APK-Signer re-sign rebuilt APKs with a debug or custom key.
- Why Chosen:
- apktool is the open-source tool for resource decoding.
- Uber-APK-Signer automatically aligns and signs with minimum fuss.
- Used in method 2
## Android NDK + Custom C Code
- Purpose: Build a small native library (libdump.so) that uses JNI to traverse app directories and dump files.
- Why Chosen:
- Native code can bypass Java-level sandboxing for file I/O.
- Easy to compile into the APK and load via System.loadLibrary.
- Used in method 2
## ADB (Android Debug Bridge)
- Purpose: Push patched APK, pull dump files, run shell commands.
- Used in all method to interact with phone via CLI
## Burp Suite:
- Purpose: Intercept HTTP(S) traffic, inspect payloads, replay requests.
- Used in method 1
## Frida + Frida-gadget
- Purpose: Dynamic instrumentation of native (OpenSSL) and Java methods at runtime.
- Why Chosen:
- No need to rebuild APK for live hooks.
- Can bypass certificate pinning, dump in-memory data.
- Used in method 3
# II. Methods & Methodology
## Method 1: reFlutter Injection
- Steps: inject, decode resources, extract raw AndroidManifest.xml, merge debug tags, rebuild & sign
- Challenges: resource table errors, Android 15 CA model
## Method 2: Native “dump.c” Injector
- JNI bridge: loading libdump.so in MainActivity constructor
- Directory walk: opendir → list shared_prefs, databases, files
- Content dump: read XML/DB files, /proc/net/tcp, /proc/self/maps → mega_dump.txt
- Extraction via ADB
## Method 3: Frida-based Live Dump
- Injecting libfrida-gadget.so in APK
- Native hooks: SSL_write/SSL_read, send/recv
- Java hooks: URL, HttpsURLConnection, OkHttpClient.Builder, SharedPreferences.get/putString, SQLiteDatabase.rawQuery/execSQL, FileInputStream
- Output: live request/response payloads, SQL statements, prefs operations
# III. Technical details:
## Method 1: using reFlutter
- Inject reflutter

- extract injected apk to add debug tag without resources

- AndroidManifest is in unreadable format

-> Need to obtain AndroidManifest in the extraction that containing resources for later compiling

- got multiple error since resources is not extractable, but android manifest is obtained successfully
- extracted directory (looks)
- without resources:

with resources:

- Differences: resources.asrc -> only -r has since it skipped extracting the resources part, so that an android resource table must be there for resources mapping
- Then open androidManifest.xml in the raw one (the one extracted with resources)
adding debug to reflutter

then moving it into the one without resources
-> Reason for this: the apktool mostly error all the time with decoding resources -> in order to rebuild the apk, I need to rebuild the apk based on the one that not decode resources

after copying, I recompile the decoded files back into apk

I then sign the apk file with uber-apk-signer. More over, I generated a Burp CA cert and install it in my phone

- Due to strict Android's CA Model for Android 7.0+, only system traffic is captured in BurpSuite

- Overall, this method is not working good anymore, since newer Android (15) has patched this.
## Method 2: manually crafting a function in C to dump internal data:
- In order to dump all the app data, I've crafted a dump.c alongside with Android.mk, which later being compiled into libdump.so and placed in lib/arm64-v8a to inject into the app.
```
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#define APP_DIR "/data/data/in.piggyvault.piggy_flutter/"
#define OUTPUT_FILE "/data/data/in.piggyvault.piggy_flutter/files/mega_dump.txt"
void dump_dir(FILE* out, const char* path, const char* prefix) {
DIR* dir = opendir(path);
if (!dir) return;
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG) {
char fullpath[512];
snprintf(fullpath, sizeof(fullpath), "%s/%s", path, entry->d_name);
fprintf(out, "%s%s\n", prefix, entry->d_name);
}
}
closedir(dir);
}
void dump_file_content(FILE* out, const char* filepath, const char* label) {
FILE* f = fopen(filepath, "r");
if (f) {
fprintf(out, "\n// ==== %s (%s) ====\n", label, filepath);
char buf[512];
while (fgets(buf, sizeof(buf), f)) {
fputs(buf, out);
}
fclose(f);
}
}
JNIEXPORT void JNICALL
Java_in_piggyvault_piggy_1flutter_MainActivity_nativeDump(JNIEnv* env, jobject thiz) {
FILE* out = fopen(OUTPUT_FILE, "w");
if (!out) return;
fprintf(out, "// === APP DATA DUMP START ===\n\n");
// SharedPreferences
fprintf(out, "// SharedPreferences XML Files:\n");
dump_dir(out, APP_DIR "shared_prefs", " ");
dump_file_content(out, APP_DIR "shared_prefs/FlutterSharedPreferences.xml", "FlutterPrefs");
dump_file_content(out, APP_DIR "shared_prefs/OneSignalTriggers.xml", "OneSignal");
// Databases
fprintf(out, "\n// Databases:\n");
dump_dir(out, APP_DIR "databases", " ");
// Internal Files
fprintf(out, "\n// Files:\n");
dump_dir(out, APP_DIR "files", " ");
// Print contents of important internal files
dump_file_content(out, APP_DIR "files/PersistedInstallation.*", "Firebase Install");
dump_file_content(out, APP_DIR "files/generatefid.lock", "generatefid.lock");
// Dump network state
fprintf(out, "\n// Network Connections (/proc/net/tcp):\n");
dump_file_content(out, "/proc/net/tcp", "TCP");
// Dump memory map
fprintf(out, "\n// Memory Map (/proc/self/maps):\n");
dump_file_content(out, "/proc/self/maps", "Memory Map");
fprintf(out, "\n// === END OF DUMP ===\n");
fclose(out);
}
```
- To make the app load this script, I also edit the MainActivity, adding into constructor to make the script run before the app starts
```
.method public constructor <init>()V
.locals 1
invoke-direct {p0}, Lio/flutter/embedding/android/f;-><init>()V
const-string v0, "dump"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
return-void
.end method
```
- These code will dump SharedPreferences,Databases, Internal Files and Memory map when the app is running, then save it to mega_dump.txt. Then we use Android Debug Bridge (ADB) to take the mega_dump.txt out.
-- Sample data from mega_dump.txt
```
// === APP DATA DUMP START ===
// SharedPreferences XML Files:
com.google.android.gms.appid.xml
FirebaseAppHeartBeat.xml
g4.xml
OneSignalTriggers.xml
GTPlayerPurchases.xml
FlutterSharedPreferences.xml
// ==== FlutterPrefs (/data/data/in.piggyvault.piggy_flutter/shared_prefs/FlutterSharedPreferences.xml) ====
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<long name="flutter.tenantId" value="1" />
<boolean name="flutter.firstAccess" value="false" />
<string name="flutter.authToken">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjIiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJhZG1pbkBkZWZhdWx0dGVuYW50LmNvbSIsIkFzcE5ldC5JZGVudGl0eS5TZWN1cml0eVN0YW1wIjoiNWFjYTE0OWMtMzU4MS02MzA2LTU3OTAtMzlmMGYxOGQ2MWI0IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW4iLCJodHRwOi8vd3d3LmFzcG5ldGJvaWxlcnBsYXRlLmNvbS9pZGVudGl0eS9jbGFpbXMvdGVuYW50SWQiOiIxIiwic3ViIjoiMiIsImp0aSI6IjllODhkMTc1LTU0ZDYtNGVjMS04ZjE3LTAwMzEwMzgzODE5MSIsImlhdCI6MTc0OTE0MDc3OCwibmJmIjoxNzQ5MTQwNzc4LCJleHAiOjE3NjQ2OTI3NzgsImlzcyI6IlBpZ2d5dmF1bHQiLCJhdWQiOiJQaWdneXZhdWx0In0.-urw65YZ04StRkH-WmEQO0GlFS8QZW_hvEFoQwmPAeI</string>
</map>
// ==== OneSignal (/data/data/in.piggyvault.piggy_flutter/shared_prefs/OneSignalTriggers.xml) ====
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map />
// Databases:
com.google.android.datatransport.events-journal
com.google.android.datatransport.events
OneSignal.db-journal
OneSignal.db
// Files:
PersistedInstallation.T05FU0lHTkFMX1NES19GQ01fQVBQX05BTUU+MTo3NTQ3OTU2MTQwNDI6YW5kcm9pZDpjNjgyYjgxNDRhOGRkNTJiYzFhZDYz.json
generatefid.lock
mega_dump.txt
// ==== generatefid.lock (/data/data/in.piggyvault.piggy_flutter/files/generatefid.lock) ====
// Network Connections (/proc/net/tcp):
// Memory Map (/proc/self/maps):
// ==== Memory Map (/proc/self/maps) ====
14000000-24000000 rw-p 00000000 00:00 0 [anon:dalvik-main space]
34000000-44000000 rw-p 00000000 00:00 0 [anon:dalvik-free list large object space]
54000000-56000000 r--s 00000000 00:01 2051 /memfd:jit-zygote-cache (deleted)
56000000-58000000 r-xs 02000000 00:01 2051 /memfd:jit-zygote-cache (deleted)
58000000-5a000000 r--s 00000000 00:01 317835 /memfd:jit-cache (deleted)
5a000000-5c000000 r-xs 02000000 00:01 317835 /memfd:jit-cache (deleted)
70510000-71a28000 rw-p 00000000 00:00 0 [anon:dalvik-/data/misc/apexdata/com.android.art/dalvik-cache/boot.art]
71a28000-71d24000 r--p 00000000 fe:3c 918615 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat
71d24000-72736000 r-xp 002fc000 fe:3c 918615 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat
72736000-72738000 ---p 00000000 00:00 0 [anon:dalvik-Boot image reservation]
72738000-72739000 rw-p 00000000 00:00 0 [anon:.bss]
72739000-7273c000 ---p 00000000 00:00 0 [anon:dalvik-Boot image reservation]
7273c000-72831000 rw-p 00000000 fe:3c 918650 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.vdex
72831000-72834000 ---p 00000000 00:00 0 [anon:dalvik-Boot image reservation]
72834000-73174000 rw-p 00000000 00:00 0
73174000-7318c000 r--p 00000000 fe:3c 918780 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot-framework-adservices.oat
7318c000-731bd000 rw-p 00000000 fe:3c 918812 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot-framework-adservices.vdex
731bd000-731c0000 ---p 00000000 00:00 0 [anon:dalvik-Boot image reservation]
731c0000-7381e000 rw-p 00000000 00:00 0 [anon:dalvik-zygote space]
7381e000-7381f000 rw-p 00000000 00:00 0 [anon:dalvik-non moving space]
7381f000-73820000 rw-p 00000000 00:00 0 [anon:dalvik-non moving space]
73820000-761c1000 ---p 00000000 00:00 0 [anon:dalvik-non moving space]
761c1000-771c0000 rw-p 00000000 00:00 0 [anon:dalvik-non moving space]
ebad6000-ebad7000 ---p 00000000 00:00 0 [anon:dalvik-Sentinel fault page]
5fffc18000-5fffc19000 rw-s 5fffc18000 00:12 1161 /dev/mali0
5fffc19000-5fffc1a000 rw-s 5fffc19000 00:12 1161 /dev/mali0
5fffc1a000-5fffc3a000 rw-s 5fffc1a000 00:12 1161 /dev/mali0
5fffc3a000-5fffc5a000 rw-s 5fffc3a000 00:12 1161 /dev/mali0
5fffc5a000-5fffc7a000 rw-s 5fffc5a000 00:12 1161 /dev/mali0
5fffc7a000-5fffc9a000 rw-s 5fffc7a000 00:12 1161 /dev/mali0
5fffc9a000-5fffcba000 rw-s 5fffc9a000 00:12 1161 /dev/mali0
5fffcba000-5fffcda000 rw-s 5fffcba000 00:12 1161 /dev/mali0
5fffcda000-5fffcfa000 rw-s 5fffcda000 00:12 1161 /dev/mali0
5fffcfa000-5fffd1a000 rw-s 5fffcfa000 00:12 1161 /dev/mali0
5fffd1a000-5fffd1b000 rw-s 5fffd1a000 00:12 1161 /dev/mali0
5fffd1b000-5fffd1e000 rw-s 05a24000 00:01 1024 /mali csf db (deleted)
5fffd1e000-5fffd2e000 rw-s 5fffd1e000 00:12 1161 /dev/mali0
5fffd2e000-5fffd31000 rw-s 05a21000 00:01 1024 /mali csf db (deleted)
5fffd31000-5fffd41000 rw-s 5fffd31000 00:12 1161 /dev/mali0
5fffd41000-5fffd44000 rw-s 05a1e000 00:01 1024 /mali csf db (deleted)
5fffd44000-5fffd54000 rw-s 5fffd44000 00:12 1161 /dev/mali0
5fffd54000-5fffd57000 rw-s 05a1b000 00:01 1024 /mali csf db (deleted)
5fffd57000-5fffd67000 rw-s 5fffd57000 00:12 1161 /dev/mali0
5fffd67000-5fffd6a000 rw-s 05a18000 00:01 1024 /mali csf db (deleted)
...
```
### Some preset value obtained from dump
- FirebaseAppHeartbeat.xml:
```
<map>
<long name="fire-global" value="1749096375993"/>
<long name="fire-iid" value="1749096376320"/>
<long name="fire-installations-id" value="1749096375993"/>
</map>
```
- FlutterSharedPreferences.xml:
```
<map>
<long name="flutter.tenantId" value="1"/>
<boolean name="flutter.firstAccess" value="false"/>
<string name="flutter.authToken">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjIiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJhZG1pbkBkZWZhdWx0dGVuYW50LmNvbSIsIkFzcE5ldC5JZGVudGl0eS5TZWN1cml0eVN0YW1wIjoiNWFjYTE0OWMtMzU4MS02MzA2LTU3OTAtMzlmMGYxOGQ2MWI0IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW4iLCJodHRwOi8vd3d3LmFzcG5ldGJvaWxlcnBsYXRlLmNvbS9pZGVudGl0eS9jbGFpbXMvdGVuYW50SWQiOiIxIiwic3ViIjoiMiIsImp0aSI6IjQ0MjM5M2U5LWEwMzYtNDEyNi05ZWNmLWI4MTllNWE2ODdiZSIsImlhdCI6MTc0OTE0MDAwNSwibmJmIjoxNzQ5MTQwMDA1LCJleHAiOjE3NjQ2OTIwMDUsImlzcyI6IlBpZ2d5dmF1bHQiLCJhdWQiOiJQaWdneXZhdWx0In0.ydM1IRtzo1eTSjJP-sx4KB7mF0JCd8XBqDL6FMyq4sQ</string>
</map>
```
- g4.xml:
```
<map>
<string name="ONESIGNAL_USERSTATE_DEPENDVALYES_CURRENT_STATE">{"subscribableStatus":1,"userSubscribePref":true,"androidPermission":false,"session":false}</string>
<string name="ONESIGNAL_USERSTATE_SYNCVALYES_CURRENT_STATE">{"notification_types":0,"app_id":"9bf198c9-442b-4619-b5c9-759fc250f15b","device_os":"15","timezone":25200,"timezone_id":"Asia\/Ho_Chi_Minh","language":"en","sdk":"040802","sdk_type":"flutter","android_package":"in.piggyvault.piggy_flutter","device_model":"Pixel 7 Pro","game_version":55,"net_type":0,"carrier":"VinaPhone","rooted":false,"identifier":"eopg3NwXRuycm7iTP755as:APA91bEqiGtp47f2Yv7_jQLHSTipbyK6U7i6KDJFxqBESl32N6oaBDGgk_ceLFTH05dto70xG69HKAlu56J3Glyg2hWKqsYPkXZh4RIYZeJuNpLtLWeACJQ","device_type":1}</string>
<boolean name="ONESIGNAL_SUBSCRIPTION_LAST" value="false"/>
<boolean name="GT_FIREBASE_TRACKING_ENABLED" value="false"/>
<int name="PREFS_OS_INDIRECT_ATTRIBUTION_WINDOW" value="60"/>
<boolean name="PREFS_OS_INDIRECT_ENABLED" value="false"/>
<boolean name="PREFS_OS_UNATTRIBUTED_ENABLED" value="false"/>
<boolean name="OS_CLEAR_GROUP_SUMMARY_CLICK" value="true"/>
<string name="ONESIGNAL_USERSTATE_SYNCVALYES_smsTOSYNC_STATE">{"app_id":"9bf198c9-442b-4619-b5c9-759fc250f15b","device_os":"15","timezone":25200,"timezone_id":"Asia\/Ho_Chi_Minh","language":"en","sdk":"040802","sdk_type":"flutter","android_package":"in.piggyvault.piggy_flutter","device_model":"Pixel 7 Pro","game_version":55,"net_type":0,"carrier":"VinaPhone","rooted":false}</string>
<string name="ONESIGNAL_USERSTATE_DEPENDVALYES_smsTOSYNC_STATE">{"subscribableStatus":1,"userSubscribePref":true,"session":true}</string>
<boolean name="PREFS_OS_OUTCOMES_V2" value="false"/>
<string name="PREFS_OS_HTTP_CACHE_PREFIX_CACHE_KEY_REMOTE_PARAMS">{"awl_list":{},"android_sender_id":"572062574561","chnl_lst":[{"chnl":{"id":"OS_474ad2c8-1334-4605-9163-29a50204e2fe","nm":"New Transaction","dscr":"Get notification whenever a transaction is added","grp_id":"OS_144aac2b-5267-4c67-8286-c7bc4b21dabb","grp_nm":"Transactions"}},{"chnl":{"id":"OS_5e194189-f80b-45dc-87ca-b8b8ad02ca6c","nm":"Transaction Updated","dscr":"Get notification whenever a transaction is updated","grp_id":"OS_144aac2b-5267-4c67-8286-c7bc4b21dabb","grp_nm":"Transactions"}}],"outcomes":{"direct":{"enabled":false},"indirect":{"notification_attribution":{"minutes_since_displayed":60,"limit":10},"enabled":false},"unattributed":{"enabled":false}},"receive_receipts_enable":false}</string>
<int name="PREFS_OS_IAM_INDIRECT_ATTRIBUTION_WINDOW" value="1440"/>
<int name="PREFS_OS_NOTIFICATION_LIMIT" value="10"/>
<string name="ONESIGNAL_PLAYER_ID_LAST">fd79bb66-e623-438f-bf27-d1070e402991</string>
<long name="GT_UNSENT_ACTIVE_TIME" value="0"/>
<int name="PREFS_OS_IAM_LIMIT" value="10"/>
<string name="ONESIGNAL_USERSTATE_SYNCVALYES_emailTOSYNC_STATE">{"app_id":"9bf198c9-442b-4619-b5c9-759fc250f15b","device_os":"15","timezone":25200,"timezone_id":"Asia\/Ho_Chi_Minh","language":"en","sdk":"040802","sdk_type":"flutter","android_package":"in.piggyvault.piggy_flutter","device_model":"Pixel 7 Pro","game_version":55,"net_type":0,"carrier":"VinaPhone","rooted":false}</string>
<long name="OS_LAST_SESSION_TIME" value="1749096456567"/>
<set name="PREFS_OS_UNATTRIBUTED_UNIQUE_OUTCOME_EVENTS_SENT"/>
<string name="ONESIGNAL_PUSH_TOKEN_LAST">eopg3NwXRuycm7iTP755as:APA91bEqiGtp47f2Yv7_jQLHSTipbyK6U7i6KDJFxqBESl32N6oaBDGgk_ceLFTH05dto70xG69HKAlu56J3Glyg2hWKqsYPkXZh4RIYZeJuNpLtLWeACJQ</string>
<boolean name="PREFS_OS_DIRECT_ENABLED" value="false"/>
<string name="ONESIGNAL_USERSTATE_DEPENDVALYES_TOSYNC_STATE">{"subscribableStatus":1,"userSubscribePref":true,"androidPermission":false,"session":false}</string>
<boolean name="ONESIGNAL_PERMISSION_ACCEPTED_LAST" value="false"/>
<string name="GT_APP_ID">9bf198c9-442b-4619-b5c9-759fc250f15b</string>
<long name="OS_LAST_LOCATION_TIME" value="1749096512962"/>
<boolean name="PREFS_OS_RECEIVE_RECEIPTS_ENABLED" value="false"/>
<string name="PREFS_OS_ETAG_PREFIX_CACHE_KEY_REMOTE_PARAMS">W/"a750154593cddcd1836695e1533860c1"</string>
<string name="GT_PLAYER_ID">fd79bb66-e623-438f-bf27-d1070e402991</string>
<boolean name="OS_RESTORE_TTL_FILTER" value="true"/>
<string name="ONESIGNAL_USERSTATE_DEPENDVALYES_emailTOSYNC_STATE">{"subscribableStatus":1,"userSubscribePref":true,"session":true}</string>
<string name="ONESIGNAL_USERSTATE_SYNCVALYES_TOSYNC_STATE">{"app_id":"9bf198c9-442b-4619-b5c9-759fc250f15b","device_os":"15","timezone":25200,"timezone_id":"Asia\/Ho_Chi_Minh","language":"en","sdk":"040802","sdk_type":"flutter","android_package":"in.piggyvault.piggy_flutter","device_model":"Pixel 7 Pro","game_version":55,"net_type":0,"carrier":"VinaPhone","rooted":false,"identifier":"eopg3NwXRuycm7iTP755as:APA91bEqiGtp47f2Yv7_jQLHSTipbyK6U7i6KDJFxqBESl32N6oaBDGgk_ceLFTH05dto70xG69HKAlu56J3Glyg2hWKqsYPkXZh4RIYZeJuNpLtLWeACJQ","device_type":1,"notification_types":0}</string>
</map>
```
- PersistedInstallation.json:
```
{"Fid":"eopg3NwXRuycm7iTP755as","Status":3,"AuthToken":"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6IjE6NzU0Nzk1NjE0MDQyOmFuZHJvaWQ6YzY4MmI4MTQ0YThkZDUyYmMxYWQ2MyIsImV4cCI6MTc0OTc0NDc4OSwiZmlkIjoiZW9wZzNOd1hSdXljbTdpVFA3NTVhcyIsInByb2plY3ROdW1iZXIiOjc1NDc5NTYxNDA0Mn0.AB2LPV8wRQIgdJMlnlO4Yrc8c4gz8oLNh4sPUACy9_-vboFT8Y37fZgCIQCeydymbD_TQp3uKhmBikiDOMD_sGu3Y9JuU7H51F4bOw","RefreshToken":"3_AS3qfwJW59m6vJGGdcivz9gLc1xhU-8uLExCQ4sI1p5zviKPNwJKG2gAzYWtNbcyKd04oXM9HkAwYnrLAKB8Ccy-lz0LxtMJnic4lFjfe20kkM4","TokenCreationEpochInSecs":1749139991,"ExpiresInSecs":604800}
```
### From these files, we can obtain many preset value, including:
- Firebase Installation Token:
```
{
"Fid": "eopg3NwXRuycm7iTP755as",
"Status": 3,
"AuthToken": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6IjE6NzU0Nzk1NjE0MDQyOmFuZHJvaWQ6YzY4MmI4MTQ0YThkZDUyYmMxYWQ2MyIsImV4cCI6MTc0OTc0NDc4OSwiZmlkIjoiZW9wZzNOd1hSdXljbTdpVFA3NTVhcyIsInByb2plY3ROdW1iZXIiOjc1NDc5NTYxNDA0Mn0.AB2LPV8wRQIgdJMlnlO4Yrc8c4gz8oLNh4sPUACy9_-vboFT8Y37fZgCIQCeydymbD_TQp3uKhmBikiDOMD_sGu3Y9JuU7H51F4bOw",
"RefreshToken": "3_AS3qfwJW59m6vJGGdcivz9gLc1xhU-8uLExCQ4sI1p5zviKPNwJKG2gAzYWtNbcyKd04oXM9HkAwYnrLAKB8Ccy-lz0LxtMJnic4lFjfe20kkM4",
"TokenCreationEpochInSecs": 1749139991,
"ExpiresInSecs": 604800
}
```
- Auth Token (JWT Access Token):
```
yJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjIiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9lbWFpbGFkZHJlc3MiOiJhZG1pbkBkZWZhdWx0dGVuYW50LmNvbSIsIkFzcE5ldC5JZGVudGl0eS5TZWN1cml0eVN0YW1wIjoiNWFjYTE0OWMtMzU4MS02MzA2LTU3OTAtMzlmMGYxOGQ2MWI0IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW4iLCJodHRwOi8vd3d3LmFzcG5ldGJvaWxlcnBsYXRlLmNvbS9pZGVudGl0eS9jbGFpbXMvdGVuYW50SWQiOiIxIiwic3ViIjoiMiIsImp0aSI6IjQ0MjM5M2U5LWEwMzYtNDEyNi05ZWNmLWI4MTllNWE2ODdiZSIsImlhdCI6MTc0OTE0MDAwNSwibmJmIjoxNzQ5MTQwMDA1LCJleHAiOjE3NjQ2OTIwMDUsImlzcyI6IlBpZ2d5dmF1bHQiLCJhdWQiOiJQaWdneXZhdWx0In0.ydM1IRtzo1eTSjJP-sx4KB7mF0JCd8XBqDL6FMyq4sQ
```
- OneSignal Identifiers
```
- Push Token: eopg3NwXRuycm7iTP755as:APA91bEqiGtp47f2Yv7_jQLHSTipbyK6U7i6KDJFxqBESl32N6oaBDGgk_ceLFTH05dto70xG69HKAlu56J3Glyg2hWKqsYPkXZh4RIYZeJuNpLtLWeACJQ
- Player ID: fd79bb66-e623-438f-bf27-d1070e402991
- App ID: 9bf198c9-442b-4619-b5c9-759fc250f15b
```
## Method 3: using Frida to dump live data
### Step-by-step:
- Since my phone is not rooted, frida-server cannot be used, so that I will inject frida-gadget into the apk.
- First, I add libfrida-gadget.so into lib/arm64-v8a and declare it in MainActivity.
```
.method static constructor <clinit>()V
.locals 1
const-string v0, "frida-gadget"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
return-void
.end method
```
- Then I craft a js script to read internal database query alongside hooking network traffic
```
'use strict';
function tryUtf8(ptr, len) {
try {
return ptr.readUtf8String(len) || '<non-utf8>';
} catch (_) {
return '<non-utf8 or error>';
}
}
const attached = new Set();
const resolver = new ApiResolver('module');
function hookSSL() {
['SSL_write', 'SSL_read'].forEach(name => {
const matches = resolver.enumerateMatches(`exports:*!${name}`);
matches.forEach(m => {
const key = `${name}@${m.address}`;
if (attached.has(key)) return;
attached.add(key);
console.log(`[+] Attaching ${name} @ ${m.address}`);
Interceptor.attach(m.address, {
onEnter(args) {
if (name === 'SSL_write') {
const len = args[2].toInt32();
const data = tryUtf8(args[1], len);
console.log(`\n--- request (${name}) len=${len}`);
console.log(data);
} else {
this.buf = args[1];
}
},
onLeave(retval) {
if (name === 'SSL_read') {
const len = retval.toInt32();
if (len > 0) {
const data = tryUtf8(this.buf, len);
console.log(`\n--- response (${name}) len=${len}`);
console.log(data);
}
}
}
});
});
});
}
function hookSocketIO() {
['send', 'recv'].forEach(name => {
const matches = resolver.enumerateMatches(`exports:*!${name}`);
matches.forEach(m => {
const key = `${name}@${m.address}`;
if (attached.has(key)) return;
attached.add(key);
console.log(`[+] Attaching ${name} @ ${m.address}`);
Interceptor.attach(m.address, {
onEnter(args) {
if (name === 'send') {
const len = args[2].toInt32();
const data = tryUtf8(args[1], len);
console.log(`\n--- request (${name}) len=${len}`);
console.log(data);
} else {
this.buf = args[1];
}
},
onLeave(retval) {
if (name === 'recv') {
const len = retval.toInt32();
if (len > 0) {
const data = tryUtf8(this.buf, len);
console.log(`\n--- response (${name}) len=${len}`);
console.log(data);
}
}
}
});
});
});
}
Process.attachModuleObserver({
onAdded(m) {
if (/ssl/i.test(m.name)) hookSSL();
if (/libc\.so/.test(m.name)) hookSocketIO();
}
});
hookSSL();
hookSocketIO();
if (Java.available) {
Java.perform(() => {
console.log('[Java] Hooking Java methods');
try {
const URL = Java.use('java.net.URL');
URL.$init.overload('java.lang.String').implementation = function (s) {
console.log('[+] New URL:', s);
return this.$init(s);
};
const HttpsURLConnection = Java.use('javax.net.ssl.HttpsURLConnection');
HttpsURLConnection.getInputStream.implementation = function () {
console.log('[+] getInputStream:', this.getURL());
return this.getInputStream();
};
} catch (e) {
console.warn('[!] URL/HttpsURLConnection hook failed:', e);
}
try {
const OkHttpBuilder = Java.use('okhttp3.OkHttpClient$Builder');
OkHttpBuilder.build.implementation = function () {
console.log('[+] OkHttpClient built');
return this.build();
};
} catch (_) {}
try {
const SP = Java.use('android.content.SharedPreferences');
const Editor = Java.use('android.content.SharedPreferences$Editor');
SP.getString.overload('java.lang.String','java.lang.String').implementation = function (k, d) {
const v = this.getString(k, d);
console.log(`[SharedPreferences] GET ${k} = ${v}`);
return v;
};
Editor.putString.overload('java.lang.String','java.lang.String').implementation = function (k, v) {
console.log(`[SharedPreferences] PUT ${k} = ${v}`);
return this.putString(k, v);
};
} catch (_) {}
try {
const DB = Java.use('android.database.sqlite.SQLiteDatabase');
DB.rawQuery.overload('java.lang.String','[Ljava.lang.String;').implementation = function (sql, a) {
console.log(`[SQLite] rawQuery: ${sql}`);
return this.rawQuery(sql, a);
};
DB.execSQL.overload('java.lang.String').implementation = function (sql) {
console.log(`[SQLite] execSQL: ${sql}`);
return this.execSQL(sql);
};
} catch (_) {}
try {
const FIS = Java.use('java.io.FileInputStream');
FIS.$init.overload('java.lang.String').implementation = function (p) {
console.log(`[FileInputStream] Open: ${p}`);
return this.$init(p);
};
} catch (_) {}
});
}
```
### Results:
- Output:
```
____
/ _ | Frida 17.1.3 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Pixel 7 Pro (id=29221FDH3009S7)
Attaching...
[+] Attaching send @ 0x7d7c476570
[+] Attaching recv @ 0x7d7c475970
[+] Attaching SSL_write @ 0x7aa1458c10
[+] Attaching SSL_read @ 0x7aa14587d8
[Java] Hooking Java methods
[Pixel 7 Pro::PID::32668 ]->
--- request (SSL_write) len=318
GET /apps/9bf198c9-442b-4619-b5c9-759fc250f15b/android_params.js HTTP/1.1
SDK-Version: onesignal/android/040802
Accept: application/vnd.onesignal.v1+json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 15; Pixel 7 Pro Build/BP1A.250505.005.B1)
Host: api.onesignal.com
Connection: Keep-Alive
Accept-Encoding: gzip
--- response (SSL_read) len=1369
{
"awl_list": {},
"android_sender_id": "572062574561",
"chnl_lst": [
{
"chnl": {
"id": "OS_474ad2c8-1334-4605-9163-29a50204e2fe",
"nm": "New Transaction",
"dscr": "Get notification whenever a transaction is added",
"grp_id": "OS_144aac2b-5267-4c67-8286-c7bc4b21dabb",
"grp_nm": "Transactions"
}
},
{
"chnl": {
"id": "OS_5e194189-f80b-45dc-87ca-b8b8ad02ca6c",
"nm": "Transaction Updated",
"dscr": "Get notification whenever a transaction is updated",
"grp_id": "OS_144aac2b-5267-4c67-8286-c7bc4b21dabb",
"grp_nm": "Transactions"
}
}
],
"outcomes": {
"direct": {
"enabled": false
},
"indirect": {
"notification_attribution": {
"minutes_since_displayed": 60,
"limit": 10
},
"enabled": false
},
"unattributed": {
"enabled": false
}
},
"receive_receipts_enable": false
}
--- response (SSL_read) len=182
<non-utf8 or error>
--- response (SSL_read) len=5
0
[SQLite] rawQuery: PRAGMA busy_timeout=0;
[SQLite] execSQL: CREATE TABLE events (_id INTEGER PRIMARY KEY, context_id INTEGER NOT NULL, transport_name TEXT NOT NULL, timestamp_ms INTEGER NOT NULL, uptime_ms INTEGER NOT NULL, payload BLOB NOT NULL, code INTEGER, num_attempts INTEGER NOT NULL,FOREIGN KEY (context_id) REFERENCES transport_contexts(_id) ON DELETE CASCADE)
[+] New URL: https://firebaseinstallations.googleapis.com/v1/projects/onesignal-shared-public/installations
[SQLite] execSQL: CREATE TABLE event_metadata (_id INTEGER PRIMARY KEY, event_id INTEGER NOT NULL, name TEXT NOT NULL, value TEXT NOT NULL,FOREIGN KEY (event_id) REFERENCES events(_id) ON DELETE CASCADE)
[SQLite] execSQL: CREATE TABLE transport_contexts (_id INTEGER PRIMARY KEY, backend_name TEXT NOT NULL, priority INTEGER NOT NULL, next_request_ms INTEGER NOT NULL)
[SQLite] execSQL: CREATE INDEX events_backend_id on events(context_id)
[SQLite] execSQL: CREATE UNIQUE INDEX contexts_backend_priority on transport_contexts(backend_name, priority)
[SQLite] execSQL: ALTER TABLE transport_contexts ADD COLUMN extras BLOB
[SQLite] execSQL: CREATE UNIQUE INDEX contexts_backend_priority_extras on transport_contexts(backend_name, priority, extras)
[SQLite] execSQL: DROP INDEX contexts_backend_priority
[SQLite] execSQL: ALTER TABLE events ADD COLUMN payload_encoding TEXT
[SQLite] execSQL: ALTER TABLE events ADD COLUMN inline BOOLEAN NOT NULL DEFAULT 1
[SQLite] execSQL: DROP TABLE IF EXISTS event_payloads
[SQLite] execSQL: CREATE TABLE event_payloads (sequence_num INTEGER NOT NULL, event_id INTEGER NOT NULL, bytes BLOB NOT NULL,FOREIGN KEY (event_id) REFERENCES events(_id) ON DELETE CASCADE,PRIMARY KEY (sequence_num, event_id))
[SQLite] execSQL: PRAGMA user_version = 4
[SQLite] rawQuery: SELECT distinct t._id, t.backend_name, t.priority, t.extras FROM transport_contexts AS t, events AS e WHERE e.context_id = t._id
--- request (SSL_write) len=988
<non-utf8 or error>
--- response (SSL_read) len=966
<non-utf8 or error>
--- response (SSL_read) len=5
0
[+] New URL: https://api.onesignal.com/players
--- request (SSL_write) len=846
POST /players HTTP/1.1
SDK-Version: onesignal/android/040802
Accept: application/vnd.onesignal.v1+json
Content-Type: application/json; charset=UTF-8
Content-Length: 511
User-Agent: Dalvik/2.1.0 (Linux; U; Android 15; Pixel 7 Pro Build/BP1A.250505.005.B1)
Host: api.onesignal.com
Connection: Keep-Alive
Accept-Encoding: gzip
{"app_id":"9bf198c9-442b-4619-b5c9-759fc250f15b","device_os":"15","timezone":25200,"timezone_id":"Asia\/Ho_Chi_Minh","language":"en","sdk":"040802","sdk_type":"flutter","android_package":"in.piggyvault.piggy_flutter","device_model":"Pixel 7 Pro","game_version":55,"net_type":0,"carrier":"VinaPhone","rooted":false,"identifier":"dVGEvzAiSz6oHfnSwzd8tM:APA91bF3OeDgwuYJ6eTTWgd6CSo9qQ0VJBMbek9wWAba92rU5VkU3IYdbPmZ9HUdgemTX4W93UUuY_i0_bj8qrMWgp5q-7_nycz4Koh4pGL1cNdi1SVMzTA","device_type":1,"notification_types":0}
--- response (SSL_read) len=1230
{
"success": true,
"id": "40f4c6eb-76b8-4770-9815-6438eebd2548"
}
--- response (SSL_read) len=5
0
[+] New URL: https://api.onesignal.com/players/40f4c6eb-76b8-4770-9815-6438eebd2548
--- request (SSL_write) len=452
PUT /players/40f4c6eb-76b8-4770-9815-6438eebd2548 HTTP/1.1
SDK-Version: onesignal/android/040802
Accept: application/vnd.onesignal.v1+json
Content-Type: application/json; charset=UTF-8
Content-Length: 82
User-Agent: Dalvik/2.1.0 (Linux; U; Android 15; Pixel 7 Pro Build/BP1A.250505.005.B1)
Host: api.onesignal.com
Connection: Keep-Alive
Accept-Encoding: gzip
{"tags":{"tenancyName":"default"},"app_id":"9bf198c9-442b-4619-b5c9-759fc250f15b"}
--- response (SSL_read) len=848
HTTP/1.1 200 OK
Date: Mon, 09 Jun 2025 08:16:22 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 16
Connection: keep-alive
access-control-allow-headers: SDK-Version,Content-Type,Origin,Authorization,OneSignal-Subscription-Id
access-control-allow-origin: *
traceparent: 00-4c68e9def85d4cfab89b5cc3e52f54ea-7a92b120caa04a09-00
via: 1.1 google
alt-svc: h3=":443"; ma=86400
cf-cache-status: DYNAMIC
Set-Cookie: __cf_bm=Zuk5RPf4cIOt2MikMcNpyCS8VnYzt83..cFxKvxpAGg-1749456982-1.0.1.1-81bz0boIkf6Cbc9o.I0lLL93Tg0M9_TbWqr1sHW.Y54ig0C5I7BaiIw6nVEm552vlY6._IM6HVlKjYFTY5kxm.X_OYlOx8ta2e._vjG635w; path=/; expires=Mon, 09-Jun-25 08:46:22 GMT; domain=.onesignal.com; HttpOnly; Secure; SameSite=None
Strict-Transport-Security: max-age=15552000; includeSubDomains
Server: cloudflare
CF-RAY: 94cf2fb80c38b42b-HKG
{"success":true}
Connection terminated
Thank you for using Frida!
```
#### SQL Query:
- From the query, I rebuilt the database of the app:

#### Network traffics:
##### * Get https://api.onesignal.com/apps/9bf198c9-442b-4619-b5c9-759fc250f15b/android_params.js
- Header:
```
GET /apps/9bf198c9-442b-4619-b5c9-759fc250f15b/android_params.js HTTP/1.1
SDK-Version: onesignal/android/040802
Accept: application/vnd.onesignal.v1+json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 15; Pixel 7 Pro Build/BP1A.250505.005.B1)
Host: api.onesignal.com
Connection: Keep-Alive
Accept-Encoding: gzip
```
- Response:
```
{
"awl_list": {},
"android_sender_id": "572062574561",
"chnl_lst": [
{
"chnl": {
"id": "OS_474ad2c8-1334-4605-9163-29a50204e2fe",
"nm": "New Transaction",
"dscr": "Get notification whenever a transaction is added",
"grp_id": "OS_144aac2b-5267-4c67-8286-c7bc4b21dabb",
"grp_nm": "Transactions"
}
},
{
"chnl": {
"id": "OS_5e194189-f80b-45dc-87ca-b8b8ad02ca6c",
"nm": "Transaction Updated",
"dscr": "Get notification whenever a transaction is updated",
"grp_id": "OS_144aac2b-5267-4c67-8286-c7bc4b21dabb",
"grp_nm": "Transactions"
}
}
],
"outcomes": {
"direct": {
"enabled": false
},
"indirect": {
"notification_attribution": {
"minutes_since_displayed": 60,
"limit": 10
},
"enabled": false
},
"unattributed": {
"enabled": false
}
},
"receive_receipts_enable": false
}
```
##### * POST https://api.onesignal.com/players
- Header:
```
POST /players HTTP/1.1
SDK-Version: onesignal/android/040802
Accept: application/vnd.onesignal.v1+json
Content-Type: application/json; charset=UTF-8
Content-Length: 511
User-Agent: Dalvik/2.1.0 (Linux; U; Android 15; Pixel 7 Pro Build/BP1A.250505.005.B1)
Host: api.onesignal.com
Connection: Keep-Alive
Accept-Encoding: gzip
{"app_id":"9bf198c9-442b-4619-b5c9-759fc250f15b","device_os":"15","timezone":25200,"timezone_id":"Asia\/Ho_Chi_Minh","language":"en","sdk":"040802","sdk_type":"flutter","android_package":"in.piggyvault.piggy_flutter","device_model":"Pixel 7 Pro","game_version":55,"net_type":0,"carrier":"VinaPhone","rooted":false,"identifier":"dVGEvzAiSz6oHfnSwzd8tM:APA91bF3OeDgwuYJ6eTTWgd6CSo9qQ0VJBMbek9wWAba92rU5VkU3IYdbPmZ9HUdgemTX4W93UUuY_i0_bj8qrMWgp5q-7_nycz4Koh4pGL1cNdi1SVMzTA","device_type":1,"notification_types":0}
```
- Response:
```
{
"success": true,
"id": "40f4c6eb-76b8-4770-9815-6438eebd2548"
}
```
- Role: register session
##### * PUT https://api.onesignal.com/players/40f4c6eb-76b8-4770-9815-6438eebd2548
- Header:
```
PUT /players/40f4c6eb-76b8-4770-9815-6438eebd2548 HTTP/1.1
SDK-Version: onesignal/android/040802
Accept: application/vnd.onesignal.v1+json
Content-Type: application/json; charset=UTF-8
Content-Length: 82
User-Agent: Dalvik/2.1.0 (Linux; U; Android 15; Pixel 7 Pro Build/BP1A.250505.005.B1)
Host: api.onesignal.com
Connection: Keep-Alive
Accept-Encoding: gzip
{"tags":{"tenancyName":"default"},"app_id":"9bf198c9-442b-4619-b5c9-759fc250f15b"}
```
- Response:
```
HTTP/1.1 200 OK
Date: Mon, 09 Jun 2025 08:16:22 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 16
Connection: keep-alive
access-control-allow-headers: SDK-Version,Content-Type,Origin,Authorization,OneSignal-Subscription-Id
access-control-allow-origin: *
traceparent: 00-4c68e9def85d4cfab89b5cc3e52f54ea-7a92b120caa04a09-00
via: 1.1 google
alt-svc: h3=":443"; ma=86400
cf-cache-status: DYNAMIC
Set-Cookie: __cf_bm=Zuk5RPf4cIOt2MikMcNpyCS8VnYzt83..cFxKvxpAGg-1749456982-1.0.1.1-81bz0boIkf6Cbc9o.I0lLL93Tg0M9_TbWqr1sHW.Y54ig0C5I7BaiIw6nVEm552vlY6._IM6HVlKjYFTY5kxm.X_OYlOx8ta2e._vjG635w; path=/; expires=Mon, 09-Jun-25 08:46:22 GMT; domain=.onesignal.com; HttpOnly; Secure; SameSite=None
Strict-Transport-Security: max-age=15552000; includeSubDomains
Server: cloudflare
CF-RAY: 94cf2fb80c38b42b-HKG
{"success":true}
```
- Role: update metadata
# IV. Data Obtained
## 1. SharedPreferences
- FlutterSharedPreferences.xml → flutter.authToken (JWT), flutter.tenantId…
- OneSignal prefs → player IDs, HTTP cache keys, feature flags
## 2. Databases
- Schema of OneSignal.db, journal files, transport event tables
## 3. Internal Files
- PersistedInstallation.json (Firebase IDs, AuthToken, RefreshToken)
- generatefid.lock, custom lock/status files
## 4. Memory Maps
- /proc/self/maps regions, memfd segments (Frida gadget)
## 5. Network Traffic
- GET android_params.js (channel list, outcomes)
- POST/PUT to api.onesignal.com/players (register/update metadata)
- Calls to firebaseinstallations.googleapis.com (AuthToken lifecycle)
# V. Further Applications Based on Leaked Data
- Token reuse & session hijacking: forge Authorization headers for API calls
- Offline replay / fuzzing: feed dumped DB into local server emulator
- Custom tooling: script automated extractor for any Flutter app with similar structure
- Feature-flag manipulation: toggle hidden UI via intercepted prefs
- Compliance testing: check whether sensitive data are encrypted at rest
# IV. Conclusion
## 1. Summary of Extracted Data
#### Over the course of our analysis, we were able to recover and inspect the following artifacts from Piggy Flutter:
- Authentication tokens & identifiers
- JWTs and Firebase AuthTokens from SharedPreferences and PersistedInstallation.json
- OneSignal Player IDs, feature-flag keys, and push-notification tokens
- Database contents
- Full schema and record dumps of OneSignal.db (user events, subscription status, cached payloads)
- Internal files & filesystem metadata
- App-specific JSON files (e.g. generatefid.lock), file timestamps, lock states
- Network traffic flows
- Plain-text HTTP(S) requests to api.onesignal.com and firebaseinstallations.googleapis.com
- Dynamic headers (Authorization, Content-Type) and payload bodies for registration/update calls
- In-memory regions
- /proc/self/maps insights showing loaded libraries (Flutter engine, Frida gadget) and RWX pages
- Live SSL_read/SSL_write interception exposing unencrypted request/response buffers
#### These combined techniques demonstrate that, with minimal instrumentation, an attacker can reconstruct both static and dynamic app state, including sensitive credentials and behavioral telemetry.
## 2. Security Posture of Piggy Flutter
#### Overall, Piggy Flutter exhibits several critical weaknesses in its default configuration:
- Lack of at-rest encryption
- SharedPreferences and SQLite databases store tokens and user data in cleartext on disk.
- Predictable file-naming & storage paths
- JSON lock files and DB names follow static conventions, simplifying automated extraction.
- Minimal code obfuscation
- Neither Dart AOT blob nor native libraries are obfuscated, so reverse-engineers can map symbols easily.
## 3. Limitations of My Analysis
- Device-specific requirements
- Some methods (e.g. Burp on Android 7+) require root or system-CA installation to bypass OS restrictions.
- Incomplete native coverage
- I focused on high-value hooks (SSL, JNI I/O) but did not audit every C++ plugin or isolate native code paths.
- Live-only insights
- Frida hooks capture runtime activity, but static obfuscation or anti-tamper could still evade simple instrumentation.