Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending a Ctrl+C to a QProcess on Windows

Tags:

c++

windows

qt

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

like image 349
Cássio Renan Avatar asked Dec 06 '25 18:12

Cássio Renan


1 Answers

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);
        }
    }
}
like image 137
RbMm Avatar answered Dec 08 '25 08:12

RbMm



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!