Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added sharedprefs monitor for Android #622

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 220 additions & 0 deletions agent/src/android/sharedprefs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import { colors as c } from "../lib/color";
import { IJob } from "../lib/interfaces";
import * as jobs from "../lib/jobs";
import {
wrapJavaPerform
} from "./lib/libjava";

function setToArray(set) {
return set == null ? null : set.toArray()
}

export const monitor = (encrypted_only: boolean): Promise<void> => {
send(`Monitoring shared preferences`);

const job: IJob = {
identifier: jobs.identifier(),
implementations: [],
type: `shared-prefs-monitor`,
};
jobs.add(job)
return wrapJavaPerform(() => {
if (encrypted_only == false) {
// SharedPreferences
const hSharedPrefs = Java.use('android.app.SharedPreferencesImpl')

hSharedPrefs["getBoolean"].overload('java.lang.String', 'boolean').implementation = function (key, _default) {
const returnValue = this["getBoolean"].apply(this, arguments)
send(`SharedPreferences::getBoolean(${c.green(key)}, ${c.yellow(_default)}) -> ${c.yellow(returnValue)}`)


return returnValue
}
job.implementations.push(hSharedPrefs["getBoolean"])

hSharedPrefs["getFloat"].overload('java.lang.String', 'float').implementation = function (key, _default) {
const returnValue = this["getFloat"].apply(this, arguments)
send(`SharedPreferences::getFloat(${c.green(key)}, ${c.yellow(_default)}) -> ${c.yellow(returnValue)}`)
return returnValue
}
job.implementations.push(hSharedPrefs["getFloat"])

hSharedPrefs["getInt"].overload('java.lang.String', 'int').implementation = function (key, _default) {
const returnValue = this["getInt"].apply(this, arguments)
send(`SharedPreferences::getInt(${c.green(key)}, ${c.yellow(_default)}) -> ${c.yellow(returnValue)}`)
return returnValue
}
job.implementations.push(hSharedPrefs["getInt"].overload('java.lang.String', 'int').implementation)

hSharedPrefs["getLong"].overload('java.lang.String', 'long').implementation = function (key, _default) {
const returnValue = this["getLong"].apply(this, arguments)
send(`SharedPreferences::getLong(${c.green(key)}, ${c.yellow(_default)}) -> ${c.yellow(returnValue)}`)
return returnValue
}
job.implementations.push(hSharedPrefs["getLong"])

hSharedPrefs["getString"].overload('java.lang.String', 'java.lang.String').implementation = function (key, _default) {
const returnValue = this["getString"].apply(this, arguments)
send(`SharedPreferences::getString(${c.green(key)}, ${c.yellow(_default)}) -> ${c.yellow(returnValue)}`)
return returnValue
}
job.implementations.push(hSharedPrefs["getString"])

hSharedPrefs["getStringSet"].overload('java.lang.String', 'java.util.Set').implementation = function (key, _default) {
const returnValue = this["getStringSet"].apply(this, arguments)
send(`SharedPreferences::getStringSet(${c.green(key)}, ${c.yellow(setToArray(_default))}) -> ${c.yellow(setToArray(returnValue))}`)
return returnValue
}
job.implementations.push(hSharedPrefs["getStringSet"])

// SharedPreferences$Editor
const hSharedPrefsEditor = Java.use('android.app.SharedPreferencesImpl$EditorImpl')

hSharedPrefsEditor["putBoolean"].overload('java.lang.String', 'boolean').implementation = function (key, val) {
send(`SharedPreferences$Editor::putBoolean(${c.green(key)}, ${c.yellow(val)})`)
const returnValue = this["putBoolean"].apply(this, arguments)
return returnValue
}
job.implementations.push(hSharedPrefsEditor["putBoolean"])

hSharedPrefsEditor["putFloat"].overload('java.lang.String', 'float').implementation = function (key, val) {
send(`SharedPreferences$Editor::putFloat(${c.green(key)}, ${c.yellow(val)})`)
const returnValue = this["putFloat"].apply(this, arguments)
return returnValue
}
job.implementations.push(hSharedPrefsEditor["putFloat"])

hSharedPrefsEditor["putInt"].overload('java.lang.String', 'int').implementation = function (key, val) {
send(`SharedPreferences$Editor::putInt(${c.green(key)}, ${c.yellow(val)})`)
const returnValue = this["putInt"].apply(this, arguments)
return returnValue
}
job.implementations.push(hSharedPrefsEditor["putInt"])

hSharedPrefsEditor["putLong"].overload('java.lang.String', 'long').implementation = function (key, val) {
send(`SharedPreferences$Editor::putLong(${c.green(key)}, ${c.yellow(val)})`)
const returnValue = this["putLong"].apply(this, arguments)
return returnValue
}
job.implementations.push(hSharedPrefsEditor["putLong"])

hSharedPrefsEditor["putString"].overload('java.lang.String', 'java.lang.String').implementation = function (key, val) {
send(`SharedPreferences$Editor::putString(${c.green(key)}, ${c.yellow(val)})`)
const returnValue = this["putString"].apply(this, arguments)
return returnValue
}
job.implementations.push(hSharedPrefsEditor["putString"])

hSharedPrefsEditor["putStringSet"].overload('java.lang.String', 'java.util.Set').implementation = function (key, val) {
send(`SharedPreferences$Editor::putStringSet(${c.green(key)}, ${c.yellow(val.toArray())})`)
const returnValue = this["putStringSet"].apply(this, arguments)
return returnValue
}
job.implementations.push(hSharedPrefsEditor["putStringSet"])
}

try {
// EncryptedSharedPreferences
const hEncryptedSharedPrefs = Java.use('androidx.security.crypto.EncryptedSharedPreferences')

hEncryptedSharedPrefs["getBoolean"].overload('java.lang.String', 'boolean').implementation = function (key, _default) {
const returnValue = this["getBoolean"].apply(this, arguments)
send(`EncryptedSharedPreferences::getBoolean(${c.green(key)}, ${c.yellow(_default)}) -> ${c.yellow(returnValue)}`)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefs["getBoolean"])

hEncryptedSharedPrefs["getFloat"].overload('java.lang.String', 'float').implementation = function (key, _default) {
const returnValue = this["getFloat"].apply(this, arguments)
send(`EncryptedSharedPreferences::getFloat(${c.green(key)}, ${c.yellow(_default)}) -> ${c.yellow(returnValue)}`)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefs["getFloat"])

hEncryptedSharedPrefs["getInt"].overload('java.lang.String', 'int').implementation = function (key, _default) {
const returnValue = this["getInt"].apply(this, arguments)
send(`EncryptedSharedPreferences::getInt(${c.green(key)}, ${c.yellow(_default)}) -> ${c.yellow(returnValue)}`)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefs["getInt"])

hEncryptedSharedPrefs["getLong"].overload('java.lang.String', 'long').implementation = function (key, _default) {
const returnValue = this["getLong"].apply(this, arguments)
send(`EncryptedSharedPreferences::getLong(${c.green(key)}, ${c.yellow(_default)}) -> ${c.yellow(returnValue)}`)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefs["getLong"])

hEncryptedSharedPrefs["getString"].overload('java.lang.String', 'java.lang.String').implementation = function (key, _default) {
const returnValue = this["getString"].apply(this, arguments)
send(`EncryptedSharedPreferences::getString(${c.green(key)}, ${c.yellow(_default)}) -> ${c.yellow(returnValue)}`)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefs["getString"])

hEncryptedSharedPrefs["getStringSet"].overload('java.lang.String', 'java.util.Set').implementation = function (key, _default) {
const returnValue = this["getStringSet"].apply(this, arguments)
send(`EncryptedSharedPreferences::getStringSet(${c.green(key)}, ${c.yellow(setToArray(_default))}) -> ${c.yellow(setToArray(returnValue))}`)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefs["getStringSet"])

// EncryptedSharedPreferences$Editor
const hEncryptedSharedPrefsEditor = Java.use('androidx.security.crypto.EncryptedSharedPreferences$Editor')

hEncryptedSharedPrefsEditor["putBoolean"].overload('java.lang.String', 'boolean').implementation = function (key, val) {
send(`EncryptedSharedPreferences$Editor::putBoolean(${c.green(key)}, ${c.yellow(val)})`)
const returnValue = this["putBoolean"].apply(this, arguments)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefsEditor["putBoolean"])

hEncryptedSharedPrefsEditor["putFloat"].overload('java.lang.String', 'float').implementation = function (key, val) {
send(`EncryptedSharedPreferences$Editor::putFloat(${c.green(key)}, ${c.yellow(val)})`)
const returnValue = this["putFloat"].apply(this, arguments)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefsEditor["putFloat"])

hEncryptedSharedPrefsEditor["putInt"].overload('java.lang.String', 'int').implementation = function (key, val) {
send(`EncryptedSharedPreferences$Editor::putInt(${c.green(key)}, ${c.yellow(val)})`)
const returnValue = this["putInt"].apply(this, arguments)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefsEditor["putInt"])

hEncryptedSharedPrefsEditor["putLong"].overload('java.lang.String', 'long').implementation = function (key, val) {
send(`EncryptedSharedPreferences$Editor::putLong(${c.green(key)}, ${c.yellow(val)})`)
const returnValue = this["putLong"].apply(this, arguments)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefsEditor["putLong"])

hEncryptedSharedPrefsEditor["putString"].overload('java.lang.String', 'java.lang.String').implementation = function (key, val) {
send(`EncryptedSharedPreferences$Editor::putString(${c.green(key)}, ${c.yellow(val)})`)
const returnValue = this["putString"].apply(this, arguments)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefsEditor["putString"])

hEncryptedSharedPrefsEditor["putStringSet"].overload('java.lang.String', 'java.util.Set').implementation = function (key, val) {
send(`EncryptedSharedPreferences$Editor::putStringSet(${c.green(key)}, ${c.yellow(val.toArray())})`)
const returnValue = this["putStringSet"].apply(this, arguments)
return returnValue
}
job.implementations.push(hEncryptedSharedPrefsEditor["putStringSet"])
} catch (error) {
// Ignore this if it occurs, probably encrypted shared preferences is not used
}
});
};

/*
This code has a lot of repetition that isn't required
for a future PR maybe we could iterate over all loaded
classes that extend SharedPrefs and Editor

Since the interfaces developers use are going to be
abstractions anyway, the same hooks could be applied
to all instances.
*/
4 changes: 4 additions & 0 deletions agent/src/rpc/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as root from "../android/root";
import * as androidshell from "../android/shell";
import * as userinterface from "../android/userinterface";
import * as proxy from "../android/proxy";
import * as sharedprefs from "../android/sharedprefs";
import * as general from "../android/general";

import {
Expand Down Expand Up @@ -91,4 +92,7 @@ export const android = {
// android user interface
androidUiScreenshot: () => userinterface.screenshot(),
androidUiSetFlagSecure: (v: boolean): Promise<void> => userinterface.setFlagSecure(v),

// android shared preferences
androidSharedprefsMonitor: (encrypted_only: boolean) => sharedprefs.monitor(encrypted_only),
};
23 changes: 23 additions & 0 deletions objection/commands/android/sharedprefs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from objection.state.connection import state_connection

def _should_watch_encrypted_only(args: list = None) -> bool:
"""
Check if --encrypted_only is part of the arguments.

:param args:
:return:
"""

return '--encrypted-only' in args

def monitor(args: list = None) -> None:
"""
Monitors the use of Android shared preferences.

:param args:
:return:
"""

api = state_connection.get_api()
should_watch_encrypted_only = _should_watch_encrypted_only(args)
api.android_sharedprefs_monitor(should_watch_encrypted_only)
11 changes: 11 additions & 0 deletions objection/console/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from ..commands.android import pinning as android_pinning
from ..commands.android import proxy as android_proxy
from ..commands.android import root
from ..commands.android import sharedprefs
from ..commands.ios import binary
from ..commands.ios import bundles
from ..commands.ios import cookies
Expand Down Expand Up @@ -496,6 +497,16 @@
},
}
},
'sharedprefs': {
'meta': 'Work with shared preferences',
'commands': {
'monitor': {
'meta': 'Monitor usage of shared preferences',
'flags': ['--encrypted-only'],
'exec': sharedprefs.monitor
}
}
},
},
},
# ios commands
Expand Down