Related
Im trying to write memory to a user mode process with kernel driver,
the current address im trying to write memory for is read only, I want to write 4 bytes to the current address,
the thing is if i change protection ( page ) of the process with VirtualProtectEx , it works and it writes the memory but this is only on user mode level, my intention is to change the protection of the process from kernel mode, i want to make it READWRITE, then change it back to READ from kernel space,
Now what I tried to do is giving me a BSOD ( blue screen of death ) with error : Kmode_exception_not_handld
I cant understand what in my code is triggering this BSOD my PC have very limited specs and i cant debug in VM to know..
I will write the code that works but in user mode , and what the code is not working for me in kernel space:
here the code that works:
void dispatch::handler(void* info_struct)
{
PINFO_STRUCT info = (PINFO_STRUCT)info_struct;
if (info->code == CODE_READ_MEMORY)
{
PEPROCESS target_process = NULL;
if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)info->process_id, &target_process)))
{
memory::read_memory(target_process, (void*)info->address, &info->buffer, info->size);
}
DbgPrintEx(0, 0, "[TEST]: Read Memory\n");
}
else if (info->code == CODE_WRITE_MEMORY)
{
PEPROCESS target_process = NULL;
if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)info->process_id, &target_process)))
{
memory::write_memory(target_process, &info->buffer, (void*)info->address, info->size);
}
DbgPrintEx(0, 0, "[TEST]: Write Memory\n");
}
}
NTSTATUS memory::write_memory(PEPROCESS target_process, void* source, void* target, size_t size)
{
if (!target_process) { return STATUS_INVALID_PARAMETER; }
size_t bytes = 0;
NTSTATUS status = MmCopyVirtualMemory(IoGetCurrentProcess(), source, target_process, target, size, KernelMode, &bytes);
if (!NT_SUCCESS(status) || !bytes)
{
return STATUS_INVALID_ADDRESS;
}
return status;
}
int main()
{
DWORD oldprt
ULONG writeTest1 = 3204497152;
VirtualProtectEx(ProcManager::hProcess, (PVOID)(testAddr), 4, PAGE_READWRITE, &oldprt);
driver_control::write_memory(process_id, testAddr, writeTest1);
VirtualProtectEx(ProcManager::hProcess, (PVOID)(testAddr), 4, PAGE_READONLY, &oldprt);
return 0;
}
Now what I want to do is stop using the VirtualProtectEx, and change the PAGE protection to READWRITE from kernel space, so what i did is add this in the dispatch::handler function:
else if (info->code == CODE_WRITE_MEMORY)
{
PEPROCESS target_process = NULL;
if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)info->process_id, &target_process)))
{
PMDL Mdl = IoAllocateMdl((void*)info->address, info->size, FALSE, FALSE, NULL);
if (!Mdl)
return false;
// Locking and mapping memory with RW-rights:
MmProbeAndLockPages(Mdl, KernelMode, IoReadAccess);
PVOID Mapping = MmMapLockedPagesSpecifyCache(Mdl, KernelMode, MmNonCached, NULL, FALSE, NormalPagePriority);
MmProtectMdlSystemAddress(Mdl, PAGE_READWRITE);
memory::write_memory(target_process, &info->buffer, (void*)info->address, info->size);
// Resources freeing:
MmUnmapLockedPages(Mapping, Mdl);
MmUnlockPages(Mdl);
IoFreeMdl(Mdl);
}
DbgPrintEx(0, 0, "[TEST]: Write Read Only Memory\n");
}
So this what I've added caused the BSOD, but why I cannot understand, what am i doing wrong here?
here is the info struct if needed to understand more, :
#define CODE_READ_MEMORY 0x1
#define CODE_WRITE_MEMORY 0x2
typedef struct _INFO_STRUCT
{
ULONG code;
ULONG process_id;
ULONG address;
ULONG buffer;
ULONG size;
}INFO_STRUCT, * PINFO_STRUCT;
any suggestions on solving this problem?
I am currently trying to get the wintun driver to work with my program for simple tunneling (see: https://www.wintun.net/ ).
I successfully find and open the network device, but when it comes to registering the buffer, I get the result ERROR_INVALID_PARAMETER (87). Like I said, opening works just fine and registering is done with SYSTEM privileges (if this is not done, I get ERROR_ACCESS_DENIED (5)).
First attempt was to malloc the ring buffers, but after that did not work I looked at how OpenVPN does it (yes, it added wintun support) and they seem to do with with CreateFileMapping.
First of all, here is my struct:
typedef struct _TUN_RING {
volatile ULONG Head;
volatile ULONG Tail;
volatile LONG Alertable;
UCHAR Data[(1024 * 1024) + 0x10000];
} TUN_RING;
which is according to the docs (https://git.zx2c4.com/wintun/about/ section "Ring Layout). Also its the same as OpenVPN does.
After that I create the file mapping
send_ring_handle_ = CreateFileMapping(INVALID_HANDLE_VALUE,
nullptr,
PAGE_READWRITE,
0,
sizeof(TUN_RING),
nullptr);
recv_ring_handle_ = CreateFileMapping(INVALID_HANDLE_VALUE,
nullptr,
PAGE_READWRITE,
0,
sizeof(TUN_RING),
nullptr);
Then I create the mappings:
send_ring_ = (TUN_RING *)MapViewOfFile(send_ring_handle_,
FILE_MAP_ALL_ACCESS,
0,
0,
sizeof(TUN_RING));
recv_ring_ = (TUN_RING *)MapViewOfFile(recv_ring_handle_,
FILE_MAP_ALL_ACCESS,
0,
0,
sizeof(TUN_RING));
and finally (after impersonating the system user) trying to register it with DeviceIoControl:
TUN_REGISTER_RINGS reg_rings;
memset(®_rings, 0, sizeof(TUN_REGISTER_RINGS));
reg_rings.Send.RingSize = sizeof(TUN_RING);
reg_rings.Send.Ring = send_ring_;
reg_rings.Send.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
reg_rings.Receive.RingSize = sizeof(TUN_RING);
reg_rings.Receive.Ring = recv_ring_;
reg_rings.Receive.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
DWORD len;
if (!DeviceIoControl(tun_fd_,
TUN_IOCTL_REGISTER_RINGS,
®_rings,
sizeof(reg_rings),
nullptr,
0,
&len,
nullptr))
{
printf("Could not register ring buffers (%d).", ::GetLastError());
return false;
}
Can anybody point me to where I am wrong? Like I said, with malloc instead of the file mapping the same error arieses.
I have written a complete example by now using malloc:
#include <windows.h>
#include <winioctl.h>
#include <IPHlpApi.h>
#include <ndisguid.h>
#include <TlHelp32.h>
#include <tchar.h>
#include <securitybaseapi.h>
#include <cfgmgr32.h>
#include <stdint.h>
#include <stdio.h>
#include <string>
#include <assert.h>
#pragma pack(push, 1)
typedef struct _TUN_PACKET_PROTO {
ULONG Size;
UCHAR Data[]; // max packet size as defined by the driver.
} TUN_PACKET_PROTO;
typedef struct _TUN_RING_PROTO {
volatile ULONG Head;
volatile ULONG Tail;
volatile LONG Alertable;
UCHAR Data[];
} TUN_RING_PROTO;
#define TUN_IOCTL_REGISTER_RINGS CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
#define TUN_IOCTL_FORCE_CLOSE_HANDLES CTL_CODE(51820U, 0x971U, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA)
#define WINTUN_RING_CAPACITY 0x800000
#define WINTUN_RING_TRAILING_BYTES 0x10000
#define WINTUN_MAX_PACKET_SIZE 0xffff
#define WINTUN_PACKET_ALIGN 4
/* Memory alignment of packets and rings */
#define TUN_ALIGNMENT sizeof(ULONG)
#define TUN_ALIGN(Size) (((ULONG)(Size) + ((ULONG)TUN_ALIGNMENT - 1)) & ~((ULONG)TUN_ALIGNMENT - 1))
#define TUN_IS_ALIGNED(Size) (!((ULONG)(Size) & ((ULONG)TUN_ALIGNMENT - 1)))
/* Maximum IP packet size */
#define TUN_MAX_IP_PACKET_SIZE 0xFFFF
/* Maximum packet size */
#define TUN_MAX_PACKET_SIZE TUN_ALIGN(sizeof(TUN_PACKET_PROTO) + TUN_MAX_IP_PACKET_SIZE)
/* Minimum ring capacity. */
#define TUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */
/* Maximum ring capacity. */
#define TUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */
/* Calculates ring capacity */
#define TUN_RING_CAPACITY(Size) ((Size) - sizeof(TUN_RING_PROTO) - (TUN_MAX_PACKET_SIZE - TUN_ALIGNMENT))
/* Calculates ring offset modulo capacity */
#define TUN_RING_WRAP(Value, Capacity) ((Value) & (Capacity - 1))
#define IS_POW2(x) ((x) && !((x) & ((x)-1)))
typedef struct _TUN_RING {
volatile ULONG Head;
volatile ULONG Tail;
volatile LONG Alertable;
UCHAR Data[WINTUN_RING_CAPACITY + (TUN_MAX_PACKET_SIZE-TUN_ALIGNMENT)];
} TUN_RING;
typedef struct _TUN_PACKET {
ULONG Size;
UCHAR Data[WINTUN_MAX_PACKET_SIZE]; // max packet size as defined by the driver.
} TUN_PACKET;
typedef struct _TUN_REGISTER_RINGS {
struct {
ULONG RingSize;
TUN_RING *Ring;
HANDLE TailMoved;
} Send, Receive;
} TUN_REGISTER_RINGS;
#pragma pack(pop)
class regkey_t
{
public:
regkey_t(void);
regkey_t(HKEY handle);
~regkey_t(void);
void attach(HKEY handle);
void release(void);
HKEY detach(void);
operator HKEY (void) const;
HKEY &get(void);
HKEY *operator &(void);
private:
regkey_t(const regkey_t &);
regkey_t &operator = (const regkey_t &);
HKEY handle_;
};
regkey_t::regkey_t():
handle_(0)
{
}
regkey_t::regkey_t(HKEY handle):
handle_(handle)
{
}
regkey_t::~regkey_t(void)
{
release();
}
void regkey_t::attach(HKEY handle)
{
release();
handle_ = handle;
}
void regkey_t::release(void)
{
if (handle_)
{
const LONG res (::RegCloseKey(handle_));
if (res != ERROR_SUCCESS)
{
printf("Couldn't close a reg handle (%lu).\n", res);
}
handle_ = 0;
}
}
HKEY regkey_t::detach(void)
{
const HKEY result (handle_);
handle_ = 0;
return result;
}
HKEY ®key_t::get(void)
{
return handle_;
}
HKEY *regkey_t::operator &(void)
{
return &handle_;
}
regkey_t::operator HKEY(void) const
{
return handle_;
}
bool impersonate_as_system()
{
HANDLE thread_token, process_snapshot, winlogon_process, winlogon_token, duplicated_token;
PROCESSENTRY32 entry;
BOOL ret;
DWORD pid = 0;
TOKEN_PRIVILEGES privileges;
::memset(&entry, 0, sizeof(entry));
::memset(&privileges, 0, sizeof(privileges));
entry.dwSize = sizeof(PROCESSENTRY32);
privileges.PrivilegeCount = 1;
privileges.Privileges->Attributes = SE_PRIVILEGE_ENABLED;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &privileges.Privileges[0].Luid))
{
return false;
}
if (!ImpersonateSelf(SecurityImpersonation))
{
return false;
}
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &thread_token))
{
RevertToSelf();
return false;
}
if (!AdjustTokenPrivileges(thread_token, FALSE, &privileges, sizeof(privileges), NULL, NULL))
{
CloseHandle(thread_token);
RevertToSelf();
return false;
}
CloseHandle(thread_token);
process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (process_snapshot == INVALID_HANDLE_VALUE)
{
RevertToSelf();
return false;
}
for (ret = Process32First(process_snapshot, &entry); ret; ret = Process32Next(process_snapshot, &entry))
{
if (::strcmp(entry.szExeFile, "winlogon.exe") == 0)
{
pid = entry.th32ProcessID;
break;
}
}
CloseHandle(process_snapshot);
if (!pid)
{
RevertToSelf();
return false;
}
winlogon_process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!winlogon_process)
{
RevertToSelf();
return false;
}
if (!OpenProcessToken(winlogon_process, TOKEN_IMPERSONATE | TOKEN_DUPLICATE, &winlogon_token))
{
CloseHandle(winlogon_process);
RevertToSelf();
return false;
}
CloseHandle(winlogon_process);
if (!DuplicateToken(winlogon_token, SecurityImpersonation, &duplicated_token))
{
CloseHandle(winlogon_token);
RevertToSelf();
return false;
}
CloseHandle(winlogon_token);
if (!SetThreadToken(NULL, duplicated_token))
{
CloseHandle(duplicated_token);
RevertToSelf();
return false;
}
CloseHandle(duplicated_token);
return true;
}
std::string get_instance_id(uint32_t device_index)
{
const std::string key_name("SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}");
std::string device_id("");
regkey_t adapters;
DWORD ret = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, key_name.c_str(), 0, KEY_READ, &adapters);
if (ret != ERROR_SUCCESS)
{
printf("Could not open registry key %s (%d).\n", key_name.c_str(), ret);
return device_id;
}
DWORD sub_keys(0);
ret = ::RegQueryInfoKey(adapters, NULL, NULL, NULL, &sub_keys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
if (ret != ERROR_SUCCESS)
{
printf("Could not get info from %s (%d).\n", key_name.c_str(), ret);
return device_id;
}
if (sub_keys <= 0)
{
printf("Wrong registry key %s.\n", key_name.c_str());
return device_id;
}
if (device_index >= sub_keys)
{
return device_id;
}
uint32_t index(0);
for (DWORD i = 0; i < sub_keys; i++)
{
const uint32_t max_key_length = 255;
TCHAR key[max_key_length];
DWORD keylen(max_key_length);
// Get the adapter name
ret = ::RegEnumKeyEx(adapters, i, key, &keylen, NULL, NULL, NULL, NULL);
if (ret != ERROR_SUCCESS)
{
continue;
}
// Append it to NETWORK_ADAPTERS and open it
regkey_t device;
const std::string new_key(key_name + "\\" + std::string(key));
ret = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, new_key.c_str(), 0, KEY_READ, &device);
if (ret != ERROR_SUCCESS)
{
continue;
}
TCHAR data[256];
DWORD len(sizeof(data));
ret = ::RegQueryValueEx(device, "ComponentId", NULL, NULL, (LPBYTE)data, &len);
if (ret != ERROR_SUCCESS)
{
continue;
}
std::string device_name("wintun");
if (::_tcsnccmp(data, device_name.c_str(), sizeof(TCHAR) * device_name.length()) == 0)
{
if (device_index != index)
{
index++;
continue;
}
DWORD type;
len = sizeof(data);
ret = ::RegQueryValueEx(device, "DeviceInstanceID", NULL, &type, (LPBYTE)data, &len);
if (ret != ERROR_SUCCESS)
{
printf("Could not get info from %s (%d).\n", key_name.c_str(), ret);
}
device_id = data;
break;
}
}
return device_id;
}
bool open_tun_device()
{
HANDLE tun_fd_ = INVALID_HANDLE_VALUE;
std::string device_id;
uint32_t device_index;
{
TCHAR *interface_list = nullptr;
for (device_index = 0; device_index < 256; ++device_index)
{
device_id = get_instance_id(device_index);
if (device_id.empty())
{
continue;
}
CONFIGRET status = CR_SUCCESS;
// This loop is recommended as "robust code" by MSDN. See the Remarks of CM_Get_Device_Interface_list.
do
{
DWORD required_chars(0);
if ((status = ::CM_Get_Device_Interface_List_Size(&required_chars,
(LPGUID)&GUID_DEVINTERFACE_NET,
(char *)device_id.c_str(),
CM_GET_DEVICE_INTERFACE_LIST_PRESENT)) != CR_SUCCESS || !required_chars)
{
break;
}
assert(required_chars > 0);
interface_list = (TCHAR *)::malloc(sizeof(TCHAR) * required_chars);
status = ::CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_NET,
(char *)device_id.c_str(),
interface_list,
required_chars,
CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
if (status == CR_SUCCESS)
{
break;
}
::free(interface_list);
interface_list = nullptr;
} while(status == CR_BUFFER_SMALL);
if (interface_list)
{
break;
}
}
if (!interface_list)
{
printf("Could not find wintun interface.\n");
return false;
}
else
{
tun_fd_ = ::CreateFile(interface_list,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING, 0, nullptr);
}
::free(interface_list);
}
if (!tun_fd_ || tun_fd_ == INVALID_HANDLE_VALUE)
{
printf("Could not open wintun device.\n");
return false;
}
printf("Opened wintun device.\n");
::Sleep(1000);
TUN_RING * send_ring_ = (TUN_RING *)::malloc(sizeof(TUN_RING));
TUN_RING * recv_ring_ = (TUN_RING *)::malloc(sizeof(TUN_RING));
if (!recv_ring_ || !send_ring_)
{
printf("Could not malloc.\n");
return false;
}
::memset(send_ring_, 0, sizeof(*send_ring_));
::memset(recv_ring_, 0, sizeof(*recv_ring_));
recv_ring_->Alertable = TRUE;
recv_ring_->Head = 0;
recv_ring_->Tail = 0;
send_ring_->Alertable = TRUE;
send_ring_->Head = 0;
send_ring_->Tail = 0;
HANDLE send_event = ::CreateEvent(0, FALSE, FALSE, 0);
HANDLE recv_event = ::CreateEvent(0, FALSE, FALSE, 0);
// register the rings
if (impersonate_as_system())
{
TUN_REGISTER_RINGS reg_rings;
::memset(®_rings, 0, sizeof(TUN_REGISTER_RINGS));
reg_rings.Send.RingSize = sizeof(TUN_RING);
reg_rings.Send.Ring = send_ring_;
reg_rings.Send.TailMoved = send_event;
reg_rings.Receive.RingSize = sizeof(TUN_RING);
reg_rings.Receive.Ring = recv_ring_;
reg_rings.Receive.TailMoved = recv_event;
int send_capacity = TUN_RING_CAPACITY(reg_rings.Send.RingSize);
if ((send_capacity < TUN_MIN_RING_CAPACITY || send_capacity > TUN_MAX_RING_CAPACITY ||
!IS_POW2(send_capacity) || !reg_rings.Send.TailMoved || !reg_rings.Send.Ring))
{
printf("Fuck this shit I am out...\n");
}
DWORD len;
DWORD fuckyou = 0;
if (!::DeviceIoControl(tun_fd_, TUN_IOCTL_FORCE_CLOSE_HANDLES,
&fuckyou, sizeof(fuckyou), nullptr, 0, &len, nullptr))
{
printf("Error releasing handles (%d).\n", ::GetLastError());
}
if (!::DeviceIoControl(tun_fd_,
TUN_IOCTL_REGISTER_RINGS,
®_rings,
sizeof(reg_rings),
nullptr,
0,
&len,
nullptr))
{
printf("Could not register ring buffers (%d).\n", ::GetLastError());
::Sleep(10000);
RevertToSelf();
return false;
}
::Sleep(10000);
RevertToSelf();
}
else
{
printf("Could not elevate to SYSTEM\n");
return false;
}
return true;
}
int main()
{
if (!open_tun_device())
{
printf("Experiment failed.\n");
}
printf("Size TUNRING: %d (%d)\n", sizeof(TUN_RING), 0x800000 + 0x010000 + 0x0C);
printf("Capacity: %d\n", TUN_RING_CAPACITY(sizeof(TUN_RING)));
if (!IS_POW2(TUN_RING_CAPACITY(sizeof(TUN_RING))))
{
printf("Shit gone wrong...\n");
}
return 0;
}
Please make sure to RUN THIS ELEVATED or you will get error 5 ERROR_ACCESS_DENIED.
I can see a difference in your code when registering rings.
You are doing:
reg_rings.Send.RingSize = sizeof(TUN_RING);
reg_rings.Receive.RingSize = sizeof(TUN_RING);
While the docs says:
Send.RingSize, Receive.RingSize: Sizes of the rings (sizeof(TUN_RING) + capacity + 0x10000, as above)
Your ring is sizeof(TUN_RING) + UCHAR[(1024 * 1024) + 0x10000]
I guess it can't accept a ring that has no data space?
Sorry, I see your TUN_RING includes de data...
May be the events aren't good:
If an event is created from a service or a thread that is impersonating a different user, you can either apply a security descriptor to the event when you create it, or change the default security descriptor for the creating process by changing its default DACL
reg_rings.Send.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
reg_rings.Receive.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
You seem to be using the default DACL.
There may be aligning problems. If malloc isn't returning an aligned address for your buffer (as may be in debug mode, because there are memory management bytes) your Data member for the packet could be not aligned.
You can check the alignment against the address:
template <unsigned int alignment>
struct IsAligned
{
static_assert((alignment & (alignment - 1)) == 0, "Alignment must be a power of 2");
static inline bool Value(const void * ptr)
{
return (((uintptr_t)ptr) & (alignment - 1)) == 0;
}
};
std::cout << IsAligned<32>::Value(ptr + i) << std::endl;
Giving the first packet address &(TUN_RING.Data[0]) (I guess.)
As said in your comment, it is the case, it is unaligned.
You can try two things.
First reserve memory with aligned_alloc which will give you an aligned buffer for TUN_RING.
Second, if TUN_RING is already aligned and the packet alignment is the problem, then you should give the correct offset to the head and tail:
recv_ring_->Head = 0; // <- aligned byte offset
recv_ring_->Tail = 0;
send_ring_->Head = 0;
send_ring_->Tail = 0;
Remember:
Head: Byte offset of the first packet in the ring. Its value must be a multiple of 4 and less than ring capacity.
Tail: Byte offset of the start of free space in the ring. Its value must be multiple of 4 and less than ring capacity.
The byte offset must be a multiple of 4.
You have to increment those skipped bytes to the buffer size. For that you may need to allocate extra space that won't be used, but I think it won't be too much.
In a second view to events, in the docs it says event has to be auto-reset:
Send.TailMoved: A handle to an auto-reset event created by the client that Wintun signals after it moves the Tail member of the send ring.
Receive.TailMoved: A handle to an auto-reset event created by the client that the client will signal when it changes Receive.Ring->Tail and Receive.Ring->Alertable is non-zero.
In your example, the event is auto-reset:
HANDLE send_event = ::CreateEvent(0, FALSE, FALSE, 0);
HANDLE recv_event = ::CreateEvent(0, FALSE, FALSE, 0);
but in the code you show (at top of question) isn't:
reg_rings.Send.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
reg_rings.Receive.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
I don't know if the parameter checking goes so far as to verify event auto-reset setting (not even if that's possible.) Moreover, the openvpn code creates them non auto-reset (although may be there is some code around to signal them before registering.)
Okay, after a lot of trial and error I have translated the whole setup routine from the WireGuard Go code (see here: https://github.com/WireGuard/wireguard-go ) to C++, which seems to make it work. It accepts the rings now just as in the first post and the device is shown as connected afterwards...
They are doing some registry tweaks after installing the device (see https://github.com/WireGuard/wireguard-go/blob/4369db522b3fd7adc28a2a82b89315a6f3edbcc4/tun/wintun/wintun_windows.go#L207 ) which I think takes the cake. Thanks for everyone helping in finding this.
For me the fix to get rid off ERROR_INVALID_PARAMETER (87) was to switch from x86 to x64 architecture in Visual Studio
The issue here is the alignment of the structs. You align your structs to 1 byte [#pragma pack(push, 1)] while the wintun driver does 8(/ZP8 in the solution). This will result in differing struct sizes and thus the size checks will fall through. Furthermore I would like to recommend that you use VirtualAlloc or the Mapping instead of malloc.
With admin privileges, I need to enumerate all users on a Windows 7+ system (even ones that are logged off). Then I need to load the registry hive for each user and set a key.
NetUserEnum gives me the SID (I guess LsaEnumerateLogonSessions would as well). WTSEnumerateSessions followed by WTSQueryUserToken (to get a token) would be nice but it does not work for users who are not actively logged on.
So, my question, after calling NetUserEnum, how do I use the SID to load the registry for that user? Any recommended way of doing this?
Information about local user profiles is stored in this Registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
It is possible to enumerate it subkeys, where each subkey has a ProfileImagePath that points to the folder where ntuser.dat is located.
But, directly loading a user profile by RegLoadKey() is very bad. First, the profile may already be loaded. Second, it is possible that after you load the profile yourself, the system may also try loading the profile. Note the RefCount value. The system uses that value to load the profile if it is not already loaded, incrementing RefCount. And UnloadUserProfile() decrements RefCount and unloads the profile only when it become 0 by calling RegUnLoadKey(). So all profile load/unload operations must be synchronized.
There is only one correct way to load a profile - call LoadUserProfile(). (internally it performs a RPC call to profsvc.LoadUserProfileServer in svchost.exe -k netsvcs, where all synchronization is done).
So how do you get the user token for LoadUserProfile() ? I guess call LogonUser(), which you said you do not want to do (and cannot unless you have the user's password).
But, there does exist another way that works (I tested this), but it is undocumented. LoadUserProfile used only the user Sid from token (query for TOKEN_USER information with TokenUser iformation class) and then work with
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\<Sid>
key
It is possible to create a token by calling ZwCreateToken() with any given SID, but for this call you need SE_CREATE_TOKEN_PRIVILEGE. This priviledge exists only in the lsass.exe process. So a possible solution is:
open lsass.exe and get its token, or impersonate its thread.
enable SE_CREATE_TOKEN_PRIVILEGE in the token, after impersonation
enumerate HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\ProfileList, and for each subkey query its Sid value, or (if Sid does not exist) convert the subkey name to a SID using ConvertStringSidToSid()
create a token with that SID
and finally call LoadUserProfile()
-------------- EDIT code example by request ----------------------------
code used ntdll export (which somebody here very not like) but as is
we need got SE_CREATE_TOKEN_PRIVILEGE to create token by yourself in
future
enum processes in the system, open token for every process, look are SE_CREATE_TOKEN_PRIVILEGE exist in token, if yes - duplicate this token and if need enable SE_CREATE_TOKEN_PRIVILEGE in it. finally impersonate with duplicated token
BOOL g_IsXP;// true if we on winXP, false otherwise
static volatile UCHAR guz;
static OBJECT_ATTRIBUTES zoa = { sizeof(zoa) };
NTSTATUS ImpersonateIfConformToken(HANDLE hToken)
{
ULONG cb = 0, rcb = 0x200;
PVOID stack = alloca(guz);
union {
PVOID buf;
PTOKEN_PRIVILEGES ptp;
};
NTSTATUS status;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQueryInformationToken(hToken, TokenPrivileges, buf, cb, &rcb)))
{
if (ULONG PrivilegeCount = ptp->PrivilegeCount)
{
PLUID_AND_ATTRIBUTES Privileges = ptp->Privileges;
do
{
if (Privileges->Luid.LowPart == SE_CREATE_TOKEN_PRIVILEGE && !Privileges->Luid.HighPart)
{
static SECURITY_QUALITY_OF_SERVICE sqos = {
sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING, FALSE
};
static OBJECT_ATTRIBUTES soa = { sizeof(soa), 0, 0, 0, 0, &sqos };
if (0 <= (status = ZwDuplicateToken(hToken, TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE, &soa, FALSE, TokenImpersonation, &hToken)))
{
if (Privileges->Attributes & SE_PRIVILEGE_ENABLED)
{
status = STATUS_SUCCESS;
}
else
{
static TOKEN_PRIVILEGES tp = {
1, { { { SE_CREATE_TOKEN_PRIVILEGE }, SE_PRIVILEGE_ENABLED } }
};
status = ZwAdjustPrivilegesToken(hToken, FALSE, &tp, 0, 0, 0);
}
if (status == STATUS_SUCCESS)
{
status = ZwSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hToken, sizeof(HANDLE));
}
ZwClose(hToken);
}
return status;
}
} while (Privileges++, --PrivilegeCount);
}
return STATUS_PRIVILEGE_NOT_HELD;
}
} while (status == STATUS_BUFFER_TOO_SMALL);
return status;
}
NTSTATUS GetCreateTokenPrivilege()
{
BOOLEAN b;
RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &b);
ULONG cb = 0, rcb = 0x10000;
PVOID stack = alloca(guz);
union {
PVOID buf;
PBYTE pb;
PSYSTEM_PROCESS_INFORMATION pspi;
};
NTSTATUS status;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQuerySystemInformation(SystemProcessInformation, buf, cb, &rcb)))
{
status = STATUS_UNSUCCESSFUL;
ULONG NextEntryOffset = 0;
do
{
pb += NextEntryOffset;
if (pspi->InheritedFromUniqueProcessId && pspi->UniqueProcessId)
{
CLIENT_ID cid = { pspi->UniqueProcessId };
NTSTATUS s = STATUS_UNSUCCESSFUL;
HANDLE hProcess, hToken;
if (0 <= ZwOpenProcess(&hProcess, g_IsXP ? PROCESS_QUERY_INFORMATION : PROCESS_QUERY_LIMITED_INFORMATION, &zoa, &cid))
{
if (0 <= ZwOpenProcessToken(hProcess, TOKEN_DUPLICATE|TOKEN_QUERY, &hToken))
{
s = ImpersonateIfConformToken(hToken);
NtClose(hToken);
}
NtClose(hProcess);
}
if (s == STATUS_SUCCESS)
{
return STATUS_SUCCESS;
}
}
} while (NextEntryOffset = pspi->NextEntryOffset);
return status;
}
} while (status == STATUS_INFO_LENGTH_MISMATCH);
return STATUS_UNSUCCESSFUL;
}
if we have SE_CREATE_TOKEN_PRIVILEGE - we can create token !
NTSTATUS CreateUserToken(PHANDLE phToken, PSID Sid)
{
HANDLE hToken;
TOKEN_STATISTICS ts;
NTSTATUS status = ZwOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &hToken);
if (0 <= status)
{
if (0 <= (status = ZwQueryInformationToken(hToken, TokenStatistics, &ts, sizeof(ts), &ts.DynamicCharged)))
{
ULONG cb = 0, rcb = 0x200;
PVOID stack = alloca(guz);
union {
PVOID buf;
PTOKEN_PRIVILEGES ptp;
};
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQueryInformationToken(hToken, TokenPrivileges, buf, cb, &rcb)))
{
TOKEN_USER User = { { Sid } };
static TOKEN_SOURCE Source = { {' ','U','s','e','r','3','2', ' '} };
static TOKEN_DEFAULT_DACL tdd;// 0 default DACL
static TOKEN_GROUPS Groups;// no groups
static SECURITY_QUALITY_OF_SERVICE sqos = {
sizeof sqos, SecurityImpersonation, SECURITY_DYNAMIC_TRACKING
};
static OBJECT_ATTRIBUTES oa = {
sizeof oa, 0, 0, 0, 0, &sqos
};
status = ZwCreateToken(phToken, TOKEN_ALL_ACCESS, &oa, TokenPrimary,
&ts.AuthenticationId, &ts.ExpirationTime, &User, &Groups, ptp, (PTOKEN_OWNER)&Sid,
(PTOKEN_PRIMARY_GROUP)&Sid, &tdd, &Source);
break;
}
} while (status == STATUS_BUFFER_TOO_SMALL);
}
ZwClose(hToken);
}
return status;
}
and finally enumerate and load/unload user profiles
void EnumProf()
{
PROFILEINFO pi = { sizeof(pi), PI_NOUI };
pi.lpUserName = L"*";
STATIC_OBJECT_ATTRIBUTES(soa, "\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList");
HANDLE hKey;
if (0 <= ZwOpenKey(&hKey, KEY_READ, &soa))
{
PVOID stack = alloca(sizeof(WCHAR));
union
{
PVOID buf;
PKEY_BASIC_INFORMATION pkbi;
PKEY_VALUE_PARTIAL_INFORMATION pkvpi;
} u = {};
DWORD cb = 0, rcb = 64;
NTSTATUS status;
ULONG Index = 0;
do
{
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(u.buf = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwEnumerateKey(hKey, Index, KeyBasicInformation, u.buf, cb, &rcb)))
{
*(PWSTR)RtlOffsetToPointer(u.pkbi->Name, u.pkbi->NameLength) = 0;
PSID Sid;
if (ConvertStringSidToSidW(u.pkbi->Name, &Sid))
{
HANDLE hToken;
if (0 <= CreateUserToken(&hToken, Sid))
{
if (LoadUserProfile(hToken, &pi))
{
UnloadUserProfile(hToken, pi.hProfile);
}
NtClose(hToken);
}
LocalFree(Sid);
}
}
} while (status == STATUS_BUFFER_OVERFLOW);
Index++;
} while (0 <= status);
ZwClose(hKey);
}
}
Disclaimer:
The only reason for the question and the code below to exist is an external component used in my application, which cannot be replaced, at least in the near future. This component's logic intercepts WinAPI calls from the application and performs various tasks based on these calls.
One of the things the component does, it creates mutex for each thread initialized inside the application. However, it doesn't close the mutexes, which results in handles leak.
Therefore, in order to prevent the leak and because I don't have access to the component's source code, I have to invent ugly workarounds and use esoteric API's.
End of disclaimer
I am trying to check state of mutexes in my application. In order to do this without changing the state of each of the objects I check, I have to use the NtQueryMutant method from ntdll.dll.
Based on examples here and here I wrote the following code to achieve this:
enum MUTANT_INFORMATION_CLASS
{
MutantBasicInformation
};
struct MUTANT_BASIC_INFORMATION {
LONG CurrentCount;
BOOLEAN OwnedByCaller;
BOOLEAN AbandonedState;
};
typedef NTSTATUS(WINAPI*QueryMutexHandler)(HANDLE, MUTANT_INFORMATION_CLASS, PVOID, ULONG, PULONG);
//somewhere in the code:
QueryMutexHandler queryMutex = reinterpret_cast<QueryMutexHandler>(GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQueryMutant"));
MUTANT_BASIC_INFORMATION mutantInfo;
NTSTATUS status = queryMutex(objectHandleCopy, MutantBasicInformation, &mutantInfo, sizeof(MUTANT_BASIC_INFORMATION), nullptr);
if (NT_SUCCESS(status))
{
//never arriving here
}
The status I receive here is always -1073741790 (0xFFFF FFFF C000 0022) which is, except being negative number, looks exactly like STATUS_ACCESS_DENIED.
That is very strange, because previously in code I use both NtQuerySystemInformation and NtQueryObject without any problem.
Additional details: my OS is Windows 7 SP1, the mutexes I try to query belong to the process I am performing the query from.
for effective test Mutant you need it handle and it access mask. you can got it from SYSTEM_HANDLE_INFORMATION_EX structure. if we already have MUTANT_QUERY_STATE - can direct query, if no - need reopen handle with MUTANT_QUERY_STATE
NTSTATUS QueryMutant(HANDLE hMutant, ULONG GrantedAccess, MUTANT_BASIC_INFORMATION* pmbi)
{
if (GrantedAccess & MUTANT_QUERY_STATE)
{
return ZwQueryMutant(hMutant, MutantBasicInformation, pmbi, sizeof(MUTANT_BASIC_INFORMATION), 0);
}
NTSTATUS status = ZwDuplicateObject(NtCurrentProcess(), hMutant, NtCurrentProcess(),&hMutant,
MUTANT_QUERY_STATE, 0, 0);
if (0 <= status)
{
status = ZwQueryMutant(hMutant, MutantBasicInformation, pmbi, sizeof(MUTANT_BASIC_INFORMATION), 0);
ZwClose(hMutant);
}
return status;
}
and you not need all time use NtQueryObject for determinate type of handle. you can use SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex . for get OBJECT_TYPE_INFORMATION by this index. for this you need only once call ZwQueryObject(0, ObjectAllTypeInformation, ) at start, but exist problem how convert SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex to array index (zero bassed). begin from win8.1 'OBJECT_TYPE_INFORMATION.TypeIndex' is valid and match to SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex, but for early version - you need once get SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX.ObjectTypeIndex for some known object type and calc delta
static volatile UCHAR guz;
NTSTATUS getProcessIndex(USHORT& ObjectTypeIndex)
{
HANDLE hProcess;
NTSTATUS status = ZwDuplicateObject(NtCurrentProcess(), NtCurrentProcess(), NtCurrentProcess(), &hProcess, 0, 0, DUPLICATE_SAME_ACCESS);
if (0 <= status)
{
PVOID stack = alloca(guz);
DWORD cb = 0, rcb = 0x10000;
union {
PVOID buf;
PSYSTEM_HANDLE_INFORMATION_EX pshti;
};
do
{
if (cb < rcb) cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = ZwQuerySystemInformation(SystemExtendedHandleInformation, buf, cb, &rcb)))
{
if (ULONG NumberOfHandles = (ULONG)pshti->NumberOfHandles)
{
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles = pshti->Handles;
ULONG_PTR UniqueProcessId = GetCurrentProcessId();
do
{
if (Handles->UniqueProcessId == UniqueProcessId && (HANDLE)Handles->HandleValue == hProcess)
{
ObjectTypeIndex = Handles->ObjectTypeIndex;
goto __break;
}
} while (Handles++, --NumberOfHandles);
}
}
} while (STATUS_INFO_LENGTH_MISMATCH == status);
__break:
ZwClose(hProcess);
}
return status;
}
class ZOBJECT_ALL_TYPES_INFORMATION
{
OBJECT_TYPE_INFORMATION* _TypeInformation;
DWORD _NumberOfTypes, _TypeIndexDelta;
public:
operator DWORD()
{
return _NumberOfTypes;
}
operator OBJECT_TYPE_INFORMATION*()
{
return _TypeInformation;
}
DWORD operator[](OBJECT_TYPE_INFORMATION* TypeInformation)
{
return (DWORD)(TypeInformation - _TypeInformation) + _TypeIndexDelta;
}
OBJECT_TYPE_INFORMATION* operator[](DWORD Index)
{
return Index < _NumberOfTypes ? _TypeInformation + Index : 0;
}
ULONG TypeIndexToIndex(DWORD ObjectTypeIndex)
{
return ObjectTypeIndex -= _TypeIndexDelta;
}
OBJECT_TYPE_INFORMATION* operator[](PCUNICODE_STRING TypeName);
ZOBJECT_ALL_TYPES_INFORMATION();
~ZOBJECT_ALL_TYPES_INFORMATION();
};
ZOBJECT_ALL_TYPES_INFORMATION g_AOTI;
OBJECT_TYPE_INFORMATION* ZOBJECT_ALL_TYPES_INFORMATION::operator[](PCUNICODE_STRING TypeName)
{
if (DWORD NumberOfTypes = _NumberOfTypes)
{
OBJECT_TYPE_INFORMATION* TypeInformation = _TypeInformation;
do
{
if (RtlEqualUnicodeString(TypeName, &TypeInformation->TypeName, TRUE))
{
return TypeInformation;
}
} while (TypeInformation++, -- NumberOfTypes);
}
return 0;
}
ZOBJECT_ALL_TYPES_INFORMATION::ZOBJECT_ALL_TYPES_INFORMATION()
{
_TypeInformation = 0, _NumberOfTypes = 0;
USHORT ProcessTypeIndex;
if (0 > getProcessIndex(ProcessTypeIndex))
{
return ;
}
NTSTATUS status;
PVOID stack = alloca(guz);
union {
PVOID pv;
OBJECT_TYPES_INFORMATION* poati;
};
DWORD cb = 0, rcb = 0x2000;
do
{
if (cb < rcb)
{
cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
}
if (0 <= (status = ZwQueryObject(0, ObjectAllTypeInformation, poati, cb, &rcb)))
{
if (DWORD NumberOfTypes = poati->NumberOfTypes)
{
if (OBJECT_TYPE_INFORMATION* TypeInformation = (OBJECT_TYPE_INFORMATION*)LocalAlloc(0, rcb))
{
_NumberOfTypes = NumberOfTypes;
_TypeInformation = TypeInformation;
ULONG Index = 0;
union {
ULONG_PTR uptr;
OBJECT_TYPE_INFORMATION* pti;
};
union {
PWSTR buf;
PBYTE pb;
PVOID pv;
};
pti = poati->TypeInformation;
pv = TypeInformation + NumberOfTypes;
do
{
STATIC_UNICODE_STRING_(Process);
if (RtlEqualUnicodeString(&Process, &pti->TypeName, TRUE))
{
_TypeIndexDelta = ProcessTypeIndex - Index;
}
ULONG Length = pti->TypeName.Length, MaximumLength = pti->TypeName.MaximumLength;
memcpy(buf, pti->TypeName.Buffer, Length);
*TypeInformation = *pti;
TypeInformation++->TypeName.Buffer = buf;
pb += Length;
uptr += (sizeof(OBJECT_TYPE_INFORMATION) + MaximumLength + sizeof(PVOID)-1) & ~ (sizeof(PVOID)-1);
} while (Index++, --NumberOfTypes);
}
}
}
} while (status == STATUS_INFO_LENGTH_MISMATCH);
}
ZOBJECT_ALL_TYPES_INFORMATION::~ZOBJECT_ALL_TYPES_INFORMATION()
{
if (_TypeInformation)
{
LocalFree(_TypeInformation);
}
}
and finally use next code, without NtQueryObject:
void TestMutant()
{
NTSTATUS status;
PVOID stack = alloca(guz);
DWORD cb = 0, rcb = 0x10000;
union {
PVOID buf;
PSYSTEM_HANDLE_INFORMATION_EX pshti;
};
do
{
if (cb < rcb) cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = ZwQuerySystemInformation(SystemExtendedHandleInformation, buf, cb, &rcb)))
{
if (ULONG NumberOfHandles = (ULONG)pshti->NumberOfHandles)
{
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles = pshti->Handles;
ULONG_PTR UniqueProcessId = GetCurrentProcessId();
do
{
if (Handles->UniqueProcessId == UniqueProcessId)
{
if (OBJECT_TYPE_INFORMATION* poti = g_AOTI[g_AOTI.TypeIndexToIndex(Handles->ObjectTypeIndex)])
{
STATIC_UNICODE_STRING_(Mutant);
if (RtlEqualUnicodeString(&Mutant, &poti->TypeName, TRUE))
{
MUTANT_BASIC_INFORMATION mbi;
QueryMutant((HANDLE)Handles->HandleValue, Handles->GrantedAccess, &mbi);
}
}
}
} while (Handles++, --NumberOfHandles);
}
}
} while (STATUS_INFO_LENGTH_MISMATCH == status);
}
can test with
void Az()
{
HANDLE hMutant;
if (0 <= ZwCreateMutant(&hMutant, SYNCHRONIZE, 0, TRUE))
{
TestMutant();
ZwClose(hMutant);
}
}
When I do not set timeout settings via GetTimeout & SetTimeout after I connect my USB-UART converter to my laptop, the ReadFile becomes blocking. However, once I set them via SetTimeOut, the ReadFile is not blocking anymore and returns true even if the given number of bytes were not read.
When ReadFile returns true but the dwRead parameter says 0 since no data was going into my PC via serial port, I conclude that the ReadFile function must have timed out. However, using GetLastError returns 0. How can I be verify in my C++ program that the ReadFile has actually timedout?
When using SetCommTimeouts() with ReadFile(), the COMMTIMEOUTS documentation says:
ReadIntervalTimeout
The maximum time allowed to elapse before the arrival of the next byte on the communications line, in milliseconds. If the interval between the arrival of any two bytes exceeds this amount, the ReadFile operation is completed and any buffered data is returned. A value of zero indicates that interval time-outs are not used.
If a timeout occurs, the read is complete (ReadFile() returns TRUE), and the number of bytes that had been buffered before the timeout elapsed is reflected in your dwRead variable. So, you will know if a timeout occurred if dwRead is less than the number of bytes you asked ReadFile() to read. If there was no buffered data, dwRead will be 0. If dwRead is equal to the number of bytes you asked for, there was no timeout since ReadFile() exited when the last requested byte was read.
most simplest and accurate way use NT api NtReadFile and check final status of operation. timeout <=> iosb.Status == STATUS_TIMEOUT. kernel32 api ReadFile - lost info for this status(STATUS_TIMEOUT), because it check only for STATUS_PENDING and 0 > status
for synchronous work, can use code like this:
void SyncTest(POBJECT_ATTRIBUTES poa)
{
HANDLE hFile;
IO_STATUS_BLOCK iosb;
NTSTATUS status = NtOpenFile(&hFile, FILE_ALL_ACCESS, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_SYNCHRONOUS_IO_NONALERT);
if (0 <= status)
{
SERIAL_TIMEOUTS st = { 4000, 1, 0, 1, 0 };
status = NtDeviceIoControlFile(hFile, 0, 0, 0, &iosb, IOCTL_SERIAL_SET_TIMEOUTS, &st, sizeof(st), 0, 0);
DbgPrint("time %x[%x,%p]\n", status, iosb.Status, iosb.Information);
if (0 <= status)
{
UCHAR buf[256];
status = NtReadFile(hFile, 0, 0, 0, &iosb, buf, sizeof(buf), 0, 0);
DbgPrint("read %x [%x,%p]\n", status, iosb.Status, iosb.Information);
//assert(status == iosb.Status);
if (status == STATUS_TIMEOUT)
{
DbgPrint("timeout\n");
}
}
NtClose(hFile);
}
}
for asynchronous:
class __declspec(novtable) IoObject
{
friend class UserIrp;
protected:
HANDLE _hFile;
private:
LONG _dwRef;
protected:
virtual ~IoObject()
{
if (_hFile) NtClose(_hFile);
}
virtual void OnIoComplete(NTSTATUS status, ULONG_PTR Information, ULONG code, PVOID pv) = 0;
public:
NTSTATUS BindIoCompletion();
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef)) delete this;
}
IoObject()
{
_hFile = 0;
_dwRef = 1;
}
};
class UserIrp : public IO_STATUS_BLOCK
{
friend IoObject;
IoObject* _pObj;
PVOID _pv;
LONG _dwRef;
ULONG _code;
static VOID WINAPI OnComplete(NTSTATUS Status, ULONG_PTR Information, UserIrp* This)
{
This->_pObj->OnIoComplete(Status, Information, This->_code, This->_pv);
This->Release();
}
~UserIrp()
{
_pObj->Release();
}
public:
NTSTATUS CheckStatus(NTSTATUS status)
{
if (NT_ERROR(status))
{
OnComplete(status, Information, this);
}
return status;
}
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef)) delete this;
}
UserIrp(IoObject* pObj, ULONG code, PVOID pv) : _pObj(pObj), _dwRef(1), _code(code), _pv(pv)
{
pObj->AddRef();
}
};
NTSTATUS IoObject::BindIoCompletion()
{
return RtlSetIoCompletionCallback(_hFile, (LPOVERLAPPED_COMPLETION_ROUTINE)UserIrp::OnComplete, 0);
}
class MySerial : public IoObject
{
void OnIoComplete(NTSTATUS status, ULONG_PTR Information, ULONG code, PVOID pv)
{
DbgPrint("OnIoComplete(%x, %p, %.4s, %p)\n", status, Information, &code, pv);
switch (code)
{
case 'time':
if (0 <= status)
{
if (PUCHAR buf = new UCHAR[256])
{
if (UserIrp* Irp = new UserIrp(this, 'read', buf))
{
static LARGE_INTEGER ByteOffset;
status = Irp->CheckStatus(NtReadFile(_hFile, 0, 0, Irp, Irp, buf, 256, &ByteOffset, 0));
DbgPrint("read begin = %x\n", status);
return ;
}
delete buf;
}
}
break;
case 'read':
DbgPrint("read end(%x, %p)\n", status, Information);
if (status == STATUS_TIMEOUT)
{
DbgPrint("timeout\n");
}
delete pv;
break;
}
}
virtual ~MySerial()
{
DbgPrint("--MySerial<%p>\n", this);
}
public:
MySerial()
{
DbgPrint("++MySerial<%p>\n", this);
}
NTSTATUS Open(POBJECT_ATTRIBUTES poa)
{
IO_STATUS_BLOCK iosb;
return NtOpenFile(&_hFile, FILE_ALL_ACCESS, poa, &iosb, FILE_SHARE_VALID_FLAGS, 0);
}
NTSTATUS SetTimeouts(ULONG ms)
{
if (UserIrp* Irp = new UserIrp(this, 'time', 0))
{
SERIAL_TIMEOUTS st = { ms, 1, 0, 1, 0 };
return Irp->CheckStatus(ZwDeviceIoControlFile(_hFile, 0, 0, Irp, Irp, IOCTL_SERIAL_SET_TIMEOUTS, &st, sizeof(st), 0, 0));
}
return STATUS_INSUFFICIENT_RESOURCES;
}
};
void AsyncTest(POBJECT_ATTRIBUTES poa)
{
if (MySerial* p = new MySerial)
{
if (0 <= p->Open(poa) && 0 <= p->BindIoCompletion())
{
NTSTATUS status = p->SetTimeouts(4000);
DbgPrint("set timeout=%x\n", status);
}
p->Release();
}
}