Hold on to your saddles, this is a long one! Skip to "MCVE" part if you don't want to read everything.
I'm trying to make a process started with QProcess exit gracefully. I do not control how the offending process exits, and it only accepts a Ctrl+C signal. What baffles me is that this sounds really simple and obvious to have in QProcess's API. Yet, here I am :D
This is what I got so far:
Like I said, QProcess does not really support this. So I have to dive into the Windows ecosystem and try to implement it natively. I found GenerateConsoleCtrlEvent in Microsoft Docs. It seems like it does exactly what I need, so I tried using it. After some struggling with handling error messages in the Windows API, this is what I got:
QProcess myprocess = new QProcess(this);
myprocess->setReadChannel(QProcess::StandardOutput);
// I'm sorry that I have to be vague here. I can't really share this part.
myprocess->start("myexec", {"arg1", "arg2"});
//...
auto success = GenerateConsoleCtrlEvent(CTRL_C_EVENT, myprocess->pid()->dwProcessId);
if (!success) {
LPVOID lpMsgBuf;
auto err = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPTSTR>(&lpMsgBuf),
0, nullptr );
// probably could have used wcerr, but after making this work I was happy enough with it :D
auto error_string = QString::fromWCharArray((reinterpret_cast<LPTSTR>(lpMsgBuf)));
std::cerr << error_string.toStdString();
LocalFree(lpMsgBuf);
}
This just prints the handle is invalid. to standard error. I kind of expected it, because the docs for GenerateConsoleCtrlEvent say:
dwProcessGroupId [in]
The identifier of the process group to receive the signal. A process group is created when the CREATE_NEW_PROCESS_GROUP flag is specified in a call to the CreateProcess function. The process identifier of the new process is also the process group identifier of a new process group.
... and I was rooting for Qt to be already passing that flag in. This got me stuck for a while, and it seems to be the place where most questions about this here on SO (yes, I've seen them all - I think) seem to have died as well. Then I found QProcess::setCreateProcessArgumentsModifier (With a nice example of usage here) which allows me to inject arguments into the CreateProcess call. Then I updated my code to do this:
QProcess myprocess = new QProcess(this);
myprocess->setCreateProcessArgumentsModifier([this] (QProcess::CreateProcessArguments *args) {
args->flags |= CREATE_NEW_PROCESS_GROUP;
});
myprocess->setReadChannel(QProcess::StandardOutput);
myprocess->start("myexec", {"arg1", "arg2"});
//...
auto success = GenerateConsoleCtrlEvent(CTRL_C_EVENT, myprocess->pid()->dwProcessId);
if (!success) {
LPVOID lpMsgBuf;
auto err = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPTSTR>(&lpMsgBuf),
0, nullptr );
auto error_string = QString::fromWCharArray((reinterpret_cast<LPTSTR>(lpMsgBuf)));
std::cerr << error_string.toStdString();
LocalFree(lpMsgBuf);
}
This however gives me the same error (the handle is invalid). From there, I tried other things, like injecting my own PROCESS_INFORMATION struct to make sure I had the correct process identifier, or even adding CREATE_NEW_PROCESS_GROUP to the lpStartupInfo instead - what I now know to be the wrong place, as this caused some strange behavior (The asker in this link is not me :D)
Any ideas? Could I be doing this differently?
I'm using Qt 5.14.2, compiling with MSVC 2017 (64 bit).
MCVE
Making a "Minimal" MCVE for this is not easy :)
I have created a trivial windows application that handles Ctrl+C by simply printing a message. The goal is to make a QProcess trigger this handler, with no side effects. This is the source code for the child process:
#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
#include "windows.h"
std::atomic<bool> should_stop = false;
BOOL WINAPI consoleHandler(DWORD signal) {
if (signal == CTRL_C_EVENT) {
std::cout << "\nThank you for your Ctrl+C event!\n";
should_stop.store(true);
}
return TRUE;
}
int main() {
if (!SetConsoleCtrlHandler(consoleHandler, TRUE)) {
std::cout << "Failed to set console handler\n";
return 1;
}
while (!should_stop) {
std::cout << "I'll keep printing this message until you stop me." << std::endl; // Yes, I want to flush every time.
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
My "MVCE" for the parent application has a trivial main.cpp, along with a ProcessHolder class with a header and a source file. This is required so that I can have an event loop, and for Qt to be able to moc the class properly (for use in said event loop).
main.cpp
#include <QCoreApplication>
#include <QTimer>
#include <memory>
#include "processholder.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
std::unique_ptr<ProcessHolder> ph(new ProcessHolder());
// Just so I can get the event loop running
QTimer::singleShot(0, ph.get(), &ProcessHolder::waitForInput);
return a.exec();
}
processholder.h
#ifndef PROCESSHOLDER_H
#define PROCESSHOLDER_H
#include <QObject>
#include <QProcess>
class ProcessHolder : public QObject
{
Q_OBJECT
public:
explicit ProcessHolder(QObject *parent = nullptr);
signals:
public slots:
void waitForInput();
private:
QProcess* p;
};
#endif // PROCESSHOLDER_H
processholder.cpp
#include "processholder.h"
#include <iostream>
#include <QTimer>
#include "Windows.h"
void tryFinishProcess(QProcess* p) {
auto success = GenerateConsoleCtrlEvent(CTRL_C_EVENT, p->pid()->dwProcessId);
if (!success) {
LPVOID lpMsgBuf;
auto err = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPTSTR>(&lpMsgBuf),
0, nullptr );
// probably could have used wcerr, but after making this work I was happy enough with it :D
auto error_string = QString::fromWCharArray((reinterpret_cast<LPTSTR>(lpMsgBuf)));
std::cerr << error_string.toStdString();
LocalFree(lpMsgBuf);
}
}
ProcessHolder::ProcessHolder(QObject *parent) : QObject(parent), p(new QProcess(this))
{
connect(p, &QProcess::readyReadStandardOutput, [this]() {
auto lines = p->readAllStandardOutput();
std::cout << lines.toStdString();
});
// Doing this for this example makes things fail miserably when trying to close the parent program.
// An when not doing it, the CtrlC event that is generated on tryFinishProcess actually ends the
// parent program, rather than the child one.
/*p->setCreateProcessArgumentsModifier([this] (QProcess::CreateProcessArguments *args) {
args->flags |= CREATE_NEW_PROCESS_GROUP;
});*/
std::cout << "starting process...\n";
p->start(R"(path\to\TrivialConsoleApp.exe)");
}
void ProcessHolder::waitForInput(){
char c;
bool quit = false;
// Print a small prompt just so we can differentiate input from output
std::cout << "> ";
if (std::cin >> c) {
switch(c) {
case 'k':
p->kill();
break;
case 't':
p->terminate();
break;
case 'c':
p->close();
break;
case 'g':
tryFinishProcess(p);
}
// any other character will just reliquinsh the hold on standard io for a small time, enough for the
// messages that were sent via cout to show up.
if (!quit) {
QTimer::singleShot(0, this, &ProcessHolder::waitForInput);
}
}
}
A few example runs:
Using QProcess::kill(). Child process is terminated, but no CtrlC message.
Using tryFinishProcess(see implementation above) actually made the parent process exit:
Again, using tryFinishProcess, but this time with CREATE_NEW_PROCESS_GROUP added (See comment on ProcessHolder's constructor). A thing to note here is that pressing RETURN as asked by the terminal at the end does not work anymore (it does nothing), so something broke there:
My expectation for the three samples above (or at least for the last two) is to see a "Thank you for your Ctrl+C event!" message (look at consoleHandler on the child process) somewhere after asking for the process to finish. As it happens if I run it on console then press Ctrl+C:
when CTRL+C is input to a console process, system create thread in this process with entry point
EXTERN_C
WINBASEAPI
ULONG
WINAPI
CtrlRoutine(_In_ DWORD dwCtrlEvent);
this function is exported by kernel32.dll (can be forward export to another dll, say kernelbase.dll)
this CtrlRoutine do next:
if process is being debugged - raise DBG_CONTROL_C exception, then
called registered by SetConsoleCtrlHandler callbacks. if no registered callbacks or all it return false - DefaultHandler is called, which simply call ExitProcess(STATUS_CONTROL_C_EXIT) (Application Exit by CTRL+C)
but possible by self, direct call CreateRemoteThread in target process with entry point at CtrlRoutine and CTRL_C_EVENT as parameter. if target process have the same digit capacity as our - both 32 or 64 bit - no any problem at all - we can import CtrlRoutine already at link time (it defined in kernel32.lib ) or get it address via GetProcAddress. but if our process is 64-bit native and target process is 32-bit (WoW64) - here problem - we need get address of CtrlRoutine inside 32-bit kernel32.dll - but we can not direct load it in self 64-bit process and call GetProcAddress. need map this dll by self and by self get it base and parse export. this task already not trivial. and if we run inside wow64 process (32-bit process on 64bit windows) and target process is 64-bit(native) - task already become really hard (despite solution also exist, without create additional processes). but i assume that control process is native (64bit on 64 bit windows)
#pragma warning( disable : 4201)
#include <Windows.h>
#include <malloc.h>
#define LDR_DATA_TABLE_ENTRY _LDR_DATA_TABLE_ENTRY_
#define PLDR_DATA_TABLE_ENTRY _PLDR_DATA_TABLE_ENTRY_
#include <winternl.h>
#undef PLDR_DATA_TABLE_ENTRY
#undef LDR_DATA_TABLE_ENTRY
#define MAXUSHORT 0xffff
#define MAXULONG 0xffffffff
#define RtlOffsetToPointer(B,O) ((PCHAR)( ((PCHAR)(B)) + ((ULONG_PTR)(O)) ))
#define RtlPointerToOffset(B,P) ((ULONG)( ((PCHAR)(P)) - ((PCHAR)(B)) ))
#define RTL_CONSTANT_STRING(s) { sizeof( s ) - sizeof( (s)[0] ), sizeof( s ), const_cast<PWSTR>(s) }
#define NtCurrentProcess() ( (HANDLE)(LONG_PTR) -1 )
typedef enum _SECTION_INHERIT {
ViewShare = 1,
ViewUnmap = 2
} SECTION_INHERIT;
typedef enum _SECTION_INFORMATION_CLASS
{
SectionBasicInformation, // q; SECTION_BASIC_INFORMATION
SectionImageInformation, // q; SECTION_IMAGE_INFORMATION
SectionRelocationInformation, // name:wow64:whNtQuerySection_SectionRelocationInformation
SectionOriginalBaseInformation, // PVOID BaseAddress
SectionInternalImageInformation, // SECTION_INTERNAL_IMAGE_INFORMATION // since REDSTONE2
MaxSectionInfoClass
} SECTION_INFORMATION_CLASS;
typedef struct SECTION_IMAGE_INFORMATION
{
PVOID TransferAddress;
ULONG ZeroBits;
SIZE_T MaximumStackSize;
SIZE_T CommittedStackSize;
ULONG SubSystemType;
union
{
struct
{
USHORT SubSystemMinorVersion;
USHORT SubSystemMajorVersion;
};
ULONG SubSystemVersion;
};
union
{
struct
{
USHORT MajorOperatingSystemVersion;
USHORT MinorOperatingSystemVersion;
};
ULONG OperatingSystemVersion;
};
USHORT ImageCharacteristics;
USHORT DllCharacteristics;
USHORT Machine;
BOOLEAN ImageContainsCode;
union
{
UCHAR ImageFlags;
struct
{
UCHAR ComPlusNativeReady : 1;
UCHAR ComPlusILOnly : 1;
UCHAR ImageDynamicallyRelocated : 1;
UCHAR ImageMappedFlat : 1;
UCHAR BaseBelow4gb : 1;
UCHAR ComPlusPrefer32bit : 1;
UCHAR Reserved : 2;
};
};
ULONG LoaderFlags;
ULONG ImageFileSize;
ULONG CheckSum;
} *PSECTION_IMAGE_INFORMATION;
typedef struct LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
void *DllBase;
void *EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
// .. more members. trucated
} *PLDR_DATA_TABLE_ENTRY;
EXTERN_C_START
NTSYSAPI
NTSTATUS
NTAPI
LdrFindEntryForAddress(
_In_ PVOID DllHandle,
_Out_ PLDR_DATA_TABLE_ENTRY *Entry
);
NTSYSAPI
PIMAGE_NT_HEADERS
NTAPI
RtlImageNtHeader(
_In_ PVOID Base
);
EXTERN_C
NTSYSAPI
PVOID
NTAPI
RtlImageDirectoryEntryToData(
_In_ PVOID Base,
_In_ BOOLEAN MappedAsImage,
_In_ USHORT DirectoryEntry,
_Out_ PULONG Size
);
NTSYSAPI
NTSTATUS
NTAPI
RtlMultiByteToUnicodeSize(
_Out_ PULONG BytesInUnicodeString,
_In_reads_bytes_(BytesInMultiByteString) const CHAR *MultiByteString,
_In_ ULONG BytesInMultiByteString
);
NTSYSAPI
NTSTATUS
NTAPI
RtlMultiByteToUnicodeSize(
_Out_ PULONG BytesInUnicodeString,
_In_reads_bytes_(BytesInMultiByteString) const CHAR *MultiByteString,
_In_ ULONG BytesInMultiByteString
);
NTSYSAPI
NTSTATUS
NTAPI
RtlMultiByteToUnicodeN(
_Out_writes_bytes_to_(MaxBytesInUnicodeString, *BytesInUnicodeString) PWCH UnicodeString,
_In_ ULONG MaxBytesInUnicodeString,
_Out_opt_ PULONG BytesInUnicodeString,
_In_reads_bytes_(BytesInMultiByteString) const CHAR *MultiByteString,
_In_ ULONG BytesInMultiByteString
);
NTSYSAPI
NTSTATUS
NTAPI
RtlAppendUnicodeToString (
_Inout_ PUNICODE_STRING Destination,
_In_opt_z_ PCWSTR Source
);
NTSYSAPI
NTSTATUS
NTAPI
RtlAppendUnicodeStringToString (
_Inout_ PUNICODE_STRING Destination,
_In_ PCUNICODE_STRING Source
);
NTSYSAPI
BOOLEAN
NTAPI
RtlPrefixUnicodeString(
_In_ PCUNICODE_STRING String1,
_In_ PCUNICODE_STRING String2,
_In_ BOOLEAN CaseInSensitive
);
NTSYSAPI
NTSTATUS
NTAPI
ZwUnmapViewOfSection(
_In_ HANDLE ProcessHandle,
_In_opt_ PVOID BaseAddress
);
NTSYSAPI
NTSTATUS
NTAPI
ZwMapViewOfSection(
_In_ HANDLE SectionHandle,
_In_ HANDLE ProcessHandle,
_Outptr_result_bytebuffer_(*ViewSize) PVOID *BaseAddress,
_In_ ULONG_PTR ZeroBits,
_In_ SIZE_T CommitSize,
_Inout_opt_ PLARGE_INTEGER SectionOffset,
_Inout_ PSIZE_T ViewSize,
_In_ SECTION_INHERIT InheritDisposition,
_In_ ULONG AllocationType,
_In_ ULONG Win32Protect
);
NTSYSAPI
NTSTATUS
NTAPI
ZwOpenSection(
_Out_ PHANDLE SectionHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes
);
NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySection(
_In_ HANDLE SectionHandle,
_In_ ULONG SectionInformationClass,
_Out_ PVOID SectionInformation,
_In_ ULONG SectionInformationLength,
_Out_ PSIZE_T ResultLength OPTIONAL
);
NTSYSAPI
NTSTATUS
NTAPI
LdrLoadDll(
_In_opt_ PWSTR DllPath,
_In_opt_ PULONG DllCharacteristics,
_In_ PUNICODE_STRING DllName,
_Out_ HMODULE *DllHandle
);
NTSYSAPI
NTSTATUS
NTAPI
LdrUnloadDll(
_In_ PVOID DllHandle
);
NTSYSAPI
NTSTATUS
NTAPI
LdrGetProcedureAddress(
_In_ PVOID DllHandle,
_In_opt_ PANSI_STRING ProcedureName,
_In_opt_ ULONG ProcedureNumber,
_Out_ PVOID *ProcedureAddress
);
EXTERN_C_END
ULONG GetNameOrdinal(PVOID Base, PDWORD AddressOfNames, DWORD NumberOfNames, PCSTR Name)
{
if (NumberOfNames)
{
DWORD a = 0, o;
do
{
o = (a + NumberOfNames) >> 1;
int i = strcmp(RtlOffsetToPointer(Base, AddressOfNames[o]), Name);
if (!i)
{
return o;
}
0 > i ? a = o + 1 : NumberOfNames = o;
} while (a < NumberOfNames);
}
return MAXULONG;
}
PVOID getWowProcs(PCUNICODE_STRING DllName, PCSTR Name);
PVOID getWowProcs(PVOID ImageBase, PVOID BaseAddress, PCSTR Name)
{
ULONG exportSize, exportRVA;
PIMAGE_EXPORT_DIRECTORY pied = (PIMAGE_EXPORT_DIRECTORY)
RtlImageDirectoryEntryToData(BaseAddress, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &exportSize);
if (!pied) return 0;
exportRVA = RtlPointerToOffset(BaseAddress, pied);
ULONG NumberOfFunctions = pied->NumberOfFunctions;
if (!NumberOfFunctions) return 0;
ULONG NumberOfNames = pied->NumberOfNames;
ULONG OrdinalBase = pied->Base;
PULONG AddressOfFunctions = (PULONG)RtlOffsetToPointer(BaseAddress, pied->AddressOfFunctions);
PULONG AddressOfNames = (PULONG)RtlOffsetToPointer(BaseAddress, pied->AddressOfNames);
PWORD AddressOfNameOrdinals = (PWORD)RtlOffsetToPointer(BaseAddress, pied->AddressOfNameOrdinals);
ULONG o;
if (*Name == '#')
{
if ((o = strtoul(Name + 1, const_cast<char**>(&Name), 10)) < OrdinalBase || *Name)
{
return 0;
}
o -= OrdinalBase;
}
else
{
o = GetNameOrdinal(BaseAddress, AddressOfNames, NumberOfNames, Name);
if (o < NumberOfNames)
{
o = AddressOfNameOrdinals[o];
}
}
if (o >= NumberOfFunctions)
{
return 0;
}
DWORD Rva = AddressOfFunctions[o];
if ((ULONG_PTR)Rva - (ULONG_PTR)exportRVA >= exportSize)
{
return RtlOffsetToPointer(ImageBase, Rva);
}
// forward export
PCSTR pfn = RtlOffsetToPointer(BaseAddress, Rva);
if (!(Name = strrchr(pfn, '.')))
{
return 0;
}
static const WCHAR DLL[] = L"DLL";
ULONG BytesInUnicodeString, BytesInMultiByteString = RtlPointerToOffset(pfn, ++Name);
if (0 > RtlMultiByteToUnicodeSize(&BytesInUnicodeString, pfn, BytesInMultiByteString) ||
(BytesInUnicodeString += sizeof(DLL)) >= MAXUSHORT )
{
return 0;
}
UNICODE_STRING DllName = {
0, (USHORT)BytesInUnicodeString, (PWSTR)alloca(BytesInUnicodeString)
};
if (0 > RtlMultiByteToUnicodeN(DllName.Buffer, DllName.MaximumLength,
&BytesInUnicodeString, pfn, BytesInMultiByteString))
{
return 0;
}
DllName.Length = (USHORT)BytesInUnicodeString;
if (0 > RtlAppendUnicodeToString(&DllName, DLL))
{
return 0;
}
static const UNICODE_STRING API_ = RTL_CONSTANT_STRING(L"API-");
static const UNICODE_STRING EXT_ = RTL_CONSTANT_STRING(L"EXT-");
if (!RtlPrefixUnicodeString(&API_, &DllName, TRUE) &&
!RtlPrefixUnicodeString(&EXT_, &DllName, TRUE))
{
return getWowProcs(&DllName, Name);
}
HMODULE hmod;
if (0 <= LdrLoadDll(0, 0, &DllName, &hmod))
{
ANSI_STRING as, *pas;
if (*Name == '#')
{
pas = 0;
o = strtoul(Name + 1, const_cast<char**>(&Name), 10);
if (*Name)
{
o = 0;
}
}
else
{
o = 0;
RtlInitAnsiString(pas = &as, Name);
}
PVOID pv, pvfn = 0;
if (0 <= LdrGetProcedureAddress(hmod, pas, o, &pv))
{
PLDR_DATA_TABLE_ENTRY ldte;
if (0 <= LdrFindEntryForAddress(pv, &ldte))
{
pvfn = getWowProcs(&ldte->BaseDllName, Name);
}
}
LdrUnloadDll(hmod);
return pvfn;
}
return 0;
}
PVOID getWowProcs(PCUNICODE_STRING DllName, PCSTR Name)
{
static const WCHAR KnownDlls32[] = L"\\KnownDlls32\\";
UNICODE_STRING ObjectName = {
0,
(USHORT)(sizeof(KnownDlls32) + DllName->Length),
(PWSTR)alloca(ObjectName.MaximumLength)
};
if (0 > RtlAppendUnicodeToString(&ObjectName, KnownDlls32) ||
0 > RtlAppendUnicodeStringToString(&ObjectName, DllName))
{
return 0;
}
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE };
HANDLE hSection;
PVOID pv = 0;
if (0 <= ZwOpenSection(&hSection, SECTION_QUERY|SECTION_MAP_READ, &oa))
{
SECTION_IMAGE_INFORMATION sii;
if (0 <= ZwQuerySection(hSection, SectionImageInformation, &sii, sizeof(sii), 0))
{
PVOID BaseAddress = 0;
SIZE_T ViewSize = 0;
if (0 <= ZwMapViewOfSection(hSection, NtCurrentProcess(), &BaseAddress, 0, 0, 0, &ViewSize, ViewUnmap, 0, PAGE_READONLY))
{
__try
{
if (PIMAGE_NT_HEADERS32 pinth = (PIMAGE_NT_HEADERS32)RtlImageNtHeader(BaseAddress))
{
pv = getWowProcs((PBYTE)sii.TransferAddress - pinth->OptionalHeader.AddressOfEntryPoint, BaseAddress, Name);
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
ZwUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
}
}
NtClose(hSection);
}
return pv;
}
PVOID GetCtrlRoutine(HANDLE hProcess)
{
BOOL bWow;
if (IsWow64Process(hProcess, &bWow))
{
static const UNICODE_STRING kernel32 = RTL_CONSTANT_STRING(L"kernel32.dll");
if (bWow)
{
static PVOID pvCtrlRoutine = 0;
if (!pvCtrlRoutine)
{
// GetOverlappedResultEx
// just for some extreme case, for better understand code in getWowProcs
// it not need here
// pvCtrlRoutine = getWowProcs(&kernel32, "GetOverlappedResultEx");
pvCtrlRoutine = getWowProcs(&kernel32, "CtrlRoutine");
}
return pvCtrlRoutine;
}
static PVOID pvCtrlRoutine = 0;
if (!pvCtrlRoutine)
{
if (HMODULE hmod = GetModuleHandle(kernel32.Buffer))
{
pvCtrlRoutine = GetProcAddress(hmod, "CtrlRoutine");
}
}
return pvCtrlRoutine;
}
return 0;
}
void SendCtrlEvent(HANDLE hProcess)
{
if (PVOID pv = GetCtrlRoutine(hProcess))
{
if (HANDLE hThread = CreateRemoteThread(hProcess, 0, 0, (PTHREAD_START_ROUTINE)pv, CTRL_C_EVENT, 0, 0))
{
CloseHandle(hThread);
return ;
}
}
TerminateProcess(hProcess, STATUS_CONTROL_C_EXIT);
}
void DemoUseCtrlC()
{
WCHAR AppName[MAX_PATH];
if (ExpandEnvironmentStringsW(L"%SystemRoot%\\syswow64\\ping.exe", AppName, _countof(AppName)))
{
STARTUPINFO si = { sizeof(si)};
PROCESS_INFORMATION pi;
if (CreateProcessW(AppName, const_cast<PWSTR>(L"* -n 999 8.8.8.8"), 0, 0, 0, 0, 0, 0, &si, &pi))
{
CloseHandle(pi.hThread);
MessageBoxW(HWND_DESKTOP, L"Break..", L"CTRL+C", MB_ICONINFORMATION);
SendCtrlEvent(pi.hProcess);
CloseHandle(pi.hProcess);
}
}
}
For starters, you're not running the event loop. All you did with the single shot timer was to run your blocking code from within the event loop, but you never return control to the event loop other than to let your code block it again, and QProcess is not guaranteed to be very helpful to you when you do that.
Running the event loop means that your code does a "small" amount of work, never blocks, and returns as soon as it's done. You cannot use std::cin if you want the event loop to run - not without platform-specific trickery, anyway.
In a simple demo, instead of making it interactive, you could be sending that Ctrl-C once the process has started - in reaction to a signal sent by QProcess - and then add perhaps some extra time (say 500ms).
For interactive use, don't use console, but make a minimal graphical program, e.g. use a widget with a couple of buttons. For an MCVE, use a single main.cpp file, no header files, begin with #include <QtWidgets> and end with #include "main.moc" (the latter only if you had any Q_OBJECT macros or you otherwise need moc output to be compiled into your code), and don't allocate on the heap unless you need to - it's just visual noise (e.g. that std::unique_ptr for the process holder in main is unnecessary, as is pretty much every single dynamic allocation you make elsewhere).
Related
My project contains managed part(dotnet) and native(C++) part. I want to hook Winapi CreateFiles in my project by detours and log callstack. Now I have obtained a native callstack by invoking CaptureStackBackTrace and SymFromAddr api in dbghelp. But I meet some troubles when those functions are used in dotnet. as shown in the following picture, it can obtain correct callstack of native code(22, 21, 1, 0) but not work for managed code(20-2). may I have some methods to obtain both managed(dotnet) and un-managed(C++) callstack correctly? or maybe you have the best method to get callstack, could you help me?
dotnet produce:
public class CreateFileOrFolder
{
public void writeToFile(string pathString) {
System.IO.File.Create(pathString);
}
static void Main()
{
System.Console.WriteLine("Press any key to start.\n");
System.Console.ReadKey();
string fileName = "c.txt";
string pathString = System.IO.Path.Combine(#"D:\JunFiles\testDotNetProceduce", fileName);
// Verify the path that you have constructed.
Console.WriteLine("Path to my file: {0}\n", pathString);
CreateFileOrFolder creater = new CreateFileOrFolder();
if (!System.IO.File.Exists(pathString)) {
creater.writeToFile(pathString);
}
else
{
System.IO.File.Delete(pathString);
Console.WriteLine("File \"{0}\" already exists. but now it is deleted\n", fileName);
creater.writeToFile(pathString);
}
}
}
detours hook function:
HANDLE(*__stdcall oldCreateFile)(LPCWSTR,
DWORD,
DWORD,
LPSECURITY_ATTRIBUTES,
DWORD,
DWORD,
HANDLE) = CreateFileW;
HANDLE WINAPI newCreateFile(
_In_ LPCWSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
) {
static BOOL state = FALSE;
printStack();
symbolSolve(process);
printf("HOOK sucess!\n");
std::cout << current_working_directory() << endl;
wchar_t newFileName[] = L".\\newFile.txt";
return oldCreateFile(
newFileName, // L".\\NewFile.txt", // Filename
//lpFileName,
dwDesiredAccess, // Desired access
dwShareMode, // Share mode
lpSecurityAttributes, // Security attributes
dwCreationDisposition, // Creates a new file, only if it doesn't already exist
dwFlagsAndAttributes, // Flags and attributes
NULL);
}
get callstack:
#define STACK_INFO_LEN 200
static HANDLE process;
unsigned int i;
void* stack[100];
unsigned short frames;
SYMBOL_INFO* symbol;
std::queue<std::pair<void*, unsigned short>> myQueue;
PDWORD hashValue = (PDWORD)malloc(sizeof(DWORD));
void printStack(void)
{
frames = CaptureStackBackTrace(0, 100, stack, hashValue);
void* stackTmp = stack;
}
DWORD WINAPI symbolSolve(LPVOID p) {
int myqueue_size = myQueue.size();
char* szBriefInfo = NULL;
static const int MAX_STACK_FRAMES = 12;
void* pStack[MAX_STACK_FRAMES];
static char szStackInfo[STACK_INFO_LEN * MAX_STACK_FRAMES];
static char szFrameInfo[STACK_INFO_LEN];
if (szBriefInfo == NULL) {
strcpy_s(szStackInfo, "stack traceback:\n");
}
else {
strcpy_s(szStackInfo, szBriefInfo);
}
symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
IMAGEHLP_LINE line;
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
DWORD displacementLine = 0;
std::string strs = "";
strs += std::to_string(*hashValue);
for (i = 0; i < frames; i++) {
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
BOOL ret = SymGetLineFromAddr64(process, (DWORD64)(stack[i]), &displacementLine, &line);
_snprintf_s(szFrameInfo, sizeof(szFrameInfo), "%i: %s - 0x%0X ", frames - i - 1, symbol->Name, symbol->Address);
strcat_s(szStackInfo, szFrameInfo);
if (ret) {
_snprintf_s(szFrameInfo, sizeof(szFrameInfo), " | %s at %o \n", line.FileName, line.LineNumber);
strcat_s(szStackInfo, szFrameInfo);
}
else {
_snprintf_s(szFrameInfo, sizeof(szFrameInfo), "|error %d \n", GetLastError());
strcat_s(szStackInfo, szFrameInfo);
}
}
printf("%s \n", szStackInfo);
free(symbol);
return 0;
}
result:
The short answer is that dbhlp can't help with the managed parts of the stack walk.
It can be done, as windbg can do mixed mode stacks but it's specific to the version of .net and it also requires access to the full process memory to do it. I don't think the results from CaptureStackBackTrace will be enough, although since you are doing this in-process you may get away with it.
See the Mixed Mode Stackwalk article, which should give you pointers in the right direction.
I want to call NtCreateProcessEx, But i get no exception and error and nothing happens. Also i don't want to use CreateProcess. My intention is to create and run a process from a file with this specific function.
This what i have tried so far:
#include <Windows.h>
#include <bcrypt.h>
#include "winternl.h"
#pragma comment(lib, "ntdll")
NTSTATUS NTAPI NtCreateProcessEx(
OUT HANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN OBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN HANDLE ParentProcess,
IN BOOLEAN InheritObjectTable,
IN HANDLE SectionHandle OPTIONAL,
IN HANDLE DebugPort OPTIONAL,
IN HANDLE ExceptionPort OPTIONAL,
IN BOOLEAN InJob);
int main()
{
const HANDLE ph = nullptr;
OBJECT_ATTRIBUTES oa;
UNICODE_STRING fileName;
RtlInitUnicodeString(&fileName, PCWSTR(L"\\??\\C:\\Windows\\System32\\calc.exe"));
(&oa)->Length = sizeof(OBJECT_ATTRIBUTES);
(&oa)->RootDirectory = nullptr;
(&oa)->Attributes = 0x00000040L;
(&oa)->ObjectName = &fileName;
(&oa)->SecurityDescriptor = nullptr;
(&oa)->SecurityQualityOfService = nullptr;;
NtCreateProcessEx(ph, PROCESS_ALL_ACCESS, oa, nullptr, FALSE, nullptr, nullptr, nullptr, FALSE);
return 0;
}
There is no document and example on whole internet about this specific function. I am able to do something somewhat similar to this for NtCreateFile, But this is my closest try for NtCreateProcessEx and no luck.
I work with Visual Studio 2019 and windows 10 1909.
These are some resources that i tried:
NtCreateProcess(Ex) - Can I have a child process inherit the parents address space while running under a different process name?
http://www.rohitab.com/discuss/topic/40191-ntcreateuserprocess/
https://github.com/Microwave89/createuserprocess/blob/master/createuserprocess/main.c
http://www.rohitab.com/discuss/topic/42229-start-a-process-using-ntcreateprocessex-usermode/
https://hshrzd.wordpress.com/2017/12/18/process-doppelganging-a-new-way-to-impersonate-a-process/
First of all, the 3rd parameter is a pointer to the OBJECT_ATTRIBUTES:
typedef NTSTATUS(NTAPI* fpNtCreateProcessEx)
(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
HANDLE ParentProcess,
ULONG Flags,
HANDLE SectionHandle OPTIONAL,
HANDLE DebugPort OPTIONAL,
HANDLE ExceptionPort OPTIONAL,
BOOLEAN InJob
);
A sample to use(remove error checking):
#include <windows.h>
#include <iostream>
using namespace std;
#define NtCurrentProcess() ( (HANDLE)(LONG_PTR) -1 )
typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} LSA_UNICODE_STRING, * PLSA_UNICODE_STRING, UNICODE_STRING, * PUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;
typedef NTSTATUS(NTAPI* fpNtCreateProcessEx)
(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
HANDLE ParentProcess,
ULONG Flags,
HANDLE SectionHandle OPTIONAL,
HANDLE DebugPort OPTIONAL,
HANDLE ExceptionPort OPTIONAL,
BOOLEAN InJob
);
typedef NTSTATUS(NTAPI* fpNtCreateTransaction)
(
PHANDLE TransactionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
LPGUID Uow,
HANDLE TmHandle,
ULONG CreateOptions,
ULONG IsolationLevel,
ULONG IsolationFlags,
PLARGE_INTEGER Timeout,
PUNICODE_STRING Description
);
typedef NTSTATUS (NTAPI *fpNtCreateSection)
(
PHANDLE SectionHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PLARGE_INTEGER MaximumSize,
ULONG SectionPageProtection,
ULONG AllocationAttributes,
HANDLE FileHandle
);
typedef NTSTATUS (NTAPI *fpNtClose)
(
HANDLE Handle
);
#define PS_INHERIT_HANDLES 4
int main()
{
HANDLE hProcess;
OBJECT_ATTRIBUTES objattr;
UNICODE_STRING objname;
NTSTATUS status;
WCHAR wstrObjName[MAX_PATH];
lstrcpyW(wstrObjName, L"C:\\test.exe");
HINSTANCE hinst = LoadLibrary(L"ntdll.dll");
fpNtCreateProcessEx _NtCreateProcessEx = (fpNtCreateProcessEx)GetProcAddress(hinst, "NtCreateProcessEx");
fpNtCreateTransaction _NtCreateTransaction = (fpNtCreateTransaction)GetProcAddress(hinst, "NtCreateTransaction");
fpNtCreateSection _NtCreateSection = (fpNtCreateSection)GetProcAddress(hinst, "NtCreateSection");
fpNtClose _NtClose = (fpNtClose)GetProcAddress(hinst, "NtClose");
// Initialize ObjectName UNICODE_STRING
objname.Buffer = wstrObjName;
objname.Length = wcslen(wstrObjName) * sizeof(WCHAR); // Length in bytes of string, without null terminator
objname.MaximumLength = MAX_PATH * sizeof(WCHAR);
// Initialize OBJECT_ATTRIBUTES
objattr.Length = sizeof(OBJECT_ATTRIBUTES);
objattr.Attributes = 0x00000040L; //OBJ_CASE_INSENSITIVE
objattr.ObjectName = NULL;
objattr.RootDirectory = NULL;
objattr.SecurityDescriptor = NULL;
objattr.SecurityQualityOfService = NULL;
HANDLE hTransaction = NULL;
status = _NtCreateTransaction(&hTransaction,
TRANSACTION_ALL_ACCESS,
&objattr,
NULL,
NULL,
0,
0,
0,
NULL,
NULL);
HANDLE hTransactedFile = CreateFileTransacted(wstrObjName,
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL,
hTransaction,
NULL,
NULL);
HANDLE hSection = NULL;
status = _NtCreateSection(&hSection,
SECTION_ALL_ACCESS,
NULL,
0,
PAGE_READONLY,
SEC_IMAGE,
hTransactedFile);
status = _NtCreateProcessEx(&hProcess, PROCESS_ALL_ACCESS, NULL, NtCurrentProcess(), PS_INHERIT_HANDLES, hSection, NULL, NULL, false);
DWORD pid = GetProcessId(hProcess);
printf("Pid = %d\n", pid);
CloseHandle(hTransactedFile);
_NtClose(hTransaction);
_NtClose(hSection);
_NtClose(hProcess);
return 0;
}
Do i have to change a specific flag or use another standard function to resume the process state?
You must create thread. New process doesn't have any thread. You must allocate memory for thread and then call ZwCreateThread. For details, see Garry Nebeth book for windows 2000.
Not sure if it's possible to get such information, but i was messing with the WinAPI docs trying to figure out a way to get the number of opened handles to my process. First i created a console application that retrieves the number of handles the current process has, then i created another console app that gets a handle to the first app with an OpenProcess call. The code used in the first application is the following:
auto hProcess = GetCurrentProcess();
while (true)
{
DWORD Handles = 0;
if (GetProcessHandleCount(hProcess, &Handles))
{
std::cout << Handles << '\n';
}
Sleep(2000);
}
I was expecting the first application to print 1, but i got 50 when i ran in my Windows 10 PC, and 58 when i ran it with elevated privilegies. Also, when i ran the code above in a virtual machine with Windows 7, 11 handles were printed. What is going on?
The method of enumerating handle has been given in the comments. All I have done is to provide a code reference.
You may need to traverse all process names and print the required pid.
Traversal process:
#include <stdio.h>
#include <Windows.h>
#include <winternl.h>
#include <cstringt.h>
#include <winternl.h>
#include <string>
#include <wchar.h>
#pragma comment(lib,"ntdll.lib") // Need to link with ntdll.lib import library. You can find the ntdll.lib from the Windows DDK.
typedef struct _SYSTEM_PROCESS_INFO
{
ULONG NextEntryOffset;
ULONG NumberOfThreads;
LARGE_INTEGER Reserved[3];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ImageName;
ULONG BasePriority;
HANDLE ProcessId;
HANDLE InheritedFromProcessId;
}SYSTEM_PROCESS_INFO, *PSYSTEM_PROCESS_INFO;
int main()
{
NTSTATUS status;
PVOID buffer;
PSYSTEM_PROCESS_INFO spi;
buffer = VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // We need to allocate a large buffer because the process list can be large.
if (!buffer)
{
printf("\nError: Unable to allocate memory for process list (%d)\n", GetLastError());
return -1;
}
printf("\nProcess list allocated at address %#x\n", buffer);
spi = (PSYSTEM_PROCESS_INFO)buffer;
if (!NT_SUCCESS(status = NtQuerySystemInformation(SystemProcessInformation, spi, 1024 * 1024, NULL)))
{
printf("\nError: Unable to query process list (%#x)\n", status);
VirtualFree(buffer, 0, MEM_RELEASE);
return -1;
}
while (spi->NextEntryOffset) // Loop over the list until we reach the last entry.
{
const wchar_t* str = L"Test.exe"; //The process name you specified
spi = (PSYSTEM_PROCESS_INFO)((LPBYTE)spi + spi->NextEntryOffset); // Calculate the address of the next entry.
if (wcscmp(spi->ImageName.Buffer, str) == 0)
{
printf("\nProcess name: %ws | Process ID: %d\n", spi->ImageName.Buffer, spi->ProcessId); // Display process information.
}
}
printf("\nPress any key to continue.\n");
getchar();
VirtualFree(buffer, 0, MEM_RELEASE); // Free the allocated buffer.
return 0;
}
Then according to the given PID, use NT API to enumerate the handles.
Enumerate handles:
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <wchar.h>
#include <stdlib.h>
#pragma comment(lib,"ntdll.lib")
#define NT_SUCCESS(x) ((x) >= 0)
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004
#define SystemHandleInformation 16
#define ObjectBasicInformation 0
#define ObjectNameInformation 1
#define ObjectTypeInformation 2
static int num = 0;
typedef NTSTATUS(NTAPI *_NtQuerySystemInformation)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
typedef NTSTATUS(NTAPI *_NtDuplicateObject)(
HANDLE SourceProcessHandle,
HANDLE SourceHandle,
HANDLE TargetProcessHandle,
PHANDLE TargetHandle,
ACCESS_MASK DesiredAccess,
ULONG Attributes,
ULONG Options
);
typedef NTSTATUS(NTAPI *_NtQueryObject)(
HANDLE ObjectHandle,
ULONG ObjectInformationClass,
PVOID ObjectInformation,
ULONG ObjectInformationLength,
PULONG ReturnLength
);
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _SYSTEM_HANDLE
{
ULONG ProcessId;
BYTE ObjectTypeNumber;
BYTE Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;
typedef struct _SYSTEM_HANDLE_INFORMATION
{
ULONG HandleCount;
SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
typedef enum _POOL_TYPE
{
NonPagedPool,
PagedPool,
NonPagedPoolMustSucceed,
DontUseThisType,
NonPagedPoolCacheAligned,
PagedPoolCacheAligned,
NonPagedPoolCacheAlignedMustS
} POOL_TYPE, *PPOOL_TYPE;
typedef struct _OBJECT_TYPE_INFORMATION
{
UNICODE_STRING Name;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccess;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
USHORT MaintainTypeList;
POOL_TYPE PoolType;
ULONG PagedPoolUsage;
ULONG NonPagedPoolUsage;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
typedef struct _SYSTEM_PROCESS_INFO
{
ULONG NextEntryOffset;
ULONG NumberOfThreads;
LARGE_INTEGER Reserved[3];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ImageName;
ULONG BasePriority;
HANDLE ProcessId;
HANDLE InheritedFromProcessId;
}SYSTEM_PROCESS_INFO, *PSYSTEM_PROCESS_INFO;
PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName)
{
return GetProcAddress(GetModuleHandleA(LibraryName), ProcName);
}
int wmain(int argc, WCHAR *argv[])
{
_NtQuerySystemInformation NtQuerySystemInformation =
(_NtQuerySystemInformation)GetLibraryProcAddress((PSTR)"ntdll.dll", (PSTR)"NtQuerySystemInformation");
_NtDuplicateObject NtDuplicateObject =
(_NtDuplicateObject)GetLibraryProcAddress((PSTR)"ntdll.dll", (PSTR) "NtDuplicateObject");
_NtQueryObject NtQueryObject =
(_NtQueryObject)GetLibraryProcAddress((PSTR)"ntdll.dll", (PSTR)"NtQueryObject");
NTSTATUS status;
PSYSTEM_HANDLE_INFORMATION handleInfo;
ULONG handleInfoSize = 0x10000;
ULONG pid;
HANDLE processHandle;
ULONG i;
pid = 10228; //PID you get
if (!(processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid)))
{
printf("Could not open PID %d! (Don't try to open a system process.)\n", pid);
return 1;
}
handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);
/* NtQuerySystemInformation won't give us the correct buffer size,
so we guess by doubling the buffer size. */
while ((status = NtQuerySystemInformation(
SystemHandleInformation,
handleInfo,
handleInfoSize,
NULL
)) == STATUS_INFO_LENGTH_MISMATCH)
handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);
/* NtQuerySystemInformation stopped giving us STATUS_INFO_LENGTH_MISMATCH. */
if (!NT_SUCCESS(status))
{
printf("NtQuerySystemInformation failed!\n");
return 1;
}
for (i = 0; i < handleInfo->HandleCount; i++)
{
SYSTEM_HANDLE handle = handleInfo->Handles[i];
HANDLE dupHandle = NULL;
POBJECT_TYPE_INFORMATION objectTypeInfo;
PVOID objectNameInfo;
UNICODE_STRING objectName;
ULONG returnLength;
/* Check if this handle belongs to the PID the user specified. */
if (handle.ProcessId != pid)
continue;
/* Duplicate the handle so we can query it. */
if (!NT_SUCCESS(NtDuplicateObject(
processHandle,
(HANDLE)handle.Handle,
GetCurrentProcess(),
&dupHandle,
0,
0,
0
)))
{
printf("[%#x] Error!\n", handle.Handle);
num++;
continue;
}
/* Query the object type. */
objectTypeInfo = (POBJECT_TYPE_INFORMATION)malloc(0x1000);
if (!NT_SUCCESS(NtQueryObject(
dupHandle,
ObjectTypeInformation,
objectTypeInfo,
0x1000,
NULL
)))
{
printf("[%#x] Error!\n", handle.Handle);
num++;
CloseHandle(dupHandle);
continue;
}
/* Query the object name (unless it has an access of
0x0012019f, on which NtQueryObject could hang. */
if (handle.GrantedAccess == 0x0012019f)
{
/* We have the type, so display that. */
printf(
"[%#x] %.*S: (did not get name)\n",
handle.Handle,
objectTypeInfo->Name.Length / 2,
objectTypeInfo->Name.Buffer
);
num++;
free(objectTypeInfo);
CloseHandle(dupHandle);
continue;
}
objectNameInfo = malloc(0x1000);
if (!NT_SUCCESS(NtQueryObject(
dupHandle,
ObjectNameInformation,
objectNameInfo,
0x1000,
&returnLength
)))
{
/* Reallocate the buffer and try again. */
objectNameInfo = realloc(objectNameInfo, returnLength);
if (!NT_SUCCESS(NtQueryObject(
dupHandle,
ObjectNameInformation,
objectNameInfo,
returnLength,
NULL
)))
{
/* We have the type name, so just display that. */
printf(
"[%#x] %.*S: (could not get name)\n",
handle.Handle,
objectTypeInfo->Name.Length / 2,
objectTypeInfo->Name.Buffer
);
num++;
free(objectTypeInfo);
free(objectNameInfo);
CloseHandle(dupHandle);
continue;
}
}
/* Cast our buffer into an UNICODE_STRING. */
objectName = *(PUNICODE_STRING)objectNameInfo;
/* Print the information! */
if (objectName.Length)
{
/* The object has a name. */
printf(
"[%#x] %.*S: %.*S\n",
handle.Handle,
objectTypeInfo->Name.Length / 2,
objectTypeInfo->Name.Buffer,
objectName.Length / 2,
objectName.Buffer
);
num++;
}
else
{
/* Print something else. */
printf(
"[%#x] %.*S: (unnamed)\n",
handle.Handle,
objectTypeInfo->Name.Length / 2,
objectTypeInfo->Name.Buffer
);
num++;
}
free(objectTypeInfo);
free(objectNameInfo);
CloseHandle(dupHandle);
}
free(handleInfo);
CloseHandle(processHandle);
printf("Enumerate handles: %d\n", num);
getchar();
return 0;
}
I've tested the two code samples and they can work well.
Useful reference links for you:
Enumerate handles
How to enumerate process' handles?
First, GetProcessHandleCount returns the number of HANDLEs that the calling process has opened. Second, it is slightly unreliable.
GetProcessHandleCount will return the number of HANDLEs AFTER the object table has been initialized by the kernel and your process has started up. For different versions of Windows, the number of already-existing handles will vary, depending on how the kernel is designed. Some of these handles are handles to known DLL directories, HYPORVISOR_SHARED_DATA, etc. Once all the 'base' handles are created and the process's main is called, GetProcessHandleCount returns the number of opened handles from that point.
So the number of actual HANDLEs in the process's object table is slightly off using GetProcessHandleCount. The reason why you get something above 1 is because even after the kernel has initialized the object table, the CRT will open files and memory sections, which return HANDLEs
I think you were expecting 1 because of the call to GetCurrentProcess(). But that returns (HANDLE)-1, which is not a real HANDLE.
If you want the real count of the number of HANDLE's your process has, use NtQuerySystemInformation and loop for open HANDLE's with your process's ID. (Side note, GetProcessHandleCount calls NtQueryInformationProcess.)
since most links to this particular issue on http://undocumented.ntinternals.net are apparently dead and the NtQueryInfoThread along with relevant THREADINFOCLASSes has vanished from the Winternl.h I am now sitting here struggling to find the TEB of a process that I know the handle of.
I tried loading the method from the ntdll.dll, which was another solution that seemed to work but sadly I still fail to get the desired address.
typedef NTSTATUS(*ThreadInfoProc)(HANDLE, THREADINFOCLASS, PVOID, ULONG, PULONG);
PVOID CProcessHelper::GetThreadStackTopAddress(HANDLE hThread)
{
HINSTANCE ntdllInstance;
ThreadInfoProc NtQueryInfoThread;
ntdllInstance = LoadLibrary("Ntdll.dll");
if (ntdllInstance != NULL)
{
NtQueryInfoThread = (ThreadInfoProc)GetProcAddress(ntdllInstance, "NtQueryInformationThread");
if (NtQueryInfoThread != NULL)
{
THREAD_BASIC_INFORMATION bi;
NT_TIB tib;
NTSTATUS ntstat = 0;
NTSTATUS ntstat = (NtQueryInfoThread)(hThread, (THREADINFOCLASS)0, &bi, sizeof(THREAD_BASIC_INFORMATION),NULL);
ReadProcessMemory(CurrentProcessHandle, bi.TebBaseAddress, &tib, sizeof(NT_TIB), 0);
PrintHex(tib.StackBase); // output: CCCCCCCCCC
}
}
return nullptr;
}
Is there any other way, perhaps using public api calls to get the TEB of a thread? (As MSDN states that this approach should not be used any longer.)
Best Regards,
Alex
Works fine :S The only other way to get the TEB of a thread is to read it using:
NT_TIB* tib = (NT_TIB*)__readfsdword(0x18);
and read the base address from that.
Your calls may be failing because you might not have the right permissions to read the memory. Try using VirtualProtect?
The below works but I've only tested it on the current process..
#include <iostream>
#include <windows.h>
typedef LONG NTSTATUS;
typedef DWORD KPRIORITY;
typedef WORD UWORD;
typedef struct _CLIENT_ID
{
PVOID UniqueProcess;
PVOID UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
typedef struct _THREAD_BASIC_INFORMATION
{
NTSTATUS ExitStatus;
PVOID TebBaseAddress;
CLIENT_ID ClientId;
KAFFINITY AffinityMask;
KPRIORITY Priority;
KPRIORITY BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
enum THREADINFOCLASS
{
ThreadBasicInformation,
};
void* GetThreadStackTopAddress(HANDLE hProcess, HANDLE hThread)
{
bool loadedManually = false;
HMODULE module = GetModuleHandle("ntdll.dll");
if (!module)
{
module = LoadLibrary("ntdll.dll");
loadedManually = true;
}
NTSTATUS (__stdcall *NtQueryInformationThread)(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass, PVOID ThreadInformation, ULONG ThreadInformationLength, PULONG ReturnLength);
NtQueryInformationThread = reinterpret_cast<decltype(NtQueryInformationThread)>(GetProcAddress(module, "NtQueryInformationThread"));
if (NtQueryInformationThread)
{
NT_TIB tib = {0};
THREAD_BASIC_INFORMATION tbi = {0};
NTSTATUS status = NtQueryInformationThread(hThread, ThreadBasicInformation, &tbi, sizeof(tbi), nullptr);
if (status >= 0)
{
ReadProcessMemory(hProcess, tbi.TebBaseAddress, &tib, sizeof(tbi), nullptr);
if (loadedManually)
{
FreeLibrary(module);
}
return tib.StackBase;
}
}
if (loadedManually)
{
FreeLibrary(module);
}
return nullptr;
}
void __stdcall Test()
{
for (int i = 0; i < 10; ++i)
{
printf("Hi. ");
Sleep(500);
}
}
int main()
{
std::cout<<GetThreadStackTopAddress(GetCurrentProcess(), GetCurrentThread())<<"\n";
DWORD threadID = 0;
HANDLE hThread = CreateThread(nullptr, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(Test), nullptr, 0, &threadID);
std::cout<<GetThreadStackTopAddress(GetCurrentProcess(), hThread)<<"\n\n";
CloseHandle(hThread);
Sleep(7000);
return 0;
}
I'm creating a user-mode CMD app using VS 2013 in C++ and I'm trying to use the native registry editing functions in it. I'm trying to open certain key with 'NtOpenKey' but it always fails with 'STATUS_OBJECT_NAME_NOT_FOUND' and I'm sure that the 'object' is in it's place so the reason must be somewhere else. I want to use the native registry API's because they can handle 'Hidden Registry Keys' - look here for more info. Here is a snippet of my code:
#include <Windows.h>
#include <tchar.h>
#include <wininet.h>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "Nt_Funcs_declr.h" //here I have manually included the native api declarations as normally I'm denied to use them but they're exported in ntdll.dll so basically it is possible
#include <zlib.h>
//Obitain Steam folder path
wchar_t *GetSteamPathBuffer()
{
//Open the Sofware Steam registry
OBJECT_ATTRIBUTES objAttrs;
objAttrs.Length = sizeof(OBJECT_ATTRIBUTES);
objAttrs.RootDirectory = NULL;
wchar_t strRegSteam [] = L"\\Registry\\Machine\\SOFTWARE\\Valve";
UNICODE_STRING uStrTmp = { sizeof(strRegSteam), sizeof(strRegSteam), strRegSteam };
objAttrs.ObjectName = &uStrTmp;
objAttrs.Attributes = OBJ_CASE_INSENSITIVE; //
objAttrs.SecurityDescriptor = NULL;
objAttrs.SecurityQualityOfService = NULL;
HANDLE pKey;
ULONG tmmp = NtOpenKey(&pKey, GENERIC_READ, &objAttrs); //here it fails with 'STATUS_OBJECT_NAME_NOT_FOUND'
if(tmmp)
{
cout << "Error: " << GetLastError();
return NULL;
}
//....
}
And Nt_Funcs_declr.h:
#pragma once
//NTDLL import declarations
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
//
// Unicode strings are counted 16-bit character strings. If they are
// NULL terminated, Length does not include trailing NULL.
//
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
#ifdef MIDL_PASS
[size_is(MaximumLength / 2), length_is((Length) / 2)] USHORT * Buffer;
#else // MIDL_PASS
_Field_size_bytes_part_(MaximumLength, Length) PWCH Buffer;
#endif // MIDL_PASS
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef const UNICODE_STRING *PCUNICODE_STRING;
//
// Valid values for the Attributes field
//
#define OBJ_INHERIT 0x00000002L
#define OBJ_PERMANENT 0x00000010L
#define OBJ_EXCLUSIVE 0x00000020L
#define OBJ_CASE_INSENSITIVE 0x00000040L
#define OBJ_OPENIF 0x00000080L
#define OBJ_OPENLINK 0x00000100L
#define OBJ_KERNEL_HANDLE 0x00000200L
#define OBJ_FORCE_ACCESS_CHECK 0x00000400L
#define OBJ_VALID_ATTRIBUTES 0x000007F2L
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR
PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
typedef CONST OBJECT_ATTRIBUTES *PCOBJECT_ATTRIBUTES;
extern "C"
NTSYSAPI
NTSTATUS
NTAPI
NtOpenKey(
_Out_ PHANDLE KeyHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes
);
typedef enum _KEY_INFORMATION_CLASS {
KeyBasicInformation,
KeyNodeInformation,
KeyFullInformation,
KeyNameInformation,
KeyCachedInformation,
KeyFlagsInformation,
KeyVirtualizationInformation,
KeyHandleTagsInformation,
KeyTrustInformation,
MaxKeyInfoClass // MaxKeyInfoClass should always be the last enum
} KEY_INFORMATION_CLASS;
extern "C"
NTSYSAPI
NTSTATUS
NTAPI
NtQueryKey(
_In_ HANDLE KeyHandle,
_In_ KEY_INFORMATION_CLASS KeyInformationClass,
_Out_writes_bytes_opt_(Length) PVOID KeyInformation,
_In_ ULONG Length,
_Out_ PULONG ResultLength
);
typedef enum _KEY_VALUE_INFORMATION_CLASS {
KeyValueBasicInformation,
KeyValueFullInformation,
KeyValuePartialInformation,
KeyValueFullInformationAlign64,
KeyValuePartialInformationAlign64,
MaxKeyValueInfoClass // MaxKeyValueInfoClass should always be the last enum
} KEY_VALUE_INFORMATION_CLASS;
typedef struct _KEY_VALUE_PARTIAL_INFORMATION {
ULONG TitleIndex;
ULONG Type;
ULONG DataLength;
_Field_size_bytes_(DataLength) UCHAR Data[1]; // Variable size
} KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION;
extern "C"
NTSYSAPI
NTSTATUS
NTAPI
NtQueryValueKey(
_In_ HANDLE KeyHandle,
_In_ PUNICODE_STRING ValueName,
_In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
_Out_writes_bytes_opt_(Length) PVOID KeyValueInformation,
_In_ ULONG Length,
_Out_ PULONG ResultLength
);
NOTE: It's for educational prupose so please don't ask me why I don't use WIN32 API.
NB: using the kernel API from user mode is unsupported. I strongly recommend against doing so, unless there is a compelling reason why it is necessary.
Here's the problem:
UNICODE_STRING uStrTmp = { sizeof(strRegSteam), sizeof(strRegSteam), strRegSteam };
From the documentation for UNICODE_STRING:
If the string is null-terminated, Length does not include the trailing null character.
So you should be saying something like
UNICODE_STRING uStrTmp = { sizeof(strRegSteam) - sizeof(wchar_t),
sizeof(strRegSteam),
strRegSteam };
As written, your code was attempting to open a key named L"Valve\0" instead of the key named L"Valve".
Addendum: it has been disputed whether so-called "hidden" keys (an unfortunate name IMO; the keys are visible to Win32 code, they simply can't be manipulated) are actually possible, so here's working code to create one:
#include <Windows.h>
#include <stdio.h>
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWCH Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef const UNICODE_STRING *PCUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
typedef CONST OBJECT_ATTRIBUTES *PCOBJECT_ATTRIBUTES;
#define OBJ_CASE_INSENSITIVE 0x00000040L
#pragma comment(lib, "ntdll.lib")
__declspec(dllimport) NTSTATUS NTAPI NtCreateKey(
__out PHANDLE KeyHandle,
__in ACCESS_MASK DesiredAccess,
__in POBJECT_ATTRIBUTES ObjectAttributes,
__reserved ULONG TitleIndex,
__in_opt PUNICODE_STRING Class,
__in ULONG CreateOptions,
__out_opt PULONG Disposition
);
NTSYSAPI NTSTATUS NTAPI NtOpenKey(
__out PHANDLE KeyHandle,
__in ACCESS_MASK DesiredAccess,
__in POBJECT_ATTRIBUTES ObjectAttributes
);
NTSYSAPI NTSTATUS NTAPI NtDeleteKey(
__out HANDLE KeyHandle
);
int main(int argc, char ** argv)
{
HANDLE pKey;
NTSTATUS result;
OBJECT_ATTRIBUTES objAttrs;
wchar_t strSoftwareKey [] = L"\\Registry\\Machine\\SOFTWARE\\Test\0Key";
// If you use this string instead, the key functions normally, proving that the
// issue isn't because we're using UTF-16 rather than ANSI strings:
//
// wchar_t strSoftwareKey [] = L"\\Registry\\Machine\\SOFTWARE\\Test\u2D80Key";
UNICODE_STRING uStrSoftwareKey = {
sizeof(strSoftwareKey) - sizeof(wchar_t),
sizeof(strSoftwareKey),
strSoftwareKey };
objAttrs.Length = sizeof(OBJECT_ATTRIBUTES);
objAttrs.RootDirectory = NULL;
objAttrs.ObjectName = &uStrSoftwareKey;
objAttrs.Attributes = OBJ_CASE_INSENSITIVE;
objAttrs.SecurityDescriptor = NULL;
objAttrs.SecurityQualityOfService = NULL;
result = NtCreateKey(&pKey, GENERIC_ALL, &objAttrs, 0, NULL, 0, NULL);
if(result)
{
printf("NtCreateKey: %x\n", result);
return NULL;
}
#if 0 // enable this section to delete the key
// you won't be able to use regedit!
result = NtDeleteKey(pKey);
if(result)
{
printf("NtDeleteKey: %x\n", result);
return NULL;
}
#endif
}
As of Windows 7, at least, this still works. (You'll need a copy of ntdll.lib, available from the DDK/WDK, in order to build this code.)
Please do not do this in production code or on other people's machines.