Skip to content

8. Contributing and Extending SQLRecon

Sanjiv Kawa edited this page Jul 2, 2024 · 1 revision

Adding your own module to SQLRecon is pretty straight forward.

The following example demonstrates extending SQLRecon to add a new SQL server module called demo, which supports standard, impersonation, linked and chain execution. The demo module will take one argument.

The demo module will be executed using the demo command. This module will take an arbitrary SQL query as a command line argument and execute it on a remote SQL server.

1. Register a new Command

The commands/GlobalVariables.cs file contains all of the commands supported by SQLRecon.

To create a SQL server module called demo that takes one argument, update the SqlModulesAndArgumentCount dictionary to include {"demo", new[] { 1, 2, 2 }},. The multi-dimensional array translates to:

  • When executed in a standard context, the demo module needs 1 argument, which is the query.
  • When executed in a impersonation context, the demo module needs 2 arguments, which is the user to impersonate, and the query.
  • When executed in a linked context, the demo module needs 2 arguments, which is the linked SQL server, and the query.

For example:

internal static Dictionary<string, int[]> SqlModulesAndArgumentCount
{
    get 
    {
        return new Dictionary<string, int[]>()
        {          
            {"agentstatus", new[] { 0, 1, 1 }},
            {"checkrpc", new[] { 0, 1, 1 }},
            {"agentcmd", new[] { 1, 2, 2 }},
            {"disablerpc", new[] { 1, 2, -1 }},
            
            ...
            <snipped>
            ...
            
 ---------> {"demo", new[] { 1, 2, 3 }},
            {"enablerpc", new[] { 1, 2, -1 }},
            {"olecmd", new[] { 1, 2, 2 }},
            
            ...
            <snipped>
            ...
        };
    }
}

If impersonation is not supported, then the array would be `{ 1, -1, 2}`, as -1 ensures that this module will not be compatible in an impersonation context.

If execution against a linked or chain link server is not supported, then the array would be `{ 1, 2, -1}`, as -1 ensures that this module will not be compatible in a linked context.

2. Configure Argument Handling

The commands/SqlModules.cs file contains a class called CheckSqlArguments, within this class we can create argument handling for the demo module. The argument that we will pass into the demo module will be the command flag.

/// <summary>
/// Required arguments for the demo module.
/// Checks performed for standard, impersonation, linked, and chained.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
internal static bool Demo(string context)
{
    switch (context)
    {
        case "standard":
            if (Var.ParsedArguments.ContainsKey("command") && !string.IsNullOrEmpty(Var.ParsedArguments["command"]))
            {
                Var.Arg1 = Var.ParsedArguments["command"];
                return true;
            }
            else
            {
                Print.Error("Must supply a command (/c:, /command:) for this module.", true);
                // Go no further. Gracefully exit.
                return false;
            }
        case "impersonation":
            if (Var.ParsedArguments.ContainsKey("command") && !string.IsNullOrEmpty(Var.ParsedArguments["command"]) &&
                Var.ParsedArguments.ContainsKey("iuser") && !string.IsNullOrEmpty(Var.ParsedArguments["iuser"]))
            {
                Var.Arg1 = Var.ParsedArguments["command"];
                return true;
            }
            else
            {
                Print.Error("Must supply a user to impersonate (/i:, /iuser:) and a command for this module (/c, /command:).", true);
                // Go no further. Gracefully exit.
                return false;
            }
        case "linked" or "chained":
            if (Var.ParsedArguments.ContainsKey("command") && !string.IsNullOrEmpty(Var.ParsedArguments["command"]) &&
                Var.ParsedArguments.ContainsKey("link") && !string.IsNullOrEmpty(Var.ParsedArguments["link"]))
            {
                Var.Arg1 = Var.ParsedArguments["command"];
                return true;
            }
            else
            {
                Print.Error("Must supply a linked SQL server (/l:, /link:) " +
                                "and a command for this module (/c, /command:).", true);
                // Go no further. Gracefully exit.
                return false;
            }
        default:
            Print.Error($"'{context}' is not a valid context.", true);
            // Go no further. Gracefully exit.
            return false;
    }   
}

3. Create a new Module

The commands/SqlModules.cs file contains a class called SqlModules which contains methods that execute the desired functionality of commands.

Reflection is used to match the command name against the method name to execute the module. Therefore, when creating method, the method's name must match the command name.

The following example demonstrates creating a method called demo, which will execute when the demo command is provided as a command line argument.

/// <summary>
/// The demo method is used against SQL server to execute a user supplied SQL query.
/// This module supports execution against SQL server using a standard authentication context,
/// impersonation, linked SQL servers, and chained SQL servers.
/// This method needs to be public as reflection is used to match the
/// module name that is supplied via command line, to the actual method name.
/// </summary>
public static void demo()
{
    // Check if the required arguments are in place, otherwise, gracefully exit.
    if (CheckSqlArguments.Demo(Var.Context) == false) return;

    Print.Status($"Executing '{Var.Arg1}'", true);

    switch(Var.Context) 
    { 
        case "standard": 
            _query = Var.Arg1;
            break;
        case "impersonation": 
            _query = Format.ImpersonationQuery(Var.Impersonate, Var.Arg1);
            break;
        case "linked":
            _query = Format.LinkedQuery(Var.LinkedSqlServer, Var.Arg1);
            break;
        case "chained":
            _query = Format.LinkedChainQuery(Var.LinkedSqlServersChain, Var.Arg1);
            break;
        default:
            Print.Error($"'{Var.Context}' is not a valid context.", true);
            break;
    }
    
    Console.WriteLine();
    Print.IsOutputEmpty(Sql.CustomQuery(Var.Connect, _query), true);
}

4. Update the Help Menu

Now that the demo command has been registered, update the help menu in utilities/Help.cs to include a brief description of the command.

5. Test

To validate that the new command has been registered and the module works as expected, compile the project and execute the demo command with a command line argument.

Standard Execution

SQLRecon.exe /a:wintoken /h:SQL01 /m:demo /command:"SELECT @@SERVERNAME";

[*] Executing the 'demo' module on SQL01

[*] Executing 'SELECT @@SERVERNAME'

| column0 |
| ------- |
| SQL01   |

Impersonation Execution

SQLRecon.exe /a:wintoken /h:SQL02 /i:sa /m:demo /command:"SELECT @@SERVERNAME";

[*] Executing the 'demo' module on SQL02 as 'sa'

[*] Executing 'SELECT @@SERVERNAME'

| column0 |
| ------- |
| SQL02   |

Linked Execution

SQLRecon.exe /a:wintoken /h:SQL02 /l:SQL03 /m:demo /command:"SELECT @@SERVERNAME";

[*] Executing the 'demo' on SQL03 via SQL02

[*] Executing 'SELECT @@SERVERNAME'

| column0 |
| ------- |
| SQL03   |

Linked Chain Execution

SQLRecon.exe /a:wintoken /h:SQL01 /l:SQL02,SQL03,MECM01 /chain /m:demo /command:"SELECT @@SERVERNAME";

[*] Setting the chain path to SQL01 -> SQL02 -> SQL03 -> MECM01

[*] Executing the 'demo' module on MECM01

[*] Executing 'SELECT @@SERVERNAME'

| column0 |
| ------- |
| MECM01  |

6. Update Documentation

After testing that the command works you should also update the README.md file, test harnesses and wiki.