wintun:ERROR_INVALID_PARAMETER on registering ring buffers - c++

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(&reg_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,
&reg_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 &regkey_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(&reg_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,
&reg_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.

Related

How to find all connected removable USB storage devices in Windows?

I'm writing a program that processes USB drives, to get information about connecting a new device, I signed up for a window message WM_DEVICECHANGE. But I, of course, do not receive messages about the devices that were connected before my program was launched. To process such devices, I wrote a search function, but I get a strange result. It finds my flash drive, but does not recognize it to be removable. Why?
Function
bool FindConnectedRemovableUsbstorDevices(std::list<std::wstring>& UsbList)
{
std::wstring ClassGuidString(L"{53F56307-B6BF-11D0-94F2-00A0C91EFB8B}");
GUID ClassGuid;
BYTE buf[1024];
PSP_DEVICE_INTERFACE_DETAIL_DATA_W pspdidd = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA_W>(buf);
SP_DEVICE_INTERFACE_DATA spdid;
SP_DEVINFO_DATA spdd;
DWORD size;
SP_DEVINFO_DATA dev_data;
DWORD properties;
if(NOERROR != CLSIDFromString(ClassGuidString.c_str(), &ClassGuid))
return false;
HDEVINFO dev_info = INVALID_HANDLE_VALUE;
dev_info = SetupDiGetClassDevs(&ClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (dev_info == INVALID_HANDLE_VALUE)
return false;
DWORD index = 0;
BOOL ret = FALSE;
spdid.cbSize = sizeof(spdid);
while (true)
{
ret = SetupDiEnumDeviceInterfaces(dev_info, NULL, &ClassGuid, index, &spdid);
if (!ret)
break;
size = 0;
SetupDiGetDeviceInterfaceDetail(dev_info, &spdid, NULL, 0, &size, NULL);
//Get required size
if (size == 0 || size >= sizeof(buf))
continue;
//prepare structs
ZeroMemory(reinterpret_cast<PVOID>(pspdidd), 1024);
pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes!
ZeroMemory(reinterpret_cast<PVOID>(&spdd), sizeof(spdd));
spdd.cbSize = sizeof(spdd);
BOOL res = SetupDiGetDeviceInterfaceDetail(dev_info, &spdid, pspdidd, size, &size, &spdd);
//Get info
if (!res)
continue;
HANDLE drive = CreateFileW(pspdidd->DevicePath, FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);//open device
if (drive == INVALID_HANDLE_VALUE)
continue;
printf("\n%S\r\n", pspdidd->DevicePath);
DWORD bytes_returned = 0;
BOOL b = DeviceIoControl(drive, IOCTL_STORAGE_CHECK_VERIFY2, NULL, 0, NULL, 0, &bytes_returned, NULL);
if (!b) //check is card reader?
{
printf("IOCTL_STORAGE_CHECK_VERIFY2 error = %d\r\n", GetLastError());
goto stop_process_device;
}
bytes_returned = 0;
STORAGE_DEVICE_NUMBER sdn;
//Get Drive number
b = DeviceIoControl(drive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &bytes_returned, NULL);
if (!b)
goto stop_process_device;
RtlZeroMemory(&dev_data, sizeof(SP_DEVINFO_DATA));
dev_data.cbSize = sizeof(dev_data);
if (SetupDiEnumDeviceInfo(dev_info, sdn.DeviceNumber, &dev_data))
{
//check property
b = SetupDiGetDeviceRegistryProperty(dev_info, &dev_data, SPDRP_REMOVAL_POLICY, NULL,
reinterpret_cast<PBYTE>(&properties), sizeof(properties), NULL);
if (b && properties != CM_REMOVAL_POLICY_EXPECT_NO_REMOVAL)
{
UsbList.push_back(pspdidd->DevicePath);
printf("REMOVAL\r\n");
}
}
stop_process_device:
CloseHandle(drive);
index++;
}
SetupDiDestroyDeviceInfoList(dev_info);
return true;
}
And output
\\?\usbstor#disk&ven_generic-&prod_ms#ms-pro#hg&rev_1.00#20090703819900000&1#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
IOCTL_STORAGE_CHECK_VERIFY2 error = 21
\\?\ide#diskst3500418as_____________________________cc38____#5&5c6cfd6&0&1.0.0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
REMOVAL
\\?\usbstor#disk&ven_generic-&prod_sd#mmc&rev_1.00#20090703819900000&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
IOCTL_STORAGE_CHECK_VERIFY2 error = 21
\\?\scsi#disk&ven_ocz&prod_revodrive3_x2#5&19ad1f72&0&000000#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
\\?\ide#diskst1000lm014-1ej164______________________sm30____#5&2ea7e938&0&0.1.0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
\\?\usbstor#disk&ven_sandisk&prod_extreme&rev_0001#aa010823150434152862&0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
\\?\ide#diskwdc_wd1002fbys-02a6b0___________________03.00c06#5&2ea7e938&0&0.0.0#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
REMOVAL
Error 21 is empty card reader.
Sandisk is my flash. In Debug i saw that SetupDiGetDeviceRegistryProperty return property CM_REMOVAL_POLICY_EXPECT_NO_REMOVAL, but i really don't know why...
If you just want to find the connected removable devices, there's a much simpler alternative using GetLogicalDriveStrings() and GetDriveType():
#define MAX_DRIVES 256
bool FindConnectedRemovableUsbstorDevices(std::list<std::wstring>& UsbList)
{
wchar_t drives[MAX_DRIVES];
wchar_t* temp = drives;
if (GetLogicalDriveStringsW(MAX_DRIVES, drives) == 0)
return false;
while (*temp != NULL)
{
if (GetDriveTypeW(temp) == 2 /* DRIVE_REMOVABLE */)
UsbList.push_back(temp);
// Go to the next drive
temp += lstrlenW(temp) + 1;
}
return true;
}
I don't know why my fisrt method of detection removable media works so strange, but method of RbMm works great. I send IOCTL query to every found device with IOCTL_STORAGE_QUERY_PROPERTY with StorageDeviceProperty and look for STORAGE_DEVICE_DESCRIPTOR.RemovableMedia field. All my devices recognized successfully and right.

Using ReadDirectoryChangesW asynchronously in a loop

INTRODUCTION:
I am trying to use ReadDirectoryChangesW asynchronously in a loop.
Below snippet illustrates what I am trying to achieve:
DWORD example()
{
DWORD error = 0;
OVERLAPPED ovl = { 0 };
ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == ovl.hEvent) return ::GetLastError();
char buffer[1024];
while(1)
{
process_list_of_existing_files();
error = ::ReadDirectoryChangesW(
m_hDirectory, // I have added FILE_FLAG_OVERLAPPED in CreateFile
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
// we have new files, append them to the list
if(error) append_new_files_to_the_list(buffer);
// just continue with the loop
else if(::GetLastError() == ERROR_IO_PENDING) continue;
// RDCW error, this is critical -> exit
else return ::GetLastError();
}
}
PROBLEM:
I do not know how to handle the case when ReadDirectoryChangesW returns FALSE with GetLastError() code being ERROR_IO_PENDING.
In that case I should just continue with the loop and keep looping until ReadDirectoryChangesW returns buffer I can process.
MY EFFORTS TO SOLVE THIS:
I have tried using WaitForSingleObject(ovl.hEvent, 1000) but it crashes with error 1450 ERROR_NO_SYSTEM_RESOURCES. Below is the MVCE that reproduces this behavior:
#include <iostream>
#include <Windows.h>
DWORD processDirectoryChanges(const char *buffer)
{
DWORD offset = 0;
char fileName[MAX_PATH] = "";
FILE_NOTIFY_INFORMATION *fni = NULL;
do
{
fni = (FILE_NOTIFY_INFORMATION*)(&buffer[offset]);
// since we do not use UNICODE,
// we must convert fni->FileName from UNICODE to multibyte
int ret = ::WideCharToMultiByte(CP_ACP, 0, fni->FileName,
fni->FileNameLength / sizeof(WCHAR),
fileName, sizeof(fileName), NULL, NULL);
switch (fni->Action)
{
case FILE_ACTION_ADDED:
{
std::cout << fileName << std::endl;
}
break;
default:
break;
}
::memset(fileName, '\0', sizeof(fileName));
offset += fni->NextEntryOffset;
} while (fni->NextEntryOffset != 0);
return 0;
}
int main()
{
HANDLE hDir = ::CreateFile("C:\\Users\\nenad.smiljkovic\\Desktop\\test",
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
if (INVALID_HANDLE_VALUE == hDir) return ::GetLastError();
OVERLAPPED ovl = { 0 };
ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == ovl.hEvent) return ::GetLastError();
DWORD error = 0, br;
char buffer[1024];
while (1)
{
error = ::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
if (0 == error)
{
error = ::GetLastError();
if (ERROR_IO_PENDING != error)
{
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
}
error = ::WaitForSingleObject(ovl.hEvent, 0);
switch (error)
{
case WAIT_TIMEOUT:
break;
case WAIT_OBJECT_0:
{
error = processDirectoryChanges(buffer);
if (error > 0)
{
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
if (0 == ::ResetEvent(ovl.hEvent))
{
error = ::GetLastError();
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
}
}
break;
default:
error = ::GetLastError();
::CloseHandle(ovl.hEvent);
::CloseHandle(hDir);
return error;
break;
}
}
return 0;
}
Reading through the documentation, it seems that I need GetOverlappedResult with last parameter set to FALSE but I do not know how to use this API properly.
QUESTION:
Since the MVCE illustrates very well what I am trying to do (print the names of the newly added files), can you show me what must be fixed in the while loop in order for it to work?
Again, the point is to use ReadDirectoryChangesW asynchronously, in a loop, as shown in the snippet from the INTRODUCTION.
The basic structure of your program looks more or less OK, you're just using the asynchronous I/O calls incorrectly. Whenever there are no new files, the wait on the event handle times out immediately, which is fine, but you then issue a brand new I/O request, which isn't.
That's why you're running out of system resources; you're issuing I/O requests full tilt without waiting for any of them to complete. You should only issue a new request after the existing request has completed.
(Also, you should be calling GetOverlappedResult to check whether the I/O was successful or not.)
So your loop should look more like this:
::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
while (1)
{
DWORD dw;
DWORD result = ::WaitForSingleObject(ovl.hEvent, 0);
switch (result)
{
case WAIT_TIMEOUT:
processBackgroundTasks();
break;
case WAIT_OBJECT_0:
::GetOverlappedResult(hDir, &ovl, &dw, FALSE);
processDirectoryChanges(buffer);
::ResetEvent(ovl.hEvent);
::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL);
break;
}
}
Notes:
The error handling has been elided for simplicity; I have not done any testing or checked your code for any other problems.
If there might not be any background tasks to perform, you should test for that case and set the timeout to INFINITE rather than 0 when it occurs, otherwise you will be spinning.
I wanted to only show the minimal changes necessary to make it work, but calling WaitForSingleObject followed by GetOverlappedResult is redundant; a single call to GetOverlappedResult can both check whether the I/O is complete and retrieve the results if it is.
As requested, the modified version using only GetOverlappedResult and with minimal error checking. I've also added an example of how you might deal with the case where you've run out of work to do; if whatever processing you're doing on the files really does run forever, you don't need that bit.
::ResetEvent(ovl.hEvent);
if (!::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
error = GetLastError();
if (error != ERROR_IO_PENDING) fail();
}
while (1)
{
BOOL wait;
result = process_list_of_existing_files();
if (result == MORE_WORK_PENDING)
{
wait = FALSE;
}
else if (result == NO_MORE_WORK_PENDING)
{
wait = TRUE;
}
if (!::GetOverlappedResult(hDir, &ovl, &dw, wait))
{
error = GetLastError();
if (error == ERROR_IO_INCOMPLETE) continue;
fail();
}
processDirectoryChanges(buffer);
::ResetEvent(ovl.hEvent);
if (!::ReadDirectoryChangesW(hDir,
buffer, sizeof(buffer), FALSE,
FILE_NOTIFY_CHANGE_FILE_NAME,
NULL, &ovl, NULL))
{
error = GetLastError();
if (error != ERROR_IO_PENDING) fail();
}
}
Variant of indirect using IOCP
Create a class/struct inherited (containing) OVERLAPPED (or
IO_STATUS_BLOCK), a reference counter, directory handle and data which
you need
Call BindIoCompletionCallback (RtlSetIoCompletionCallback) for
directory handle, for setup your callback
Have a DoRead() routine, which we'll call first-time from the main thread, and then from the callback
In DoRead(), before every call to ReadDirectoryChangesW call
AddRef(); because we pass reference (across OVERLAPPED) to our
struct to kernel
Main (say GUI thread) can continue to do own task after the initial call
to DoRead(), unlike the APC variant, we do not need to wait in alertable state
In the callback, we got a pointer to our struct from inherited (containing)
OVERLAPPED. Do any tasks (processDirectoryChanges), if need
continue spy - call DoRead() and finally call Release()
If ReadDirectoryChangesW from DoRead() fails (as result will be no callback) - we need direct call callback
with error code
For stopping we can simply close the directory handle - as a result, we got
STATUS_NOTIFY_CLEANUP in callback
==================================
//#define _USE_NT_VERSION_
class SPYDATA :
#ifdef _USE_NT_VERSION_
IO_STATUS_BLOCK
#else
OVERLAPPED
#endif
{
HANDLE _hFile;
LONG _dwRef;
union {
FILE_NOTIFY_INFORMATION _fni;
UCHAR _buf[PAGE_SIZE];
};
void DumpDirectoryChanges()
{
union {
PVOID buf;
PBYTE pb;
PFILE_NOTIFY_INFORMATION pfni;
};
buf = _buf;
for (;;)
{
DbgPrint("%x <%.*S>\n", pfni->Action, pfni->FileNameLength >> 1, pfni->FileName);
ULONG NextEntryOffset = pfni->NextEntryOffset;
if (!NextEntryOffset)
{
break;
}
pb += NextEntryOffset;
}
}
#ifdef _USE_NT_VERSION_
static VOID WINAPI _OvCompRoutine(
_In_ NTSTATUS dwErrorCode,
_In_ ULONG_PTR dwNumberOfBytesTransfered,
_Inout_ PIO_STATUS_BLOCK Iosb
)
{
static_cast<SPYDATA*>(Iosb)->OvCompRoutine(dwErrorCode, (ULONG)dwNumberOfBytesTransfered);
}
#else
static VOID WINAPI _OvCompRoutine(
_In_ DWORD dwErrorCode, // really this is NTSTATUS
_In_ DWORD dwNumberOfBytesTransfered,
_Inout_ LPOVERLAPPED lpOverlapped
)
{
static_cast<SPYDATA*>(lpOverlapped)->OvCompRoutine(dwErrorCode, dwNumberOfBytesTransfered);
}
#endif
VOID OvCompRoutine(NTSTATUS status, DWORD dwNumberOfBytesTransfered)
{
DbgPrint("[%x,%x]\n", status, dwNumberOfBytesTransfered);
if (0 <= status)
{
if (status != STATUS_NOTIFY_CLEANUP)
{
if (dwNumberOfBytesTransfered) DumpDirectoryChanges();
process_list_of_existing_files();// so hard do this here ?!?
DoRead();
}
else
{
DbgPrint("\n---- NOTIFY_CLEANUP -----\n");
}
}
Release();
MyReleaseRundownProtection();
}
~SPYDATA()
{
Cancel();
}
public:
void DoRead()
{
if (MyAcquireRundownProtection())
{
AddRef();
#ifdef _USE_NT_VERSION_
NTSTATUS status = ZwNotifyChangeDirectoryFile(_hFile, 0, 0, this, this, &_fni, sizeof(_buf), FILE_NOTIFY_VALID_MASK, TRUE);
if (NT_ERROR(status))
{
OvCompRoutine(status, 0);
}
#else
if (!ReadDirectoryChangesW(_hFile, _buf, sizeof(_buf), TRUE, FILE_NOTIFY_VALID_MASK, (PDWORD)&InternalHigh, this, 0))
{
OvCompRoutine(RtlGetLastNtStatus(), 0);
}
#endif
}
}
SPYDATA()
{
_hFile = 0;// ! not INVALID_HANDLE_VALUE because use ntapi for open file
_dwRef = 1;
#ifndef _USE_NT_VERSION_
RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
#endif
}
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef))
{
delete this;
}
}
BOOL Create(POBJECT_ATTRIBUTES poa)
{
IO_STATUS_BLOCK iosb;
NTSTATUS status = ZwOpenFile(&_hFile, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_DIRECTORY_FILE);
if (0 <= status)
{
return
#ifdef _USE_NT_VERSION_
0 <= RtlSetIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#else
BindIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#endif
}
return FALSE;
}
void Cancel()
{
if (HANDLE hFile = InterlockedExchangePointer(&_hFile, 0))
{
NtClose(hFile);
}
}
};
void DemoF()
{
if (MyInitializeRundownProtection())
{
STATIC_OBJECT_ATTRIBUTES(oa, "<SOME_DIRECTORY>");
if (SPYDATA* p = new SPYDATA)
{
if (p->Create(&oa))
{
p->DoRead();
}
//++ GUI thread run
MessageBoxW(0, L"wait close program...", L"", MB_OK);
//-- GUI thread end
p->Cancel();
p->Release();
}
MyWaitForRundownProtectionRelease();
}
}

How to retrieve information from multiple/dual code signatures on an executable file

I've been using the following code (taken from KB323809 article) to retrieve information about the code signature on the executable file. This works fine for a single digital signature.
But how to retrieve information for multiple code signatures?
In that case the Microsoft code below simply retrives info only for the first signature.
My thought was to call CryptMsgGetParam with CMSG_SIGNER_COUNT_PARAM to get the number of signatures and then pass each signature index to the subsequent call to CryptMsgGetParam with CMSG_SIGNER_INFO_PARAM (in the code below.) But this approach always returns 1 signature, even if I clearly have more, like 3 in this example:
#include <windows.h>
#include <wincrypt.h>
#include <wintrust.h>
#include <stdio.h>
#include <tchar.h>
#pragma comment(lib, "crypt32.lib")
#define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)
typedef struct {
LPWSTR lpszProgramName;
LPWSTR lpszPublisherLink;
LPWSTR lpszMoreInfoLink;
} SPROG_PUBLISHERINFO, *PSPROG_PUBLISHERINFO;
BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,
PSPROG_PUBLISHERINFO Info);
BOOL GetDateOfTimeStamp(PCMSG_SIGNER_INFO pSignerInfo, SYSTEMTIME *st);
BOOL PrintCertificateInfo(PCCERT_CONTEXT pCertContext);
BOOL GetTimeStampSignerInfo(PCMSG_SIGNER_INFO pSignerInfo,
PCMSG_SIGNER_INFO *pCounterSignerInfo);
int _tmain(int argc, TCHAR *argv[])
{
WCHAR szFileName[MAX_PATH];
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
PCCERT_CONTEXT pCertContext = NULL;
BOOL fResult;
DWORD dwEncoding, dwContentType, dwFormatType;
PCMSG_SIGNER_INFO pSignerInfo = NULL;
PCMSG_SIGNER_INFO pCounterSignerInfo = NULL;
DWORD dwSignerInfo;
CERT_INFO CertInfo;
SPROG_PUBLISHERINFO ProgPubInfo;
SYSTEMTIME st;
ZeroMemory(&ProgPubInfo, sizeof(ProgPubInfo));
__try
{
if (argc != 2)
{
_tprintf(_T("Usage: SignedFileInfo <filename>\n"));
return 0;
}
#ifdef UNICODE
lstrcpynW(szFileName, argv[1], MAX_PATH);
#else
if (mbstowcs(szFileName, argv[1], MAX_PATH) == -1)
{
printf("Unable to convert to unicode.\n");
__leave;
}
#endif
// Get message handle and store handle from the signed file.
fResult = CryptQueryObject(CERT_QUERY_OBJECT_FILE,
szFileName,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
&dwEncoding,
&dwContentType,
&dwFormatType,
&hStore,
&hMsg,
NULL);
if (!fResult)
{
_tprintf(_T("CryptQueryObject failed with %x\n"), GetLastError());
__leave;
}
// Get signer information size.
fResult = CryptMsgGetParam(hMsg,
CMSG_SIGNER_INFO_PARAM,
0,
NULL,
&dwSignerInfo);
if (!fResult)
{
_tprintf(_T("CryptMsgGetParam failed with %x\n"), GetLastError());
__leave;
}
// Allocate memory for signer information.
pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo);
if (!pSignerInfo)
{
_tprintf(_T("Unable to allocate memory for Signer Info.\n"));
__leave;
}
// Get Signer Information.
fResult = CryptMsgGetParam(hMsg,
CMSG_SIGNER_INFO_PARAM,
0,
(PVOID)pSignerInfo,
&dwSignerInfo);
if (!fResult)
{
_tprintf(_T("CryptMsgGetParam failed with %x\n"), GetLastError());
__leave;
}
// Get program name and publisher information from
// signer info structure.
if (GetProgAndPublisherInfo(pSignerInfo, &ProgPubInfo))
{
if (ProgPubInfo.lpszProgramName != NULL)
{
wprintf(L"Program Name : %s\n",
ProgPubInfo.lpszProgramName);
}
if (ProgPubInfo.lpszPublisherLink != NULL)
{
wprintf(L"Publisher Link : %s\n",
ProgPubInfo.lpszPublisherLink);
}
if (ProgPubInfo.lpszMoreInfoLink != NULL)
{
wprintf(L"MoreInfo Link : %s\n",
ProgPubInfo.lpszMoreInfoLink);
}
}
_tprintf(_T("\n"));
// Search for the signer certificate in the temporary
// certificate store.
CertInfo.Issuer = pSignerInfo->Issuer;
CertInfo.SerialNumber = pSignerInfo->SerialNumber;
pCertContext = CertFindCertificateInStore(hStore,
ENCODING,
0,
CERT_FIND_SUBJECT_CERT,
(PVOID)&CertInfo,
NULL);
if (!pCertContext)
{
_tprintf(_T("CertFindCertificateInStore failed with %x\n"),
GetLastError());
__leave;
}
// Print Signer certificate information.
_tprintf(_T("Signer Certificate:\n\n"));
PrintCertificateInfo(pCertContext);
_tprintf(_T("\n"));
// Get the timestamp certificate signerinfo structure.
if (GetTimeStampSignerInfo(pSignerInfo, &pCounterSignerInfo))
{
// Search for Timestamp certificate in the temporary
// certificate store.
CertInfo.Issuer = pCounterSignerInfo->Issuer;
CertInfo.SerialNumber = pCounterSignerInfo->SerialNumber;
pCertContext = CertFindCertificateInStore(hStore,
ENCODING,
0,
CERT_FIND_SUBJECT_CERT,
(PVOID)&CertInfo,
NULL);
if (!pCertContext)
{
_tprintf(_T("CertFindCertificateInStore failed with %x\n"),
GetLastError());
__leave;
}
// Print timestamp certificate information.
_tprintf(_T("TimeStamp Certificate:\n\n"));
PrintCertificateInfo(pCertContext);
_tprintf(_T("\n"));
// Find Date of timestamp.
if (GetDateOfTimeStamp(pCounterSignerInfo, &st))
{
_tprintf(_T("Date of TimeStamp : %02d/%02d/%04d %02d:%02d\n"),
st.wMonth,
st.wDay,
st.wYear,
st.wHour,
st.wMinute);
}
_tprintf(_T("\n"));
}
}
__finally
{
// Clean up.
if (ProgPubInfo.lpszProgramName != NULL)
LocalFree(ProgPubInfo.lpszProgramName);
if (ProgPubInfo.lpszPublisherLink != NULL)
LocalFree(ProgPubInfo.lpszPublisherLink);
if (ProgPubInfo.lpszMoreInfoLink != NULL)
LocalFree(ProgPubInfo.lpszMoreInfoLink);
if (pSignerInfo != NULL) LocalFree(pSignerInfo);
if (pCounterSignerInfo != NULL) LocalFree(pCounterSignerInfo);
if (pCertContext != NULL) CertFreeCertificateContext(pCertContext);
if (hStore != NULL) CertCloseStore(hStore, 0);
if (hMsg != NULL) CryptMsgClose(hMsg);
}
return 0;
}
BOOL PrintCertificateInfo(PCCERT_CONTEXT pCertContext)
{
BOOL fReturn = FALSE;
LPTSTR szName = NULL;
DWORD dwData;
__try
{
// Print Serial Number.
_tprintf(_T("Serial Number: "));
dwData = pCertContext->pCertInfo->SerialNumber.cbData;
for (DWORD n = 0; n < dwData; n++)
{
_tprintf(_T("%02x "),
pCertContext->pCertInfo->SerialNumber.pbData[dwData - (n + 1)]);
}
_tprintf(_T("\n"));
// Get Issuer name size.
if (!(dwData = CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
NULL,
0)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// Allocate memory for Issuer name.
szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));
if (!szName)
{
_tprintf(_T("Unable to allocate memory for issuer name.\n"));
__leave;
}
// Get Issuer name.
if (!(CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
szName,
dwData)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// print Issuer name.
_tprintf(_T("Issuer Name: %s\n"), szName);
LocalFree(szName);
szName = NULL;
// Get Subject name size.
if (!(dwData = CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
NULL,
0)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// Allocate memory for subject name.
szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));
if (!szName)
{
_tprintf(_T("Unable to allocate memory for subject name.\n"));
__leave;
}
// Get subject name.
if (!(CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
szName,
dwData)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// Print Subject Name.
_tprintf(_T("Subject Name: %s\n"), szName);
fReturn = TRUE;
}
__finally
{
if (szName != NULL) LocalFree(szName);
}
return fReturn;
}
LPWSTR AllocateAndCopyWideString(LPCWSTR inputString)
{
LPWSTR outputString = NULL;
outputString = (LPWSTR)LocalAlloc(LPTR,
(wcslen(inputString) + 1) * sizeof(WCHAR));
if (outputString != NULL)
{
lstrcpyW(outputString, inputString);
}
return outputString;
}
BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,
PSPROG_PUBLISHERINFO Info)
{
BOOL fReturn = FALSE;
PSPC_SP_OPUS_INFO OpusInfo = NULL;
DWORD dwData;
BOOL fResult;
__try
{
// Loop through authenticated attributes and find
// SPC_SP_OPUS_INFO_OBJID OID.
for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)
{
if (lstrcmpA(SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)
{
// Get Size of SPC_SP_OPUS_INFO structure.
fResult = CryptDecodeObject(ENCODING,
SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
NULL,
&dwData);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}
// Allocate memory for SPC_SP_OPUS_INFO structure.
OpusInfo = (PSPC_SP_OPUS_INFO)LocalAlloc(LPTR, dwData);
if (!OpusInfo)
{
_tprintf(_T("Unable to allocate memory for Publisher Info.\n"));
__leave;
}
// Decode and get SPC_SP_OPUS_INFO structure.
fResult = CryptDecodeObject(ENCODING,
SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
OpusInfo,
&dwData);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}
// Fill in Program Name if present.
if (OpusInfo->pwszProgramName)
{
Info->lpszProgramName =
AllocateAndCopyWideString(OpusInfo->pwszProgramName);
}
else
Info->lpszProgramName = NULL;
// Fill in Publisher Information if present.
if (OpusInfo->pPublisherInfo)
{
switch (OpusInfo->pPublisherInfo->dwLinkChoice)
{
case SPC_URL_LINK_CHOICE:
Info->lpszPublisherLink =
AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszUrl);
break;
case SPC_FILE_LINK_CHOICE:
Info->lpszPublisherLink =
AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszFile);
break;
default:
Info->lpszPublisherLink = NULL;
break;
}
}
else
{
Info->lpszPublisherLink = NULL;
}
// Fill in More Info if present.
if (OpusInfo->pMoreInfo)
{
switch (OpusInfo->pMoreInfo->dwLinkChoice)
{
case SPC_URL_LINK_CHOICE:
Info->lpszMoreInfoLink =
AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszUrl);
break;
case SPC_FILE_LINK_CHOICE:
Info->lpszMoreInfoLink =
AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszFile);
break;
default:
Info->lpszMoreInfoLink = NULL;
break;
}
}
else
{
Info->lpszMoreInfoLink = NULL;
}
fReturn = TRUE;
break; // Break from for loop.
} // lstrcmp SPC_SP_OPUS_INFO_OBJID
} // for
}
__finally
{
if (OpusInfo != NULL) LocalFree(OpusInfo);
}
return fReturn;
}
BOOL GetDateOfTimeStamp(PCMSG_SIGNER_INFO pSignerInfo, SYSTEMTIME *st)
{
BOOL fResult;
FILETIME lft, ft;
DWORD dwData;
BOOL fReturn = FALSE;
// Loop through authenticated attributes and find
// szOID_RSA_signingTime OID.
for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)
{
if (lstrcmpA(szOID_RSA_signingTime,
pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)
{
// Decode and get FILETIME structure.
dwData = sizeof(ft);
fResult = CryptDecodeObject(ENCODING,
szOID_RSA_signingTime,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
(PVOID)&ft,
&dwData);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
break;
}
// Convert to local time.
FileTimeToLocalFileTime(&ft, &lft);
FileTimeToSystemTime(&lft, st);
fReturn = TRUE;
break; // Break from for loop.
} //lstrcmp szOID_RSA_signingTime
} // for
return fReturn;
}
BOOL GetTimeStampSignerInfo(PCMSG_SIGNER_INFO pSignerInfo, PCMSG_SIGNER_INFO *pCounterSignerInfo)
{
PCCERT_CONTEXT pCertContext = NULL;
BOOL fReturn = FALSE;
BOOL fResult;
DWORD dwSize;
__try
{
*pCounterSignerInfo = NULL;
// Loop through unathenticated attributes for
// szOID_RSA_counterSign OID.
for (DWORD n = 0; n < pSignerInfo->UnauthAttrs.cAttr; n++)
{
if (lstrcmpA(pSignerInfo->UnauthAttrs.rgAttr[n].pszObjId,
szOID_RSA_counterSign) == 0)
{
// Get size of CMSG_SIGNER_INFO structure.
fResult = CryptDecodeObject(ENCODING,
PKCS7_SIGNER_INFO,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].cbData,
0,
NULL,
&dwSize);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}
// Allocate memory for CMSG_SIGNER_INFO.
*pCounterSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSize);
if (!*pCounterSignerInfo)
{
_tprintf(_T("Unable to allocate memory for timestamp info.\n"));
__leave;
}
// Decode and get CMSG_SIGNER_INFO structure
// for timestamp certificate.
fResult = CryptDecodeObject(ENCODING,
PKCS7_SIGNER_INFO,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].cbData,
0,
(PVOID)*pCounterSignerInfo,
&dwSize);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}
fReturn = TRUE;
break; // Break from for loop.
}
}
}
__finally
{
// Clean up.
if (pCertContext != NULL) CertFreeCertificateContext(pCertContext);
}
return fReturn;
}
In addition to the answer of Daniel Sie.
Found an attribute (szOID_NESTED_SIGNATURE) that would contain the CMSG_SIGNER_INFO that need to be decoded with following steps (this is Delphi code but sense is clear):
LNestedMsg := CryptMsgOpenToDecode(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, 0, 0, 0, nil, 0);
CryptMsgUpdate(LNestedMsg, LFindedAttr.rgValue.pbData, LFindedAttr.rgValue.cbData, True);
CryptMsgGetParam(LNestedMsg, CMSG_SIGNER_INFO_PARAM, 0, nil, #LSize);
CryptMsgGetParam(LNestedMsg, CMSG_SIGNER_INFO_PARAM, 0, LNestedSignerInfo, #LSize);
The acquired CMSG_SIGNER_INFO (LNestedSignerInfo) is the nested signature (in our case SHA2 signature).
To obtain the time-stamp information (RFC3161) of that signature - search the Unauthenticated attribute with pszObjId = szOID_RFC3161_counterSign (1.3.6.1.4.1.311.3.3.1).
Found attribute would contain the CMSG_SIGNER_INFO of the time-stamp counter signature, that also need to be decoded by previously described steps (CryptMsgOpenToDecode, CryptMsgUpdate, CryptMsgGetParam).
The CERT_CONTEXT of nested signature or timestamp counter signature is need to be searched in store with is obtained from corresponding HCRYPTMSG (result of CryptMsgOpenToDecode).
LNestedStore := CertOpenStore(CERT_STORE_PROV_MSG, PKCS_7_ASN_ENCODING or X509_ASN_ENCODING, 0, 0, LNestedMsg);
LTimeStampStore := CertOpenStore(CERT_STORE_PROV_MSG, PKCS_7_ASN_ENCODING or X509_ASN_ENCODING, 0, 0, LTimeStampMsg);
Example to decode szOID_RFC3161_counterSign attribute:
LNestedSignerAttr := LNestedSigner.UnauthAttrs.rgAttr;
for I := 0 to LNestedSigner.UnauthAttrs.cAttr - 1 do
begin
if SameText(string(LNestedSignerAttr.pszObjId), szOID_RFC3161_counterSign) then
begin
LNestedTimeStampMsg := CryptMsgOpenToDecode(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, 0, 0, 0, nil, nil);
if not Assigned(LNestedTimeStampMsg) then
RaiseLastOSError;
try
if not CryptMsgUpdate(LNestedTimeStampMsg, LNestedSignerAttr.rgValue.pbData, LNestedSignerAttr.rgValue.cbData, True) then
RaiseLastOSError;
if not CryptMsgGetParam(LNestedTimeStampMsg, CMSG_SIGNER_INFO_PARAM, 0, nil, #LSize) then
RaiseLastOSError;
GetMem(LTimeStampSigner, LSize);
try
if not CryptMsgGetParam(LNestedTimeStampMsg, CMSG_SIGNER_INFO_PARAM, 0, LTimeStampSigner, #LSize) then
RaiseLastOSError;
LAttr := LTimeStampSigner.AuthAttrs.rgAttr;
for J := 0 to LTimeStampSigner.AuthAttrs.cAttr - 1 do
begin
if SameText(string(LAttr.pszObjId), szOID_RSA_signingTime) then
begin
LSize := SizeOf(LFileTime);
if not CryptDecodeObject(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING,
szOID_RSA_signingTime, LAttr.rgValue.pbData, LAttr.rgValue.cbData, 0, #LFileTime, #LSize) then
RaiseLastOSError;
if FileTimeToLocalFileTime(#LFileTime, LLocalFileTime)
and FileTimeToSystemTime(#LLocalFileTime, LSystemTime) then
SHA2TimeStamp := SystemTimeToDateTime(LSystemTime)
else
SHA2TimeStamp := 0;
end;
Inc(LAttr);
end;
finally
FreeMem(LTimeStampSigner);
end;
finally
if not CryptMsgClose(LNestedTimeStampMsg) then
RaiseLastOSError;
end;
end;
Inc(LNestedSignerAttr);
end;
Authenticode stores secondary signatures in the UnauthenticatedAttributes of primary signer (index 0), instead of additional PKCS 7 signer.
From the primary signature, search the UnauthenticatedAttribue for below:
//Indicates the attribute is an octet encoded PKCS7
define szOID_NESTED_SIGNATURE "1.3.6.1.4.1.311.2.4.1"
The encoded object of this attribute is a full PKCS 7 signer.
Thanks.

Error while reading disk

I'm creating file deletion tool, so working with raw disk access. Have made some functions to read data.
A bit sorry because posting so much code, but not sure where is the real problem.
struct Extent
{
LONGLONG ClustersCount;
LARGE_INTEGER Lcn; //lcn - logical cluster number - the offset of a cluster from some arbitary point within volume
Extent() : ClustersCount(), Lcn()
{}
Extent(LONGLONG clustersCount, LARGE_INTEGER lcn) : ClustersCount(clustersCount), Lcn(lcn)
{}
};
typedef std::vector<Extent> ExtentsVector;
bool GetFileExtentPoints(const std::wstring& filePath, ExtentsVector& output)
{
output.clear();
DWORD err = ERROR_SUCCESS;
HANDLE file = CreateFile(filePath.c_str(), FILE_READ_ATTRIBUTES, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), NULL, OPEN_EXISTING, 0, 0);
if (file != INVALID_HANDLE_VALUE)
{
STARTING_VCN_INPUT_BUFFER vcnStartBuffer = {};
RETRIEVAL_POINTERS_BUFFER pointsBuffer = {};
DWORD deviceIoControlDataritten = 0;
do
{
if (DeviceIoControl(file, FSCTL_GET_RETRIEVAL_POINTERS, &vcnStartBuffer, sizeof(vcnStartBuffer), &pointsBuffer, sizeof(pointsBuffer), &deviceIoControlDataritten, NULL))
{
Extent extent(pointsBuffer.Extents->NextVcn.QuadPart - pointsBuffer.StartingVcn.QuadPart, pointsBuffer.Extents[0].Lcn);
output.push_back(extent);
CloseHandle(file);
return true;
}
if (pointsBuffer.ExtentCount == 0) //small files could be stroed in master file table, so this part shouldn't always return false
{
CloseHandle(file);
return false;
}
Extent extent(pointsBuffer.Extents->NextVcn.QuadPart - pointsBuffer.StartingVcn.QuadPart, pointsBuffer.Extents[0].Lcn);
output.push_back(extent);
vcnStartBuffer.StartingVcn = pointsBuffer.Extents->NextVcn;
}
while (ERROR_MORE_DATA == GetLastError());
CloseHandle(file);
}
return false;
}
bool PermanentDeleteFile/*for now just read...*/(const std::wstring& filePath)
{
ExtentsVector extents;
if (!GetFileExtentPoints(filePath, extents))
return false;
DWORD sectorsPerCluster = 0;
DWORD bytesPerSector = 0;
LARGE_INTEGER fileSize = {};
HANDLE file = CreateFile(filePath.c_str(), FILE_READ_ATTRIBUTES, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), NULL, OPEN_EXISTING, 0, 0);
if (file != INVALID_HANDLE_VALUE)
{
TCHAR drive[] = L"?:\\";
drive[0] = filePath[0];
if (!GetDiskFreeSpace(drive, &sectorsPerCluster, &bytesPerSector, NULL, NULL))
{
CloseHandle(file);
return false;
}
if (!GetFileSizeEx(file, &fileSize))
{
CloseHandle(file);
return false;
}
CloseHandle(file);
}
LONGLONG clusterSize = sectorsPerCluster * bytesPerSector;
LONGLONG clustersCount = fileSize.QuadPart / clusterSize + ((fileSize.QuadPart % clusterSize == 0) ? 0 : 1);
TCHAR rawDrive[] = L"\\\\.\\?:";
rawDrive[4] = filePath[0];
HANDLE driveHandle = CreateFile(rawDrive, /*GENERIC_WRITE*/GENERIC_READ/**/, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
if (driveHandle != INVALID_HANDLE_VALUE)
{
for (ExtentsVector::iterator it = extents.begin(); it != extents.end(); ++it)
{
LARGE_INTEGER distance = {};
distance.QuadPart = it->Lcn.QuadPart * clusterSize;
BOOL b = SetFilePointerEx(driveHandle, distance, NULL, FILE_BEGIN);
if (b)
{
std::string buffer;
buffer.resize(clusterSize * it->ClustersCount);
DWORD read = 0;
BOOL B = ReadFile(driveHandle, &buffer[0], clusterSize * it->ClustersCount, &read, NULL);
B = FALSE;//here I have breakpoint and for FAT type drives buffer contains invalid data
}
else
{
CloseHandle(driveHandle);
return false;
}
}
}
return false;
}
GetFileExtentPoints should collect clusters entries and cluster chain size for latter actions according to given file.
The place where the problem is seen is in PermanentDeleteFile I have marked that place with comment.
I have tried to read all my FAT32 drive and found that data I'm looking for is in drive but in other place so, as I understand FAT uses some kind of data offset. After that read this. As I think the offset is described by first 3 bytes in drive, but don't understand how to use it. In my case it is 0xEB 0x58 0x90. Want to ask, how to decode file system header into data offset.
BTW. I'm coding with C++ and using WinAPI.
EDIT: I have change my line into distance.QuadPart = it->Lcn.QuadPart * clusterSize + 0x01000000;, this works for FAT, however I don't want to have hardcoded constants, so can someone tell, if it is always this value, or it is calculated some way.

C++: Get MAC address of network adapters on Vista?

We are currently using the NetBios method, and it works ok under XP. Preliminary tests under Vista show that it also works, but there are caveats - NetBIOS has to be present, for instance, and from what I've been reading, the order of the adapters is bound to change. Our alternative method - with SNMPExtensionQuery - seems to be broken under Vista.
The question is: do you know of a reliable way to get a list of the local MAC addresses on a Vista machine? Backwards compatibility with XP is a plus (I'd rather have one single method than lots of ugly #ifdef's). Thanks!
This will give you a list of all MAC addresses on your computer. It will work with all versions of Windows as well:
void getdMacAddresses(std::vector<std::string> &vMacAddresses;)
{
vMacAddresses.clear();
IP_ADAPTER_INFO AdapterInfo[32]; // Allocate information for up to 32 NICs
DWORD dwBufLen = sizeof(AdapterInfo); // Save memory size of buffer
DWORD dwStatus = GetAdaptersInfo( // Call GetAdapterInfo
AdapterInfo, // [out] buffer to receive data
&dwBufLen); // [in] size of receive data buffer
//No network card? Other error?
if(dwStatus != ERROR_SUCCESS)
return;
PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo;
char szBuffer[512];
while(pAdapterInfo)
{
if(pAdapterInfo->Type == MIB_IF_TYPE_ETHERNET)
{
sprintf_s(szBuffer, sizeof(szBuffer), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x"
, pAdapterInfo->Address[0]
, pAdapterInfo->Address[1]
, pAdapterInfo->Address[2]
, pAdapterInfo->Address[3]
, pAdapterInfo->Address[4]
, pAdapterInfo->Address[5]
);
vMacAddresses.push_back(szBuffer);
}
pAdapterInfo = pAdapterInfo->Next;
}
}
Could you use the WMIService? I used it to get the mac-address of a machine in pre-Vista days though.
Old question, already answered, but this is safer code - in case WMI can't be fully initialized.
For getting access to information about your system, here is a minimalist class that tries to stay safe:
NOTE: ToString, convertToUtf8 and convertFromUtf8 are left as an exercise for the reader. :)
NOTE: I've just shown the safe initialization and tear-down of the WMI system, and the basics of getting values from WMI, and getting the MAC Addresses (the question in the OP).
This came from working code, but was modified as I pasted it in here. So it is possible other things got left out that ought to have been included. Oops.
class WmiAccessor
{
public:
WmiAccessor()
: _pWbemLocator(NULL)
, _pWbemServices(NULL)
, _com_initialized(false)
, _com_need_uninitialize(false)
, _svc_initialized(false)
, _loc_initialized(false)
, _all_initialized(false)
, _errors("")
, m_mutex()
{
HRESULT hr;
hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
switch (hr)
{
case S_OK:
// The COM library was initialized successfully on this thread.
_com_initialized = true;
_com_need_uninitialize = true;
break;
case S_FALSE:
// The COM library is already initialized on this thread.
_com_initialized = true;
_com_need_uninitialize = true;
break;
case RPC_E_CHANGED_MODE:
// A previous call to CoInitializeEx specified the concurrency model
// for this thread as multithread apartment (MTA).
// This could also indicate that a change from neutral-threaded apartment to
// single-threaded apartment has occurred.
_com_initialized = true;
_com_need_uninitialize = false;
break;
default:
_com_initialized = false;
_com_need_uninitialize = false;
_errors += "Failed to initialize COM.\r\n";
return;
}
hr = ::CoInitializeSecurity(NULL, -1, NULL, NULL,
0 /*RPC_C_AUTHN_LEVEL_DEFAULT*/,
3 /*RPC_C_IMP_LEVEL_IMPERSONATE*/,
NULL, EOAC_NONE, NULL);
// RPC_E_TOO_LATE == Security must be initialized before!
// It cannot be changed once initialized. I don't care!
if (FAILED(hr) && (hr != RPC_E_TOO_LATE))
{
_errors += "Failed to initialize COM Security.\r\n";
if (_com_need_uninitialize)
{
::CoUninitialize();
_com_need_uninitialize = false;
}
return;
}
hr = _pWbemLocator.CoCreateInstance(CLSID_WbemLocator);
if (FAILED(hr) || (_pWbemLocator == nullptr))
{
_errors += "Failed to initialize WBEM Locator.\r\n";
return;
}
_loc_initialized = true;
hr = _pWbemLocator->ConnectServer(
CComBSTR(L"root\\cimv2"), NULL, NULL, 0, NULL, 0, NULL, &_pWbemServices);
if (FAILED(hr) || (_pWbemServices == nullptr))
{
_errors += "Failed to connect WBEM Locator.\r\n";
_pWbemLocator.Release();
_loc_initialized = false;
return;
}
else
{
_svc_initialized = true;
// Set security Levels on the proxy
hr = CoSetProxyBlanket(_pWbemServices,
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hr))
{
_errors += "Failed to set proxy blanket.\r\n";
return;
}
}
_all_initialized = true;
}
~WmiAccessor()
{
std::unique_lock<std::mutex> slock(m_mutex);
if (_svc_initialized)
{
if (_pWbemServices)
_pWbemServices.Release();
_svc_initialized = false;
}
if (_loc_initialized)
{
if (_pWbemLocator)
_pWbemLocator.Release();
_loc_initialized = false;
}
if (_com_initialized)
{
if (_com_need_uninitialize)
{
::CoUninitialize();
}
_com_initialized = false;
_com_need_uninitialize = false;
}
_all_initialized = false;
}
// public: must lock
std::string get_and_clear_errors()
{
std::string result = "";
std::unique_lock<std::mutex> slock(m_mutex);
std::swap(result, _errors);
return result;
}
// public: must lock
std::string get_string(const std::string& name, const std::string& dflt /*= ""*/)
{
std::unique_lock<std::mutex> slock(m_mutex);
return _all_initialized ? _string(name) : dflt;
}
// public: must lock
uint32_t get_uint32(const std::string& name, uint32_t dflt /*= 0*/)
{
std::unique_lock<std::mutex> slock(m_mutex);
return _all_initialized ? _uint32(name) : dflt;
}
// similarly for other public accessors of basic types.
private:
CComPtr<IWbemLocator> _pWbemLocator;
CComPtr<IWbemServices> _pWbemServices;
volatile bool _com_initialized;
volatile bool _com_need_uninitialize;
volatile bool _svc_initialized;
volatile bool _loc_initialized;
volatile bool _all_initialized;
std::string _errors;
CComVariant _variant(const std::wstring& name);
std::string _string(const std::string& name);
uint32_t _uint32(const std::string& name);
uint16_t _uint16(const std::string& name);
uint8_t _uint8(const std::string& name);
std::vector<std::string> _macAddresses(bool forceReCalculate = false);
// to protect internal objects, public methods need to protect the internals.
//
mutable std::mutex m_mutex;
std::vector<std::string> _macs; // unlikely to change, so save them once found.
// internal: assumes inside a lock
CComVariant _variant(const std::wstring& name)
{
if (!_all_initialized)
return CComVariant();
CComPtr<IEnumWbemClassObject> pEnum;
CComBSTR cbsQuery = std::wstring(L"Select " + name + L" from Win32_OperatingSystem").c_str();
HRESULT hr = _pWbemServices->ExecQuery(
CComBSTR(L"WQL"), cbsQuery, WBEM_FLAG_FORWARD_ONLY, NULL, &pEnum);
CComVariant cvtValue;
if (FAILED(hr) || !pEnum)
{
std::wstring wquery(cbsQuery, SysStringLen(cbsQuery));
_errors += "Failed to exec WMI query: '" + convertToUtf8(wquery) + "'\r\n";
return cvtValue;
}
ULONG uObjectCount = 0;
CComPtr<IWbemClassObject> pWmiObject;
hr = pEnum->Next(WBEM_INFINITE, 1, &pWmiObject, &uObjectCount);
if (FAILED(hr) || !pWmiObject)
{
_errors
+= "Failed to get WMI Next result for: '" + convertToUtf8(name) + "'\r\n";
return cvtValue;
}
hr = pWmiObject->Get(name.c_str(), 0, &cvtValue, 0, 0);
if (FAILED(hr))
{
_errors
+= "Failed to get WMI result value for: '" + convertToUtf8(name) + "'\r\n";
}
return cvtValue;
}
// internal: assumes inside a lock
std::string _string(const std::string& name)
{
if (!_all_initialized)
return "";
CComVariant cvtValue = _variant(convertFromUtf8(name).c_str());
std::wstring wValue(cvtValue.bstrVal, SysStringLen(cvtValue.bstrVal));
std::string sValue = convertToUtf8(wValue);
return sValue;
}
// internal: assumes inside a lock
uint32_t _uint32(const std::string& name)
{
if (!_all_initialized)
return 0;
CComVariant cvtValue = _variant(convertFromUtf8(name).c_str());
uint32_t uValue = static_cast<uint32_t>(cvtValue.lVal);
return uValue;
}
// similarly for other internal access of basic types.
// internal: assumes inside a lock
std::vector<std::string> _macAddresses(bool forceReCalculate /*= false*/)
{
if (!_all_initialized)
{
return _macs; // it will still be empty at this point.
}
if (forceReCalculate)
{
_macs.clear();
}
if (_macs.empty())
{
// hr == 0x80041010 == WBEM_E_INVALID_CLASS
// hr == 0x80041017 == WBEM_E_INVALID_QUERY
// hr == 0x80041018 == WBEM_E_INVALID_QUERY_TYPE
CComBSTR cbsQuery = std::wstring(L"Select * from Win32_NetworkAdapter").c_str();
CComPtr<IEnumWbemClassObject> pEnum;
HRESULT hr = _pWbemServices->ExecQuery(
CComBSTR(L"WQL"), cbsQuery, WBEM_RETURN_IMMEDIATELY, NULL, &pEnum);
if (FAILED(hr))
{
_errors += "error: MacAddresses: ExecQuery('"
+ convertToUtf8((LPWSTR)cbsQuery) + "') returned "
+ ToString(hr) + "\r\n";
}
if (SUCCEEDED(hr))
{
ULONG fetched;
VARIANT var;
IWbemClassObject* pclsObj = NULL;
while (pEnum)
{
hr = pEnum->Next(WBEM_INFINITE, 1, &pclsObj, &fetched);
if (0 == fetched)
break;
std::string theMac = "";
VariantInit(&var);
hr = pclsObj->Get(L"MACAddress", 0, &var, 0, 0);
if (SUCCEEDED(hr))
{
switch (var.vt)
{
case VT_NULL: break;
case VT_BSTR:
theMac = (var.bstrVal == NULL)
? ""
: convertToUtf8(var.bstrVal);
break;
case VT_LPSTR:
theMac = (var.bstrVal == NULL)
? ""
: (const char*)var.bstrVal;
break;
case VT_LPWSTR:
theMac = (var.bstrVal == NULL)
? ""
: convertToUtf8((LPWSTR)var.bstrVal);
break;
// _could_ be array of BSTR, LPSTR, LPWSTR; unlikely, but ....
case VT_ARRAY | VT_BSTR:
case VT_ARRAY | VT_LPSTR:
case VT_ARRAY | VT_LPWSTR:
_errors += "warning: MacAddresses: unexpected array of addresses";
_errors += "\r\n";
// yet another exercise for the reader :)
break;
default:
_errors += "error: MacAddresses: unexpected VARIANT.vt = "
+ ToString(var.vt) + "\r\n";
break;
}
// local loopback has an empty address?
if (!theMac.empty())
{
_macs.push_back(theMac);
}
}
VariantClear(&var);
pclsObj->Release();
}
}
}
return _macs;
}
...
}
GetAdaptersInfo() is the official method, it enumerates all adapters even ones that are disconnected.
See this post for example code codeguru
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdio.h>
#include <vector>
#include <Windows.h>
#include <Iphlpapi.h>
#include <Assert.h>
#include <string>
#pragma comment(lib, "iphlpapi.lib")
char* getdMacAddresses()
{
IP_ADAPTER_INFO AdapterInfo[32]; // Allocate information for up to 32 NICs
DWORD dwBufLen = sizeof(AdapterInfo); // Save memory size of buffer
DWORD dwStatus = GetAdaptersInfo( // Call GetAdapterInfo
AdapterInfo, // [out] buffer to receive data
&dwBufLen); // [in] size of receive data buffer
//Exit When Error
if (dwStatus != ERROR_SUCCESS)
return "ERROR";
PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo;
char szBuffer[512];
while (pAdapterInfo)
{
if (pAdapterInfo->Type == MIB_IF_TYPE_ETHERNET)
{
sprintf_s(szBuffer, sizeof(szBuffer), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x"
, pAdapterInfo->Address[0]
, pAdapterInfo->Address[1]
, pAdapterInfo->Address[2]
, pAdapterInfo->Address[3]
, pAdapterInfo->Address[4]
, pAdapterInfo->Address[5]
);
return szBuffer;
}
pAdapterInfo = pAdapterInfo->Next;
}
return "ERROR";
}
You can use WMI on both XP and Vista, there are a number of examples online. e.g:
Use Windows Management Instrumentation (WMI) to get a MAC Address