Tutorial Terminate process via threads (C++)

Opcode

Level 28
Content Creator
Joined
Aug 17, 2017
Messages
1,733
#1
Images uploaded in case they do not load correctly.


Introduction

When terminating a process, most people will open a handle to it via OpenProcess (KERNEL32) and then call TerminateProcess (KERNEL32). Behind the curtains, OpenProcess will lead to a call to NtOpenProcess (NTDLL - Native API) and TerminateProcess will lead to a call to NtTerminateProcess (NTDLL - Native API).

The process is the container for the threads, the threads are actually responsible for executing the code for the program. Code is executed on threads and you can execute different code simultaneously at the same time thanks to threads. On one thread you could be enumerating through all the files on the Desktop and on another thread you could be performing a network request... Without the current mechanism implementation with threads, this would not be possible - you'd have to do one thing at a time. Think of the threads as the heart and the process as the body; the threads will execute the code to actually get something done and the process holds everything together.

Due to how the process mechanism works, you can terminate a process without opening a handle to the process itself - open a handle to the threads instead. Once you have acquired a handle to a thread you can terminate it. There is a main thread and if it is terminated then all the other threads will be, however it is easier to just start looping through all the threads for that process and attempt to terminate - eventually you'll run into that main thread and it'll be terminated, and then the operation is complete.

To open a handle to a process' thread you can use the Win32 API function OpenThread (KERNEL32) and to terminate a thread using the handle you can use the Win32 API function TerminateThread (KERNEL32). However, OpenThread will lead to a call to NtOpenThread (NTDLL - undocumented by Microsoft) and TerminateThread will lead to NtTerminateThread (NTDLL - also undocumented by Microsoft).


The function prototype for NtOpenThread is the following.
Code:
typedef NTSTATUS(NTAPI *pNtOpenThread)(
    PHANDLE ThreadHandle,
    ACCESS_MASK AccessMask,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PCLIENT_ID ClientId
    );
The first parameter is for the thread handle, however you won't be passing it in. The data-type is PHANDLE which is HANDLE * (pointer), you use it to assign a HANDLE variable to hold the handle of the thread which will be acquired.

The second parameter is for the access rights you desire for the handle. There are many different access rights which can be requested, however you can use MAXIMUM_ALLOWED to try to open a handle with the maximum access rights you will be granted. An example of an access right for a thread handle would be THREAD_SUSPEND_RESUME or THREAD_TERMINATE. You can find more on thread access rights over on MSDN: Thread Security and Access Rights (Windows)

The third parameter is for the object attributes. This is essential for NtOpenThread however you don't need to actually do anything with it, you can just set the size of it as OBJECT_ATTRIBUTES. You can use InitializeObjectAttributes to do this.

The last parameter is the ClientId (CLIENT_ID */PCLIENT_ID). It is a pointer to the structure CLIENT_ID, which holds two entries: PVOID values. One of them is for the target process and the other is for the target thread (Process ID and Thread ID). Since we are opening a thread handle, you will set the first PVOID value to the Process ID (which holds your target thread) and you will set the second to the thread ID of the thread you need a handle for.

Code:
typedef struct _CLIENT_ID {
    PVOID UniqueProcess;
    PVOID UniqueThread;
}CLIENT_ID, *PCLIENT_ID;

The function prototype for NtTerminateThread is the following.
Code:
typedef NTSTATUS(NTAPI *pNtTerminateThread)(
    HANDLE ThreadHandle,
    NTSTATUS ExitStatus
    );
The first parameter is for the handle of the thread you wish to terminate, acquired previously by a call to NtOpenThread.

The second parameter is for the exit code of the thread.



Example code
The example code below will create a snapshot of all the threads for all the processes and then loop through the snapshot until it finds the threads for the target process only. At this point, it will attempt to open a handle to the thread with NtOpenThread and if this is successful then NtTerminateThread is called. This will recursively occur until the snapshot loop has completed, however once the main thread has become the target and has been terminated, the process will be terminated.











header.h
Code:
#pragma once
#include <Windows.h>
#include <winternl.h>
#include <TlHelp32.h>
#include <iostream>
#include "ntdef.h"
ntdef.h
Code:
#pragma once
#include "header.h"

typedef struct _CLIENT_ID {
    PVOID UniqueProcess;
    PVOID UniqueThread;
}CLIENT_ID, *PCLIENT_ID;

typedef NTSTATUS(NTAPI *pNtOpenThread)(
    PHANDLE ThreadHandle,
    ACCESS_MASK AccessMask,
    POBJECT_ATTRIBUTES ObjectAttributes,
    PCLIENT_ID ClientId
    );

typedef NTSTATUS(NTAPI *pNtTerminateThread)(
    HANDLE ThreadHandle,
    NTSTATUS ExitStatus
    );
main.cpp
Code:
#include "header.h"
using namespace std;

BOOL TerminateProcessThreads(DWORD dwProcessId)
{
    NTSTATUS NtStatus = 0;
    HANDLE ThreadHandle = 0, ThreadHandle32 = 0;
    THREADENTRY32 ThreadEntry32 = { 0 };
    OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
    CLIENT_ID ClientId = { 0 };

    // Get the addresses for NTAPI functions
    FARPROC fpNtFunctions[2] = {
        GetProcAddress(GetModuleHandle("ntdll.dll"), "NtOpenThread"),
        GetProcAddress(GetModuleHandle("ntdll.dll"), "NtTerminateThread")
    };

    // loop the array containing the addresses
    for (FARPROC &fpNtAddress : fpNtFunctions)
    {
        if (!fpNtAddress) // did we get the address successfully?
        {
            return FALSE;
        }
    }

    // setup memory
    pNtOpenThread fNtOpenThread = (pNtOpenThread)fpNtFunctions[0];
    pNtTerminateThread fNtTerminateThread = (pNtTerminateThread)fpNtFunctions[1];

    // any problems with the functions?
    if (!fNtOpenThread ||!fNtTerminateThread)
    {
        return FALSE;
    }

    InitializeObjectAttributes(&ObjectAttributes, NULL, NULL, NULL, NULL); // required for NtOpenThread
    ClientId.UniqueProcess = (PVOID)dwProcessId; // PID of process we are targeting

    // create a snapshot of all the threads & setup the structure for usage
    ThreadHandle32 = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    ThreadEntry32.dwSize = sizeof(THREADENTRY32);

    // loop through the thread snapshot
    do {
        // check if the thread belongs to the target process
        if (ThreadEntry32.th32OwnerProcessID == dwProcessId)
        {
            ClientId.UniqueThread = (PVOID)ThreadEntry32.th32ThreadID; // set the thread ID to the structure

            // try to open a handle to the thread
            NtStatus = fNtOpenThread(&ThreadHandle, MAXIMUM_ALLOWED, &ObjectAttributes, &ClientId);
            if (!NT_SUCCESS(NtStatus))
            {
                // failed so cleanup
                CloseHandle(ThreadHandle);
                CloseHandle(ThreadHandle32);
                return FALSE;
            }

            fNtTerminateThread(ThreadHandle, 0); // we have a handle so lets try and terminate it
        }
    } while (Thread32Next(ThreadHandle32, &ThreadEntry32));

    // cleanup
    CloseHandle(ThreadHandle);
    CloseHandle(ThreadHandle32);

    return TRUE;
}

DWORD ProcessIdFromName(char *TargetExe)
{
    HANDLE ProcessHandle = 0;
    PROCESSENTRY32 ProcessEntry32 = { 0 };

    // create a snapshot of the processes & setup the structure
    ProcessHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    ProcessEntry32.dwSize = sizeof(PROCESSENTRY32);

    // loop the snapshot of processes
    do {
        if (!strcmp(ProcessEntry32.szExeFile, TargetExe)) // check if the process is the one we are interested in
        {
            // cleanup and return, we got the PID
            CloseHandle(ProcessHandle);
            return ProcessEntry32.th32ProcessID;
        }
    } while (Process32Next(ProcessHandle, &ProcessEntry32));

    // cleanup, we failed
    CloseHandle(ProcessHandle);
    return 0;
}

int main()
{
    if (TerminateProcessThreads(ProcessIdFromName("Calculator.exe")))
    {
        MessageBox(NULL, "Process has been terminated.", NULL, MB_OK);
    }
    else
    {
        MessageBox(NULL, "Process could not be terminated.", NULL, MB_OK);
    }

    return 0;
}

Thank you for reading.
- Opcode

-
 

Attachments

Opcode

Level 28
Content Creator
Joined
Aug 17, 2017
Messages
1,733
#5
Is there any advantage to terminate a process this way?
Sometimes.

If NtOpenProcess or NtTerminateProcess have been detoured by a rootkit (which are two commonly targeted functions), NtOpenThread and NtTerminateThread may not have been targeted - in this scenario, it makes your life much easier because you wouldn't have to focus on bypassing the hooks to attack the malicious process.

It is just another way of doing something, although opening a handle to the process and terminating it normally is the most expected behavior as opposed to targeting the threads instead. Most people will use OpenProcess (KERNEL32) and TerminateProcess (KERNEL32) which will internally call NtOpenProcess (NTDLL) and NtTerminateProcess (NTDLL) anyway.

Terminating the threads allows you to prevent code execution occurring on a specific thread as well. You don't have to fully terminate the process when dealing with attacking the threads. If the process has 10 threads and you want to prevent specific operations from continuing to occur which are taking place on the 7th thread, as long as you have the Thread ID (TID) you could target it and terminate/suspend it. As long as it wasn't the main thread (first thread), the process will normally continue to execute and the thread you wanted to stop will be gone - nothing stops re-creation of a thread for specific tasks though.

You can also suspend the threads with SuspendThread (KERNEL32) or NtSuspendThread (NTDLL - called by kernel32!SuspendThread) of a process, allowing you to target them all or only specific threads.
 
Joined
Mar 13, 2017
Messages
29
OS
Windows 10
Antivirus
ESET
#6
Sometimes.

If NtOpenProcess or NtTerminateProcess have been detoured by a rootkit (which are two commonly targeted functions), NtOpenThread and NtTerminateThread may not have been targeted - in this scenario, it makes your life much easier because you wouldn't have to focus on bypassing the hooks to attack the malicious process.

It is just another way of doing something, although opening a handle to the process and terminating it normally is the most expected behavior as opposed to targeting the threads instead. Most people will use OpenProcess (KERNEL32) and TerminateProcess (KERNEL32) which will internally call NtOpenProcess (NTDLL) and NtTerminateProcess (NTDLL) anyway.
Aha, that's good point, thank you
 

Similar Threads

Similar Threads