486 lines
11 KiB
Go
486 lines
11 KiB
Go
package system
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/amidaware/rmmagent/agent/utils"
|
|
ps "github.com/elastic/go-sysinfo"
|
|
"github.com/go-ole/go-ole"
|
|
"github.com/go-ole/go-ole/oleutil"
|
|
wapf "github.com/wh1te909/go-win64api"
|
|
trmm "github.com/wh1te909/trmm-shared"
|
|
"golang.org/x/sys/windows"
|
|
"golang.org/x/sys/windows/registry"
|
|
)
|
|
|
|
const (
|
|
ProgFilesName = "TacticalAgent"
|
|
winExeName = "tacticalrmm.exe"
|
|
)
|
|
|
|
func RunScript(code string, shell string, args []string, timeout int) (stdout, stderr string, exitcode int, e error) {
|
|
content := []byte(code)
|
|
dir := filepath.Join(os.TempDir(), "trmm")
|
|
if !trmm.FileExists(dir) {
|
|
utils.CreateTRMMTempDir()
|
|
}
|
|
|
|
const defaultExitCode = 1
|
|
var (
|
|
outb bytes.Buffer
|
|
errb bytes.Buffer
|
|
exe string
|
|
ext string
|
|
cmdArgs []string
|
|
)
|
|
|
|
switch shell {
|
|
case "powershell":
|
|
ext = "*.ps1"
|
|
case "python":
|
|
ext = "*.py"
|
|
case "cmd":
|
|
ext = "*.bat"
|
|
}
|
|
|
|
tmpfn, err := ioutil.TempFile(dir, ext)
|
|
if err != nil {
|
|
return "", err.Error(), 85, err
|
|
}
|
|
|
|
defer os.Remove(tmpfn.Name())
|
|
if _, err := tmpfn.Write(content); err != nil {
|
|
return "", err.Error(), 85, err
|
|
}
|
|
|
|
if err := tmpfn.Close(); err != nil {
|
|
return "", err.Error(), 85, err
|
|
}
|
|
|
|
switch shell {
|
|
case "powershell":
|
|
exe = "Powershell"
|
|
cmdArgs = []string{"-NonInteractive", "-NoProfile", "-ExecutionPolicy", "Bypass", tmpfn.Name()}
|
|
case "python":
|
|
exe = GetPythonBin()
|
|
cmdArgs = []string{tmpfn.Name()}
|
|
case "cmd":
|
|
exe = tmpfn.Name()
|
|
}
|
|
|
|
if len(args) > 0 {
|
|
cmdArgs = append(cmdArgs, args...)
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
|
defer cancel()
|
|
var timedOut bool = false
|
|
cmd := exec.Command(exe, cmdArgs...)
|
|
cmd.Stdout = &outb
|
|
cmd.Stderr = &errb
|
|
if cmdErr := cmd.Start(); cmdErr != nil {
|
|
return "", cmdErr.Error(), 65, cmdErr
|
|
}
|
|
|
|
pid := int32(cmd.Process.Pid)
|
|
|
|
// custom context handling, we need to kill child procs if this is a batch script,
|
|
// otherwise it will hang forever
|
|
// the normal exec.CommandContext() doesn't work since it only kills the parent process
|
|
go func(p int32) {
|
|
|
|
<-ctx.Done()
|
|
|
|
_ = utils.KillProc(p)
|
|
timedOut = true
|
|
}(pid)
|
|
|
|
cmdErr := cmd.Wait()
|
|
if timedOut {
|
|
stdout = utils.CleanString(outb.String())
|
|
stderr = fmt.Sprintf("%s\nScript timed out after %d seconds", utils.CleanString(errb.String()), timeout)
|
|
exitcode = 98
|
|
//a.Logger.Debugln("Script check timeout:", ctx.Err())
|
|
} else {
|
|
stdout = utils.CleanString(outb.String())
|
|
stderr = utils.CleanString(errb.String())
|
|
|
|
// get the exit code
|
|
if cmdErr != nil {
|
|
if exitError, ok := cmdErr.(*exec.ExitError); ok {
|
|
if ws, ok := exitError.Sys().(syscall.WaitStatus); ok {
|
|
exitcode = ws.ExitStatus()
|
|
} else {
|
|
exitcode = defaultExitCode
|
|
}
|
|
} else {
|
|
exitcode = defaultExitCode
|
|
}
|
|
|
|
} else {
|
|
if ws, ok := cmd.ProcessState.Sys().(syscall.WaitStatus); ok {
|
|
exitcode = ws.ExitStatus()
|
|
} else {
|
|
exitcode = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
return stdout, stderr, exitcode, nil
|
|
}
|
|
|
|
func CMD(exe string, args []string, timeout int, detached bool) (output [2]string, e error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
|
defer cancel()
|
|
|
|
var outb, errb bytes.Buffer
|
|
cmd := exec.CommandContext(ctx, exe, args...)
|
|
if detached {
|
|
cmd.SysProcAttr = &windows.SysProcAttr{
|
|
CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP,
|
|
}
|
|
}
|
|
cmd.Stdout = &outb
|
|
cmd.Stderr = &errb
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return [2]string{"", ""},
|
|
fmt.Errorf("%s: %s", err, utils.CleanString(errb.String()))
|
|
}
|
|
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return [2]string{"", ""}, ctx.Err()
|
|
}
|
|
|
|
return [2]string{
|
|
utils.CleanString(outb.String()),
|
|
utils.CleanString(errb.String()),
|
|
}, nil
|
|
}
|
|
|
|
func SetDetached() *syscall.SysProcAttr {
|
|
return &syscall.SysProcAttr{
|
|
CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP,
|
|
}
|
|
}
|
|
|
|
func CMDShell(shell string, cmdArgs []string, command string, timeout int, detached bool) (output [2]string, e error) {
|
|
var (
|
|
outb bytes.Buffer
|
|
errb bytes.Buffer
|
|
cmd *exec.Cmd
|
|
timedOut = false
|
|
)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
|
defer cancel()
|
|
|
|
if len(cmdArgs) > 0 && command == "" {
|
|
switch shell {
|
|
case "cmd":
|
|
cmdArgs = append([]string{"/C"}, cmdArgs...)
|
|
cmd = exec.Command("cmd.exe", cmdArgs...)
|
|
case "powershell":
|
|
cmdArgs = append([]string{"-NonInteractive", "-NoProfile"}, cmdArgs...)
|
|
cmd = exec.Command("powershell.exe", cmdArgs...)
|
|
}
|
|
} else {
|
|
switch shell {
|
|
case "cmd":
|
|
cmd = exec.Command("cmd.exe")
|
|
cmd.SysProcAttr = &windows.SysProcAttr{
|
|
CmdLine: fmt.Sprintf("cmd.exe /C %s", command),
|
|
}
|
|
case "powershell":
|
|
cmd = exec.Command("Powershell", "-NonInteractive", "-NoProfile", command)
|
|
}
|
|
}
|
|
|
|
// https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
|
|
if detached {
|
|
cmd.SysProcAttr = &windows.SysProcAttr{
|
|
CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP,
|
|
}
|
|
}
|
|
cmd.Stdout = &outb
|
|
cmd.Stderr = &errb
|
|
cmd.Start()
|
|
|
|
pid := int32(cmd.Process.Pid)
|
|
|
|
go func(p int32) {
|
|
|
|
<-ctx.Done()
|
|
|
|
_ = utils.KillProc(p)
|
|
timedOut = true
|
|
}(pid)
|
|
|
|
err := cmd.Wait()
|
|
|
|
if timedOut {
|
|
return [2]string{
|
|
utils.CleanString(outb.String()),
|
|
utils.CleanString(errb.String())},
|
|
ctx.Err()
|
|
}
|
|
|
|
if err != nil {
|
|
return [2]string{
|
|
utils.CleanString(outb.String()),
|
|
utils.CleanString(errb.String())},
|
|
err
|
|
}
|
|
|
|
return [2]string{
|
|
utils.CleanString(outb.String()),
|
|
utils.CleanString(errb.String())},
|
|
nil
|
|
}
|
|
|
|
func GetProgramDirectory() string {
|
|
pd := filepath.Join(os.Getenv("ProgramFiles"), ProgFilesName)
|
|
return pd
|
|
}
|
|
|
|
func GetProgramEXE() string {
|
|
exe := filepath.Join(GetProgramDirectory(), winExeName)
|
|
return exe
|
|
}
|
|
|
|
func GetPythonBin() string {
|
|
var pybin string
|
|
switch runtime.GOARCH {
|
|
case "amd64":
|
|
pybin = filepath.Join(GetProgramDirectory(), "py38-x64", "python.exe")
|
|
case "386":
|
|
pybin = filepath.Join(GetProgramDirectory(), "py38-x32", "python.exe")
|
|
}
|
|
|
|
return pybin
|
|
}
|
|
|
|
// LoggedOnUser returns the first logged on user it finds
|
|
func LoggedOnUser() string {
|
|
pyCode := `
|
|
import psutil
|
|
|
|
try:
|
|
u = psutil.users()[0].name
|
|
if u.isascii():
|
|
print(u, end='')
|
|
else:
|
|
print('notascii', end='')
|
|
except Exception as e:
|
|
print("None", end='')
|
|
|
|
`
|
|
// try with psutil first, if fails, fallback to golang
|
|
user, err := RunPythonCode(pyCode, 5, []string{})
|
|
if err == nil && user != "notascii" {
|
|
return user
|
|
}
|
|
|
|
users, err := wapf.ListLoggedInUsers()
|
|
if err != nil {
|
|
//a.Logger.Debugln("LoggedOnUser error", err)
|
|
return "None"
|
|
}
|
|
|
|
if len(users) == 0 {
|
|
return "None"
|
|
}
|
|
|
|
for _, u := range users {
|
|
// remove the computername or domain
|
|
return strings.Split(u.FullUser(), `\`)[1]
|
|
}
|
|
|
|
return "None"
|
|
}
|
|
|
|
func PlatVer() (string, error) {
|
|
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.ALL_ACCESS)
|
|
if err != nil {
|
|
return "n/a", err
|
|
}
|
|
|
|
defer k.Close()
|
|
dv, _, err := k.GetStringValue("DisplayVersion")
|
|
if err == nil {
|
|
return dv, nil
|
|
}
|
|
|
|
relid, _, err := k.GetStringValue("ReleaseId")
|
|
if err != nil {
|
|
return "n/a", err
|
|
}
|
|
|
|
return relid, nil
|
|
}
|
|
|
|
// EnablePing enables ping
|
|
func EnablePing() {
|
|
args := make([]string, 0)
|
|
cmd := `netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow`
|
|
_, err := CMDShell("cmd", args, cmd, 10, false)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
// EnableRDP enables Remote Desktop
|
|
func EnableRDP() {
|
|
k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Terminal Server`, registry.ALL_ACCESS)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
defer k.Close()
|
|
|
|
err = k.SetDWordValue("fDenyTSConnections", 0)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
|
|
args := make([]string, 0)
|
|
cmd := `netsh advfirewall firewall set rule group="remote desktop" new enable=Yes`
|
|
_, cerr := CMDShell("cmd", args, cmd, 10, false)
|
|
if cerr != nil {
|
|
fmt.Println(cerr)
|
|
}
|
|
}
|
|
|
|
// DisableSleepHibernate disables sleep and hibernate
|
|
func DisableSleepHibernate() {
|
|
k, _, err := registry.CreateKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Power`, registry.ALL_ACCESS)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
defer k.Close()
|
|
|
|
err = k.SetDWordValue("HiberbootEnabled", 0)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
|
|
args := make([]string, 0)
|
|
|
|
var wg sync.WaitGroup
|
|
currents := []string{"ac", "dc"}
|
|
for _, i := range currents {
|
|
wg.Add(1)
|
|
go func(c string) {
|
|
defer wg.Done()
|
|
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /set%svalueindex scheme_current sub_buttons lidaction 0", c), 5, false)
|
|
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -standby-timeout-%s 0", c), 5, false)
|
|
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -hibernate-timeout-%s 0", c), 5, false)
|
|
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -disk-timeout-%s 0", c), 5, false)
|
|
_, _ = CMDShell("cmd", args, fmt.Sprintf("powercfg /x -monitor-timeout-%s 0", c), 5, false)
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
_, _ = CMDShell("cmd", args, "powercfg -S SCHEME_CURRENT", 5, false)
|
|
}
|
|
|
|
// NewCOMObject creates a new COM object for the specifed ProgramID.
|
|
func NewCOMObject(id string) (*ole.IDispatch, error) {
|
|
unknown, err := oleutil.CreateObject(id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to create initial unknown object: %v", err)
|
|
}
|
|
defer unknown.Release()
|
|
|
|
obj, err := unknown.QueryInterface(ole.IID_IDispatch)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to create query interface: %v", err)
|
|
}
|
|
|
|
return obj, nil
|
|
}
|
|
|
|
// SystemRebootRequired checks whether a system reboot is required.
|
|
func SystemRebootRequired() (bool, error) {
|
|
regKeys := []string{
|
|
`SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired`,
|
|
}
|
|
|
|
for _, key := range regKeys {
|
|
k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.QUERY_VALUE)
|
|
if err == nil {
|
|
k.Close()
|
|
return true, nil
|
|
} else if err != registry.ErrNotExist {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func KillHungUpdates() {
|
|
procs, err := ps.Processes()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, process := range procs {
|
|
p, err := process.Info()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if strings.Contains(p.Exe, "winagent-v") {
|
|
//a.Logger.Debugln("killing process", p.Exe)
|
|
utils.KillProc(int32(p.PID))
|
|
}
|
|
}
|
|
}
|
|
|
|
func OsString() string {
|
|
host, _ := ps.Host()
|
|
info := host.Info()
|
|
osInf := info.OS
|
|
|
|
var arch string
|
|
switch info.Architecture {
|
|
case "x86_64":
|
|
arch = "64 bit"
|
|
case "x86":
|
|
arch = "32 bit"
|
|
}
|
|
|
|
var osFullName string
|
|
platver, err := PlatVer()
|
|
if err != nil {
|
|
osFullName = fmt.Sprintf("%s, %s (build %s)", osInf.Name, arch, osInf.Build)
|
|
} else {
|
|
osFullName = fmt.Sprintf("%s, %s v%s (build %s)", osInf.Name, arch, platver, osInf.Build)
|
|
}
|
|
|
|
return osFullName
|
|
}
|
|
|
|
func AddDefenderExlusions() error {
|
|
code := `
|
|
Add-MpPreference -ExclusionPath 'C:\Program Files\TacticalAgent\*'
|
|
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\winagent-v*.exe'
|
|
Add-MpPreference -ExclusionPath 'C:\Windows\Temp\trmm\*'
|
|
Add-MpPreference -ExclusionPath 'C:\Program Files\Mesh Agent\*'
|
|
`
|
|
_, _, _, err := RunScript(code, "powershell", []string{}, 20)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|