Skip to content

A sudo "like" framework for dealing with Windows UAC.

License

Notifications You must be signed in to change notification settings

brad-jones/winsudo

Repository files navigation

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