Android Malware Forensic Script
4 views
e440625c...
Description
Android malware hides actions like data theft, SMS interception, shell commands, and camera misuse. Our Frida-based script hooks sensitive APIs and decodes parameters at runtime, revealing clear high-level behaviors. This helps forensic analysts accurately reconstruct the malware’s true impact.
How to Use
Download the script and run it with Frida CLI:
Download ScriptThen run with Frida:
frida -U -f YOUR_PACKAGE_NAME -l android-malware-forensic-script.js
Replace YOUR_PACKAGE_NAME with the target app's package name.
Source Code
JavaScript
'use strict';
/*
* Frida: Framework-level exfil + shell + SMS + network hooks (Android 7–14)
* KEPT: Shell invoke, SmsManager.sendTextMessage
* ADDED: File staging, compression, Base64, ContentResolver queries/streams, Socket connects
* Output: Human-readable text lines (no JSON)
*/
function nowIso() { return new Date().toISOString(); }
function jStack() {
try {
const Log = Java.use('android.util.Log');
const Exception = Java.use('java.lang.Exception');
return Log.getStackTraceString(Exception.$new());
} catch (e) { return '(stack unavailable: ' + e + ')'; }
}
function jStr(x) {
try { return (x === null || x === undefined) ? null : x.toString(); }
catch (_) { return '<toString() failed>'; }
}
function jHost(addrObj) {
try {
if (!addrObj) return null;
const s = addrObj.toString();
// Typical forms: /192.168.1.5 or hostname/1.2.3.4
if (s.indexOf('/') >= 0) return s.split('/').pop();
return s;
} catch (_) { return null; }
}
// Track the most recent outbound socket context to annotate later events
const lastNet = {
localIp: null,
localPort: null,
remoteIp: null,
remotePort: null,
ts: null
};
function setLastNetFromSocket(sock) {
try {
const laddr = sock.getLocalAddress();
const raddr = sock.getInetAddress();
lastNet.localIp = jHost(laddr);
lastNet.localPort = sock.getLocalPort();
lastNet.remoteIp = jHost(raddr);
lastNet.remotePort = sock.getPort();
lastNet.ts = nowIso();
} catch (_) {}
}
function sessionSuffix() {
if (lastNet.remoteIp && lastNet.localIp) {
return ` [session local ${lastNet.localIp}:${lastNet.localPort} → remote ${lastNet.remoteIp}:${lastNet.remotePort}]`;
}
return '';
}
function logLine(s) { try { console.log(s); } catch (_) {} }
function logEvent(type, payload) {
const ts = nowIso();
const p = payload || {};
let msg = '';
switch (type) {
// === Shell ===
case 'shell.exec':
msg = `[${ts}] Executed shell command: ${p.cmd}` + sessionSuffix();
break;
case 'shell.processbuilder.start':
msg = `[${ts}] ProcessBuilder start with command: ${Array.isArray(p.cmd) ? p.cmd.join(' ') : p.cmd}` + sessionSuffix();
break;
// === SMS ===
case 'sms.sendTextMessage':
msg = `[${ts}] Sent SMS \"${p.text}\" to ${p.dest}`;
break;
// === File I/O ===
case 'file.fos.open':
msg = `[${ts}] Opened file for write: ${p.path} (append=${p.append})` + sessionSuffix();
break;
case 'file.fos.write':
msg = `[${ts}] Wrote ${p.len} bytes to ${p.path}` + sessionSuffix();
break;
case 'file.fis.open':
msg = `[${ts}] Opened file for read: ${p.path}` + sessionSuffix();
break;
case 'file.fis.read':
msg = `[${ts}] Read ${p.len} bytes from ${p.path}` + sessionSuffix();
break;
// === Compression / Archiving ===
case 'zip.putNextEntry':
msg = `[${ts}] Adding file to ZIP: ${p.entry}` + sessionSuffix();
break;
case 'zip.closeEntry':
msg = `[${ts}] Closed ZIP entry: ${p.entry}` + sessionSuffix();
break;
case 'zip.write':
msg = `[${ts}] Wrote ${p.len} bytes into ZIP entry ${p.entry}` + sessionSuffix();
break;
case 'gzip.init':
msg = `[${ts}] Started GZIP compression` + sessionSuffix();
break;
// === Base64 ===
case 'b64.encodeToString':
msg = `[${ts}] Encoded data to Base64 (input ${p.inLen} bytes → output ${p.outLen})` + sessionSuffix();
break;
case 'b64.encode':
msg = `[${ts}] Encoded data to Base64 (input ${p.inLen} bytes → output ${p.outLen})` + sessionSuffix();
break;
// === ContentResolver ===
case 'content.query.sensitive':
msg = `[${ts}] Queried sensitive content: ${p.uri}` + sessionSuffix();
break;
case 'content.openInputStream':
msg = `[${ts}] Opened input stream on ${p.uri}` + sessionSuffix();
break;
case 'content.openOutputStream':
msg = `[${ts}] Opened output stream on ${p.uri} (mode=${p.mode})` + sessionSuffix();
break;
// === Network ===
case 'net.connect': {
const li = p.localIp, lp = p.localPort, ri = p.remoteIp, rp = p.remotePort;
msg = `[${ts}] Socket connect: local ${li}:${lp} → remote ${ri}:${rp}`;
// Heuristic label for common Meterpreter ports
const mports = { 4444: true, 5555: true, 6666: true, 8080: true };
if (ri && rp && mports[rp]) msg += ' (possible Meterpreter)';
break;
}
// === Misc ===
case 'hook.error':
msg = `[${ts}] Hook error in ${p.where}: ${p.error}`;
break;
case 'hook.warn':
msg = `[${ts}] Hook warning in ${p.where}: ${p.warn}`;
break;
case 'frida.ready':
msg = `[${ts}] Hooks armed: ${p.message}`;
break;
default:
msg = `[${ts}] ${type}` + (p && Object.keys(p).length ? ' ' + jStr(p) : '');
}
logLine(msg);
}
Java.perform(function () {
// ===================== Shell invoking (kept) =====================
try {
const Runtime = Java.use('java.lang.Runtime');
Runtime.exec.overload('java.lang.String').implementation = function (cmd) {
logEvent('shell.exec', { cmd: jStr(cmd) });
return this.exec(cmd);
};
Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;').implementation = function (cmd, envp) {
logEvent('shell.exec', { cmd: jStr(cmd) });
return this.exec(cmd, envp);
};
Runtime.exec.overload('[Ljava.lang.String;').implementation = function (cmds) {
logEvent('shell.exec', { cmd: jStr(cmds) });
return this.exec(cmds);
};
Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;').implementation = function (cmds, envp) {
logEvent('shell.exec', { cmd: jStr(cmds) });
return this.exec(cmds, envp);
};
Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;', 'java.io.File').implementation = function (cmd, envp, dir) {
logEvent('shell.exec', { cmd: jStr(cmd) });
return this.exec(cmd, envp, dir);
};
Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File').implementation = function (cmds, envp, dir) {
logEvent('shell.exec', { cmd: jStr(cmds) });
return this.exec(cmds, envp, dir);
};
const ProcessBuilder = Java.use('java.lang.ProcessBuilder');
ProcessBuilder.start.implementation = function () {
try {
const list = this.command();
const arr = [];
for (let i = 0; i < list.size(); i++) arr.push(jStr(list.get(i)));
logEvent('shell.processbuilder.start', { cmd: arr });
} catch (_) { logEvent('shell.processbuilder.start', {}); }
return this.start();
};
} catch (e) { logEvent('hook.error', { where: 'shell', error: String(e) }); }
// ===================== SMS send (kept) =====================
try {
const SmsManager = Java.use('android.telephony.SmsManager');
SmsManager.sendTextMessage.overload(
'java.lang.String', 'java.lang.String', 'java.lang.String',
'android.app.PendingIntent', 'android.app.PendingIntent'
).implementation = function (dest, scAddr, text, sentPI, deliveryPI) {
logEvent('sms.sendTextMessage', {
dest: jStr(dest),
scAddr: jStr(scAddr),
text: jStr(text)
});
return this.sendTextMessage(dest, scAddr, text, sentPI, deliveryPI);
};
} catch (e) { logEvent('hook.warn', { where: 'smsmanager', warn: String(e) }); }
// ===================== Exfil signals (added) =====================
// ---- File staging: track per-stream path + read/write sizes ----
const fosPath = new Map(); // key: this.$h => path
const fisPath = new Map();
try {
const FileOutputStream = Java.use('java.io.FileOutputStream');
// Constructors
FileOutputStream.$init.overload('java.lang.String').implementation = function (path) {
const ret = this.$init(path);
fosPath.set(this.$h, jStr(path));
logEvent('file.fos.open', { path: jStr(path), append: false });
return ret;
};
FileOutputStream.$init.overload('java.lang.String', 'boolean').implementation = function (path, append) {
const ret = this.$init(path, append);
fosPath.set(this.$h, jStr(path));
logEvent('file.fos.open', { path: jStr(path), append: !!append });
return ret;
};
FileOutputStream.$init.overload('java.io.File').implementation = function (file) {
const ret = this.$init(file);
const p = jStr(file);
fosPath.set(this.$h, p);
logEvent('file.fos.open', { path: p, append: false });
return ret;
};
FileOutputStream.$init.overload('java.io.File', 'boolean').implementation = function (file, append) {
const ret = this.$init(file, append);
const p = jStr(file);
fosPath.set(this.$h, p);
logEvent('file.fos.open', { path: p, append: !!append });
return ret;
};
// Writes
FileOutputStream.write.overload('[B').implementation = function (b) {
const path = fosPath.get(this.$h) || null;
const len = b ? b.length : -1;
logEvent('file.fos.write', { path, len });
return this.write(b);
};
FileOutputStream.write.overload('[B', 'int', 'int').implementation = function (b, off, len) {
const path = fosPath.get(this.$h) || null;
logEvent('file.fos.write', { path, len: len, off: off });
return this.write(b, off, len);
};
FileOutputStream.write.overload('int').implementation = function (one) {
const path = fosPath.get(this.$h) || null;
logEvent('file.fos.write', { path, len: 1 });
return this.write(one);
};
} catch (e) { logEvent('hook.warn', { where: 'fos', warn: String(e) }); }
try {
const FileInputStream = Java.use('java.io.FileInputStream');
// Constructors
FileInputStream.$init.overload('java.lang.String').implementation = function (path) {
const ret = this.$init(path);
fisPath.set(this.$h, jStr(path));
logEvent('file.fis.open', { path: jStr(path) });
return ret;
};
FileInputStream.$init.overload('java.io.File').implementation = function (file) {
const ret = this.$init(file);
const p = jStr(file);
fisPath.set(this.$h, p);
logEvent('file.fis.open', { path: p });
return ret;
};
// Reads
FileInputStream.read.overload('[B').implementation = function (b) {
const bytes = this.read(b);
const path = fisPath.get(this.$h) || null;
logEvent('file.fis.read', { path, len: bytes });
return bytes;
};
FileInputStream.read.overload('[B', 'int', 'int').implementation = function (b, off, len) {
const n = this.read(b, off, len);
const path = fisPath.get(this.$h) || null;
logEvent('file.fis.read', { path, len: n, off: off, req: len });
return n;
};
FileInputStream.read.overload().implementation = function () {
const n = this.read();
const path = fisPath.get(this.$h) || null;
logEvent('file.fis.read', { path, len: (n >= 0 ? 1 : n) });
return n;
};
} catch (e) { logEvent('hook.warn', { where: 'fis', warn: String(e) }); }
// ---- Compression / Archiving (common before exfil) ----
try {
const ZipOutputStream = Java.use('java.util.zip.ZipOutputStream');
const zipNames = new Map(); // ZipOutputStream -> current entry
ZipOutputStream.putNextEntry.implementation = function (entry) {
const name = entry ? jStr(entry.getName()) : null;
zipNames.set(this.$h, name);
logEvent('zip.putNextEntry', { entry: name });
return this.putNextEntry(entry);
};
ZipOutputStream.closeEntry.implementation = function () {
const name = zipNames.get(this.$h) || null;
logEvent('zip.closeEntry', { entry: name });
return this.closeEntry();
};
ZipOutputStream.write.overload('[B', 'int', 'int').implementation = function (b, off, len) {
const name = zipNames.get(this.$h) || null;
logEvent('zip.write', { entry: name, len: len });
return this.write(b, off, len);
};
} catch (e) { logEvent('hook.warn', { where: 'zip', warn: String(e) }); }
try {
const GZIPOutputStream = Java.use('java.util.zip.GZIPOutputStream');
GZIPOutputStream.$init.overload('java.io.OutputStream').implementation = function (os) {
const ret = this.$init(os);
logEvent('gzip.init', {});
return ret;
};
} catch (e) { /* optional */ }
// ---- Base64 (often used before network/SMS exfil) ----
try {
const Base64 = Java.use('android.util.Base64');
Base64.encodeToString.overload('[B', 'int').implementation = function (b, flags) {
const s = this.encodeToString(b, flags);
logEvent('b64.encodeToString', { inLen: (b ? b.length : -1), outLen: (s ? s.length : -1), flags });
return s;
};
Base64.encode.overload('[B', 'int').implementation = function (b, flags) {
const out = this.encode(b, flags);
logEvent('b64.encode', { inLen: (b ? b.length : -1), outLen: (out ? out.length : -1), flags });
return out;
};
} catch (e) { /* optional */ }
// ---- ContentResolver: queries to sensitive stores + streams ----
try {
const ContentResolver = Java.use('android.content.ContentResolver');
function trackIfSensitive(uriStr) {
if (!uriStr) return false;
return uriStr.startsWith('content://sms') ||
uriStr.startsWith('content://call_log') ||
uriStr.startsWith('content://com.android.contacts') ||
uriStr.startsWith('content://contacts');
}
// query(Uri, String[], String, String[], String) and query(Uri, String[], Bundle, CancellationSignal)
[
['android.net.Uri', '[Ljava.lang.String;', 'java.lang.String', '[Ljava.lang.String;', 'java.lang.String'],
['android.net.Uri', '[Ljava.lang.String;', 'android.os.Bundle', 'android.os.CancellationSignal']
].forEach(function (sig) {
try {
const ov = ContentResolver.query.overload.apply(ContentResolver.query, sig);
ov.implementation = function () {
const uri = arguments[0];
const uriStr = jStr(uri);
if (trackIfSensitive(uriStr)) {
const payload = { uri: uriStr };
if (sig.length === 5) {
payload.projection = jStr(arguments[1]);
payload.selection = jStr(arguments[2]);
payload.selectionArgs = jStr(arguments[3]);
payload.sortOrder = jStr(arguments[4]);
} else {
payload.queryArgsBundle = jStr(arguments[2]);
}
logEvent('content.query.sensitive', payload);
}
return ov.apply(this, arguments);
};
} catch (_) {}
});
// Streams via content URIs
try {
const openIn = ContentResolver.openInputStream.overload('android.net.Uri');
openIn.implementation = function (uri) {
const uriStr = jStr(uri);
if (trackIfSensitive(uriStr)) {
logEvent('content.openInputStream', { uri: uriStr });
}
return openIn.call(this, uri);
};
} catch (_) {}
try {
const openOut1 = ContentResolver.openOutputStream.overload('android.net.Uri');
openOut1.implementation = function (uri) {
const uriStr = jStr(uri);
if (trackIfSensitive(uriStr)) {
logEvent('content.openOutputStream', { uri: uriStr, mode: null });
}
return openOut1.call(this, uri);
};
} catch (_) {}
try {
const openOut2 = ContentResolver.openOutputStream.overload('android.net.Uri', 'java.lang.String');
openOut2.implementation = function (uri, mode) {
const uriStr = jStr(uri);
if (trackIfSensitive(uriStr)) {
logEvent('content.openOutputStream', { uri: uriStr, mode: jStr(mode) });
}
return openOut2.call(this, uri, mode);
};
} catch (_) {}
} catch (e) { logEvent('hook.warn', { where: 'contentresolver', warn: String(e) }); }
// ===================== Network sockets (added) =====================
try {
const Socket = Java.use('java.net.Socket');
const InetSocketAddress = Java.use('java.net.InetSocketAddress');
const SocketAddress = Java.use('java.net.SocketAddress');
// Constructors with host/port
Socket.$init.overload('java.lang.String', 'int').implementation = function (host, port) {
const ret = this.$init(host, port);
setLastNetFromSocket(this);
logEvent('net.connect', {
localIp: lastNet.localIp, localPort: lastNet.localPort,
remoteIp: lastNet.remoteIp, remotePort: lastNet.remotePort
});
return ret;
};
Socket.$init.overload('java.net.InetAddress', 'int').implementation = function (addr, port) {
const ret = this.$init(addr, port);
setLastNetFromSocket(this);
logEvent('net.connect', {
localIp: lastNet.localIp, localPort: lastNet.localPort,
remoteIp: lastNet.remoteIp, remotePort: lastNet.remotePort
});
return ret;
};
// Post-construction connect(SocketAddress) variants
Socket.connect.overload('java.net.SocketAddress').implementation = function (endpoint) {
const r = this.connect(endpoint);
try {
setLastNetFromSocket(this);
logEvent('net.connect', {
localIp: lastNet.localIp, localPort: lastNet.localPort,
remoteIp: lastNet.remoteIp, remotePort: lastNet.remotePort
});
} catch (_) {}
return r;
};
Socket.connect.overload('java.net.SocketAddress', 'int').implementation = function (endpoint, timeout) {
const r = this.connect(endpoint, timeout);
try {
setLastNetFromSocket(this);
logEvent('net.connect', {
localIp: lastNet.localIp, localPort: lastNet.localPort,
remoteIp: lastNet.remoteIp, remotePort: lastNet.remotePort
});
} catch (_) {}
return r;
};
} catch (e) { logEvent('hook.warn', { where: 'socket', warn: String(e) }); }
logEvent('frida.ready', { message: 'Shell+SMS+Exfil+Network hooks armed (narrative output).' });
});
Comments