I'm writing a series of tools I had previously written in Python, I need to have my Nim versions run themselves as Admin (escalate privs) if they are not currently running as admin. I started to re-write admin.py in Nim, but really all I need to do is figure out how to use ShellExecuteEx() from shellapi.nim passing lpVerb='runas'. I have the following links to go off for implementing this: Detecting UAC - SO and Python UAC Elevation - SO I'm having a problem understanding ShellExecuteEx enough to write a PoC to test elevation at the moment.
Any direction, code examples, or resource links (besides that of the shellapi module) would be greatly appreciated.
I played around some time with ShellExecuteW and UAC. Not sure if the code runs with the latest Nim version. It was one of my first attempts with Nim, so probably it can be improved a lot.
import windows
import strutils
import utils
import os
type
TOKEN_ELEVATION {.final, pure.} = object
TokenIsElevated*: DWORD
TOK_INFO_CLASS {.pure.} = enum
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin,
TokenElevationType,
TokenLinkedToken,
TokenElevation,
TokenHasRestrictions,
TokenAccessInformation,
TokenVirtualizationAllowed,
TokenVirtualizationEnabled,
TokenIntegrityLevel,
TokenUIAccess,
TokenMandatoryPolicy,
TokenLogonSid,
TokenIsAppContainer,
TokenCapabilities,
TokenAppContainerSid,
TokenAppContainerNumber,
TokenUserClaimAttributes,
TokenDeviceClaimAttributes,
TokenRestrictedUserClaimAttributes,
TokenRestrictedDeviceClaimAttributes,
TokenDeviceGroups,
TokenRestrictedDeviceGroups,
TokenSecurityAttributes,
TokenIsRestricted,
MaxTokenInfoClass
proc GetTokenInformation(TokenHandle: HANDLE;
TokenInformationClass: TOK_INFO_CLASS;
TokenInformation: LPVOID;
TokenInformationLength: DWORD; ReturnLength: PDWORD): WINBOOL {.
stdcall, dynlib: "advapi32", importc: "GetTokenInformation".}
proc isUserElevated*(): bool =
var
tokenHandle: HANDLE
elevation = TOKEN_ELEVATION()
cbsize: DWORD = 0
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, cast[PHANDLE](addr(tokenHandle))) == 0:
raise newException(Exception, "Cannot query tokens ($1)" % [$GetLastError()])
if GetTokenInformation(tokenHandle, TOK_INFO_CLASS.TokenElevation, cast[LPVOID](addr(elevation)), cast[DWORD](sizeOf(elevation)), cast[PDWORD](addr(cbsize))) == 0:
let lastError = GetLastError()
discard CloseHandle(tokenHandle)
raise newException(Exception, "Cannot retrieve token information ($1)" % [$lastError])
result = elevation.TokenIsElevated != 0
proc elevate*() =
var
pathToExe: WideCString
verb = newWideCString("runas")
params: WideCString
pathToExe = newWideCString(getAppFilename())
params = newWideCString(commandLineParams().join(" "))
var lastError = ShellExecuteW(0, cast[LPWSTR](addr verb[0]), cast[LPWSTR](addr pathToExe[0]), cast[LPWSTR](addr params[0]), cast[LPWSTR](0), SW_SHOWNORMAL)
if lastError <= 32:
raise newException(Exception, "Cannot elevate $1 ($2)" % [$pathToExe, $lastError])
I'm going to test this on my other computer, but the code is compiling when i remove import utils. When I do the below I just get a bunch of the same process spawned continuously and I'm not sure where I'm messing up. I think I need to step away from this a while, I'm not even understanding the purpose of all of these variables but that shows my lack of knowledge with Windows internals.
Let's say i added the below code to your above file instead of using it as an import
when isMainModule:
if not isUserElevated():
elevate()
else:
doAdminStuffHere()
So it launches so many instances of itself and none of them are getting the permissions needed to delete the file I'm testing with. I need to test this on a machine that isn't messed up.
I really appreciate the code you've contributed.
I tested this code and it works for me. The executable is not called infinitely. I compiled it with Nim 0.10.3.
import windows
import strutils
import os
type
TOKEN_ELEVATION {.final, pure.} = object
TokenIsElevated*: DWORD
TOK_INFO_CLASS {.pure.} = enum
TokenUser = 1,
TokenGroups,
TokenPrivileges,
TokenOwner,
TokenPrimaryGroup,
TokenDefaultDacl,
TokenSource,
TokenType,
TokenImpersonationLevel,
TokenStatistics,
TokenRestrictedSids,
TokenSessionId,
TokenGroupsAndPrivileges,
TokenSessionReference,
TokenSandBoxInert,
TokenAuditPolicy,
TokenOrigin,
TokenElevationType,
TokenLinkedToken,
TokenElevation,
TokenHasRestrictions,
TokenAccessInformation,
TokenVirtualizationAllowed,
TokenVirtualizationEnabled,
TokenIntegrityLevel,
TokenUIAccess,
TokenMandatoryPolicy,
TokenLogonSid,
TokenIsAppContainer,
TokenCapabilities,
TokenAppContainerSid,
TokenAppContainerNumber,
TokenUserClaimAttributes,
TokenDeviceClaimAttributes,
TokenRestrictedUserClaimAttributes,
TokenRestrictedDeviceClaimAttributes,
TokenDeviceGroups,
TokenRestrictedDeviceGroups,
TokenSecurityAttributes,
TokenIsRestricted,
MaxTokenInfoClass
proc GetTokenInformation(TokenHandle: HANDLE;
TokenInformationClass: TOK_INFO_CLASS;
TokenInformation: LPVOID;
TokenInformationLength: DWORD; ReturnLength: PDWORD): WINBOOL {.
stdcall, dynlib: "advapi32", importc: "GetTokenInformation".}
proc isUserElevated(): bool =
var
tokenHandle: HANDLE
elevation = TOKEN_ELEVATION()
cbsize: DWORD = 0
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, cast[PHANDLE](addr(tokenHandle))) == 0:
raise newException(Exception, "Cannot query tokens ($1)" % [$GetLastError()])
if GetTokenInformation(tokenHandle, TOK_INFO_CLASS.TokenElevation, cast[LPVOID](addr(elevation)), cast[DWORD](sizeOf(elevation)), cast[PDWORD](addr(cbsize))) == 0:
let lastError = GetLastError()
discard CloseHandle(tokenHandle)
raise newException(Exception, "Cannot retrieve token information ($1)" % [$lastError])
result = elevation.TokenIsElevated != 0
proc elevate() =
var
pathToExe: WideCString
verb = newWideCString("runas")
params: WideCString
pathToExe = newWideCString(getAppFilename())
params = newWideCString(commandLineParams().join(" "))
var lastError = ShellExecuteW(0, cast[LPWSTR](addr verb[0]), cast[LPWSTR](addr pathToExe[0]), cast[LPWSTR](addr params[0]), cast[LPWSTR](0), SW_SHOWNORMAL)
if lastError <= 32:
raise newException(Exception, "Cannot elevate $1 ($2)" % [$pathToExe, $lastError])
when isMainModule:
echo "Start"
if not isUserElevated():
elevate()
echo "Now elevating!"
else:
echo "Already elevated!"
var line: TaintedString = ""
discard stdin.readLine(line)
Uh, @singularity, you might want to add a {.size:sizeof(cint).} pragma to that enum. (I think that's how the pragma goes. I could be wrong)
Technically, the proper way to do this is embed a manifest in your executable.