winsudo

package module
v1.0.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 23, 2021 License: MIT Imports: 19 Imported by: 1

README

winsudo

PkgGoDev GoReport GoLang .github/workflows/main.yml semantic-release Conventional Commits KeepAChangelog License

Package winsudo is a sudo "like" framework for dealing with Windows UAC.

Originally inspired by https://gist.github.com/jerblack/d0eb182cc5a1c1d92d92a4c4fcc416c6

No Encryption Yet!

WARNING, WARNING, WARNING

Encryption between both "parent" & "child" has not been implemented. So possible bad actors on the local TCP network stack might be able to compromise this.

Consider this a Proof of Concept, your milage may vary, use at your own risk, you have been warned!

Installation

Direct download

Go to https://github.com/brad-jones/winsudo/releases and download the archive for your Operating System, extract the binary and and add it to your $PATH.

Scoop

https://scoop.sh

scoop bucket add brad-jones https://github.com/brad-jones/scoop-bucket.git;
scoop install winsudo;

Example Usage

Can execute any binary in an elevated context:

sudo ping 1.1.1.1

Can execute arbitrary PowerShell like this:

sudo Start-Service foobar

Can also execute powershell and batch scripts like:

sudo ./my-script.ps1
sudo ./my-script.bat
sudo ./my-script.cmd

How it Works

If you are running in an already elevated context, then the sudo binary will simply execute a child process and stream STDIN/STDOUT/STDERR in the normal manner.

If however you call sudo in an unprivileged context then, sudo creates a child process of it's self - like forking sort of, kind of, not really... it does this by means of the ShellExecute function from kernel32.dll and the runas verb.

see: https://bit.ly/31u4Iw1 also: https://docs.microsoft.com/en-us/windows/win32/shell/launch

I'm no low level Windows dev so take this with a grain of salt but as far as I can tell ShellExecute does not allow the parent process to attach to the child processes stdio streams in the usual way, essentially because a brand new console is created.

There are other calls like FreeConsole & AttachConsole which might be able to be used to get the desired results. For example what sudo.ps1 (of which I discovered after building most of this) does but I was unsuccessful in getting those calls to work from Go.

Anyway so there are now 2 instances of sudo running, the "parent" & the "child". Where the "parent" is unprivileged and the "child" is privileged.

When the "parent" started the "child" it passed a TCP port number of a gRPC server that it also started at the same time. So now the "child" can communicate with the "parent" via gRPC.

The "child" then executes another child process, the actual process that you wanted to run, and streams it's STDIO back to the "parent" sudo process via gRPC.

The end result is something a-kin to https://www.sudo.ws/ on a *nix OS.

NOTE: This does not bypass UAC, the user will still receive a UAC prompt, this tool just makes the UX friendlier by not having multiple console windows shown & connects the stdio back to the unprivileged caller like you would intuitively expect.

Programmatic Usage

This package does expose an API that you might decide to use in your own custom Go projects.

Essentially this allows you to run any arbitrary Go code you would like in an elevated context. You just need to supply your own gRPC Server & Client.

package main

import (
	"github.com/brad-jones/winsudo"
)

func main() {
	if err := winsudo.ElevatedFork(
		func(s *grpc.Server) error { /* ... */ },
		func(conn *grpc.ClientConn) { /* ... */ },
	); err != nil {
		panic(err)
	}
}

For more details refer to the docs at: https://pkg.go.dev/github.com/brad-jones/winsudo

And the actual implementation of the sudo binary should be a reasonable example to follow:
https://github.com/brad-jones/winsudo/blob/master/cmd/sudo/main.go

Documentation

Rendered for windows/amd64

Overview

Package winsudo is a sudo "like" framework for dealing with Windows UAC. credit: https://gist.github.com/jerblack/d0eb182cc5a1c1d92d92a4c4fcc416c6

I later came across this PowerShell script: https://github.com/lukesampson/psutils/blob/master/sudo.ps1

I even tried to use the FreeConsole() & AttachConsole(pid) methods from kernel32.dll but I couldn't make it work in the same way as the PowerShell, hence this gRPC approach.

Also intresting: https://stackoverflow.com/questions/62759757

I guess the advantage of using this over the PowerShell script is the performance / latency. Starting PowerShell isn't exactly fast. Maybe one day I'll do some benchmarks.

This also allows you to run any arbitrary go code in an elevated environment so thats something I guess.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Elevate

func Elevate(config *ElevateConfig) (err error)

Elevate is a wrapper around the lower level "ShellExecute" function.

To relaunch the tool as Admin with a UAC prompt, I used the ShellExecute function in the golang.org/x/sys/windows package, using the "runas" verb that I learned about from here: https://bit.ly/31u4Iw1

func ElevatedExec

func ElevatedExec(cmd string, decorators ...func(*exec.Cmd) error) (exitCode int, err error)

ElevatedExec wraps ElevatedFork to allow you to execute any executable in an elevated context. It uses the same API as "github.com/brad-jones/goexec/v2".

For example usage refer to "cmd/sudo/main.go".

func ElevatedFork

func ElevatedFork(setup func(*grpc.Server) error, action func(*grpc.ClientConn)) (err error)

ElevatedFork wraps Elevate to provide a fork-like pattern.

ShellExecute does not provide a way to connect to the underlying process in the normal manner, via STDIN/STDOUT/STDERR. So we use GRPC to communicate between the unprivileged parent process and the privilaged child process.

The first function allows you to register a custom grpc server.

The second function will be run by the privilaged child process and is passed a grpc client connection. You can then initate your grpc client with that connection.

For example usage refer to the ElevatedExec function.

func IsElevated

func IsElevated() bool

IsElevated returns true if the current process is privilaged otherwise false.

I found this post on Reddit that recommended attempting to os.Open \\.\PHYSICALDRIVE0 which is not something that is virtualized, and this worked well for my purpose.

https://bit.ly/3waufc2

func StripParentArg

func StripParentArg(args []string) []string

StripParentArg removes the "--winsudoParent <port>" arguments from the given string slice, usually os.Args.

Types

type ElevateConfig

type ElevateConfig struct {
	// The path to the executable to run in an elevated environment
	Exe string

	// The arguments to pass to the executable
	Args []string

	// The initial working directory for the new process
	Cwd string

	// If true then a Window will not be displayed on the
	// desktop even if the application is a GUI.
	Hidden bool
}

type ElevatedForkResults

type ElevatedForkResults struct {
	// An exit code from the privilaged child process
	ExitCode int
}

Directories

Path Synopsis
cmd
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL