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.bug/bundler_fix
parent
596b62b831
commit
59d2f05c94
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<center><h1>Armitage 1.45</h1></center>
|
||||
|
||||
<p>An attack management tool for Metasploit®
|
||||
<br />Release: 12 Feb 13</p>
|
||||
<br />Release: 6 Mar 13</p>
|
||||
<br />
|
||||
<p>Developed by:</p>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 + ")");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue