BowTiedGnome
BowTiedGnome2mo ago

FFI on windows and handles

I keep hitting an issue of getting a windows error code of 6, which is invalid handle on windows. Are there any extra steps that I'm missing that I need to do for invoking windows APIs with deno FFI that is maybe undocumented?. I can get this to work in other languages, but FFI/interop uses different types. so its not a 1:1 translation.
code is trying to determine if process is elevated/privileged. the following should be run with deno run -A --unstable-ffi ./main.ts will have to split over multiple posts due to discord limits
/**
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess
HANDLE GetCurrentProcess();

https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken
BOOL GetTokenInformation(
[in] HANDLE TokenHandle,
[in] TOKEN_INFORMATION_CLASS TokenInformationClass,
[out, optional] LPVOID TokenInformation,
[in] DWORD TokenInformationLength,
[out] PDWORD ReturnLength
);


https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-gettokeninformation
BOOL OpenProcessToken(
[in] HANDLE ProcessHandle,
[in] DWORD DesiredAccess,
[out] PHANDLE TokenHandle
);
*/


const kernel32 = Deno.dlopen("kernel32.dll", {
GetCurrentProcess: { parameters: [], result: "pointer" },
CloseHandle: { parameters: ["pointer"], result: "i32" },
GetLastError: { parameters: [], result: "i32" }
});

const advapi32 = Deno.dlopen("advapi32.dll", {
OpenProcessToken: {
parameters: ["pointer", "i32", "pointer"],
result: "bool",
},
GetTokenInformation: {
parameters: ["pointer", "u32", "buffer", "u32", "buffer"],
result: "bool",
},
});




/**
https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess
HANDLE GetCurrentProcess();

https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken
BOOL GetTokenInformation(
[in] HANDLE TokenHandle,
[in] TOKEN_INFORMATION_CLASS TokenInformationClass,
[out, optional] LPVOID TokenInformation,
[in] DWORD TokenInformationLength,
[out] PDWORD ReturnLength
);


https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-gettokeninformation
BOOL OpenProcessToken(
[in] HANDLE ProcessHandle,
[in] DWORD DesiredAccess,
[out] PHANDLE TokenHandle
);
*/


const kernel32 = Deno.dlopen("kernel32.dll", {
GetCurrentProcess: { parameters: [], result: "pointer" },
CloseHandle: { parameters: ["pointer"], result: "i32" },
GetLastError: { parameters: [], result: "i32" }
});

const advapi32 = Deno.dlopen("advapi32.dll", {
OpenProcessToken: {
parameters: ["pointer", "i32", "pointer"],
result: "bool",
},
GetTokenInformation: {
parameters: ["pointer", "u32", "buffer", "u32", "buffer"],
result: "bool",
},
});




2 Replies
BowTiedGnome
BowTiedGnome2mo ago
const log = console.log;
enum TokenAccess {
AssignPrimary = 0x00000001,
Duplicate = 0x00000002,
Impersonate = 0x00000004,
Query = 0x00000008,
QuerySource = 0x00000010,
AdjustPrivileges = 0x00000020,
AdjustGroups = 0x00000040,
AdjustDefault = 0x00000080,
AdjustSessionId = 0x00000100,

Read = 0x00020000 | Query,

Write = 0x00020000 | AdjustPrivileges | AdjustGroups | AdjustDefault,

AllAccess = 0x000F0000 |
AssignPrimary |
Duplicate |
Impersonate |
Query |
QuerySource |
AdjustPrivileges |
AdjustGroups |
AdjustDefault |
AdjustSessionId,

MaximumAllowed = 0x02000000
}

const TokenElevation = 20;

const log = console.log;
enum TokenAccess {
AssignPrimary = 0x00000001,
Duplicate = 0x00000002,
Impersonate = 0x00000004,
Query = 0x00000008,
QuerySource = 0x00000010,
AdjustPrivileges = 0x00000020,
AdjustGroups = 0x00000040,
AdjustDefault = 0x00000080,
AdjustSessionId = 0x00000100,

Read = 0x00020000 | Query,

Write = 0x00020000 | AdjustPrivileges | AdjustGroups | AdjustDefault,

AllAccess = 0x000F0000 |
AssignPrimary |
Duplicate |
Impersonate |
Query |
QuerySource |
AdjustPrivileges |
AdjustGroups |
AdjustDefault |
AdjustSessionId,

MaximumAllowed = 0x02000000
}

const TokenElevation = 20;

let code = kernel32.symbols.GetLastError();

// windows api last error code
log("ec", code);

const currentProcessHandle = kernel32.symbols.GetCurrentProcess();
log("currentProcessHandle ptr", Deno.UnsafePointer.value(currentProcessHandle));

// handle
const token = new Uint8Array(8); // using long sing one 64 bit machine, but always returns int.
const tokenHandle = Deno.UnsafePointer.of(token);
// show that its basically a null pointer
log("token ptr addr", Deno.UnsafePointer.value(tokenHandle))
log("token intptr", new DataView(token.buffer).getBigInt64(0, true))

log("")
log("call OpenProcessToken")
if (!advapi32.symbols.OpenProcessToken(currentProcessHandle, TokenAccess.Read, tokenHandle)) {
throw new Error("Failed to open process token");
}

code = kernel32.symbols.GetLastError();
log("ec", code);
log("token ptr addr", Deno.UnsafePointer.value(tokenHandle))
log("token intptr", new DataView(token.buffer).getBigInt64(0, true))

/**
* sequential struct that is 4 bytes
* struct TokenElevation {
* int TokenIsElevanted { get; set; } // int32, 4 bytes, but used as boolean value
* }
*/
const elevation = new Uint8Array(4);
const cbSize = new Uint32Array(1);

log("")
log("call GetTokenInformation")
const pass = advapi32.symbols.GetTokenInformation(
tokenHandle,
TokenElevation,
elevation,
elevation.byteLength,
cbSize
);
code = kernel32.symbols.GetLastError();

const length = cbSize[0];
const elevated = new DataView(elevation.buffer).getInt32(0, true);
log("length", length); // 4, so its updating
log("elevated", elevated); // 0
log("ec", code); // 6, invalid handle
log("success?", pass); // false
log("elevation", elevation); // empty array, null pointer
let code = kernel32.symbols.GetLastError();

// windows api last error code
log("ec", code);

const currentProcessHandle = kernel32.symbols.GetCurrentProcess();
log("currentProcessHandle ptr", Deno.UnsafePointer.value(currentProcessHandle));

// handle
const token = new Uint8Array(8); // using long sing one 64 bit machine, but always returns int.
const tokenHandle = Deno.UnsafePointer.of(token);
// show that its basically a null pointer
log("token ptr addr", Deno.UnsafePointer.value(tokenHandle))
log("token intptr", new DataView(token.buffer).getBigInt64(0, true))

log("")
log("call OpenProcessToken")
if (!advapi32.symbols.OpenProcessToken(currentProcessHandle, TokenAccess.Read, tokenHandle)) {
throw new Error("Failed to open process token");
}

code = kernel32.symbols.GetLastError();
log("ec", code);
log("token ptr addr", Deno.UnsafePointer.value(tokenHandle))
log("token intptr", new DataView(token.buffer).getBigInt64(0, true))

/**
* sequential struct that is 4 bytes
* struct TokenElevation {
* int TokenIsElevanted { get; set; } // int32, 4 bytes, but used as boolean value
* }
*/
const elevation = new Uint8Array(4);
const cbSize = new Uint32Array(1);

log("")
log("call GetTokenInformation")
const pass = advapi32.symbols.GetTokenInformation(
tokenHandle,
TokenElevation,
elevation,
elevation.byteLength,
cbSize
);
code = kernel32.symbols.GetLastError();

const length = cbSize[0];
const elevated = new DataView(elevation.buffer).getInt32(0, true);
log("length", length); // 4, so its updating
log("elevated", elevated); // 0
log("ec", code); // 6, invalid handle
log("success?", pass); // false
log("elevation", elevation); // empty array, null pointer
AapoAlas
AapoAlas2mo ago
I believe you've made a mistake with the handling of tokenHandle parameter: In OpenProcessHandle it is an out pointer of type PHANDLE so a pointer to a HANDLE. In GetTokenInformation it is just a HANDLE but you're still passing in the same Uint8Array. You should be passing in the contents of it, so that which you've logged as token intptr