mirror of
https://github.com/jankammerath/BogusMips.git
synced 2026-11-07 10:15:59 -10:00
fixed frida script loading and bypass
This commit is contained in:
1 parent
f231c97db1
commit
b392670d1d
4 files changed
+511
-195
No files matched your search
+64
-19
@@ -53,35 +53,40 @@ fn main() {
|
||||
std::process::exit(2);
|
||||
};
|
||||
let seconds: u64 = positional.next().and_then(|s| s.parse().ok()).unwrap_or(90);
|
||||
let path = PathBuf::from(&input);
|
||||
let pkg = Package::open(&path).expect("open package");
|
||||
|
||||
// Extract the APK(s) to install.
|
||||
let tmp = std::env::temp_dir().join(format!("bogusmips-net-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&tmp).unwrap();
|
||||
let mut apks = Vec::new();
|
||||
let mut package_name = None;
|
||||
if pkg.kind == PackageKind::Apk {
|
||||
apks.push(path.clone());
|
||||
if let Some(pkg_id) = input.strip_prefix("pkg:") {
|
||||
package_name = Some(pkg_id.to_owned());
|
||||
} else {
|
||||
for (i, archive) in pkg.archives.iter().enumerate().skip(1) {
|
||||
if let Some(file) = archive.source_file {
|
||||
let data = pkg.read_file(file).expect("read inner apk");
|
||||
let out = tmp.join(format!("part{i}.apk"));
|
||||
std::fs::write(&out, &data.bytes).unwrap();
|
||||
apks.push(out);
|
||||
let path = PathBuf::from(&input);
|
||||
let pkg = Package::open(&path).expect("open package");
|
||||
|
||||
// Extract the APK(s) to install.
|
||||
if pkg.kind == PackageKind::Apk {
|
||||
apks.push(path.clone());
|
||||
} else {
|
||||
for (i, archive) in pkg.archives.iter().enumerate().skip(1) {
|
||||
if let Some(file) = archive.source_file {
|
||||
let data = pkg.read_file(file).expect("read inner apk");
|
||||
let out = tmp.join(format!("part{i}.apk"));
|
||||
std::fs::write(&out, &data.bytes).unwrap();
|
||||
apks.push(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for archive in &pkg.archives {
|
||||
if let Some(m) = &archive.manifest {
|
||||
if let Some(p) = &m.package {
|
||||
package_name = Some(p.clone());
|
||||
break;
|
||||
for archive in &pkg.archives {
|
||||
if let Some(m) = &archive.manifest {
|
||||
if let Some(p) = &m.package {
|
||||
package_name = Some(p.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!("package: {package_name:?}, {} apk(s)", apks.len());
|
||||
let controller_pkg = package_name.clone();
|
||||
|
||||
let mut controller = NetworkController::new();
|
||||
std::fs::write(tmp.join("ca.pem"), controller.ca_pem()).unwrap();
|
||||
@@ -94,12 +99,19 @@ fn main() {
|
||||
|
||||
let deadline = Instant::now() + Duration::from_secs(seconds);
|
||||
let mut last_phase = String::new();
|
||||
let mut last_log_len = 0;
|
||||
let mut last_count = 0;
|
||||
while Instant::now() < deadline {
|
||||
let (phase, error, log) = controller.status_snapshot();
|
||||
if phase.label() != last_phase {
|
||||
last_phase = phase.label().to_owned();
|
||||
eprintln!("[{last_phase}] {}", log.last().cloned().unwrap_or_default());
|
||||
eprintln!("[{last_phase}]");
|
||||
}
|
||||
if log.len() > last_log_len {
|
||||
for line in &log[last_log_len..] {
|
||||
eprintln!(" log: {line}");
|
||||
}
|
||||
last_log_len = log.len();
|
||||
}
|
||||
if let Some(error) = error {
|
||||
eprintln!("FAILED: {error}");
|
||||
@@ -120,6 +132,39 @@ fn main() {
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
|
||||
for (spec, st) in controller.script_statuses() {
|
||||
eprintln!("script {spec}: {st:?}");
|
||||
}
|
||||
let diag_pkg = controller_pkg.unwrap_or_else(|| "de.clara.sharing.app".to_owned());
|
||||
if let Some(sdk) = controller.sdk() {
|
||||
let cmd = format!(
|
||||
"PID=$(pidof {diag_pkg}); echo PID=$PID; ls -laZ /data/local/tmp/bogusmips*; cat /data/local/tmp/bogusmips-frida.log; if [ -n \"$PID\" ]; then grep -iE 'bogusmips|frida' /proc/$PID/maps; logcat -d --pid=$PID | tail -n 80; fi"
|
||||
);
|
||||
if let Ok(out) = std::process::Command::new(sdk.adb())
|
||||
.args(["shell", "su", "root", "sh", "-c", &cmd])
|
||||
.output()
|
||||
{
|
||||
eprintln!("--- process & gadget diagnostics ---\n{}", String::from_utf8_lossy(&out.stdout));
|
||||
}
|
||||
if let Ok(out) = std::process::Command::new(sdk.adb())
|
||||
.args(["logcat", "-d"])
|
||||
.output()
|
||||
{
|
||||
let text = String::from_utf8_lossy(&out.stdout);
|
||||
eprintln!("--- relevant logcat ---");
|
||||
for line in text.lines().filter(|l| {
|
||||
let low = l.to_ascii_lowercase();
|
||||
low.contains("bogusmips")
|
||||
|| low.contains("frida")
|
||||
|| low.contains("androidruntime")
|
||||
|| low.contains(&diag_pkg)
|
||||
|| low.contains("fatal")
|
||||
}) {
|
||||
eprintln!(" {line}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let flows = controller.capture().snapshot();
|
||||
let https = flows.iter().filter(|f| f.scheme == bogusmips::net::capture::Scheme::Https).count();
|
||||
eprintln!(
|
||||
|
||||
+287
-126
@@ -53,6 +53,13 @@ pub fn gadget_url(version: &str, abi: &str) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// GitHub release URL of the standalone `frida-inject` binary `.xz` for `abi`.
|
||||
pub fn inject_url(version: &str, abi: &str) -> String {
|
||||
format!(
|
||||
"https://github.com/frida/frida/releases/download/{version}/frida-inject-{version}-android-{abi}.xz"
|
||||
)
|
||||
}
|
||||
|
||||
/// Directory where downloaded gadgets are cached between sessions.
|
||||
pub fn cache_dir() -> PathBuf {
|
||||
if let Some(dir) = std::env::var_os("XDG_CACHE_HOME") {
|
||||
@@ -91,6 +98,32 @@ pub fn ensure_gadget(cache_dir: &Path, abi: &str) -> Result<PathBuf, String> {
|
||||
Ok(dest)
|
||||
}
|
||||
|
||||
/// Ensures the decompressed standalone `frida-inject` binary for `abi` exists
|
||||
/// in `cache_dir`, downloading and decompressing it on first use.
|
||||
pub fn ensure_inject(cache_dir: &Path, abi: &str) -> Result<PathBuf, String> {
|
||||
let version = frida_version();
|
||||
let dest = cache_dir.join(format!("frida-inject-{version}-android-{abi}"));
|
||||
if dest.metadata().map(|m| m.len() > 0).unwrap_or(false) {
|
||||
return Ok(dest);
|
||||
}
|
||||
std::fs::create_dir_all(cache_dir).map_err(|e| e.to_string())?;
|
||||
|
||||
let url = inject_url(&version, abi);
|
||||
let compressed = cache_dir.join(format!("frida-inject-{version}-android-{abi}.xz"));
|
||||
download(&url, &compressed)?;
|
||||
|
||||
let bytes = std::fs::read(&compressed).map_err(|e| e.to_string())?;
|
||||
let mut out = Vec::new();
|
||||
lzma_rs::xz_decompress(&mut std::io::Cursor::new(bytes), &mut out)
|
||||
.map_err(|e| format!("could not decompress frida-inject: {e}"))?;
|
||||
let _ = std::fs::remove_file(&compressed);
|
||||
if out.get(..4) != Some(ELF_MAGIC) {
|
||||
return Err("the downloaded frida-inject binary is not a valid ELF executable".into());
|
||||
}
|
||||
std::fs::write(&dest, &out).map_err(|e| e.to_string())?;
|
||||
Ok(dest)
|
||||
}
|
||||
|
||||
const ELF_MAGIC: &[u8] = b"\x7fELF";
|
||||
|
||||
/// A user-defined Frida script entry (CodeShare slug, URL, or local `.js` path)
|
||||
@@ -477,8 +510,61 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
|
||||
let mut out = String::from(
|
||||
r#"(function () {
|
||||
function safeSet(obj, prop, val) {
|
||||
if (!obj) return false;
|
||||
try {
|
||||
obj[prop] = val;
|
||||
if (obj[prop] === val) return true;
|
||||
} catch (e) {}
|
||||
try {
|
||||
Object.defineProperty(obj, prop, {
|
||||
value: val,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
return obj[prop] === val;
|
||||
} catch (e) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
function makeDelegate(target, overrides) {
|
||||
if (!target) return overrides;
|
||||
var allSucceeded = true;
|
||||
for (var k in overrides) {
|
||||
if (Object.prototype.hasOwnProperty.call(overrides, k)) {
|
||||
if (!safeSet(target, k, overrides[k])) {
|
||||
allSucceeded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allSucceeded) return target;
|
||||
if (typeof Proxy === 'function') {
|
||||
try {
|
||||
return new Proxy({}, {
|
||||
get: function (_dummy, prop) {
|
||||
if (Object.prototype.hasOwnProperty.call(overrides, prop)) {
|
||||
return overrides[prop];
|
||||
}
|
||||
var v = target[prop];
|
||||
return (typeof v === 'function') ? v.bind(target) : v;
|
||||
},
|
||||
set: function (_dummy, prop, val) {
|
||||
overrides[prop] = val;
|
||||
safeSet(target, prop, val);
|
||||
return true;
|
||||
},
|
||||
has: function (_dummy, prop) {
|
||||
return (prop in overrides) || (prop in target);
|
||||
}
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
if (typeof globalThis.__bogusmipsWrapped !== 'undefined') return;
|
||||
globalThis.__bogusmipsWrapped = true;
|
||||
safeSet(globalThis, '__bogusmipsWrapped', true);
|
||||
var origSetTimeout = globalThis.setTimeout;
|
||||
var origSetImmediate = globalThis.setImmediate;
|
||||
var FridaFile = (typeof File === 'function') ? File : null;
|
||||
@@ -537,53 +623,59 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
return parts.join(' ');
|
||||
}
|
||||
|
||||
var origConsoleLog = console.log ? console.log.bind(console) : function () {};
|
||||
var origConsoleWarn = console.warn ? console.warn.bind(console) : origConsoleLog;
|
||||
var origConsoleError = console.error ? console.error.bind(console) : origConsoleLog;
|
||||
console.log = function () {
|
||||
writeOutput(formatArgs(arguments));
|
||||
try { origConsoleLog.apply(console, arguments); } catch (e) {}
|
||||
};
|
||||
console.warn = function () {
|
||||
writeOutput(formatArgs(arguments));
|
||||
try { origConsoleWarn.apply(console, arguments); } catch (e) {}
|
||||
};
|
||||
console.error = function () {
|
||||
writeOutput(formatArgs(arguments));
|
||||
try { origConsoleError.apply(console, arguments); } catch (e) {}
|
||||
};
|
||||
if (typeof send === 'function') {
|
||||
var origSend = send;
|
||||
globalThis.send = function (message, data) {
|
||||
try {
|
||||
var text = (typeof message === 'string') ? message : JSON.stringify(message);
|
||||
writeOutput(text);
|
||||
} catch (e) {}
|
||||
var origConsoleLog = (typeof console !== 'undefined' && console.log) ? console.log.bind(console) : function () {};
|
||||
var origConsoleWarn = (typeof console !== 'undefined' && console.warn) ? console.warn.bind(console) : origConsoleLog;
|
||||
var origConsoleError = (typeof console !== 'undefined' && console.error) ? console.error.bind(console) : origConsoleLog;
|
||||
var bogusmipsConsole = makeDelegate(typeof console !== 'undefined' ? console : {}, {
|
||||
log: function () {
|
||||
writeOutput(formatArgs(arguments));
|
||||
try { origConsoleLog.apply(console, arguments); } catch (e) {}
|
||||
},
|
||||
warn: function () {
|
||||
writeOutput(formatArgs(arguments));
|
||||
try { origConsoleWarn.apply(console, arguments); } catch (e) {}
|
||||
},
|
||||
error: function () {
|
||||
writeOutput(formatArgs(arguments));
|
||||
try { origConsoleError.apply(console, arguments); } catch (e) {}
|
||||
}
|
||||
});
|
||||
var origSend = (typeof globalThis.send === 'function') ? globalThis.send : null;
|
||||
var bogusmipsSend = function (message, data) {
|
||||
try {
|
||||
var text = (typeof message === 'string') ? message : JSON.stringify(message);
|
||||
writeOutput(text);
|
||||
} catch (e) {}
|
||||
if (origSend) {
|
||||
try { return origSend(message, data); } catch (e) {}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
safeSet(globalThis, 'send', bogusmipsSend);
|
||||
|
||||
writeOutput('[bogusmips:gadget:loaded]');
|
||||
|
||||
// 2. Polyfill Frida 12 Memory.* helpers used by older CodeShare scripts
|
||||
// (e.g. fdciabdul/frida-multiple-bypass).
|
||||
var bogusmipsMemory = (typeof Memory !== 'undefined') ? Memory : {};
|
||||
if (typeof Memory !== 'undefined') {
|
||||
var memOverrides = {};
|
||||
if (typeof Memory.readCString !== 'function') {
|
||||
Memory.readCString = function (p) {
|
||||
memOverrides.readCString = function (p) {
|
||||
try { return ptr(p).readCString(); } catch (e) { return ''; }
|
||||
};
|
||||
}
|
||||
if (typeof Memory.readUtf8String !== 'function') {
|
||||
Memory.readUtf8String = function (p) {
|
||||
memOverrides.readUtf8String = function (p) {
|
||||
try { return ptr(p).readUtf8String(); } catch (e) { return ''; }
|
||||
};
|
||||
}
|
||||
if (typeof Memory.writeUtf8String !== 'function') {
|
||||
Memory.writeUtf8String = function (p, _s) {
|
||||
memOverrides.writeUtf8String = function (p, _s) {
|
||||
// Never overwrite caller string literals in-place (.rodata -> SIGSEGV).
|
||||
return p;
|
||||
};
|
||||
}
|
||||
bogusmipsMemory = makeDelegate(Memory, memOverrides);
|
||||
}
|
||||
|
||||
// 3. Guard Module.findExportByName & Interceptor.attach against hooking
|
||||
@@ -595,35 +687,41 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
'access': true, 'faccessat': true, 'stat': true, 'lstat': true, 'fstat': true,
|
||||
'__system_property_get': true, '__system_property_read': true, 'system': true
|
||||
};
|
||||
var bogusmipsModule = (typeof Module !== 'undefined') ? Module : {};
|
||||
if (origFindExport) {
|
||||
Module.findExportByName = function (moduleName, exportName) {
|
||||
var addr = origFindExport(moduleName, exportName);
|
||||
try {
|
||||
var mod = moduleName ? ('' + moduleName).toLowerCase() : '';
|
||||
var sym = '' + exportName;
|
||||
if ((mod === '' || mod.indexOf('libc.so') !== -1) && unsafeLibcSymbols[sym] && addr) {
|
||||
unsafeNativeAddrs[addr.toString()] = sym;
|
||||
}
|
||||
} catch (e) {}
|
||||
return addr;
|
||||
};
|
||||
bogusmipsModule = makeDelegate(Module, {
|
||||
findExportByName: function (moduleName, exportName) {
|
||||
var addr = origFindExport(moduleName, exportName);
|
||||
try {
|
||||
var mod = moduleName ? ('' + moduleName).toLowerCase() : '';
|
||||
var sym = '' + exportName;
|
||||
if ((mod === '' || mod.indexOf('libc.so') !== -1) && unsafeLibcSymbols[sym] && addr) {
|
||||
unsafeNativeAddrs[addr.toString()] = sym;
|
||||
}
|
||||
} catch (e) {}
|
||||
return addr;
|
||||
}
|
||||
});
|
||||
}
|
||||
var bogusmipsInterceptor = (typeof Interceptor !== 'undefined') ? Interceptor : {};
|
||||
if (typeof Interceptor !== 'undefined' && typeof Interceptor.attach === 'function') {
|
||||
var origAttach = Interceptor.attach.bind(Interceptor);
|
||||
var attachedAddrs = {};
|
||||
Interceptor.attach = function (target, callbacksOrProbe, data) {
|
||||
try {
|
||||
if (!target) return { detach: function () {} };
|
||||
var key = target.toString();
|
||||
if (key === '0x0' || key === '0' || unsafeNativeAddrs[key] || attachedAddrs[key]) {
|
||||
bogusmipsInterceptor = makeDelegate(Interceptor, {
|
||||
attach: function (target, callbacksOrProbe, data) {
|
||||
try {
|
||||
if (!target) return { detach: function () {} };
|
||||
var key = target.toString();
|
||||
if (key === '0x0' || key === '0' || unsafeNativeAddrs[key] || attachedAddrs[key]) {
|
||||
return { detach: function () {} };
|
||||
}
|
||||
attachedAddrs[key] = true;
|
||||
return origAttach(target, callbacksOrProbe, data);
|
||||
} catch (e) {
|
||||
return { detach: function () {} };
|
||||
}
|
||||
attachedAddrs[key] = true;
|
||||
return origAttach(target, callbacksOrProbe, data);
|
||||
} catch (e) {
|
||||
return { detach: function () {} };
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Do NOT touch `Java` at t = 0ms (during LD_PRELOAD, before ART is
|
||||
@@ -632,15 +730,19 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
var shimsInstalled = false;
|
||||
var origPerform = null;
|
||||
var origUse = null;
|
||||
var bogusmipsJava = null;
|
||||
var bogusmipsSetTimeout = origSetTimeout;
|
||||
var bogusmipsSetImmediate = origSetImmediate;
|
||||
var envHooked = {};
|
||||
|
||||
function applyBaselineEnvHooks() {
|
||||
if (!origUse) return;
|
||||
var useFn = (bogusmipsJava && bogusmipsJava.use) ? bogusmipsJava.use : origUse;
|
||||
function hookReturn(className, method, value) {
|
||||
var key = className + '#' + method;
|
||||
if (envHooked[key]) return;
|
||||
try {
|
||||
var cls = Java.use(className);
|
||||
var cls = useFn(className);
|
||||
if (!cls || !cls[method]) return;
|
||||
cls[method].overloads.forEach(function (ov) {
|
||||
ov.implementation = function () { return value; };
|
||||
@@ -666,6 +768,63 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
hookReturn('com.scottyab.rootbeer.RootBeerNative', 'checkForRoot', 0);
|
||||
hookReturn('com.scottyab.rootbeer.RootBeerNative', 'isLibraryLoaded', false);
|
||||
|
||||
// Spoof Google Play Store installer origin & bypass Google Play PairIP license checks.
|
||||
hookReturn('android.app.ApplicationPackageManager', 'getInstallerPackageName', 'com.android.vending');
|
||||
['getInstallingPackageName', 'getInitiatingPackageName', 'getOriginatingPackageName'].forEach(function (m) {
|
||||
hookReturn('android.content.pm.InstallSourceInfo', m, 'com.android.vending');
|
||||
});
|
||||
['initializeLicenseCheck', 'connectToLicensingService', 'startErrorDialogActivity', 'handleError'].forEach(function (m) {
|
||||
hookReturn('com.pairip.licensecheck.LicenseClient', m, undefined);
|
||||
hookReturn('com.pairip.licensecheck3.LicenseClient', m, undefined);
|
||||
});
|
||||
['com.pairip.licensecheck.LicenseActivity', 'com.pairip.licensecheck3.LicenseActivity'].forEach(function (c) {
|
||||
var key = c + '#onCreate';
|
||||
if (!envHooked[key]) {
|
||||
try {
|
||||
var actCls = origUse(c);
|
||||
if (actCls && actCls.onCreate) {
|
||||
actCls.onCreate.overloads.forEach(function (ov) {
|
||||
ov.implementation = function (b) {
|
||||
try { this.onCreate(b); } catch (e) {}
|
||||
try { this.finish(); } catch (e) {}
|
||||
};
|
||||
});
|
||||
envHooked[key] = true;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
|
||||
if (!envHooked['File.exists']) {
|
||||
try {
|
||||
var JavaFile = origUse('java.io.File');
|
||||
var blockedNames = {
|
||||
'su': true, 'busybox': true, 'supersu': true, 'daemonsu': true,
|
||||
'Superuser.apk': true, 'KingoUser.apk': true, 'SuperSu.apk': true,
|
||||
'magisk': true, 'magisk.db': true, 'magisk.img': true, 'ksud': true,
|
||||
'qemud': true, 'qemu_pipe': true
|
||||
};
|
||||
JavaFile.exists.implementation = function () {
|
||||
try {
|
||||
var name = '' + this.getName();
|
||||
if (blockedNames[name]) {
|
||||
return false;
|
||||
}
|
||||
var path = '' + this.getAbsolutePath();
|
||||
if (path.indexOf('/su') !== -1 || path.indexOf('magisk') !== -1 || path.indexOf('superuser') !== -1) {
|
||||
if (path === '/system/xbin/su' || path === '/system/bin/su' || path === '/sbin/su' ||
|
||||
path === '/vendor/bin/su' || path === '/data/local/xbin/su' || path === '/data/local/bin/su' ||
|
||||
path === '/data/local/su' || path === '/su/bin/su') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
return this.exists();
|
||||
};
|
||||
envHooked['File.exists'] = true;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!envHooked['Build.TAGS']) {
|
||||
try {
|
||||
var Build = origUse('android.os.Build');
|
||||
@@ -675,27 +834,9 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!envHooked['File.exists']) {
|
||||
try {
|
||||
var JFile = Java.use('java.io.File');
|
||||
var badNames = {
|
||||
'su': true, 'busybox': true, 'supersu': true, 'Superuser.apk': true,
|
||||
'KingoUser.apk': true, 'SuperSu.apk': true, 'magisk': true, 'daemonsu': true
|
||||
};
|
||||
JFile.exists.implementation = function () {
|
||||
try {
|
||||
var name = '' + this.getName();
|
||||
if (badNames[name]) return false;
|
||||
} catch (e) {}
|
||||
return this.exists();
|
||||
};
|
||||
envHooked['File.exists'] = true;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!envHooked['SystemProperties']) {
|
||||
try {
|
||||
var SystemProperties = Java.use('android.os.SystemProperties');
|
||||
var SystemProperties = useFn('android.os.SystemProperties');
|
||||
var safe = {
|
||||
'ro.debuggable': '0',
|
||||
'ro.secure': '1',
|
||||
@@ -726,45 +867,21 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
origUse = Java.use.bind(Java);
|
||||
try { javaLog = origUse('android.util.Log'); } catch (e) {}
|
||||
|
||||
// Allow reflection Field.set(null, val) on static final fields (e.g.
|
||||
// Build.TAGS in fdciabdul/frida-multiple-bypass) to fall back to Frida's
|
||||
// JNI field setter instead of throwing IllegalAccessException on Android 12+.
|
||||
try {
|
||||
var Field = origUse('java.lang.reflect.Field');
|
||||
Field.set.overload('java.lang.Object', 'java.lang.Object').implementation = function (obj, val) {
|
||||
try {
|
||||
return this.set(obj, val);
|
||||
} catch (e) {
|
||||
try {
|
||||
var clsName = '' + this.getDeclaringClass().getName();
|
||||
var fldName = '' + this.getName();
|
||||
var w = origUse(clsName);
|
||||
if (w && w[fldName]) w[fldName].value = val;
|
||||
} catch (e2) {}
|
||||
}
|
||||
};
|
||||
} catch (e) {}
|
||||
|
||||
// Methods that need in-place overload guarding:
|
||||
// - java.lang.String#contains is blocked completely (causes infinite
|
||||
// recursion inside frida-java-bridge).
|
||||
// - File/Runtime/ProcessBuilder/SystemProperties/PackageManager methods
|
||||
// are guarded with blockAll=false so scripts like fdciabdul/frida-multiple-bypass
|
||||
// that assign `.implementation` multiple times on the same overload
|
||||
// update the JS callback in-place without crashing ART.
|
||||
// Bootstrap methods whose ART trampolines cause infinite recursion or
|
||||
// extreme slowdowns when replaced on Android 14/15 (API 34/35).
|
||||
var blockedClassMethods = {
|
||||
'java.lang.String': ['contains']
|
||||
};
|
||||
var guardedClassMethods = {
|
||||
'java.lang.String': ['contains'],
|
||||
'java.io.File': ['exists'],
|
||||
'java.io.UnixFileSystem': ['checkAccess'],
|
||||
'java.io.BufferedReader': ['readLine'],
|
||||
'java.io.BufferedReader': ['readLine']
|
||||
};
|
||||
var guardedClassMethods = {
|
||||
'android.os.SystemProperties': ['get'],
|
||||
'android.app.ApplicationPackageManager': ['getPackageInfo', 'getInstallerPackageName'],
|
||||
'java.lang.Runtime': ['exec'],
|
||||
'java.lang.ProcessImpl': ['start'],
|
||||
'java.lang.ProcessBuilder': ['start'],
|
||||
'java.lang.ProcessManager': ['exec'],
|
||||
'android.os.SystemProperties': ['get'],
|
||||
'android.app.ApplicationPackageManager': ['getPackageInfo']
|
||||
'java.lang.ProcessManager': ['exec']
|
||||
};
|
||||
var hookedJavaOverloads = {};
|
||||
|
||||
@@ -808,17 +925,17 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
}
|
||||
if (typeof m.overload === 'function') {
|
||||
var origOverload = m.overload.bind(m);
|
||||
m.overload = function () {
|
||||
safeSet(m, 'overload', function () {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var ov = origOverload.apply(m, args);
|
||||
return guardOverload(className, methodName, ov, args.join(','), blockAll);
|
||||
};
|
||||
});
|
||||
}
|
||||
m.__bogusmipsMethodGuarded = true;
|
||||
safeSet(m, '__bogusmipsMethodGuarded', true);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
Java.use = function (className) {
|
||||
var customUse = function (className) {
|
||||
var name = '' + className;
|
||||
var cls = origUse(name);
|
||||
if (!cls) return cls;
|
||||
@@ -837,35 +954,52 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
return cls;
|
||||
};
|
||||
|
||||
Java.enumerateLoadedClassesSync = function () {
|
||||
var customEnumerate = function () {
|
||||
return [];
|
||||
};
|
||||
|
||||
Java.perform = function (fn) {
|
||||
var customPerform = function (fn) {
|
||||
try {
|
||||
origPerform(function () {
|
||||
try {
|
||||
fn();
|
||||
} catch (innerErr) {
|
||||
console.log('[frida] ' + innerErr);
|
||||
bogusmipsConsole.log('[frida] ' + innerErr);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('[frida] ' + e);
|
||||
bogusmipsConsole.log('[frida] ' + e);
|
||||
}
|
||||
};
|
||||
|
||||
globalThis.setTimeout = function (cb, delay) {
|
||||
bogusmipsJava = makeDelegate(Java, {
|
||||
use: customUse,
|
||||
enumerateLoadedClassesSync: customEnumerate,
|
||||
perform: customPerform
|
||||
});
|
||||
|
||||
bogusmipsSetTimeout = function (cb, delay) {
|
||||
var d = (typeof delay === 'number' && delay > 0) ? delay : 0;
|
||||
if (d === 0) {
|
||||
if (typeof cb === 'function') {
|
||||
customPerform(cb);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return origSetTimeout(function () {
|
||||
if (typeof cb !== 'function') return;
|
||||
Java.perform(cb);
|
||||
customPerform(cb);
|
||||
}, d);
|
||||
};
|
||||
safeSet(globalThis, 'setTimeout', bogusmipsSetTimeout);
|
||||
if (typeof origSetImmediate === 'function') {
|
||||
globalThis.setImmediate = function (cb) {
|
||||
return globalThis.setTimeout(cb, 0);
|
||||
bogusmipsSetImmediate = function (cb) {
|
||||
if (typeof cb === 'function') {
|
||||
customPerform(cb);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
safeSet(globalThis, 'setImmediate', bogusmipsSetImmediate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,11 +1009,27 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
|
||||
for (spec, source) in user_scripts {
|
||||
let label = spec.replace('\\', "\\\\").replace('\'', "\\'");
|
||||
// Guard unguarded reflection `.set(null, ...)` on static final Build
|
||||
// fields and recursive `ProcessImpl.start.apply` in older CodeShare
|
||||
// scripts (e.g. fdciabdul/frida-multiple-bypass).
|
||||
let patched = source
|
||||
.replace(
|
||||
"TAGS.set(null, \"release-keys\");",
|
||||
"try { TAGS.set(null, \"release-keys\"); } catch (_e) {}",
|
||||
)
|
||||
.replace(
|
||||
"FINGERPRINT.set(null,",
|
||||
"if (false) FINGERPRINT.set(null,",
|
||||
)
|
||||
.replace(
|
||||
"ProcessImpl.start.apply(this, arguments)",
|
||||
"this.start.apply(this, arguments)",
|
||||
);
|
||||
out.push_str(&format!(
|
||||
r#" {{
|
||||
spec: '{label}',
|
||||
run: function () {{
|
||||
{source}
|
||||
run: function (send, console, setTimeout, setImmediate, Memory, Module, Interceptor, Java) {{
|
||||
{patched}
|
||||
}}
|
||||
}},
|
||||
"#
|
||||
@@ -889,7 +1039,7 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
out.push_str(
|
||||
r#" ];
|
||||
|
||||
var delays = [1000, 2000, 3500, 5500, 8000];
|
||||
var delays = [0, 200, 800, 1800, 3500, 6000];
|
||||
var attempt = 0;
|
||||
function bootAfterArt() {
|
||||
try {
|
||||
@@ -898,21 +1048,32 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
applyBaselineEnvHooks();
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
var s = scripts[i];
|
||||
console.log('[bogusmips:script:start] ' + s.spec);
|
||||
bogusmipsConsole.log('[bogusmips:script:start] ' + s.spec);
|
||||
try {
|
||||
s.run();
|
||||
console.log('[bogusmips:script:ok] ' + s.spec);
|
||||
s.run(
|
||||
bogusmipsSend,
|
||||
bogusmipsConsole,
|
||||
bogusmipsSetTimeout,
|
||||
bogusmipsSetImmediate,
|
||||
bogusmipsMemory,
|
||||
bogusmipsModule,
|
||||
bogusmipsInterceptor,
|
||||
bogusmipsJava
|
||||
);
|
||||
bogusmipsConsole.log('[bogusmips:script:ok] ' + s.spec);
|
||||
} catch (scriptErr) {
|
||||
console.log('[bogusmips:script:error] ' + s.spec + ' | ' + scriptErr);
|
||||
bogusmipsConsole.log('[bogusmips:script:error] ' + s.spec + ' | ' + scriptErr);
|
||||
}
|
||||
}
|
||||
// Re-apply baseline hooks for classes (e.g. RootBeer) loaded lazily.
|
||||
origSetTimeout(function () {
|
||||
try { origPerform(applyBaselineEnvHooks); } catch (e) {}
|
||||
}, 1500);
|
||||
}, 500);
|
||||
origSetTimeout(function () {
|
||||
try { origPerform(applyBaselineEnvHooks); } catch (e) {}
|
||||
}, 3500);
|
||||
}, 1800);
|
||||
origSetTimeout(function () {
|
||||
try { origPerform(applyBaselineEnvHooks); } catch (e) {}
|
||||
}, 3800);
|
||||
});
|
||||
} catch (artErr) {
|
||||
attempt += 1;
|
||||
@@ -920,12 +1081,12 @@ pub fn build_gadget_script(user_scripts: &[(String, String)]) -> String {
|
||||
origSetTimeout(bootAfterArt, delays[attempt] - delays[attempt - 1]);
|
||||
} else {
|
||||
for (var j = 0; j < scripts.length; j++) {
|
||||
console.log('[bogusmips:script:error] ' + scripts[j].spec + ' | ' + artErr);
|
||||
bogusmipsConsole.log('[bogusmips:script:error] ' + scripts[j].spec + ' | ' + artErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
origSetTimeout(bootAfterArt, delays[0]);
|
||||
bootAfterArt();
|
||||
})();
|
||||
"#,
|
||||
);
|
||||
|
||||
+29
-2
@@ -144,6 +144,25 @@ impl Status {
|
||||
self.log(format!("✖ Frida script error ({spec}): {err}"));
|
||||
return;
|
||||
}
|
||||
if trimmed.starts_with('[') && trimmed.starts_with("[bogusmips:") {
|
||||
self.log(format!("[frida] {trimmed}"));
|
||||
return;
|
||||
}
|
||||
if trimmed.starts_with("{\"type\":\"error\"") {
|
||||
let err = trimmed
|
||||
.split("\"description\":\"")
|
||||
.nth(1)
|
||||
.and_then(|s| s.split('"').next())
|
||||
.unwrap_or(trimmed)
|
||||
.to_owned();
|
||||
for (_, state) in &mut self.scripts {
|
||||
if !matches!(state, ScriptState::Active { .. }) {
|
||||
*state = ScriptState::Error(err.clone());
|
||||
}
|
||||
}
|
||||
self.log(format!("✖ Frida runtime error: {err}"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Any output emitted by the script in the target process confirms the
|
||||
// script is running and increments its message counter.
|
||||
@@ -382,7 +401,8 @@ fn run_session(
|
||||
status
|
||||
.lock()
|
||||
.unwrap()
|
||||
.log("Downloading the frida gadget…".to_owned());
|
||||
.log("Preparing Frida runtime (frida-inject + gadget)…".to_owned());
|
||||
let inject = bypass::ensure_inject(&cache, frida_abi)?;
|
||||
let gadget = bypass::ensure_gadget(&cache, frida_abi)?;
|
||||
let mut loaded = Vec::with_capacity(config.frida_scripts.len());
|
||||
for spec in &config.frida_scripts {
|
||||
@@ -405,7 +425,7 @@ fn run_session(
|
||||
}
|
||||
let combined = bypass::build_gadget_script(&loaded);
|
||||
emulator.clear_logcat();
|
||||
let res = emulator.install_frida_script(&gadget, package, &combined)?;
|
||||
let res = emulator.install_frida_script(&inject, &gadget, package, &combined)?;
|
||||
{
|
||||
let mut s = status.lock().unwrap();
|
||||
for (canonical, _) in &loaded {
|
||||
@@ -418,6 +438,13 @@ fn run_session(
|
||||
step!(Phase::InstallingBypass, &label, install_frida());
|
||||
}
|
||||
step!(Phase::Launching, "Launching the app", emulator.launch(package));
|
||||
if needs_frida {
|
||||
step!(
|
||||
Phase::InstallingBypass,
|
||||
"Attaching Frida runtime to app process",
|
||||
emulator.attach_frida_script(package)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
set_phase(Phase::Capturing, "Capturing traffic");
|
||||
|
||||
+131
-48
@@ -308,15 +308,32 @@ impl Emulator {
|
||||
|
||||
/// Switches adbd to root and remounts the system partition.
|
||||
pub fn enable_root(&self) -> Step {
|
||||
let out = self.adb(&["root"])?;
|
||||
if out.to_lowercase().contains("cannot run as root") {
|
||||
return Err("this emulator image cannot be rooted (use a non-Play-Store image)".into());
|
||||
}
|
||||
// adbd restarts; wait for it to come back.
|
||||
std::thread::sleep(Duration::from_secs(2));
|
||||
let _ = self.adb(&["wait-for-device"]);
|
||||
let _ = self.adb(&["remount"]);
|
||||
Ok("root enabled".into())
|
||||
let mut last_err = String::new();
|
||||
for attempt in 0..3 {
|
||||
match self.adb(&["root"]) {
|
||||
Ok(out) => {
|
||||
if out.to_lowercase().contains("cannot run as root") {
|
||||
return Err(
|
||||
"this emulator image cannot be rooted (use a non-Play-Store image)"
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
std::thread::sleep(Duration::from_secs(2));
|
||||
let _ = self.adb(&["wait-for-device"]);
|
||||
let _ = self.adb(&["remount"]);
|
||||
return Ok("root enabled".into());
|
||||
}
|
||||
Err(e) => {
|
||||
last_err = e;
|
||||
if attempt + 1 < 3 {
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
let _ = self.adb(&["wait-for-device"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(last_err)
|
||||
}
|
||||
|
||||
/// Pushes the CA (PEM) and installs it as a system trust anchor.
|
||||
@@ -356,7 +373,13 @@ impl Emulator {
|
||||
} else {
|
||||
args.push("install".into());
|
||||
}
|
||||
args.extend(["-r".into(), "-t".into(), "-g".into()]);
|
||||
args.extend([
|
||||
"-r".into(),
|
||||
"-t".into(),
|
||||
"-g".into(),
|
||||
"-i".into(),
|
||||
"com.android.vending".into(),
|
||||
]);
|
||||
for apk in apks {
|
||||
args.push(apk.to_string_lossy().into_owned());
|
||||
}
|
||||
@@ -403,41 +426,56 @@ impl Emulator {
|
||||
.map(|s| s.trim().to_owned())
|
||||
}
|
||||
|
||||
/// Pushes `gadget_local`, its config, and the assembled `script_js` for
|
||||
/// the user-selected Frida scripts, then sets `wrap.<package>` so
|
||||
/// `frida-gadget` is `LD_PRELOAD`ed on app launch.
|
||||
/// Pushes `inject_local` (`frida-inject`), `gadget_local` (`frida-gadget`),
|
||||
/// and the assembled `script_js` to the device, prepares the device
|
||||
/// environment, and arms a background injector for `package`.
|
||||
pub fn install_frida_script(
|
||||
&self,
|
||||
inject_local: &Path,
|
||||
gadget_local: &Path,
|
||||
package: &str,
|
||||
script_js: &str,
|
||||
) -> Step {
|
||||
const INJECT: &str = "/data/local/tmp/bogusmips-frida-inject";
|
||||
const GADGET: &str = "/data/local/tmp/bogusmips-gadget.so";
|
||||
const SCRIPT: &str = "/data/local/tmp/bogusmips-bypass.js";
|
||||
// frida accepts either form; push both so it is found regardless.
|
||||
const CONFIG: &str = "/data/local/tmp/bogusmips-gadget.config";
|
||||
const CONFIG_SO: &str = "/data/local/tmp/bogusmips-gadget.config.so";
|
||||
const ARM_SCRIPT: &str = "/data/local/tmp/bogusmips_frida_arm.sh";
|
||||
const ATTACH_SCRIPT: &str = "/data/local/tmp/bogusmips_frida_attach.sh";
|
||||
|
||||
let local = gadget_local.to_string_lossy();
|
||||
self.adb(&["push", &local, GADGET])?;
|
||||
let inject_str = inject_local.to_string_lossy();
|
||||
self.adb(&["push", &inject_str, INJECT])?;
|
||||
let gadget_str = gadget_local.to_string_lossy();
|
||||
self.adb(&["push", &gadget_str, GADGET])?;
|
||||
self.push_bytes(script_js.as_bytes(), SCRIPT)?;
|
||||
let cfg = bypass::gadget_config(SCRIPT);
|
||||
self.push_bytes(cfg.as_bytes(), CONFIG)?;
|
||||
self.push_bytes(cfg.as_bytes(), CONFIG_SO)?;
|
||||
|
||||
let attach_sh = frida_attach_script(package);
|
||||
self.push_bytes(attach_sh.as_bytes(), ATTACH_SCRIPT)?;
|
||||
let arm_sh = frida_arm_script(package);
|
||||
self.push_bytes(arm_sh.as_bytes(), ARM_SCRIPT)?;
|
||||
let out = self
|
||||
.adb(&["shell", "su", "root", "sh", ARM_SCRIPT])
|
||||
.or_else(|_| self.adb(&["shell", "sh", ARM_SCRIPT]))?;
|
||||
if out.contains("ARMED") {
|
||||
Ok("Frida gadget armed".into())
|
||||
Ok("Frida runtime armed".into())
|
||||
} else {
|
||||
Err(format!("could not arm Frida gadget: {out}"))
|
||||
Err(format!("could not arm Frida runtime: {out}"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Attaches `frida-inject` to the running `package` process immediately
|
||||
/// after launch so scripts execute in the live ART runtime.
|
||||
pub fn attach_frida_script(&self, package: &str) -> Step {
|
||||
const ATTACH_SCRIPT: &str = "/data/local/tmp/bogusmips_frida_attach.sh";
|
||||
let _ = package;
|
||||
self.adb(&["shell", "su", "root", "sh", ATTACH_SCRIPT])
|
||||
.or_else(|_| self.adb(&["shell", "sh", ATTACH_SCRIPT]))
|
||||
}
|
||||
|
||||
/// Clears the device's logcat ring buffer and on-device Frida telemetry log
|
||||
/// before launching the instrumented app so subsequent
|
||||
/// [`Self::frida_log_lines`] calls only return fresh output.
|
||||
@@ -472,10 +510,9 @@ impl Emulator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Shell script executed as `root` on the device to label `/data/local/tmp`
|
||||
/// and the Frida gadget files so `untrusted_app` can traverse `/data/local/tmp`,
|
||||
/// map `bogusmips-gadget.so`, read its config/script, and append telemetry to
|
||||
/// [`bypass::FRIDA_DEVICE_LOG_PATH`].
|
||||
/// Shell script executed as `root` on the device to set up the Frida runtime
|
||||
/// environment (`/data/local/tmp/bogusmips-frida-inject`, gadget, script, and
|
||||
/// telemetry log) and hide `/system/xbin/su` from app mount namespaces.
|
||||
pub fn frida_arm_script(package: &str) -> String {
|
||||
let prop = format!("wrap.{package}");
|
||||
let prop_short = if prop.len() > 31 {
|
||||
@@ -484,44 +521,88 @@ pub fn frida_arm_script(package: &str) -> String {
|
||||
None
|
||||
};
|
||||
let short_cmd = prop_short
|
||||
.map(|s| format!("setprop \"{s}\" \"LD_PRELOAD=/data/local/tmp/bogusmips-gadget.so\" 2>/dev/null\n"))
|
||||
.map(|s| format!("setprop \"{s}\" \"\" 2>/dev/null\n"))
|
||||
.unwrap_or_default();
|
||||
let log_path = bypass::FRIDA_DEVICE_LOG_PATH;
|
||||
format!(
|
||||
r#"GADGET=/data/local/tmp/bogusmips-gadget.so
|
||||
r#"INJECT=/data/local/tmp/bogusmips-frida-inject
|
||||
GADGET=/data/local/tmp/bogusmips-gadget.so
|
||||
SCRIPT=/data/local/tmp/bogusmips-bypass.js
|
||||
CONFIG=/data/local/tmp/bogusmips-gadget.config
|
||||
CONFIG_SO=/data/local/tmp/bogusmips-gadget.config.so
|
||||
LOG={log_path}
|
||||
EMPTY_XBIN=/data/local/tmp/bogusmips-empty-xbin
|
||||
|
||||
killall -9 bogusmips-frida-inject 2>/dev/null
|
||||
: > "$LOG"
|
||||
chmod 777 /data/local/tmp
|
||||
chmod 755 "$GADGET"
|
||||
chmod 755 "$INJECT" "$GADGET"
|
||||
chmod 644 "$SCRIPT" "$CONFIG" "$CONFIG_SO"
|
||||
chmod 666 "$LOG"
|
||||
# On Android 14/15, untrusted_app is denied `search` on shell_data_file
|
||||
# directories; relabel /data/local/tmp to system_data_file, the gadget
|
||||
# payloads to system_file, and the telemetry log to apk_data_file.
|
||||
chcon u:object_r:system_data_file:s0 /data/local/tmp 2>/dev/null
|
||||
chcon u:object_r:system_file:s0 "$GADGET" "$SCRIPT" "$CONFIG" "$CONFIG_SO" 2>/dev/null
|
||||
chcon u:object_r:system_file:s0 "$INJECT" "$GADGET" "$SCRIPT" "$CONFIG" "$CONFIG_SO" 2>/dev/null
|
||||
chcon u:object_r:apk_data_file:s0 "$LOG" 2>/dev/null
|
||||
setenforce 0 2>/dev/null
|
||||
|
||||
# Prevent unspecialized app process (USAP) pool from bypassing wrap.<package>.
|
||||
setprop persist.device_config.runtime_native.usap_pool_enabled false 2>/dev/null
|
||||
setprop persist.sys.usap_pool_enabled false 2>/dev/null
|
||||
# Hide /system/xbin/su inside zygote and app mount namespaces while keeping
|
||||
# /proc/1/ns/mnt (init + adbd) untouched so `adb shell su root` keeps working.
|
||||
mkdir -p -m 755 "$EMPTY_XBIN"
|
||||
chown root:root "$EMPTY_XBIN" 2>/dev/null
|
||||
chcon u:object_r:system_file:s0 "$EMPTY_XBIN" 2>/dev/null
|
||||
INIT_NS=$(readlink /proc/1/ns/mnt 2>/dev/null)
|
||||
SEEN=" $INIT_NS "
|
||||
for P in /proc/[0-9]*; do
|
||||
NS=$(readlink "$P/ns/mnt" 2>/dev/null)
|
||||
[ -z "$NS" ] && continue
|
||||
case "$SEEN" in *" $NS "*) continue;; esac
|
||||
SEEN="$SEEN$NS "
|
||||
nsenter -t "${{P#/proc/}}" -m -- mount --bind "$EMPTY_XBIN" /system/xbin 2>/dev/null
|
||||
done
|
||||
|
||||
# Hide /system/xbin/su from untrusted_app (other) while keeping root:shell
|
||||
# access intact for `adb shell su root`.
|
||||
chown root:shell /system/xbin 2>/dev/null
|
||||
chmod 750 /system/xbin 2>/dev/null
|
||||
|
||||
setprop "{prop}" "LD_PRELOAD=$GADGET"
|
||||
{short_cmd}am force-stop "{package}" 2>/dev/null
|
||||
# Clear any wrap.<package> property so the app forks cleanly from Zygote
|
||||
# instead of entering WrapperInit (which triggers seccomp SIGSYS in idmap2).
|
||||
setprop "{prop}" "" 2>/dev/null
|
||||
{short_cmd}pm set-installer "{package}" com.android.vending 2>/dev/null
|
||||
am force-stop "{package}" 2>/dev/null
|
||||
echo ARMED"#
|
||||
)
|
||||
}
|
||||
|
||||
/// Shell script executed as `root` right after launching `package` to attach
|
||||
/// `frida-inject` to the running app process and stream script output to
|
||||
/// [`bypass::FRIDA_DEVICE_LOG_PATH`].
|
||||
pub fn frida_attach_script(package: &str) -> String {
|
||||
let log_path = bypass::FRIDA_DEVICE_LOG_PATH;
|
||||
format!(
|
||||
r#"INJECT=/data/local/tmp/bogusmips-frida-inject
|
||||
SCRIPT=/data/local/tmp/bogusmips-bypass.js
|
||||
LOG={log_path}
|
||||
PKG="{package}"
|
||||
|
||||
PID=""
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30; do
|
||||
PID=$(pidof "$PKG" 2>/dev/null)
|
||||
for P in $PID; do
|
||||
PID="$P"
|
||||
break
|
||||
done
|
||||
if [ -n "$PID" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
if [ -z "$PID" ]; then
|
||||
echo "[bogusmips:script:error] $PKG | target process did not start" >> "$LOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
setenforce 0 2>/dev/null
|
||||
"$INJECT" -p "$PID" -s "$SCRIPT" --eternalize >> "$LOG" 2>&1
|
||||
RC=$?
|
||||
echo "ATTACHED:$PID:RC=$RC""#
|
||||
)
|
||||
}
|
||||
|
||||
impl Drop for Emulator {
|
||||
fn drop(&mut self) {
|
||||
self.clear_proxy();
|
||||
@@ -619,15 +700,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frida_arm_script_labels_tmp_dir_and_sets_wrap_prop() {
|
||||
let script = frida_arm_script("de.clara.sharing.app");
|
||||
assert!(script.contains("chcon u:object_r:system_data_file:s0 /data/local/tmp"));
|
||||
assert!(script.contains("chcon u:object_r:system_file:s0"));
|
||||
assert!(script.contains("setenforce 0"));
|
||||
assert!(script.contains("usap_pool_enabled false"));
|
||||
assert!(script.contains("chmod 750 /system/xbin"));
|
||||
assert!(script.contains("setprop \"wrap.de.clara.sharing.app\""));
|
||||
assert!(script.contains("echo ARMED"));
|
||||
fn frida_arm_and_attach_scripts_setup_runtime_and_hide_su() {
|
||||
let arm = frida_arm_script("de.clara.sharing.app");
|
||||
assert!(arm.contains("chcon u:object_r:system_data_file:s0 /data/local/tmp"));
|
||||
assert!(arm.contains("chcon u:object_r:system_file:s0"));
|
||||
assert!(arm.contains("mount --bind \"$EMPTY_XBIN\" /system/xbin"));
|
||||
assert!(arm.contains("setprop \"wrap.de.clara.sharing.app\" \"\""));
|
||||
assert!(arm.contains("echo ARMED"));
|
||||
|
||||
let attach = frida_attach_script("de.clara.sharing.app");
|
||||
assert!(attach.contains("bogusmips-frida-inject"));
|
||||
assert!(attach.contains("ATTACHED:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user