Windows 10 COM crash under loader lock - c++

I stumbled across a Windows 10 crash that's probably new since the Windows 10 update to 2004.
The problem is a crash in ntdll when COM is starting. COM is needed to use Core Audio.
Code which triggers it:
IMMDeviceEnumerator* enumerator = nullptr;
CoInitializeEx(nullptr, COINIT_MULTITHREADED); // Also with STA
HRESULT hr = CoCreateInstance( CLSID_MMDeviceEnumerator, 0, CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&enumerator);
Popup:
Unhandled exception at 0x00007FFE7846D3D1 (ntdll.dll) in MicTest.exe:
A LIST_ENTRY has been corrupted (i.e. double remove).
Callstack:
ntdll.dll!LdrpInsertDataTableEntry()
ntdll.dll!LdrpMapDllWithSectionHandle()
ntdll.dll!LdrpMapDllNtFileName()
ntdll.dll!LdrpMapDllFullPath()
ntdll.dll!LdrpProcessWork()
ntdll.dll!LdrpLoadDllInternal()
ntdll.dll!LdrpLoadForwardedDll()
ntdll.dll!LdrpGetDelayloadExportDll()
ntdll.dll!LdrpHandleProtectedDelayload()
ntdll.dll!LdrResolveDelayLoadedAPI()
combase.dll!00007ffe769ab1c2()
combase.dll!00007ffe769c56fd()
combase.dll!00007ffe769b3b27()
ntdll.dll!RtlRunOnceExecuteOnce()
KernelBase.dll!InitOnceExecuteOnce()
combase.dll!00007ffe7696c11f()
combase.dll!00007ffe7696baf8()
combase.dll!00007ffe7696b9e6()
combase.dll!00007ffe76907df2()
combase.dll!00007ffe76906fce()
combase.dll!00007ffe76907928()
combase.dll!00007ffe76907718()
MicTest.exe!Microphone::Microphone() Line 23 C++
[External Code]
MicTest.exe!main(int argc, char * * argv) Line 67 C++
It's not 100% reproducible there, sometimes the crash is a few lines later in IAudioClient::Initialize when ntdll crashes under loader lock in
> ntdll.dll!LdrpInitializeThread()
ntdll.dll!_LdrpInitialize()
ntdll.dll!LdrpInitialize()
ntdll.dll!LdrInitializeThunk()
This happens in one of the new Windows 10 threadpool threads that Windows itself uses to load a DLL. The offending code shows up as AudioSes.dll!CAudioClient::CreateRemoteStream (Note: AudioSes.dll is Core Audio, symbols per the Microsoft Symbol Server.)
Is this a known problem? Is there a workaround?
For completeness, complete code:
Microphone::Microphone()
{
IMMDeviceEnumerator* enumerator = nullptr;
CoInitializeEx(nullptr, COINIT_MULTITHREADED); // crash near here
HRESULT hr = CoCreateInstance( CLSID_MMDeviceEnumerator, 0, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&enumerator);
if (!enumerator) throw std::system_error(hr, std::system_category());
IMMDevice* device = nullptr;
hr = enumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &device);
enumerator->Release();
if (!device) throw std::system_error(hr, std::system_category());
hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&client);
device->Release();
if (!client) throw std::system_error(hr, std::system_category());
WAVEFORMATEXTENSIBLE *pwfx = nullptr;
hr = client->GetMixFormat(reinterpret_cast<WAVEFORMATEX**>(&pwfx));
if (SUCCEEDED(hr) && pwfx)
{
this->span.sampleRate = pwfx->Format.nSamplesPerSec;
this->blockAlign = pwfx->Format.nBlockAlign;
// In general, this is NOT pwfx->wBitsPerSample;
if (pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
{
this->format = fmt_FP32;
}
else if (pwfx->SubFormat == KSDATAFORMAT_SUBTYPE_PCM)
{
this->format = fmt_PCM;
}
else
{
// Can't deal with that format, and Core Audio ought to support FP32.
pwfx->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
this->format = fmt_FP32;
}
}
// or crash here
hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 1000000, 0, &pwfx->Format, NULL);
if (!SUCCEEDED(hr))
{
throw std::system_error(hr, std::system_category());
}
CoTaskMemFree(pwfx);
// Start capture
hr = client->GetService( IID_IAudioCaptureClient, (void**)&capture);
client->Start();

As RbMm pointed out, this requires looking at the assembly. It turns out that Windows 10 2004 corrupts its PEB_LDR_DATA data structure. This is a per-process structure which contains important data. In particular, LdrpInitializeThread uses PEB_LDR_DATA::InLoadOrderModuleList directly after acquiring the Loader Lock. This can be a null pointer for unclear reasons.
Since this happens in a Windows-created thread in the default threadpool, it's unrelated to and unsynchronized with user code. The exact point of crash can vary - the damage to the PEB_LDR_DATA structure appears to be more extensive.
Workaround
While this is not a solution for the CoInitializeEx crash, it appears that the second crash due to PEB_LDR_DATA::InLoadOrderModuleList corruption can be avoided with minor changes:
Don't set a buffer size, but let Windows determine one and roll with that:
hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, 0, 0, &pwfx->Format, NULL);
UINT32 bufSize = 0;
hr = client->GetBufferSize(&bufSize);
Determine when to call GetBuffer
auto samplePollRatio = pwfx->Format.nSamplesPerSec / 2666;
auto nextPoll = std::chrono::system_clock::now() +
std::chrono::milliseconds(bufSize / samplePollRatio);
And wait for it
std::this_thread::sleep_until(nextPoll);
auto hr = capture->GetBuffer(&buf, &samples, &flags, nullptr, nullptr);
This is a reasonable pattern anyway. The MSDN example also has a Sleep call, but sleep_until is more robust against variable delays in processing. (E.g. on the first call you may have some more delay when opening a WAV file to save the results etc). But by delaying the first sample acquisition you bypass an apparent race condition in Windows 10.

Related

How to Identify an windows process with a certain executable and command line argument is running or not using C++? [duplicate]

I am trying to get another process' command-line parameters (on WinXP 32bit).
I do the following:
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, ProcList.proc_id_as_numbers[i]);
BytesNeeded = sizeof(PROCESS_BASIC_INFORMATION);
ZwQueryInformationProcess(hProcess, ProcessBasicInformation, UserPool, sizeof(PROCESS_BASIC_INFORMATION), &BytesNeeded);
pbi = (PPROCESS_BASIC_INFORMATION)UserPool;
BytesNeeded = sizeof(PEB);
res = ZwReadVirtualMemory(hProcess, pbi->PebBaseAddress, UserPool, sizeof(PEB), &BytesNeeded);
/* zero value returned */
peb = (PPEB)UserPool;
BytesNeeded = sizeof(RTL_USER_PROCESS_PARAMETERS);
res = ZwReadVirtualMemory(hProcess, peb->ProcessParameters, UserPool, sizeof(RTL_USER_PROCESS_PARAMETERS), &BytesNeeded);
ProcParam = (PRTL_USER_PROCESS_PARAMETERS)UserPool;
After the first call, pbi.UniqueProcessID is correct.
But, after calling ZwReadVirtualMemory(), I get the command-line for my process, not the requested one.
I also used ReadProcessMemory() & NtQueryInformationProcess(), but get the same result.
Can anybody help?
On this forum thread, it is said that this code works. Unfortunately, I do not have access to post on that forum to ask them.
It looks like ZwReadVirtualMemory is called only once. That is not enough. It has to be called for each level of pointer indirection. In other words when you retrieve a pointer it points to other process' address space. You cannot read it directly. You have to call ZwReadVirtualMemory again. For the case of those data structures ZwReadVirtualMemory has to be called 3 times: once to read PEB (that is what the code above does), once to read RTL_USER_PROCESS_PARAMETERS and once to read UNICODE_STRING's buffer.
The following code fragment worked for me (error handling omitted for clarity and I used documented ReadProcessMemory API instead of ZwReadVirtualMemory):
LONG status = NtQueryInformationProcess(hProcess,
0,
pinfo,
sizeof(PVOID)*6,
NULL);
PPEB ppeb = (PPEB)((PVOID*)pinfo)[1];
PPEB ppebCopy = (PPEB)malloc(sizeof(PEB));
BOOL result = ReadProcessMemory(hProcess,
ppeb,
ppebCopy,
sizeof(PEB),
NULL);
PRTL_USER_PROCESS_PARAMETERS pRtlProcParam = ppebCopy->ProcessParameters;
PRTL_USER_PROCESS_PARAMETERS pRtlProcParamCopy =
(PRTL_USER_PROCESS_PARAMETERS)malloc(sizeof(RTL_USER_PROCESS_PARAMETERS));
result = ReadProcessMemory(hProcess,
pRtlProcParam,
pRtlProcParamCopy,
sizeof(RTL_USER_PROCESS_PARAMETERS),
NULL);
PWSTR wBuffer = pRtlProcParamCopy->CommandLine.Buffer;
USHORT len = pRtlProcParamCopy->CommandLine.Length;
PWSTR wBufferCopy = (PWSTR)malloc(len);
result = ReadProcessMemory(hProcess,
wBuffer,
wBufferCopy, // command line goes here
len,
NULL);
Why we see see the command line of our own process? That is because processes are laid out in a similar way. Command line and PEB-related structures are likely to have the same addresses. So if you missed ReadProcessMemory you end up exactly with local process' command line.
I was trying to do this same thing using mingw & Qt. I ran into a problem with "undefined reference to CLSID_WbemLocator". After some research, it seems that the version of libwbemuuid.a which was included with my version of mingw only defined IID_IWbemLocator but not CLSID_WbemLocator.
I found that manually defining CLSID_WbemLocator works (although its probably not the "correct" way of doing things).
The final working code:
#include <QDebug>
#include <QString>
#include <QDir>
#include <QProcess>
#define _WIN32_DCOM
#include <windows.h>
#include "TlHelp32.h"
#include <stdio.h>
#include <tchar.h>
#include <wbemidl.h>
#include <comutil.h>
const GUID CLSID_WbemLocator = { 0x4590F811,0x1D3A,0x11D0,{ 0x89,0x1F,0x00,0xAA,0x00,0x4B,0x2E,0x24 } }; //for some reason CLSID_WbemLocator isn't declared in libwbemuuid.a (although it probably should be).
int getProcessInfo(DWORD pid, QString *commandLine, QString *executable)
{
HRESULT hr = 0;
IWbemLocator *WbemLocator = NULL;
IWbemServices *WbemServices = NULL;
IEnumWbemClassObject *EnumWbem = NULL;
//initializate the Windows security
hr = CoInitializeEx(0, COINIT_MULTITHREADED);
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &WbemLocator);
//connect to the WMI
hr = WbemLocator->ConnectServer(L"ROOT\\CIMV2", NULL, NULL, NULL, 0, NULL, NULL, &WbemServices);
//Run the WQL Query
hr = WbemServices->ExecQuery(L"WQL", L"SELECT ProcessId,CommandLine,ExecutablePath FROM Win32_Process", WBEM_FLAG_FORWARD_ONLY, NULL, &EnumWbem);
qDebug() << "Got here." << (void*)hr;
// Iterate over the enumerator
if (EnumWbem != NULL) {
IWbemClassObject *result = NULL;
ULONG returnedCount = 0;
while((hr = EnumWbem->Next(WBEM_INFINITE, 1, &result, &returnedCount)) == S_OK) {
VARIANT ProcessId;
VARIANT CommandLine;
VARIANT ExecutablePath;
// access the properties
hr = result->Get(L"ProcessId", 0, &ProcessId, 0, 0);
hr = result->Get(L"CommandLine", 0, &CommandLine, 0, 0);
hr = result->Get(L"ExecutablePath", 0, &ExecutablePath, 0, 0);
if (ProcessId.uintVal == pid)
{
*commandLine = QString::fromUtf16((ushort*)(long)CommandLine.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
*executable = QString::fromUtf16((ushort*)(long)ExecutablePath.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
qDebug() << *commandLine << *executable;
}
result->Release();
}
}
// Release the resources
EnumWbem->Release();
WbemServices->Release();
WbemLocator->Release();
CoUninitialize();
//getchar();
return(0);
}
and in my Qt project file (.pro) I link to the following libraries:
LIBS += -lole32 -lwbemuuid
Duplicate of How to query a running process for it's parameters list? (windows, C++) , so I'll just copy my answer from there over here:
You can't reliably get that information. There are various tricks to try and retrieve it, but there's no guarantee that the target process hasn't already mangled that section of memory. Raymond Chen discussed this awhile back on The Old New Thing.
You need to be more disciplined with checking return codes. It may be that any of your ZwReadVirtualMemory calls yield an error code which points you into the right direction.
In particular, the ProcList.proc_id_as_numbers[i] part suggests that you're executing this code in a loop. Chances are that the procPeb.ProcessParameters structure is still filled with the values of an earlier loop iteration - and since the ZwReadVirtualMemory call fails on your target process, you get to see the command line of whatever process was previously queried.
You don't have to read the VM of the target process to do this. Just make sure you have the correct Process ID for the target process.
Once you have the process handle via OpenProcess, you can then use NtQueryInformationProcess to get detailed process info. Use the ProcessBasicInformation option to get the PEB of the process - this contains another structure pointer RTL_USER_PROCESS_PARAMETERS, through which you can get the command line.

How WINAPI handle IAudioClient->SetEventHandle() works?

I'm writing an application that reading audio endpoint on windows, via WASAPI. For now I have similar code for capture thread (treat this code as minimal example):
DWORD CWASAPICapture::DoCaptureThread()
{
BYTE *pData;
UINT32 framesAvailable = 1;
DWORD flags, state;
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hr))
{
printf("Unable to initialize COM in render thread: %x\n", hr);
return hr;
}
while (framesAvailable != 0) {
// get the available data in the shared buffer.
state = WaitForSingleObject(_AudioSamplesReadyEvent, INFINITE);
hr = _CaptureClient->GetBuffer(&pData, &framesAvailable, &flags, NULL, NULL);
if ((state != 0) || (hr != S_OK)) {
// my breakpoint
assert(false);
}
UINT32 framesToCopy = min(framesAvailable, static_cast<UINT32>((_CaptureBufferSize - _CurrentCaptureIndex) / _FrameSize));
if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
{
ZeroMemory(&_CaptureBuffer[_CurrentCaptureIndex], framesToCopy*_FrameSize);
} else {
CopyMemory(&_CaptureBuffer[_CurrentCaptureIndex], pData, framesToCopy*_FrameSize);
}
_CurrentCaptureIndex += framesToCopy*_FrameSize;
// release data
hr = _CaptureClient->ReleaseBuffer(framesAvailable);
assert(hr == S_OK);
}
CoUninitialize();
return 0;
}
In Init section I requested buffer for 1s duration, blockAlign equal to 8, so my captureBufferSize = 3528000 bytes. _AudioSamplesReadyEvent set as _AudioClient->SetEventHandle(_AudioSamplesReadyEvent). Packet size = 441. Other code based on official windows SDK CaptureSharedEventDriven example, this method I changed.
Problem:
Program always fails on my breakpoint (by design) and i'm watching into variables, expected values are hr = AUDCLNT_S_BUFFER_EMPTY (0x08890001) and _CurrentCaptureIndex = 3528000, and I even got this values like in 1 from 10 cases... Usually hr = AUDCLNT_S_BUFFER_EMPTY (0x08890001) and _CurrentCaptureIndex equals to random values, most often is 0, 3528 (8 packets) and 10584 (24 packets).
First of all, when I just started to use WaitForSingleObject with _AudioClient->SetEventHandle, I supposed it signals when buffer is filled for requested time in _AudioClient->Initialize() (MSDN description pretty fluent), but that wrong, next my suggestion was that it waits until a single packet with data got filled, but as my example shows, I don't have available frames in buffer. I tried ti use GetNextPacketSize nex to WaitSingleObject, but still the same result.
My questions is:
Why my example works that way?
What exactly IAudioClient->SetEventHandle() signaling about to WaitForSingleObject()?
How to read buffer of size I requested properly?
UPD1: Sleep() placed before while, instead of WaitForSingleObject() works fine, I get all frames for time my thread slept.
UPD2: minimal example

DeadLock in directShow, mutlti video resizing

1,I'd like to show 9 videos at same time.
2,I have a main thread which create all the windows and handle the WM_* message, also in charge of: A), init vmrvideorender9, MediaControl B), connect filter C), stop and release the MediaControl. and other things.
3,For every video, there is also a render thread which is for sample delivering.
Now, I have many deadlock, they hang at different place and CPU is very high.
The deadlock most happens when resizing the windows.
for example: when I call the following function, sometimes it can consume 40 seconds and sometimes it just hang inside the function.
Void GetVMR9VideoRender()
{
hr = CoCreateInstance(CLSID_VideoMixingRenderer9, NULL, CLSCTX_INPROC,
IID_IBaseFilter, (LPVOID *)&pRender.p);
if (FAILED(hr))
{
return NULL;
}
CComPtr <IVMRFilterConfig9> pConfig;
hr =pRender->QueryInterface(IID_IVMRFilterConfig9, (void**)&pConfig);
if (FAILED(hr))
return NULL;
pConfig->SetRenderingMode(VMRMode_Windowless);
pConfig->SetNumberOfStreams(1);
CComPtr<IVMRWindowlessControl9> lpDefWC;
hr = pRender->QueryInterface(IID_IVMRWindowlessControl9, (LPVOID*)&lpDefWC.p);
if (FAILED(hr))
{
return NULL;
}
m_lpDefWC = (IVMRWindowlessControl*)lpDefWC.p ;
CComPtr<IVMRAspectRatioControl9> lpARC;
hr = pRender->QueryInterface(IID_IVMRWindowlessControl9, (LPVOID*)&lpARC.p);
if (FAILED(hr))
{
return NULL;
}
m_lpARC = (IVMRAspectRatioControl*)lpARC.p ;
return pRender;
}

Subsequent SpVoice instances are silent

Discovered key to the problem/crash, see the bottom of the post.
When creating instances of ISpVoice using CoCreateInstance, it seems like instances after the first one cannot speak as soon as the first one has spoken. That is, if pVoice speaks first, pVoice2 will not speak. If pVoice2 speaks first, pVoice will not speak. The order of creation/allocation does not seem to matter.
When I say "will not speak", I mean: calls to ISpVoice::Speak return immediately with the result S_OK; no voice was synthesized.
Correction: the above happens in the D version when the debugger is not attached. When the Visual Studio debugger is attached, an access violation occurs at vtjpnsapi50.dll!10004e65. Also, the access violation happens in the C++ version regardless of whether a debugger is attached or debug information is generated.
Inserting a call to sleep inbetween the calls to Speak does not change anything (except, of course, that it inserts a delay).
Reproduction in D (C++ equivalent is below):
import std.c.windows.com;
import core.sys.windows.windows;
import speech.windows.sapi;
import std.stdio;
int main()
{
if (FAILED(CoInitialize(null)))
return 1;
scope(exit) CoUninitialize();
ISpVoice pVoice;
HRESULT hr = CoCreateInstance(&CLSID_SpVoice, null, CLSCTX_ALL, &IID_ISpVoice, cast(void**)&pVoice);
assert(hr == S_OK);
hr = pVoice.Speak("Hello world", 0, null); // This speaks fine
assert(hr == S_OK);
ISpVoice pVoice2;
hr = CoCreateInstance(&CLSID_SpVoice, null, CLSCTX_ALL, &IID_ISpVoice, cast(void**)&pVoice2);
assert(hr == S_OK);
hr = pVoice2.Speak("hello again", 0, null); // This returns immediately
assert(hr == S_OK); // Yet it still returns S_OK
hr = pVoice.Speak("first voice again", 0, null); // This speaks fine too, immediately after "hello world" finishes
assert(hr == S_OK);
// The two objects are indeed at different memory addresses
writefln("voice 1: %s, voice 2: %s", cast(void*)pVoice, cast(void*)pVoice2);
pVoice.Release();
pVoice = null;
pVoice2.Release();
pVoice2 = null;
return 0;
}
This is the equivalent C++ program:
#include <sapi.h>
#include<Windows.h>
int main()
{
if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
return 1;
ISpVoice* pVoice;
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void**)&pVoice);
hr = pVoice->Speak(L"Hello world", 0, NULL); // This speaks fine
ISpVoice* pVoice2;
hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void**)&pVoice2);
hr = pVoice2->Speak(L"hello again", 0, NULL); // This causes an access violation
hr = pVoice->Speak(L"first voice again", 0, NULL);
pVoice->Release();
pVoice = NULL;
pVoice2->Release();
pVoice2 = NULL;
CoUninitialize();
return 0;
}
In the above example, both voices are allocated with the same parameters, and the first voice to speak works properly. In the D version when a debugger is not attached, the second call to Speak on pVoice also works.
If anyone has any idea what could cause this, or know about any open source software where multiple voice objects are used, please let me know, thanks!
edit
This only happens with NeoSpeech's voices. It works fine with Microsoft's and eSpeak's voices. I'd still like to know if there's anything I could do to remedy the problem (NeoSpeech has some really, really good voices...).

Getting another process command line in Windows

I am trying to get another process' command-line parameters (on WinXP 32bit).
I do the following:
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, ProcList.proc_id_as_numbers[i]);
BytesNeeded = sizeof(PROCESS_BASIC_INFORMATION);
ZwQueryInformationProcess(hProcess, ProcessBasicInformation, UserPool, sizeof(PROCESS_BASIC_INFORMATION), &BytesNeeded);
pbi = (PPROCESS_BASIC_INFORMATION)UserPool;
BytesNeeded = sizeof(PEB);
res = ZwReadVirtualMemory(hProcess, pbi->PebBaseAddress, UserPool, sizeof(PEB), &BytesNeeded);
/* zero value returned */
peb = (PPEB)UserPool;
BytesNeeded = sizeof(RTL_USER_PROCESS_PARAMETERS);
res = ZwReadVirtualMemory(hProcess, peb->ProcessParameters, UserPool, sizeof(RTL_USER_PROCESS_PARAMETERS), &BytesNeeded);
ProcParam = (PRTL_USER_PROCESS_PARAMETERS)UserPool;
After the first call, pbi.UniqueProcessID is correct.
But, after calling ZwReadVirtualMemory(), I get the command-line for my process, not the requested one.
I also used ReadProcessMemory() & NtQueryInformationProcess(), but get the same result.
Can anybody help?
On this forum thread, it is said that this code works. Unfortunately, I do not have access to post on that forum to ask them.
It looks like ZwReadVirtualMemory is called only once. That is not enough. It has to be called for each level of pointer indirection. In other words when you retrieve a pointer it points to other process' address space. You cannot read it directly. You have to call ZwReadVirtualMemory again. For the case of those data structures ZwReadVirtualMemory has to be called 3 times: once to read PEB (that is what the code above does), once to read RTL_USER_PROCESS_PARAMETERS and once to read UNICODE_STRING's buffer.
The following code fragment worked for me (error handling omitted for clarity and I used documented ReadProcessMemory API instead of ZwReadVirtualMemory):
LONG status = NtQueryInformationProcess(hProcess,
0,
pinfo,
sizeof(PVOID)*6,
NULL);
PPEB ppeb = (PPEB)((PVOID*)pinfo)[1];
PPEB ppebCopy = (PPEB)malloc(sizeof(PEB));
BOOL result = ReadProcessMemory(hProcess,
ppeb,
ppebCopy,
sizeof(PEB),
NULL);
PRTL_USER_PROCESS_PARAMETERS pRtlProcParam = ppebCopy->ProcessParameters;
PRTL_USER_PROCESS_PARAMETERS pRtlProcParamCopy =
(PRTL_USER_PROCESS_PARAMETERS)malloc(sizeof(RTL_USER_PROCESS_PARAMETERS));
result = ReadProcessMemory(hProcess,
pRtlProcParam,
pRtlProcParamCopy,
sizeof(RTL_USER_PROCESS_PARAMETERS),
NULL);
PWSTR wBuffer = pRtlProcParamCopy->CommandLine.Buffer;
USHORT len = pRtlProcParamCopy->CommandLine.Length;
PWSTR wBufferCopy = (PWSTR)malloc(len);
result = ReadProcessMemory(hProcess,
wBuffer,
wBufferCopy, // command line goes here
len,
NULL);
Why we see see the command line of our own process? That is because processes are laid out in a similar way. Command line and PEB-related structures are likely to have the same addresses. So if you missed ReadProcessMemory you end up exactly with local process' command line.
I was trying to do this same thing using mingw & Qt. I ran into a problem with "undefined reference to CLSID_WbemLocator". After some research, it seems that the version of libwbemuuid.a which was included with my version of mingw only defined IID_IWbemLocator but not CLSID_WbemLocator.
I found that manually defining CLSID_WbemLocator works (although its probably not the "correct" way of doing things).
The final working code:
#include <QDebug>
#include <QString>
#include <QDir>
#include <QProcess>
#define _WIN32_DCOM
#include <windows.h>
#include "TlHelp32.h"
#include <stdio.h>
#include <tchar.h>
#include <wbemidl.h>
#include <comutil.h>
const GUID CLSID_WbemLocator = { 0x4590F811,0x1D3A,0x11D0,{ 0x89,0x1F,0x00,0xAA,0x00,0x4B,0x2E,0x24 } }; //for some reason CLSID_WbemLocator isn't declared in libwbemuuid.a (although it probably should be).
int getProcessInfo(DWORD pid, QString *commandLine, QString *executable)
{
HRESULT hr = 0;
IWbemLocator *WbemLocator = NULL;
IWbemServices *WbemServices = NULL;
IEnumWbemClassObject *EnumWbem = NULL;
//initializate the Windows security
hr = CoInitializeEx(0, COINIT_MULTITHREADED);
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
hr = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *) &WbemLocator);
//connect to the WMI
hr = WbemLocator->ConnectServer(L"ROOT\\CIMV2", NULL, NULL, NULL, 0, NULL, NULL, &WbemServices);
//Run the WQL Query
hr = WbemServices->ExecQuery(L"WQL", L"SELECT ProcessId,CommandLine,ExecutablePath FROM Win32_Process", WBEM_FLAG_FORWARD_ONLY, NULL, &EnumWbem);
qDebug() << "Got here." << (void*)hr;
// Iterate over the enumerator
if (EnumWbem != NULL) {
IWbemClassObject *result = NULL;
ULONG returnedCount = 0;
while((hr = EnumWbem->Next(WBEM_INFINITE, 1, &result, &returnedCount)) == S_OK) {
VARIANT ProcessId;
VARIANT CommandLine;
VARIANT ExecutablePath;
// access the properties
hr = result->Get(L"ProcessId", 0, &ProcessId, 0, 0);
hr = result->Get(L"CommandLine", 0, &CommandLine, 0, 0);
hr = result->Get(L"ExecutablePath", 0, &ExecutablePath, 0, 0);
if (ProcessId.uintVal == pid)
{
*commandLine = QString::fromUtf16((ushort*)(long)CommandLine.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
*executable = QString::fromUtf16((ushort*)(long)ExecutablePath.bstrVal);// + sizeof(int)); //bstrs have their length as an integer.
qDebug() << *commandLine << *executable;
}
result->Release();
}
}
// Release the resources
EnumWbem->Release();
WbemServices->Release();
WbemLocator->Release();
CoUninitialize();
//getchar();
return(0);
}
and in my Qt project file (.pro) I link to the following libraries:
LIBS += -lole32 -lwbemuuid
Duplicate of How to query a running process for it's parameters list? (windows, C++) , so I'll just copy my answer from there over here:
You can't reliably get that information. There are various tricks to try and retrieve it, but there's no guarantee that the target process hasn't already mangled that section of memory. Raymond Chen discussed this awhile back on The Old New Thing.
You need to be more disciplined with checking return codes. It may be that any of your ZwReadVirtualMemory calls yield an error code which points you into the right direction.
In particular, the ProcList.proc_id_as_numbers[i] part suggests that you're executing this code in a loop. Chances are that the procPeb.ProcessParameters structure is still filled with the values of an earlier loop iteration - and since the ZwReadVirtualMemory call fails on your target process, you get to see the command line of whatever process was previously queried.
You don't have to read the VM of the target process to do this. Just make sure you have the correct Process ID for the target process.
Once you have the process handle via OpenProcess, you can then use NtQueryInformationProcess to get detailed process info. Use the ProcessBasicInformation option to get the PEB of the process - this contains another structure pointer RTL_USER_PROCESS_PARAMETERS, through which you can get the command line.