Armitage 04.06.13

This update to Armitage improves its responsiveness when connected
to a team server over a high latency network. This update also adds
a publish/query/subscribe API to Cortana.
This commit is contained in:
Raphael Mudge 2013-03-04 18:32:45 -05:00
parent 596b62b831
commit 59d2f05c94
19 changed files with 288 additions and 42 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,30 @@
Armitage Changelog
==================
6 Mar 13 (tested against msf ca43900a7)
--------
- Active console now gets higher priority when polling msf for output
- Improved team server responsiveness in high latency situations by
creating additional connections to server to balance messages over
- Preferences are now shared among each Armitage connection.
Cortana Updates (for scripters)
--------
- Added a &publish, &query, &subscribe API to allow inter-script
communication across the team server.
- Added &table_update to set the contents of a table tab without
disturbing the highlighted rows.
- Added an exec_error event. Fired when &m_exec or &m_exec_local fail
due to an error reported by meterpreter.
- Fixed a bug that sometimes caused session_sync to fire twice (boo!)
- Added a 60s timeout to &s_cmd commands. Cortana will give a shell
command 60s to execute. If it doesn't finish in that time, Cortana
will release the lock on the shell so the user can control it.
(ideally, this shouldn't happen... this is a safety mechanism)
- Changed Meterpreter command timeout to 2m from 12s. This is because
https meterpreter might not checkin for up to 60s, if it's been
idle for a long time. This will make &m_cmd less likely to timeout
12 Feb 13 (tested against msf 16438)
---------
- Fixed a corner case preventing the display of removed host labels

View File

@ -3,7 +3,7 @@
<center><h1>Armitage 1.45</h1></center>
<p>An attack management tool for Metasploit&reg;
<br />Release: 12 Feb 13</p>
<br />Release: 6 Mar 13</p>
<br />
<p>Developed by:</p>

View File

@ -188,13 +188,24 @@ sub table_selected_single {
# table_set($table, @rows)
sub table_set {
local('$model $row');
$model = [$1 getModel];
[$model clear: size($2) * 2];
foreach $row ($2) {
[$model addEntry: $row];
}
[$model fireListeners];
later(lambda({
local('$model $row');
$model = [$a getModel];
[$model clear: size($b) * 2];
foreach $row ($b) {
[$model addEntry: $row];
}
[$model fireListeners];
}, $a => $1, $b => $2));
}
# table_set($table, @rows)
sub table_update {
later(lambda({
[$a markSelections];
table_set($a, $b);
[$a restoreSelections];
}, $a => $1, $b => $2));
}
# table_sorter($table, index, &function);

View File

@ -583,6 +583,39 @@ sub data_add {
call("db.key_add", $1, $data);
}
#
# a publish/query/subscribe API
#
# publish("key", $object)
sub publish {
local('$data');
$data = [msf.Base64 encode: cast(pack("o", $2, 1), 'b')];
call_async("armitage.publish", $1, "$data $+ \n");
}
# query("key", "index")
sub query {
local('$r @r $result');
$r = call("armitage.query", $1, $2)['data'];
if ($r ne "") {
foreach $result (split("\n", $r)) {
push(@r, unpack("o", [msf.Base64 decode: $result])[0]);
}
}
return @r;
}
# subscribe("key", "index", "1s/5s/10s/15s/30s/1m/5m/10m/15m/20m/30m/60m")
sub subscribe {
on("heartbeat_ $+ $3", lambda({
local('$result');
foreach $result (query($key, $index)) {
fire_event_local($key, $result, $index);
}
}, $key => $1, $index => $2));
}
#
# Shell shock?
#
@ -834,7 +867,7 @@ sub m_exec {
}, \$command, \$channel, \$buffer));
}
else {
# this is probably ok...
fire_event_local("exec_error", $1, $command, ["$3" trim]);
}
}, \$command));
}

View File

@ -15,7 +15,7 @@ import graph.*;
import java.awt.image.*;
global('$frame $tabs $menubar $msfrpc_handle $REMOTE $cortana $MY_ADDRESS $DESCRIBE @CLOSEME');
global('$frame $tabs $menubar $msfrpc_handle $REMOTE $cortana $MY_ADDRESS $DESCRIBE @CLOSEME @POOL');
sub describeHost {
local('$desc');
@ -164,13 +164,14 @@ sub _connectToMetasploit {
$client = [new MsgRpcImpl: $3, $4, $1, long($2), $null, $debug];
$aclient = [new RpcAsync: $client];
$mclient = $client;
push(@POOL, $aclient);
initConsolePool();
$DESCRIBE = "localhost";
}
# we have a team server... connect and authenticate to it.
else {
[$progress setNote: "Connected: logging in"];
$client = c_client($1, $2);
setField(^msf.MeterpreterSession, DEFAULT_WAIT => 20000L);
$mclient = setup_collaboration($3, $4, $1, $2);
$aclient = $mclient;
@ -178,6 +179,17 @@ sub _connectToMetasploit {
[$progress close];
return;
}
else {
[$progress setNote: "Connected: authenticated"];
}
# create six additional connections to team server... for balancing consoles.
local('$x $cc');
for ($x = 0; $x < 6; $x++) {
$cc = c_client($1, $2);
call($cc, "armitage.validate", $3, $4, $null, "cobaltstrike", 120326);
push(@POOL, $cc);
}
}
$flag = $null;
}

View File

@ -57,12 +57,18 @@ sub parseYaml {
sub loadPreferences {
local('$file $prefs');
$file = getFileProper(systemProperties()["user.home"], ".armitage.prop");
$prefs = [new Properties];
if (-exists $file) {
[$prefs load: [new java.io.FileInputStream: $file]];
if ($__frame__ !is $null && [$__frame__ getPreferences] !is $null) {
$prefs = [$__frame__ getPreferences];
}
else {
[$prefs load: resource("resources/armitage.prop")];
$prefs = [new Properties];
if (-exists $file) {
[$prefs load: [new java.io.FileInputStream: $file]];
}
else {
[$prefs load: resource("resources/armitage.prop")];
}
[$__frame__ setPreferences: $prefs];
}
# parse command line options here.

View File

@ -290,7 +290,7 @@ sub createShellSessionTab {
return;
}
$thread = [new ConsoleClient: $console, $client, "session.shell_read", "session.shell_write", $null, $sid, 0];
$thread = [new ConsoleClient: $console, rand(@POOL), "session.shell_read", "session.shell_write", $null, $sid, 0];
[$frame addTab: "Shell $sid", $console, lambda({
call_async($mclient, "armitage.unlock", $sid);
[$thread kill];

View File

@ -78,7 +78,7 @@ sub setupEventStyle {
sub createDisplayTab {
local('$console $host $queue $file');
$queue = [new ConsoleQueue: $client];
$queue = [new ConsoleQueue: rand(@POOL)];
if ($1 eq "Log Keystrokes") {
$console = [new ActivityConsole: $preferences];
}
@ -100,7 +100,7 @@ sub createConsolePanel {
setupConsoleStyle($console);
$result = call($client, "console.create");
$thread = [new ConsoleClient: $console, $aclient, "console.read", "console.write", "console.destroy", $result['id'], $1];
$thread = [new ConsoleClient: $console, rand(@POOL), "console.read", "console.write", "console.destroy", $result['id'], $1];
[$thread setMetasploitConsole];
[$thread setSessionListener: {

View File

@ -215,6 +215,7 @@ public class ConsoleClient implements Runnable, ActionListener {
Map read;
boolean shouldRead = go_read;
String command = null;
long last = 0;
try {
while (shouldRead) {
@ -230,21 +231,23 @@ public class ConsoleClient implements Runnable, ActionListener {
lastRead = System.currentTimeMillis();
}
read = readResponse();
if (read == null || "failure".equals( read.get("result") + "" )) {
break;
}
processRead(read);
if ((System.currentTimeMillis() - lastRead) <= 500) {
Thread.sleep(10);
long now = System.currentTimeMillis();
if (this.window != null && !this.window.isShowing() && (now - last) < 1500) {
/* check if our window is not showing... if not, then we're going to switch to a very reduced
read schedule. */
}
else {
Thread.sleep(500);
read = readResponse();
if (read == null || "failure".equals( read.get("result") + "" )) {
break;
}
processRead(read);
last = System.currentTimeMillis();
}
Thread.sleep(100);
synchronized (listeners) {
shouldRead = go_read;
}

View File

@ -130,6 +130,10 @@ public class Sessions extends ManagedData {
}
}
/* calculate the differences and fire some events based on them */
Set newSessions = DataUtils.difference(after, before);
fireSessionEvents("session_open", newSessions.iterator(), dataz);
/* calculate sync events and fix the nonsync set */
Set newsync = DataUtils.intersection(syncz, nonsync);
fireSessionEvents("session_sync", newsync.iterator(), dataz);
@ -137,11 +141,9 @@ public class Sessions extends ManagedData {
/* update our list of non-synced sessions */
nonsync.removeAll(syncz);
/* calculate the differences and fire some events based on them */
Set newSessions = DataUtils.difference(after, before);
fireSessionEvents("session_open", newSessions.iterator(), dataz);
newSessions.retainAll(syncz);
/* these are sessions that are new and sync'd -- fire events for them... */
newSessions.removeAll(newsync); /* we already fired events for these */
newSessions.retainAll(syncz); /* keep anything that is synced */
fireSessionEvents("session_sync", newSessions.iterator(), dataz);
Set droppedSessions = DataUtils.difference(before, after);

View File

@ -30,11 +30,16 @@ public class UIBridge implements Loadable, Function {
if (name.equals("&later")) {
final SleepClosure f = BridgeUtilities.getFunction(args, script);
final Stack argz = EventManager.shallowCopy(args);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
SleepUtils.runCode(f, "laterz", null, argz);
}
});
if (SwingUtilities.isEventDispatchThread()) {
SleepUtils.runCode(f, "laterz", null, argz);
}
else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
SleepUtils.runCode(f, "laterz", null, argz);
}
});
}
}
return SleepUtils.getEmptyScalar();

View File

@ -75,7 +75,8 @@ public class ShellSession implements Runnable {
/* loop forever waiting for response to come back. If session is dead
then this loop will break with an exception */
while (true) {
long start = System.currentTimeMillis();
while ((System.currentTimeMillis() - start) < 60000) {
response = readResponse();
String data = (response.get("data") + "");
@ -95,6 +96,7 @@ public class ShellSession implements Runnable {
Thread.sleep(100);
}
System.err.println(session + " -> " + c.text + " (took longer than anticipated, dropping: " + (System.currentTimeMillis() - start) + ")");
}
catch (Exception ex) {
System.err.println(session + " -> " + c.text + " ( " + response + ")");

View File

@ -14,7 +14,7 @@ public class MeterpreterSession implements Runnable {
protected String session;
protected boolean teammode;
public static long DEFAULT_WAIT = 12000;
public static long DEFAULT_WAIT = 120000;
private static class Command {
public Object token;

View File

@ -10,6 +10,7 @@ import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;
import armitage.ArmitageBuffer;
/**
* This is a modification of msfgui/RpcConnection.java by scriptjunkie. Taken from
@ -85,6 +86,22 @@ public abstract class RpcConnectionImpl implements RpcConnection, Async {
protected HashMap locks = new HashMap();
protected String address = "";
protected HashMap buffers = new HashMap();
/* help implement our remote buffer API for PQS primitives */
public ArmitageBuffer getABuffer(String key) {
synchronized (buffers) {
ArmitageBuffer buffer;
if (buffers.containsKey(key)) {
buffer = (ArmitageBuffer)buffers.get(key);
}
else {
buffer = new ArmitageBuffer(16384);
buffers.put(key, buffer);
}
return buffer;
}
}
public String getLocalAddress() {
return address;
@ -133,6 +150,23 @@ public abstract class RpcConnectionImpl implements RpcConnection, Async {
locks.remove(params[0] + "");
return new HashMap();
}
else if (methodName.equals("armitage.publish")) {
ArmitageBuffer buffer = getABuffer(params[0] + "");
buffer.put(params[1] + "");
return new HashMap();
}
else if (methodName.equals("armitage.query")) {
ArmitageBuffer buffer = getABuffer(params[0] + "");
String data = (String)buffer.get(params[1] + "");
HashMap temp = new HashMap();
temp.put("data", data);
return temp;
}
else if (methodName.equals("armitage.reset")) {
ArmitageBuffer buffer = getABuffer(params[0] + "");
buffer.reset();
return new HashMap();
}
else if (hooks.containsKey(methodName)) {
RpcConnection con = (RpcConnection)hooks.get(methodName);
return con.execute(methodName, params);

View File

@ -10,8 +10,48 @@ import table.*;
import java.util.*;
public class ATable extends JTable {
public static final String indicator = " \u271A";
protected boolean alternateBackground = false;
protected int[] selected = null;
/* call this function to store selections */
public void markSelections() {
selected = getSelectedRows();
}
public void fixSelection() {
if (selected.length == 0)
return;
getSelectionModel().setValueIsAdjusting(true);
int rowcount = getModel().getRowCount();
for (int x = 0; x < selected.length; x++) {
if (selected[x] < rowcount) {
getSelectionModel().addSelectionInterval(selected[x], selected[x]);
}
}
getSelectionModel().setValueIsAdjusting(false);
}
/* call this function to restore selections after a table update */
public void restoreSelections() {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
fixSelection();
}
});
}
else {
fixSelection();
}
}
public static TableCellRenderer getDefaultTableRenderer(final JTable table, final TableModel model) {
final Set specialitems = new HashSet();
specialitems.add("Wordlist");
@ -39,7 +79,7 @@ public class ATable extends JTable {
String content = (value != null ? value : "") + "";
if (specialitems.contains(content) || content.indexOf("FILE")!= -1) {
content = content + " \u271A";
content = content + indicator;
}
JComponent c = (JComponent)render.getTableCellRendererComponent(table, content, isSelected, false, row, column);
@ -117,6 +157,47 @@ public class ATable extends JTable {
};
}
public static TableCellRenderer getTimeTableRenderer() {
return new TableCellRenderer() {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
TableCellRenderer render = table.getDefaultRenderer(String.class);
JComponent c = (JComponent)render.getTableCellRendererComponent(table, "", isSelected, false, row, column);
try {
long size = Long.parseLong(value + "");
String units = "ms";
if (size > 1000) {
size = size / 1000;
units = "s";
}
else {
((JLabel)c).setText(size + units);
return c;
}
if (size > 60) {
size = size / 60;
units = "m";
}
if (size > 60) {
size = size / 60;
units = "h";
}
((JLabel)c).setText(size + units);
}
catch (Exception ex) {
}
return c;
}
};
}
public void adjust() {
setShowGrid(false);
setIntercellSpacing(new Dimension(0, 0));

View File

@ -17,6 +17,7 @@ public class MultiFrame extends JFrame implements KeyEventDispatcher {
protected JPanel content;
protected CardLayout cards;
protected LinkedList buttons;
protected Properties prefs;
private static class ArmitageInstance {
public ArmitageApplication app;
@ -24,6 +25,14 @@ public class MultiFrame extends JFrame implements KeyEventDispatcher {
public RpcConnection client;
}
public void setPreferences(Properties prefs) {
this.prefs = prefs;
}
public Properties getPreferences() {
return prefs;
}
public Map getClients() {
synchronized (buttons) {
Map r = new HashMap();

View File

@ -1,6 +1,30 @@
Armitage Changelog
==================
6 Mar 13 (tested against msf ca43900a7)
--------
- Active console now gets higher priority when polling msf for output
- Improved team server responsiveness in high latency situations by
creating additional connections to server to balance messages over
- Preferences are now shared among each Armitage connection.
Cortana Updates (for scripters)
--------
- Added a &publish, &query, &subscribe API to allow inter-script
communication across the team server.
- Added &table_update to set the contents of a table tab without
disturbing the highlighted rows.
- Added an exec_error event. Fired when &m_exec or &m_exec_local fail
due to an error reported by meterpreter.
- Fixed a bug that sometimes caused session_sync to fire twice (boo!)
- Added a 60s timeout to &s_cmd commands. Cortana will give a shell
command 60s to execute. If it doesn't finish in that time, Cortana
will release the lock on the shell so the user can control it.
(ideally, this shouldn't happen... this is a safety mechanism)
- Changed Meterpreter command timeout to 2m from 12s. This is because
https meterpreter might not checkin for up to 60s, if it's been
idle for a long time. This will make &m_cmd less likely to timeout
12 Feb 13 (tested against msf 16438)
---------
- Fixed a corner case preventing the display of removed host labels