BowTiedGnome
BowTiedGnome5mo 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
BowTiedGnomeOP5mo 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
AapoAlas5mo 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

Did you find this page helpful?