Tutorial Terminate process via threads (C++)

Discussion in 'Develop Coding Skills - Tutorials' started by Opcode, Oct 8, 2017.

  1. Opcode

    Opcode Level 18
    Content Creator

    Aug 17, 2017
    890
    6,284
    Caille
    Windows 10
    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.

    [​IMG]

    [​IMG]

    [​IMG]

    [​IMG]

    [​IMG]

    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

    -
     

    Attached Files:

    Zhou He, edon, tim one and 7 others like this.
  2. lowdetection

    lowdetection Level 5

    Jul 1, 2017
    209
    639
    China
    Linux
    Isolation
    Opcode your tutorials are beautiful ;)
     
    tim one, Opcode, XhenEd and 2 others like this.
  3. tim one

    tim one Level 18
    Trusted AV Tester

    Jul 31, 2014
    886
    8,983
    Europe
    Windows 10
    Emsisoft
    Great tutorial as always :)
    I think I will tackle this topic, at university level, at the end of this year or the next one.
    So very interesting input for me, thank you ;)
     
  4. Zhou He

    Zhou He Level 1

    Mar 13, 2017
    29
    76
    China
    Windows 10
    ESET
    Is there any advantage to terminate a process this way?
     
    Opcode likes this.
  5. Opcode

    Opcode Level 18
    Content Creator

    Aug 17, 2017
    890
    6,284
    Caille
    Windows 10
    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.
     
    tim one, Andy Ful, XhenEd and 2 others like this.
  6. Zhou He

    Zhou He Level 1

    Mar 13, 2017
    29
    76
    China
    Windows 10
    ESET
    Aha, that's good point, thank you
     
    XhenEd and Opcode like this.
Loading...
Similar Threads Forum Date
Malware Alert New JavaScript Malware Shuts Down Your PC If You Terminate Its Process News Archive Oct 9, 2016
Exterminate It! v.2.12.09.16 Discounts & Deals Sep 16, 2014
Sandboxie Terminated Ruskie Ransomware General Security Discussions Nov 20, 2013