cfalta/PowerShellArmoury

PowerShellArmoury:绕过检测 | 混淆/规避 代码 - 图1
PowerShellArmoury适用于渗透测试人员、“在此处插入颜色”团队以及在参与期间使用各种 PowerShell 工具的其他所有人。它允许您将所有您喜欢的 PowerShell 脚本下载并存储在一个单独的混淆文件中。
您不必为手动更新 Rubeus、PowerView 等而烦恼。只需创建一次配置文件或使用工具随附的默认配置文件。从现在开始,您只需运行“New-PSarmoury”,然后再进行下一次交战。此外,PSarmoury 会混淆您的代码并附带一个包含的 AMSI 旁路。模块化设计应该可以让您轻松更改规避或混淆代码,以防万一检测到。

一般结构

当前版本的 PSarmoury 倾向于模块化设计:

  1. New-PSArmoury.ps1
  2. PSArmoury.json
  3. utilities
  4. ConvertTo-Powershell.ps1
  5. Invoke-Shuffle.ps1
  6. modules
  7. evasion.ps1
  8. obfuscation.ps1

代码被拆分为一个名为 的主生成器脚本New-PSArmoury.ps1,这是您执行的脚本。规避、混淆和反混淆的代码存储在modules目录中单独的 .ps1 文件中。这些单独的文件由主脚本调用,应该使您更容易更改特定功能(如 AMSI 绕过)。除了 modules 目录,还有utilities目录。在这里,您将找到在特定场景中有用的独立脚本。

模块目录

模块目录的默认路径是.\modules点指向 shell 的当前工作目录的位置。-ModulesDirectory您可以使用 的参数更改模块目录的路径New-PSArmoury。然而,脚本文件本身的名称在主脚本中是硬编码的,并且总是期望为:

  • evasion.ps1
  • obfuscation.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
  • 网页下载简单
  • 本地文件
    • 磁盘上的文件,例如“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进行测试,这很有用。
像这样使用:

  1. $c = get-credential
  2. New-PSArmoury -GithubCredentials $c

Github 访问令牌

您必须提供有效的 github 用户名以及个人访问令牌,以便脚本可以正确使用 github API。不要使用用户名/密码,因为如果您启用了 MFA(并且您应该启用 MFA),这无论如何都不起作用。也不推荐使用基本用户名/密码访问 API。
按照本指南创建个人访问令牌。
请注意:我们需要的访问令牌的唯一权限public_repo在该repo部分中。
这是因为您只需要令牌,因此如果您为要包含的 .ps1 文件解析更大的存储库(如 PowerSploit),Github 不会阻止我们。

示例用法

示例 1 - 全部默认

如果您想使用默认设置创建一个Armoury(注意:除了 base64 编码之外,这根本不会混淆),那么只需运行以下命令:

  1. . .\New-PSArmoury.ps1
  2. New-PSArmoury

这将使用在当前工作目录中创建一个名为“MyArmoury.ps1”的 .ps1 文件

  • 默认配置“.\PSarmoury.json”
  • 在“.\modules\evasion.ps1”中找到的默认 AMSI 绕过
  • 默认混淆/反混淆(base64)分别位于“.\modules\obfuscation.ps1”和“.\modules\deobfuscation.ps1”。

您可以使用以下命令将Armoury加载到当前会话中

  1. cat -raw .\MyArmoury.ps1 | iex

加载您的Armoury会调用以下步骤:

  • 调用规避码
  • 将控制权移交给反混淆功能,而反混淆功能又应该
  • 浏览每一个被混淆的项目,然后
    • 去混淆
    • 管道进入 IEX

之后,您放入Armoury的所有 powershell 代码都将可用。像往常一样调用 cmdlet:

  1. Invoke-Rubeus -Command "kerberoast /stats"
  2. Get-DomainGroupMember -Identity "Domain Admins" -Recurse

如果碰巧你不记得你在Armoury里放了什么,只需加载它并调用库存:-)

  1. Get-PSArmoury

示例 2 - 使用 PSarmoury 和不同配置文件附带的字节转换/json 格式混淆技术

-EvasionPath使用和参数启动 New-PSarmoury,-ObfuscationPath如下所示:

  1. New-PSArmoury -Config C:\myarmouryconfig.json -ObfuscationPath .\modules\TEMPLATE_obfuscation_byte_convert.ps1 -EvasionPath .\modules\evasion.ps1

示例 3 - 从包含 powershell 脚本的本地文件夹创建Armoury

注意:在这种情况下,由于我们提交了文件夹路径,因此将添加文件夹中的所有 .ps1 文件。如果我们提交单个文件的路径,那么我们只会处理那个文件:

  1. New-PSArmoury -FromFile C:\myscriptfolder

cfalta/PowerShellArmoury/New-PSArmoury.ps1

  1. function New-PSArmoury
  2. {
  3. <#
  4. .SYNOPSIS
  5. New-PSArmoury creates a single, obfuscated file (your armoury) containing all your favourite PowerShell scripts from multiple repositories based on a config file.
  6. Basically it's like "apt-get update" for your offensive PowerShell arsenal.
  7. Author: Christoph Falta (@cfalta)
  8. .DESCRIPTION
  9. 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.
  10. 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.
  11. 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.
  12. 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.
  13. 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.
  14. .PARAMETER Path
  15. The path to your new armoury file. The default is ".\MyArmoury.ps1"
  16. .PARAMETER FromFile
  17. Load your Powershell scripts directly from a local folder or file and you don't have to provide a config file.
  18. .PARAMETER Config
  19. The path to your JSON-config file. Have a look at the sample that comes with this script for ideas.
  20. .PARAMETER ModulesDirectory
  21. The path to the modules directory. The default is ".\modules".
  22. If ModulesDirectory is used, then the EvasionPath and ObfuscationPath Parameters cannot be used.
  23. .PARAMETER EvasionPath
  24. The path to the evasion script. If EvasionPath and ObfuscationPath are used, then the ModulesDirectory-Parameter cannot be used.
  25. .PARAMETER ObfuscationPath
  26. The path to the obfuscation script. If EvasionPath and ObfuscationPath are used, then the ModulesDirectory-Parameter cannot be used.
  27. .PARAMETER ValidateOnly
  28. Use this together with "-Config" to let the script validate the basic syntax of your JSON config file without executing it.
  29. .PARAMETER GithubCredentials
  30. 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.
  31. Use like this:
  32. $c = get-credential
  33. New-PSArmoury -GithubCredentials $c
  34. .EXAMPLE
  35. New-PSArmoury
  36. Description
  37. -----------
  38. Execute with all defaults, which means it will:
  39. - create a .ps1 file called "MyArmoury.ps1" in the current working directory using
  40. - the default config ".\PSArmoury.json"
  41. - the default AMSI bypass found at ".\modules\evasion.ps1"
  42. - the default obfuscation script found at ".\modules\obfuscation.ps1".
  43. .EXAMPLE
  44. New-PSArmoury -FromFile .\myfolderfullofps1scripts\
  45. Description
  46. -----------
  47. 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".
  48. .LINK
  49. https://github.com/cfalta/PowerShellArmoury
  50. #>
  51. [CmdletBinding(DefaultParameterSetName = "DefaultModules")]
  52. Param (
  53. [Parameter(ParameterSetName="DefaultModules")]
  54. [Parameter(ParameterSetName="Validate")]
  55. [Parameter(ParameterSetName="ModulesByFiles")]
  56. [Parameter(ParameterSetName="ModulesByDirectory")]
  57. [ValidateNotNullOrEmpty()]
  58. [String]
  59. $Path = ".\MyArmoury.ps1",
  60. [Parameter(ParameterSetName="DefaultModules")]
  61. [Parameter(ParameterSetName="Validate")]
  62. [Parameter(ParameterSetName="ModulesByFiles")]
  63. [Parameter(ParameterSetName="ModulesByDirectory")]
  64. [ValidateScript({Test-Path $_})]
  65. [String]
  66. $FromFile,
  67. [Parameter(ParameterSetName="DefaultModules")]
  68. [Parameter(ParameterSetName="Validate")]
  69. [Parameter(ParameterSetName="ModulesByFiles")]
  70. [Parameter(ParameterSetName="ModulesByDirectory")]
  71. [ValidateScript({Test-Path $_})]
  72. [String]
  73. $Config,
  74. [Parameter(ParameterSetName="ModulesByDirectory")]
  75. [ValidateScript({Test-Path $_})]
  76. [String]
  77. $ModulesDirectory,
  78. [Parameter(ParameterSetName="ModulesByFiles")]
  79. [ValidateScript({Test-Path $_})]
  80. [String]
  81. $EvasionPath,
  82. [Parameter(ParameterSetName="ModulesByFiles")]
  83. [ValidateScript({Test-Path $_})]
  84. [String]
  85. $ObfuscationPath,
  86. [Parameter(ParameterSetName="Validate")]
  87. [Switch]
  88. $ValidateOnly,
  89. [Parameter(ParameterSetName="DefaultModules")]
  90. [Parameter(ParameterSetName="Validate")]
  91. [Parameter(ParameterSetName="ModulesByFiles")]
  92. [Parameter(ParameterSetName="ModulesByDirectory")]
  93. [ValidateNotNullOrEmpty()]
  94. [System.Management.Automation.PSCredential]
  95. $GithubCredentials
  96. )
  97. if(-not $PSBoundParameters.ContainsKey("Config"))
  98. {
  99. $Config = Join-Path -Path (Get-Location) -ChildPath "PSArmoury.json"
  100. }
  101. if($PSBoundParameters.ContainsKey("GithubCredentials"))
  102. {
  103. $global:GitHubCredentials = $GithubCredentials
  104. }
  105. function Write-Banner
  106. {
  107. Write-Output " %%%%%%%%%%% "
  108. Write-Output " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% "
  109. Write-Output " %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% "
  110. Write-Output " %%%%%%%%%%%%%% %%%%%%%%%%%%%% "
  111. Write-Output "%%%%%%%%%%%% %%%%%%%%%%%%%%%%% %%%%%%%%%%%"
  112. Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%"
  113. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  114. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  115. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  116. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  117. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  118. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  119. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  120. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  121. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  122. Write-Output " %%%%% %%%%%%% %%%% @@@@@@@@@@@@@@@@@ %%%%% "
  123. Write-Output " %%%%% %%%%%%% %%%%%%%%% %% @@@@@@ @@@@ %%%%% "
  124. Write-Output " %%%%% %%%%%%% %%%%%%%%%% %% @@@@@ %%%%% "
  125. Write-Output " %%%%% %%%%%%% %% @@@@@@@@@@@@@@@@ %%%%% "
  126. Write-Output " %%%%% %%%%%%% %%%%% @@@@@@@@@@@@@ %%%%% "
  127. Write-Output " %%%%% %%%%%%%% %%%%%%%%%%%%%%%% @@@@ @@@@@ %%%%% "
  128. Write-Output " %%%%% %%%%%%%% %%%%%%%%%%%%%%%% @@@@@@@ @@@@@@@ %%%%% "
  129. Write-Output " %%%%% %%%%%%%% %%%%%%%%%%%%%%%% @@@@@@@@@@@@@@ %%%%% "
  130. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  131. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  132. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  133. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  134. Write-Output " %%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  135. Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  136. Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  137. Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%% %%%%% "
  138. Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%%%% %%%%%% "
  139. Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%%%%% %%%%%% "
  140. Write-Output " %%%%%%% %%%%%%%%%%%%%%%%%%%%% %%%%%% "
  141. Write-Output " %%%%%% %%%%%%%%%%%%%%%%%%% %%%%%%% "
  142. Write-Output " %%%%%% %%%%%%%%%%%%%%%%% %%%%%%% "
  143. Write-Output " %%%%%%% %%%%%%%%%%%%%% %%%%%%% "
  144. Write-Output " %%%%%%% %%%%%%%%%%%% %%%%%%% "
  145. Write-Output " %%%%%%% %%%%%%%%% %%%%%%%% "
  146. Write-Output " %%%%%%% %%%%%% %%%%%%% "
  147. Write-Output " %%%%%%%% %%% %%%%%%% "
  148. Write-Output " %%%%%%%% %%%%%%%% "
  149. Write-Output " %%%%%%%%% %%%%%%%%% "
  150. Write-Output " %%%%%%%%%%%%%% "
  151. Write-Output " %%%%%%% `n`n"
  152. }
  153. function Test-PSAConfig
  154. {
  155. $IsValid = $True
  156. if($global:PSArmouryConfig)
  157. {
  158. $Index = 0
  159. foreach($Item in $global:PSArmouryConfig)
  160. {
  161. if(-Not($Item.Name -and $Item.Type -and $Item.URL))
  162. {
  163. Write-Warning ("PSArmoury: Error validating item at index " + $Index + ". Name, Type and URL are mandatory.")
  164. $IsValid = $False
  165. }
  166. if(-Not(($Item.Type -eq "GitHub") -or ($Item.Type -eq "LocalFile") -or ($Item.Type -eq "WebDownloadSimple")))
  167. {
  168. Write-Warning ("PSArmoury: Error validating item at index " + $Index + ". Type needs to be either GitHub, LocalFile or WebDownloadSimple")
  169. $IsValid = $False
  170. }
  171. $Index++
  172. }
  173. }
  174. $IsValid
  175. }
  176. function Invoke-PSAGithubDownload
  177. {
  178. [CmdletBinding()]
  179. Param (
  180. [Parameter(Mandatory = $true)]
  181. [ValidateNotNullorEmpty()]
  182. [String]
  183. $URI)
  184. $CredentialsBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(($global:GitHubCredentials.Username + ":" + $global:GitHubCredentials.GetNetworkCredential().Password)))
  185. $BasicAuthHeader = ("Basic " + $CredentialsBase64)
  186. $wc = New-Object System.Net.WebClient
  187. $wc.Headers.Add("User-Agent","PSArmoury")
  188. $wc.Headers.Add("Authorization",$BasicAuthHeader)
  189. $Response = $wc.DownloadString($URI)
  190. $Response
  191. }
  192. function Get-PSAGitHubItem([string]$Name)
  193. {
  194. $PSA = $global:PSArmouryConfig | ? {$_.Name -eq $Name}
  195. $BaseURL = $PSA.URL
  196. $GitHubType = $False
  197. #Assume this is a valid file URL if it contains raw
  198. if($BaseURL.Contains("raw"))
  199. {
  200. $GitHubType = "File"
  201. $ItemName = $BaseURL.Substring($BaseURL.LastIndexOf("/")+1)
  202. Write-Verbose ("PSArmoury: Trying to download " + $ItemName)
  203. $Response = Invoke-PSAGithubDownload -URI $BaseURL
  204. $PSO = New-Object -TypeName PSObject
  205. $PSO | Add-Member -MemberType NoteProperty -Name Repository -Value $PSA.Name
  206. $PSO | Add-Member -MemberType NoteProperty -Name Name -Value $ItemName
  207. $PSO | Add-Member -MemberType NoteProperty -Name Code -Value $Response
  208. $global:PSAInventory += $PSO
  209. }
  210. #Assume this is a repo if it starts with the repo URL prefix
  211. if($BaseURL.StartsWith("https://api.github.com/repos/"))
  212. {
  213. $GitHubType = "Repo"
  214. $Response = Invoke-PSAGithubDownload -URI $BaseURL | ConvertFrom-Json
  215. if($PSA.Branch)
  216. {
  217. $ContentURL = $Response.contents_url.Substring(0,$Response.contents_url.LastIndexOf("/")) + "?ref=" + $PSA.Branch
  218. }
  219. else {
  220. $ContentURL = $Response.contents_url.Substring(0,$Response.contents_url.LastIndexOf("/"))
  221. }
  222. $ContentIndex = Invoke-PSAGithubDownload -URI $ContentURL | ConvertFrom-Json
  223. $NewItem = $True
  224. #Discover all files in the repository and download them
  225. while($NewItem)
  226. {
  227. $NewItem = $False
  228. $ContentIndex2 = @()
  229. foreach($ContentItem in $ContentIndex)
  230. {
  231. if($ContentItem.type -eq "dir")
  232. {
  233. $ContentIndex2 += (Invoke-PSAGithubDownload -URI $ContentItem.URL | ConvertFrom-Json)
  234. $NewItem = $True
  235. }
  236. if($ContentItem.type -eq "file")
  237. {
  238. $Include = $True
  239. if(($PSA.FileExclusionFilter))
  240. {
  241. foreach($f in $PSA.FileExclusionFilter)
  242. {
  243. if($ContentItem.Name -like $f)
  244. {
  245. $Include = $False
  246. }
  247. }
  248. }
  249. if(($PSA.FileInclusionFilter))
  250. {
  251. foreach($f in $PSA.FileInclusionFilter)
  252. {
  253. if($ContentItem.Name -notlike $f)
  254. {
  255. $Include = $False
  256. }
  257. }
  258. }
  259. if($Include)
  260. {
  261. Write-Verbose ("PSArmoury: Trying to download " + $PSA.Name + "/" + $ContentItem.Name)
  262. try
  263. {
  264. $Response = Invoke-PSAGithubDownload -URI $ContentItem.download_url
  265. $PSO = New-Object -TypeName PSObject
  266. $PSO | Add-Member -MemberType NoteProperty -Name Repository -Value $PSA.Name
  267. $PSO | Add-Member -MemberType NoteProperty -Name Name -Value $ContentItem.Name
  268. $PSO | Add-Member -MemberType NoteProperty -Name Code -Value $Response
  269. $global:PSAInventory += $PSO
  270. }
  271. catch
  272. {
  273. Write-Warning ("PSArmoury: Error while downloading " + $PSA.Name + "/" + $ContentItem.Name)
  274. Write-Warning $Error[0]
  275. }
  276. }
  277. }
  278. }
  279. $ContentIndex = $ContentIndex2
  280. }
  281. }
  282. if(-not $GitHubType)
  283. {
  284. 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."
  285. }
  286. }
  287. function Get-PSALocalFile([string]$Name)
  288. {
  289. $PSA = $global:PSArmouryConfig | ? {$_.Name -eq $Name}
  290. if(Test-Path $PSA.URL)
  291. {
  292. if((Get-Item -LiteralPath $PSA.URL).PSISContainer)
  293. {
  294. $Files = Get-Childitem -LiteralPath $PSA.URL -Filter *.ps1
  295. }
  296. else
  297. {
  298. $Files = Get-Item -LiteralPath $PSA.URL
  299. }
  300. foreach($f in $Files)
  301. {
  302. $PSO = New-Object -TypeName PSObject
  303. $PSO | Add-Member -MemberType NoteProperty -Name Repository -Value $PSA.Name
  304. $PSO | Add-Member -MemberType NoteProperty -Name Name -Value $f.name
  305. $PSO | Add-Member -MemberType NoteProperty -Name Code -Value (get-content -raw $f.fullname)
  306. $global:PSAInventory += $PSO
  307. }
  308. }
  309. else {
  310. Write-Warning ("PSArmoury: Error while reading local file " + $PSA.URL)
  311. }
  312. }
  313. function Get-PSASimpleWebDownload([string]$Name)
  314. {
  315. $PSA = $global:PSArmouryConfig | ? {$_.Name -eq $Name}
  316. $BaseURL = $PSA.URL
  317. $ItemName = $BaseURL.Substring($BaseURL.LastIndexOf("/")+1)
  318. try
  319. {
  320. $wc = New-Object System.Net.WebClient
  321. $wc.Headers.Add("User-Agent","PSArmoury")
  322. $Response = $wc.DownloadString($BaseURL)
  323. $PSO = New-Object -TypeName PSObject
  324. $PSO | Add-Member -MemberType NoteProperty -Name Repository -Value $PSA.Name
  325. $PSO | Add-Member -MemberType NoteProperty -Name Name -Value $ItemName
  326. $PSO | Add-Member -MemberType NoteProperty -Name Code -Value $Response
  327. $global:PSAInventory += $PSO
  328. }
  329. catch
  330. {
  331. Write-Warning ("PSArmoury: Error while downloading " + $PSA.Name + "/" + $ItemName)
  332. }
  333. }
  334. function Add-Inventory
  335. {
  336. $Content = 'function Get-PSArmoury{$i=@('
  337. foreach($Item in $global:PSAInventory)
  338. {
  339. $Content = $Content + '"' + $Item.Name + '",'
  340. }
  341. $Content = $Content.Trim(",")
  342. $Content = $Content + ');$i}'
  343. $PSO = New-Object -TypeName PSObject
  344. $PSO | Add-Member -MemberType NoteProperty -Name Repository -Value "Inventory"
  345. $PSO | Add-Member -MemberType NoteProperty -Name Name -Value "Inventory"
  346. $PSO | Add-Member -MemberType NoteProperty -Name Code -Value $Content
  347. $global:PSAInventory += $PSO
  348. }
  349. function Add-Evasion
  350. {
  351. [CmdletBinding()]
  352. Param (
  353. [Parameter(Mandatory = $False)]
  354. [ValidateNotNullOrEmpty()]
  355. [String]
  356. $Path)
  357. if($Path)
  358. {
  359. $Content = Get-Content -Raw $Path
  360. }
  361. else {
  362. $Content = 'echo "This Armoury does not include evasion techniques. Beware of AV alerts coming up."'
  363. }
  364. $ContentB64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Content))
  365. $null = $global:PSAProcessedInventory.Add($ContentB64)
  366. }
  367. function Write-LoaderFile
  368. {
  369. [CmdletBinding()]
  370. Param (
  371. [Parameter(Mandatory = $True)]
  372. [ValidateNotNullorEmpty()]
  373. [String]
  374. $Path)
  375. $LoaderStub=@"
  376. foreach(`$ef in `$global:FunkyFuncs[0..1])
  377. {
  378. [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(`$ef)) | Invoke-Expression
  379. }
  380. "@
  381. #Delete the outputfile if it exists
  382. if((Test-Path -LiteralPath $Path))
  383. {
  384. Remove-Item -LiteralPath $Path -Force
  385. }
  386. #Creates a string array of encrypted scripts, which will be included in the decryption stub defined above
  387. $SummaryArrayDefinition = '$global:FunkyFuncs = @('
  388. $ArrayItemPrefix = '$Func'
  389. $counter = 0
  390. foreach($Item in $global:PSAProcessedInventory)
  391. {
  392. $SingleArrayDefinition = (($ArrayItemPrefix + $counter) + ' = ("' + $Item + '")')
  393. $SummaryArrayDefinition += (($ArrayItemPrefix + $counter) + ",")
  394. Add-Content $Path $SingleArrayDefinition
  395. $counter++
  396. }
  397. $SummaryArrayDefinition = $SummaryArrayDefinition.TrimEnd(",")
  398. $SummaryArrayDefinition += ")"
  399. #Write the string array into the loader file
  400. Add-Content $Path $SummaryArrayDefinition
  401. #Write minimal loader stub
  402. Add-Content $Path $LoaderStub
  403. }
  404. ### MAIN ###
  405. $ScriptRequirements = $True
  406. Write-Banner
  407. if($FromFile)
  408. {
  409. $PSO = New-Object -TypeName PSObject
  410. $PSO | Add-Member -MemberType NoteProperty -Name "Name" -Value "LocalRepo"
  411. $PSO | Add-Member -MemberType NoteProperty -Name "Type" -Value "LocalFile"
  412. $PSO | Add-Member -MemberType NoteProperty -Name "URL" -Value $FromFile
  413. $global:PSArmouryConfig = @()
  414. $global:PSArmouryConfig += $PSO
  415. }
  416. else
  417. {
  418. if($Config)
  419. {
  420. try
  421. {
  422. $global:PSArmouryConfig = Get-Content -Raw $Config | ConvertFrom-Json
  423. $ScriptRequirements = Test-PSAConfig
  424. if($ValidateOnly -and $ScriptRequirements)
  425. {
  426. Write-Output "PSArmoury: No issues found in $($Config)."
  427. $ScriptRequirements = $False
  428. }
  429. }
  430. catch
  431. {
  432. Write-Warning "PSArmoury: Error while loading configuration file."
  433. Write-Warning $Error[0]
  434. $ScriptRequirements = $False
  435. }
  436. }
  437. else
  438. {
  439. Write-Warning "PSArmoury: No configuration file found. Please provide a valid configuration and try again."
  440. $ScriptRequirements = $False
  441. }
  442. }
  443. if($ScriptRequirements)
  444. {
  445. if($PSArmouryConfig -and ($PSArmouryConfig.count -gt 0))
  446. {
  447. $DoObfuscation = $False
  448. $DoEvasion = $False
  449. if($ObfuscationPath -or $EvasionPath)
  450. {
  451. if($ObfuscationPath)
  452. {
  453. $ModulesObfuscation = $ObfuscationPath
  454. Write-Output ("PSArmoury: Obfuscation script set to " + $ModulesObfuscation)
  455. $DoObfuscation = $True
  456. }
  457. if($EvasionPath)
  458. {
  459. $ModulesEvasion = $EvasionPath
  460. Write-Output ("PSArmoury: Evasion script set to " + $ModulesEvasion)
  461. $DoEvasion = $True
  462. }
  463. }
  464. else
  465. {
  466. if(-not $PSBoundParameters.ContainsKey("ModulesDirectory"))
  467. {
  468. Write-Output ("PSArmoury: No module path submitted by user. Trying to locate default modules directory.")
  469. $DefaultModulesPath = Join-Path -Path (Get-Location) -ChildPath "modules"
  470. if(Test-Path $DefaultModulesPath)
  471. {
  472. $ModulesDirectory = $DefaultModulesPath
  473. }
  474. }
  475. if($ModulesDirectory)
  476. {
  477. $ModulesObfuscation = Join-Path -Path $ModulesDirectory -ChildPath "obfuscation.ps1"
  478. $ModulesEvasion = Join-Path -Path $ModulesDirectory -ChildPath "evasion.ps1"
  479. Write-Output ("PSArmoury: --> Modules directory is " + $ModulesDirectory)
  480. if((Test-Path -Path $ModulesObfuscation))
  481. {
  482. Write-Output ("PSArmoury: --> Obfuscation script is " + $ModulesObfuscation)
  483. $DoObfuscation = $True
  484. }
  485. if((Test-Path -Path $ModulesEvasion))
  486. {
  487. Write-Output ("PSArmoury: --> Evasion script is " + $ModulesEvasion)
  488. $DoEvasion = $True
  489. }
  490. }
  491. }
  492. if(-not $DoObfuscation -or -not $DoEvasion)
  493. {
  494. if(-not $DoObfuscation)
  495. {
  496. Write-Warning "PSArmoury: --> No obfuscation script found!"
  497. }
  498. if(-not $DoEvasion)
  499. {
  500. Write-Warning "PSArmoury: --> No evasion script found!"
  501. }
  502. Write-Warning "Do you really want to continue? Press [Enter] to move on or [Ctrl+C] to abort."
  503. $null = Read-Host
  504. }
  505. Write-Output ("PSArmoury: Your armoury contains " + $PSArmouryConfig.count + " repositories. Starting to download.")
  506. $global:PSAInventory = @()
  507. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
  508. foreach($PSA in $PSArmouryConfig)
  509. {
  510. switch($PSA.Type)
  511. {
  512. GitHub{
  513. if(-Not $global:GitHubCredentials)
  514. {
  515. $global:GitHubCredentials = Get-Credential -Message "Please enter Github username and access token"
  516. }
  517. Write-Output ("PSArmoury: Downloading repository " + $PSA.Name)
  518. Get-PSAGitHubItem($PSA.Name)
  519. }
  520. WebDownloadSimple{
  521. Write-Output ("PSArmoury: Downloading repository " + $PSA.Name)
  522. Get-PSASimpleWebDownload($PSA.Name)
  523. }
  524. LocalFile{
  525. Write-Output ("PSArmoury: Downloading repository " + $PSA.Name)
  526. Get-PSALocalFile($PSA.Name)
  527. }
  528. default{
  529. }
  530. }
  531. }
  532. if($global:PSAInventory)
  533. {
  534. Write-Output "PSArmoury: Download complete, starting processing"
  535. $global:PSAProcessedInventory = New-Object -TypeName "System.Collections.ArrayList"
  536. $Identifier = 0
  537. Add-Inventory
  538. if($DoEvasion)
  539. {
  540. Add-Evasion -Path $ModulesEvasion
  541. }
  542. else {
  543. Add-Evasion
  544. }
  545. if($DoObfuscation)
  546. {
  547. #loading module code
  548. cat -raw $ModulesObfuscation | iex
  549. #load deobfuscation script into output variable
  550. $DeObfuscationScript= (Get-Command Get-PSArmouryDeObfuscation).ScriptBlock
  551. $null = $global:PSAProcessedInventory.Add([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($DeObfuscationScript)))
  552. #Obfuscate
  553. foreach($Item in $global:PSAInventory)
  554. {
  555. $ObfuscatedCode = Get-PSArmouryObfuscation -Code $Item.Code
  556. $null = $global:PSAProcessedInventory.Add([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($ObfuscatedCode)))
  557. }
  558. }
  559. else {
  560. foreach($Item in $global:PSAInventory)
  561. {
  562. $null = $global:PSAProcessedInventory.Add([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Item.Code)))
  563. }
  564. }
  565. Write-Output "PSArmoury: Script processing complete, creating armoury file. Happy hacking :-)"
  566. Write-LoaderFile -Path $Path
  567. }
  568. else
  569. {
  570. 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 ;-)"
  571. }
  572. }
  573. }
  574. }