Created
December 1, 2019 18:10
-
-
Save jerblack/74804f17a5b4bbb1948d5925c077eb9f to your computer and use it in GitHub Desktop.
GoLang: Using PendingFileRenameOperations in the Windows Registry to delete/rename files on reboot with Go.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"golang.org/x/sys/windows" | |
"golang.org/x/sys/windows/registry" | |
"log" | |
"os" | |
"strings" | |
"syscall" | |
) | |
/* | |
Windows provides a mechanism for deleting and renaming files that are currently in use through the | |
"PendingFileRenameOperations" entry in the registry. This MULTI_SZ (multi string) entry is a sequence of null-terminated (\0) | |
strings alternating between source and destination, each prefixed by `\??\` | |
\??\src_path\0 | |
\??\dst_path\0 | |
\??\src_path\0 | |
\0 | |
\??\src_path\0 | |
\??\dst_path\0 | |
If the destination is empty, the source is deleted. The Golang registry package automatically handles the | |
parsing of these null-terminator characters, and returns an array of strings with the character stripped. | |
To append a value to the "PendingFileRenameOperations", you should first read the key to get any existing | |
values, and then append two additional strings in the proper format. | |
For renames, the two strings would be "\??\src_path" and "\??\dest_path" | |
For deletes, the two strings would be "\??\src_path" and "" | |
In both cases, the formatting with the null-terminator character is handled automatically by the registry package. | |
Windows parses this key during reboots and after handling all of the renames and deletes, it deletes the | |
"PendingFileRenameOperations" entry. When reading this entry for the existing values, account for the situation | |
where it may not exist, as shown in the example. | |
Since this key is part of the HKEY_LOCAL_MACHINE hive, you must be an administrator to write to it. You can | |
do this by running the example from an command prompt opened with "Run as Administrator" or just accept the UAC | |
prompt that this example will generate. If the example detects that it is not elevated, it will relaunch as | |
elevated and prompt you with UAC to allow it. | |
After running the example, the "PendingFileRenameOperations" entry will be created in the | |
"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager" key if it does not exist, | |
and an additional value will be appended to the entry referencing FileToDelete.tmp, followed by an empty string. | |
*/ | |
const ( | |
regHive = registry.LOCAL_MACHINE | |
regKey = `SYSTEM\CurrentControlSet\Control\Session Manager` | |
regName = `PendingFileRenameOperations` | |
regType = registry.MULTI_SZ | |
) | |
func main() { | |
// Relaunch as Admin via UAC prompt is running as standard user | |
if !amIAdmin() { | |
runMeAdmin() | |
return | |
} | |
defer func() { | |
// UAC likely launched this in a new console window to run as admin, so this keeps the window up | |
// If you can see <press enter to continue> there were no errors | |
fmt.Println("<press enter to continue>") | |
var input string | |
_, _ = fmt.Scanln(&input) | |
}() | |
// Get existing values | add filename to delete with prefix to slice | add empty string to slice | |
existingVals := readKey() | |
prefix := `\??\` | |
fName := `C:\Windows\Temp\FileToDelete.tmp` | |
newVal := append(existingVals, prefix + fName, "") | |
// write new slice to registry | |
writeKey(newVal) | |
} | |
func readKey() []string { | |
k, err := registry.OpenKey(regHive, regKey, registry.QUERY_VALUE) | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer k.Close() | |
regData, _, err := k.GetStringsValue(regName) | |
if err == registry.ErrNotExist { | |
return []string{} | |
} | |
if err != nil { | |
log.Fatal(err) | |
} | |
return regData | |
} | |
func writeKey(vals []string) { | |
k, err := registry.OpenKey(regHive, regKey, registry.SET_VALUE) | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer k.Close() | |
err = k.SetStringsValue(regName, vals) | |
if err != nil { | |
log.Fatal(err) | |
} | |
} | |
func amIAdmin() bool { | |
_, err := os.Open("\\\\.\\PHYSICALDRIVE0") | |
admin := err == nil | |
return admin | |
} | |
func runMeAdmin() { | |
verb := "runas" | |
exe, _ := os.Executable() | |
args := strings.Join(os.Args[1:], " ") | |
cwd, _ := os.Getwd() | |
verbPtr, _ := syscall.UTF16PtrFromString(verb) | |
exePtr, _ := syscall.UTF16PtrFromString(exe) | |
cwdPtr, _ := syscall.UTF16PtrFromString(cwd) | |
argPtr, _ := syscall.UTF16PtrFromString(args) | |
var showCmd int32 = 1 //SW_NORMAL | |
err := windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd) | |
if err != nil { | |
log.Fatal(err) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment