Created an option to specify the command instead of the JSON file and added code to publish it to PowerShell Gallery (#1)

* add an option to include command parameter

* add option to push to PowerShell Gallery

* Update Invoke-ArgFuscator.psm1

* fix inoptionchar for windows

* Minor changes to parameters (documentation)

* Updating Invoke-ArgFuscator.ps1, README

---------

Co-authored-by: Wietze <wietze@users.noreply.github.com>
This commit is contained in:
Hare Sudhan
2025-02-14 09:16:03 -05:00
committed by GitHub
parent 0a8f4b3b7b
commit 791cfc8fa1
5 changed files with 283 additions and 35 deletions
+19
View File
@@ -0,0 +1,19 @@
name: Publish PowerShell Module
on:
release:
types: [created]
jobs:
publish-powershell-gallery:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: publishing
run: |
curl -L https://github.com/wietze/ArgFuscator.net/archive/refs/heads/main.zip -o ArgFuscator.zip && unzip ArgFuscator.zip && rm ArgFuscator.zip
mv ./ArgFuscator.net-main/models .
rm -rf ./ArgFuscator.net-main
rm ./Invoke-ArgFuscator.ps1
Publish-Module -Path '.' -NuGetApiKey ${{ secrets.PGALLERY }}
shell: pwsh
+2 -7
View File
@@ -14,12 +14,7 @@ if ($args.Length -eq 0) {
$InputFile = Read-Host "Path to configuration file"
if (!($n = Read-Host "Number of commands to generate [default=$n_default]")) { $n = $n_default }
} else {
$InputFile = $Args[0]
$n = if ($args.Length -eq 2) { $Args[1] } else { $n_default }
if ($args.Length -gt 2) {
throw "Unexpected argument count"
}
$Args = @{ InputFile = $InputFile; n = $n };
}
Invoke-ArgFuscator -InputFile $InputFile -n $n
Invoke-ArgFuscator @Args
+52
View File
@@ -0,0 +1,52 @@
@{
# Script module or binary module file associated with this manifest.
RootModule = 'Invoke-ArgFuscator.psm1'
# Version number of this module.
ModuleVersion = '1.0.0'
# ID used to uniquely identify this module
GUID = '844d9edc-57ad-4fcc-9fd5-77a69d4bf569'
# Author of this module
Author = 'wietze'
# Description of the functionality provided by this module
Description = 'A PowerShell module that generate obfuscated command-lines for common system-native executables'
# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.0'
# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
ScriptsToProcess = @()
# Functions to export from this module
FunctionsToExport = @('Invoke-ArgFuscator')
# Cmdlets to export from this module
CmdletsToExport = @()
# Variables to export from this module
VariablesToExport = @()
# Aliases to export from this module
AliasesToExport = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess
PrivateData = @{
PSData = @{
# A URL to the license for this module.
LicenseUri = 'https://github.com/wietze/Invoke-ArgFuscator/blob/main/LICENSE'
# A URL to the main website for this project.
ProjectUri = 'https://github.com/wietze/Invoke-ArgFuscator'
}
}
NestedModules = @()
}
+142 -4
View File
@@ -10,10 +10,107 @@ using module "Modifiers\Shorthands.psm1"
using module "Modifiers\UrlTransformer.psm1"
$OutputEncoding = [ System.Text.Encoding]::UTF8
function Invoke-TokeniseCommand {
param(
[string]$InputCommand
)
if ($null -eq $InputCommand) { return $null }
$SeparationChar = ' '
$QuoteChars = @('"', "'")
$ValueChars = @('=', ':')
$CommonOptionChars = @('/', '-')
$InQuote = $null
[Token[]]$Tokens = @() # Explicitly type the array as Token[]
$TokenContent = @()
$SeenValueChar = $false
for ($i = 0; $i -lt $InputCommand.Length; $i++) {
if ($TokenContent.Count -eq 0) { $SeenValueChar = $false }
$Char = $InputCommand[$i].ToString()
if (($null -eq $InQuote) -and (
($Char -eq $SeparationChar) -or (
(-not $SeenValueChar) -and
((($i -eq $InputCommand.Length) -or (-not @('\\', '/') -contains $InputCommand[$i + 1])) -and
$ValueChars.contains($Char))
)
)) {
if ($Char -ne $SeparationChar) {
$TokenContent += $Char
}
if ($TokenContent.Count -gt 0) {
$Tokens += [Token]::new($TokenContent)
}
$TokenContent = @()
}
else {
if (($null -ne $InQuote) -and ($Char -eq $InQuote)) {
$InQuote = $null
}
elseif (($null -eq $InQuote) -and ($QuoteChars | Where-Object { $_ -eq $Char })) {
$InQuote = $Char
}
$TokenContent += $Char
}
$SeenValueChar = $SeenValueChar -or ($ValueChars | Where-Object { $_ -eq $Char })
}
if ($TokenContent.Count -gt 0) {
$Tokens += [Token]::new($TokenContent)
}
# Find matching template, if available
$Tokens[0].Type = "command"
$Tokens | Select-Object -Skip 1 | ForEach-Object -Begin { $i = 0 } -Process {
$TokenText = $_.ToString()
$_TokenText = $TokenText -replace "(['`"])(.*?)\1", '$2' #Remove any surrounding quotes
# If previous token ends with a ValueChar, assume this token denotes a 'value' type;
# or, if no option char present, designate it as 'value', unless overwritten further down
if (($ValueChars | Where-Object { $Tokens[$i].TokenContent[-1] -eq $_ }) -or -not ($CommonOptionChars | Where-Object { $_TokenText.StartsWith($_) })) {
$_.Type = 'value'
# Special case: WMIC
if ($Tokens[0].ToString() -match 'wmic(\.exe)?'`
-and -not ($Tokens[1..($i + 1)] | Where-Object { $_.GetType() -eq 'disabled' })`
-and -not ($Tokens[$i].GetType() -eq 'argument' -and ($ValueChars | Where-Object { $Tokens[$i].TokenContent[-1] -eq $_ }))) {
Write-Host $Tokens[$i]
$_.Type = 'disabled'
}
}
if ($_TokenText -match '^(?:\\\\[^\\]+|[a-zA-Z]:|\.[\\/])((?:\\[^\\]+)+\\)?([^<>:]*)$' -or $_TokenText -match '^[^<>:]+\.[a-zA-Z0-9]{2,4}$') {
$_.Type = 'path' # Windows file path format
}
if ($_TokenText -match '^(HKLM|HKCC|HKCR|HKCU|HKU|HKEY_(LOCAL_MACHINE|CURRENT_CONFIG|CLASSES_ROOT|CURRENT_USER|USERS))\\?') {
$_.Type = 'disabled' # Windows Registry
}
if ($_TokenText.StartsWith('http:') -or $_TokenText.StartsWith('https:') -or $_TokenText -match '[12]?\d?\d\.[12]?\d?\d\.[12]?\d?\d\.[12]?\d?\d') {
$_.Type = 'url' #URLs (including IP addresses)
}
$i++
}
$result = @()
$Tokens | ForEach-Object {
$hashtable = @{}
$hashtable[$_.Type] = ($_.TokenContent -join '')
$result += $hashtable
}
return $result
}
function Invoke-ArgFuscator {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true)]
[Parameter(Mandatory = $true, ParameterSetName = "FromFile")]
[ValidateScript({ if ((Test-Path $_ -PathType 'Leaf') -and ((Get-Item $_ | Select-Object -Expand Extension) -eq ".json" )) {
return $true
}
@@ -21,18 +118,40 @@ function Invoke-ArgFuscator {
throw "Make sure the file exists, and has a '.json' extension."
} })]
[string]$InputFile,
[Parameter(Mandatory = $true, ParameterSetName = "FromCommand")]
[ValidateNotNullOrEmpty()]
[string]$Command,
[Parameter(ParameterSetName = "FromCommand")]
[ValidateScript({
$platformPath = Join-Path $PSScriptRoot "models" $_
if (Test-Path $platformPath -PathType Container) {
return $true
}
else {
throw "Platform '$_' not found. Make sure the platform directory exists in the models folder ($platformPath)."
}
})]
[string]$Platform = "windows",
[int]$n = 1
)
<#
.SYNOPSIS
Obfuscates a command provided in a JSON-formatted configuration file.
Obfuscates a command provided in a JSON-formatted configuration file or command string.
.DESCRIPTION
Obfuscates a command provided in a JSON-formatted configuration file by applying specified obfuscation options to the provided command.
Obfuscates a command provided in a JSON-formatted configuration file/command string by applying specified obfuscation options to the provided command.
.PARAMETER InputFile
Specifies the path to the JSON-formatted config file.
.PARAMETER Command
Specifies the command as string.
.PARAMETER Platform
Specifies the platform (windows, linux, macos). Default value is windows.
.PARAMETER n
Specifies the number of obfuscated commands to generate. Default value is 1.
@@ -46,7 +165,26 @@ function Invoke-ArgFuscator {
https://www.github.com/wietze/Invoke-Argfuscator
#>
$JSONData = Get-Content -Encoding UTF8 -Path $InputFile | ConvertFrom-Json;
if ($PSCmdlet.ParameterSetName -eq "FromFile") {
$JSONData = Get-Content -Encoding UTF8 -Path $InputFile | ConvertFrom-Json
}
else {
$CommandData = Invoke-TokeniseCommand $Command
$cmd = $CommandData[0]["command"]
$filePath = Join-Path -Path $PSScriptRoot -ChildPath "\models\$Platform\$cmd.json"
if (Test-Path $filePath) {
$ModelData = Get-Content -Encoding UTF8 -Path $filePath | ConvertFrom-Json
# Create a PSCustomObject that matches the expected format
$JSONData = [PSCustomObject]@{
"command" = ($CommandData | ConvertTo-JSON | ConvertFrom-Json)
"modifiers" = $ModelData.modifiers
}
}
else {
Write-Error("Command '{0}' could not be found in models folder ({1} does not exist)" -f $cmd,$filePath)
return $null
}
}
$ErrorModifiers = @();
for ($i = 0; $i -lt $n; $i++) {
$Tokens = [System.Collections.ArrayList]@();
+68 -24
View File
@@ -25,34 +25,72 @@ This module works on any operating system supporting PowerShell/pwsh; this inclu
* **macOS**: If you have `brew` preinstalled, run `brew install powershell/tap/powershell` to install the latest version of PowerShell. For alternative installation options, refer to Microsoft's [documentation](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-macos).
* **Linux**: Refer to Microsoft's [documentation](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux) to see how you can install PowerShell on your distribution.
### Installation
### Installation & usage
Simply clone or download the contents of this repository to a folder of choice.
1. The simplest way to install this module is via the following PowerShell command:
## Usage
1. Make sure you have a model file, in JSON format. These can be generated via [ArgFuscator.net](https://argfuscator.net/) via the 'Download' option; alternatively, you can obtain raw base files via [GitHub](https://github.com/wietze/Argfuscator.net/tree/main/models).
2. Call `Invoke-ArgFuscator.ps1` via PowerShell, e.g.:
```bash
# Windows
powershell .\Invoke-ArgFuscator.ps1
# macOS and Linux
pwsh ./Invoke-ArgFuscator.ps1
```pwsh
Install-Module -Name Invoke-ArgFuscator
```
This will allow you to pass the path to the model file interactively.
2. To use the module, call the function `Invoke-ArgFuscator` from within PowerShell, for example:
***Alternatively***, pass the path to the model file as command-line argument, e.g.:
a. To pass a command line you want to obfuscate as a command-line argument (assuming it is supported by [ArgFuscator.net](https://github.com/wietze/Argfuscator.net)):
```bash
# Windows
powershell .\Invoke-ArgFuscator.ps1 "path\to\file.json"
```bash
# Windows
powershell /c "Invoke-ArgFuscator -Command 'certutil /f /urlcache https://www.example.org/ homepage.txt'"
# macOS and Linux
pwsh ./Invoke-ArgFuscator.ps1 "path/to/file.json"
```
# macOS and Linux
pwsh -c "Invoke-ArgFuscator -Command 'certutil /f /urlcache https://www.example.org/ homepage.txt'"
```
b. To use your own model files[^1]:
```bash
# Windows
powershell /c "Invoke-ArgFuscator -InputFile path\to\file.json"
# macOS and Linux
pwsh -c "Invoke-ArgFuscator -InputFile path/to/file.json"
```
## Local Development
1. Clone this repository to your device.
2. Call `Invoke-ArgFuscator.ps1` via PowerShell, for example:
a. To run interactively pass the path of a model file[^1] via the standard input (stdin):
```bash
# Windows
powershell .\Invoke-ArgFuscator.ps1
# macOS and Linux
pwsh ./Invoke-ArgFuscator.ps1
```
b. To pass the path to the model file[^1] as a command-line argument:
```bash
# Windows
powershell .\Invoke-ArgFuscator.ps1 -InputFile "path\to\file.json"
# macOS and Linux
pwsh ./Invoke-ArgFuscator.ps1 -InputFile "path/to/file.json"
```
c. To pass a command line you want to obfuscate as a command-line argument:
*Note that this requires the [models/](https://github.com/wietze/ArgFuscator.net/tree/main/models) folder to be present in the same folder as `Invoke-ArgFuscator.ps1`.*
```bash
# Windows
powershell .\Invoke-ArgFuscator.ps1 -Command "certutil /f /urlcache https://www.example.org/ homepage.txt"
# macOS and Linux
pwsh ./Invoke-ArgFuscator.ps1 -Command "certutil /f /urlcache https://www.example.org/ homepage.txt"
```
## Integration
@@ -61,13 +99,19 @@ Because Invoke-ArgFuscator is a PowerShell module, you can add this project's fu
To leverage Invoke-ArgFuscator, add
```pwsh
Import-Module ./Invoke-ArgFuscator.psm1
Import-Module Invoke-ArgFuscator
```
to your PowerShell file, and call it as follows:
to your PowerShell file, and call it as either of the following:
```pwsh
Invoke-ArgFuscator -InputFile $InputFile -n $n
Invoke-ArgFuscator -Command $Command -Platform $Platform -n $n
```
with `$InputFile` a `string` containing a (relative/absolute) file path to the model file, and `$n` an `integer` greater than 0 for the number of obfuscated command-line equivalents that should be produced.
with
* `$InputFile` a `string` containing a (relative/absolute) file path to the model file, and `$n` an `integer` greater than 0 for the number of obfuscated command-line equivalents that should be produced (optional); or,
* `$Command` a `string` containing the command line you wish to obfuscate, `$Platform` a `string` with the relevant platform (e.g. `windows`, optional), and `$n` an `integer` greater than 0 for the number of obfuscated command-line equivalents that should be produced (optional).
[^1]: These can be generated via [ArgFuscator.net](https://argfuscator.net/) via the 'Download' option, or downloaded from [GitHub](https://github.com/wietze/Argfuscator.net/tree/main/models).