3

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.

console example using kill

Using tryFinishProcess(see implementation above) actually made the parent process exit:

console example using try finish process

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:

console example using try finish process with create new process group

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:

expected behavior

Not a real meerkat
  • 5,604
  • 1
  • 24
  • 55
  • I assume you have already tried both `QProcess::terminate` and `QProcess::kill`, right? – JarMan Nov 13 '20 at 14:16
  • It is an odd error code for an api function that doesn't use a handle. My crystal ball says that this is not a standard process as suggested in the obfuscated code, but actually a service. Doesn't work, a service doesn't have a console. Use ControlService() instead. – Hans Passant Nov 13 '20 at 14:23
  • @JarMan yes. It does kill the process, but it doesn't give it time to gracefully shutdown itself, which, as you can guess, causes several issues. – Not a real meerkat Nov 13 '20 at 15:52
  • @HansPassant not, it's not a service. It is indeed a process, and I can read from its console and write to it. In fact any process that handles a CtrlC event like this could be used for testing, I think. I'll see if I can think of one just for the sake of making my example verifiable, or maybe implement a trivial one. Thanks for the comment! – Not a real meerkat Nov 13 '20 at 15:55
  • Added a (not so minimal) MCVE. It doesn't have a `.pro`, but creating a new Qt console application should generate one for you :) – Not a real meerkat Nov 13 '20 at 18:37
  • [Here](https://stackoverflow.com/questions/813086/can-i-send-a-ctrl-c-sigint-to-an-application-on-windows) is a non-Qt version of the same question. A lot of answers there. I don't know if any of them help you. There are some third-party apps/libraries that are suggested, like [windows-kill](https://github.com/alirdn/windows-kill). – JarMan Nov 14 '20 at 21:25
  • Thank you @JarMan! SendSignal, from the accepted answer there, does look promissing. Frankly, the amount of hacking the author said was necessary to get it to work (scoping for the address of a non-exported function from the Kernel, to then create a thread on the target process, calling that function.. ugh) hints that this is not simple at all to do as an ad-hoc solution. windows-kill also looks promissing, even having an easy library that I can steal. I'll look into them. – Not a real meerkat Nov 14 '20 at 22:48
  • are your process and process which you want terminate have the same bit (32-32 or 64 -64) ? if yes, exist very simply solution. if your process is 64bit and child 32bit - also possible but more code. if your code is 32 bit and child 64 - no – RbMm Nov 14 '20 at 23:57
  • @RbMm My parent process is 64 bit. In the real world I actually have many child processes to manage, and I do not have the guarantee that they will be 64 bit either - they could be 32 bit. – Not a real meerkat Nov 15 '20 at 00:06
  • ok, from 64bit process this is possible do for both 64 and 32 bit processes (if that design handle `CTRL_C_EVENT`) - all what need - create thread in child process with entry point `kernel32.CtrlRoutine` and `CTRL_C_EVENT`. some not simply code require only get 32 bit address of `kernel32.CtrlRoutine` from 64 bit process, if want i can post this solution – RbMm Nov 15 '20 at 00:11
  • This is pretty much what `SendSignal` (from [this](https://stackoverflow.com/a/1179124/3854787) answer) does, correct? It feels a bit too hacky to me, but nevertheless, feel free to post it as an answer. I'll definitely consider it. Gotta do what you gotta do :D. – Not a real meerkat Nov 15 '20 at 00:20
  • @RbMm I won't accept right away, as I would prefer a "canonical" way to do this if possible, but if nothing else comes up before the bounty expires, it's yours. – Not a real meerkat Nov 15 '20 at 00:23
  • @CássioRenan - not, this is absolute unrelated to `SendSignal`. and for get address of `CtrlRoutine` inside wow64 kernel 32 need native api and some undocumented calls. all idea also not usual but 100% correct and work – RbMm Nov 15 '20 at 00:23
  • @RbMm Gotcha, please do post your answer if you have some time. I appreciate it! – Not a real meerkat Nov 15 '20 at 00:27
  • ok, i post in .. – RbMm Nov 15 '20 at 00:29

2 Answers2

3

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);
        }
    }
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Thank you! I haven't looked into it in detail yet to upvote, but this looks very promissing. It's a bit late for me ATM, so I'll look into this tomorrow :) – Not a real meerkat Nov 15 '20 at 02:19
  • @CássioRenan - here 99% of code - get address of `CtrlRoutine`. if only for 64/64 or 32/32 processes - all what you need - declare `EXTERN_C WINBASEAPI ULONG WINAPI CtrlRoutine(_In_ DWORD dwCtrlEvent);` and link with *kernel32.lib* (you anyway do this). or get address via `GetProcAddress(GetModuleHandle(L"kernel32"), "CtrlRoutine")` and use it in `SendCtrlEvent` but in case 64->32 need all this code. really even 32->64 possible, but for this need yet more code and more complex code – RbMm Nov 15 '20 at 02:31
  • @CássioRenan i use this code before for gracefully stop *java.exe* process started from my 64bit service (*java.exe* by self was 32 bit). and this work as correct – RbMm Nov 15 '20 at 02:32
  • I can't build this. I'm by no means a Windows expert, and this seems to require some headers that are not provided by any of the Microsoft SDKs that I looked into. Can you give an overview on what exactly do I need to do to use it? e.g.: What do I need to include, link to, definitions to add... It's ok if that's not Qt-specific, I can work with it. – Not a real meerkat Nov 16 '20 at 18:09
  • @CássioRenan - i use wdk headers (*#include * ) but this require some tricks. but for you will be more simply copy-paste some definitions from this headers to self file, instead try include *ntifs.h*. i add only **undocumented** structures and api here ( `SECTION_IMAGE_INFORMATION`, `LDR_DATA_TABLE_ENTRY`, `LdrFindEntryForAddress`) - however now i try add else another for you (for me situation more simply because i use *ntifs.h* ) – RbMm Nov 16 '20 at 18:36
  • @CássioRenan - i update code - for it compile with only *windows.h*. also add example of use *DemoUseCtrlC* - hope now will ne no problems – RbMm Nov 16 '20 at 19:21
  • Again - Thank you very much for you help! I'll try this as soon as I can. – Not a real meerkat Nov 16 '20 at 19:31
  • @CássioRenan - sorry for intial code - i simply copy-paste self existing code, which i write before. but i use *ntifs.h* and not need most of pre-definitions here. now i test and build without this. – RbMm Nov 16 '20 at 19:32
0

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).

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • I understand and appreciate the sentiment, and yes, my MCVE is not the greatest of all time, but this is not helpful to me - e.g.: It doesn't answer the question. Having said that, I'll try following your tips and making a mcve with a small GUI instead. – Not a real meerkat Nov 16 '20 at 19:46