def handleStream(tcp): def decrypt_packed_string(__src): global key global src global size src = __src key = unpack("<I", __src[0:4])[0] if key == 0x54534f50 or key == 0x50545448: return src, 0xffff, '' #chop.tsprnt(hex(key)) size = 16 stage1 = decrypt() #chop.tsprnt(repr(hex(unpack("<I", stage1[0:4])[0])),repr(hex(unpack("<I",stage1[4:8])[0])),repr(hex(unpack("H",stage1[8:10])[0])),repr(hex(unpack("H",stage1[10:12])[0]))) flags = unpack("<I", stage1[4:8])[0] #chop.tsprnt(hex(flags)) #chop.tsprnt(repr(stage1[8:10])) if flags & 0x2000000: #do not decrypt payload separately if tcp.module_data['verbose']: chop.tsprnt("do not decrypt separately") size = len(src) stage1 = decrypt() # consider removing the verbosity filter here if you're seeing the do not decrypt message and not data. if tcp.module_data['verbose']: chop.tsprnt(unpack("H",stage1[8:10])[0]) else: size = len(src[16:]) src = __src[16:] stage1 = stage1 + decrypt() if flags & 0x1000000: #do not decompress payload if tcp.module_data['verbose']: chop.tsprnt("do not decompress") return stage1, flags, '' else: if flags in module_data['flags'].keys(): comp = stage1[16:] if tcp.module_data['verbose']: chop.tsprnt("len of payload: %d len in header: %d" % (len(comp), unpack("H",stage1[8:10])[0])) if len(comp) == unpack("H",stage1[8:10]): return stage1[:16], flags, comp return stage1[:16], flags, comp[:unpack("H",stage1[8:10])[0]] decomp = '' #chop.tsprnt(repr(stage1[:16]),repr(stage1[16:])) return stage1[:16]+decomp, flags, '' def decrypt(): global key global new_key global key0 global key1 global key2 global key3 global src global dst global res global i global size key0 = key key1 = key key2 = key key3 = key dst = b'' i = 0 if size > 0: while i < size: if tcp.module_data['protocol'] == 0: key0 = (key0 + (((key0 >> 3)&0xFFFFFFFF) - 0x11111111)&0xFFFFFFFF)&0xFFFFFFFF key1 = (key1 + (((key1 >> 5)&0xFFFFFFFF) - 0x22222222)&0xFFFFFFFF)&0xFFFFFFFF key2 = (key2 + (0x44444444 - ((key2 << 9)&0xFFFFFFFF))&0xFFFFFFFF)&0xFFFFFFFF key3 = (key3 + (0x33333333 - ((key3 << 7)&0xFFFFFFFF))&0xFFFFFFFF)&0xFFFFFFFF new_key = (((key2&0xFF) + (key3&0xFF) + (key1&0xFF) + (key0&0xFF))&0xFF) elif tcp.module_data['protocol'] == 1: key0 = (key0 + ((key0 >> 3) + 3)&0xFFFFFFFF)&0xFFFFFFFF key1 = (key1 + (((key1 >> 5)&0xFFFFFFFF) + 5)&0xFFFFFFFF)&0xFFFFFFFF key2 = (0xFFFFFF81 * (key2 & 0xFFFFFFFF)-7)&0xFFFFFFFF key3 = (0xFFFFFE01 * (key3 & 0xFFFFFFFF)-9)&0xFFFFFFFF new_key = (((key2&0xFF) + (key3&0xFF) + (key1&0xFF) + (key0&0xFF))&0xFF) else: new_key = 0xFF if tcp.module_data['verbose']: chop.tsprnt(hex(new_key),hex(key0),hex(key1),hex(key2),hex(key3)) res = unpack("<B", src[i:i+1])[0] ^ new_key dst = dst + pack("<B", res) i = i + 1 return dst data = '' # collect time and IP metadata ((src, sport), (dst, dport)) = tcp.addr # handle client system packets if tcp.server.count_new > 0: data = tcp.server.data[:tcp.server.count_new] if not data[:6] == 'POST /' and not data[:6] == 'HTTP/1': if tcp.module_data['verbose']: chop.tsprnt(repr(data[:16])) if len(data) < 16: #wtf, but it happens tcp.stream_data['server_buf'] += data return (decrypted,flags, comp) = decrypt_packed_string(data) if tcp.module_data['verbose']: chop.tsprnt("c2_side - precrypt - key:%s flag:%s szComp:%s szDeComp:%s" % (repr(hex(unpack("<I", data[0:4])[0])),repr(hex(unpack("<I",data[4:8])[0])),repr(hex(unpack("H",data[8:10])[0])),repr(hex(unpack("H",data[10:12])[0])))) chop.tsprnt("c2_side - postcrypt - key:%s flag:%s szComp:%s szDeComp:%s" % (repr(hex(unpack("<I", decrypted[0:4])[0])),repr(hex(unpack("<I",decrypted[4:8])[0])),repr(hex(unpack("H",decrypted[8:10])[0])),repr(hex(unpack("H",decrypted[10:12])[0])))) if flags != 0xffff: if tcp.module_data['verbose']: chop.tsprnt("c2 decrypted header: %s flag: %s" % (repr(decrypted),hex(flags))) if comp: chop.tsprnt("printable chars sent to c2",repr(lznt1.dCompressBuf(comp).replace("\x00","")),module_data['flags'][flags]) if tcp.module_data['verbose']: chop.tsprnt("full dump of data sent to c2 %s" % repr(lznt1.dCompressBuf(comp))) tcp.discard(tcp.server.count_new) # handle server system packets if tcp.client.count_new > 0: data = tcp.client.data[:tcp.client.count_new] if not data[:6] == 'POST /' and not data[:6] == 'HTTP/1': if tcp.module_data['verbose']: chop.tsprnt(repr(data[:16])) if len(data) < 16: #wtf, but it happens tcp.stream_data['server_buf'] += data return (decrypted,flags,comp) = decrypt_packed_string(data) if tcp.module_data['verbose']: chop.tsprnt("bot_side - precrypt: %s %s %s %s" % (repr(hex(unpack("<I", data[0:4])[0])),repr(hex(unpack("<I",data[4:8])[0])),repr(hex(unpack("H",data[8:10])[0])),repr(hex(unpack("H",data[10:12])[0])))) chop.tsprnt("bot_side - postcrypt: %s %s %s %s" % (repr(hex(unpack("<I", decrypted[0:4])[0])),repr(hex(unpack("<I",decrypted[4:8])[0])),repr(hex(unpack("H",decrypted[8:10])[0])),repr(hex(unpack("H",decrypted[10:12])[0])))) chop.tsprnt(comp) if flags != 0xffff: if tcp.module_data['verbose']: chop.tsprnt("bot decrypted header: %s flag: %s" % (repr(decrypted),hex(flags))) if comp: chop.tsprnt("printable chars sent to bot",repr(lznt1.dCompressBuf(comp).replace("\x00","")),module_data['flags'][flags]) if tcp.module_data['verbose']: chop.tsprnt("full dump of data sent to bot message %s" % repr(lznt1.dCompressBuf(comp))) tcp.discard(tcp.client.count_new) if tcp.stream_data['flag']: while data: #stuff! break #chop.tsprnt("Finding flag: %s:%i->%s:%i (%i)" % (src, sport, dst, dport, len(data))) # Sometimes our data isn't all in one packet? I'm not sure why I am fighting this bug #if not tcp.stream_data['flag']: #chop.tsprnt("No flag found, skipping stream.") #tcp.stop() return
def handleStream(tcp): data = '' # collect time and IP metadata ((src, sport), (dst, dport)) = tcp.addr # handle client system packets if tcp.server.count_new > 0: data = tcp.server.data[:tcp.server.count_new] if not data[:6] == 'POST /' and not data[:6] == 'HTTP/1': if tcp.module_data['verbose']: chop.tsprnt(repr(data[:16])) if len(data) < 16: #wtf, but it happens tcp.stream_data['server_buf'] += data return (decrypted,flags, comp) = decrypt_packed_string(data) if tcp.module_data['verbose']: chop.tsprnt("c2_side - precrypt - key:%s flag:%s szComp:%s szDeComp:%s" % (repr(hex(unpack("<I", data[0:4])[0])),repr(hex(unpack("<I",data[4:8])[0])),repr(hex(unpack("H",data[8:10])[0])),repr(hex(unpack("H",data[10:12])[0])))) chop.tsprnt("c2_side - postcrypt - key:%s flag:%s szComp:%s szDeComp:%s" % (repr(hex(unpack("<I", decrypted[0:4])[0])),repr(hex(unpack("<I",decrypted[4:8])[0])),repr(hex(unpack("H",decrypted[8:10])[0])),repr(hex(unpack("H",decrypted[10:12])[0])))) if flags != 0xffff: if tcp.module_data['verbose']: chop.tsprnt("c2 decrypted header: %s flag: %s" % (repr(decrypted),hex(flags))) if comp: chop.tsprnt("printable chars sent to c2",repr(lznt1.dCompressBuf(comp).replace("\x00","")),module_data['flags'][flags]) if tcp.module_data['verbose']: chop.tsprnt("full dump of data sent to c2 %s" % repr(lznt1.dCompressBuf(comp))) tcp.discard(tcp.server.count_new) # handle server system packets if tcp.client.count_new > 0: data = tcp.client.data[:tcp.client.count_new] if not data[:6] == 'POST /' and not data[:6] == 'HTTP/1': if tcp.module_data['verbose']: chop.tsprnt(repr(data[:16])) if len(data) < 16: #wtf, but it happens tcp.stream_data['server_buf'] += data return (decrypted,flags,comp) = decrypt_packed_string(data) if tcp.module_data['verbose']: chop.tsprnt("bot_side - precrypt: %s %s %s %s" % (repr(hex(unpack("<I", data[0:4])[0])),repr(hex(unpack("<I",data[4:8])[0])),repr(hex(unpack("H",data[8:10])[0])),repr(hex(unpack("H",data[10:12])[0])))) chop.tsprnt("bot_side - postcrypt: %s %s %s %s" % (repr(hex(unpack("<I", decrypted[0:4])[0])),repr(hex(unpack("<I",decrypted[4:8])[0])),repr(hex(unpack("H",decrypted[8:10])[0])),repr(hex(unpack("H",decrypted[10:12])[0])))) chop.tsprnt(comp) if flags != 0xffff: if tcp.module_data['verbose']: chop.tsprnt("bot decrypted header: %s flag: %s" % (repr(decrypted),hex(flags))) if comp: chop.tsprnt("printable chars sent to bot",repr(lznt1.dCompressBuf(comp).replace("\x00","")),module_data['flags'][flags]) if tcp.module_data['verbose']: chop.tsprnt("full dump of data sent to bot message %s" % repr(lznt1.dCompressBuf(comp))) tcp.discard(tcp.client.count_new) if tcp.stream_data['flag']: while data: #stuff! break #chop.tsprnt("Finding flag: %s:%i->%s:%i (%i)" % (src, sport, dst, dport, len(data))) # Sometimes our data isn't all in one packet? I'm not sure why I am fighting this bug #if not tcp.stream_data['flag']: #chop.tsprnt("No flag found, skipping stream.") #tcp.stop() return
def handleStream(tcp): sd = tcp.stream_data md = tcp.module_data if tcp.client.count_new > 0: sd['client_buffer'] += tcp.client.data[:tcp.client.count_new] if sd['client_state'] == "challenged": if len(sd['client_buffer']) >= 256: challenge_resp = sd['client_buffer'][:256] sd['client_buffer'] = sd['client_buffer'][256:] if md['pwlist']: if TryKeyList(md['pwlist'], sd['challenge'], challenge_resp, md['camcrypt']): if md['verbose'] or md['debug']: chop.tsprnt("PI challenge response accepted..") sd['client_state'] = "challenge_accepted" tcp.discard(tcp.client.count_new) return if challenge_resp == CamelliaEncrypt(sd['challenge'], md['camcrypt']): if md['verbose'] or md['debug']: chop.tsprnt("PI challenge response accepted..") sd['client_state'] = "challenge_accepted" tcp.discard(tcp.client.count_new) return else: sd['client_state'] = "challenge_failed" if md['verbose'] or md['debug']: chop.tsprnt( "PI challenge response not valid for supplied passwords(s), skipping stream.." ) #sd['challenge_accepted'] = True tcp.stop() return if sd['client_state'] == "double_challenged": if len(sd['client_buffer']) >= 260: challenge_resp = sd['client_buffer'][:256] sd['client_buffer'] = sd['client_buffer'][256:] (a, b) = struct.unpack('>HH', tcp.client.data[:4]) a ^= 0xd015 if a != b: sd['client_state'] = "challenge_failed" if md['verbose'] or md['debug']: chop.tsprnt( "PI challenge not valid, skipping stream..") tcp.stop() return sd['xor'] = a & 0xFF chop.tsprnt("PI double nonce xor variant, xor key: %02X" % sd['xor']) if md['pwlist']: if TryKeyList(md['pwlist'], sd['challenge'], challenge_resp, md['camcrypt'], sd['xor']): if md['verbose'] or md['debug']: chop.tsprnt("PI challenge response accepted..") sd['client_state'] = "challenge_accepted" tcp.discard(tcp.client.count_new) return if challenge_resp == CamelliaEncrypt( one_byte_xor(sd['challenge'], sd['xor']), md['camcrypt'], sd['xor']): if md['verbose'] or md['debug']: chop.tsprnt("PI challenge response accepted..") sd['client_state'] = "challenge_accepted" tcp.discard(tcp.client.count_new) return else: sd['client_state'] = "challenge_failed" if md['verbose'] or md['debug']: chop.tsprnt( "PI double challenge response not valid for supplied passwords(s), skipping stream.." ) #sd['challenge_accepted'] = True tcp.stop() return if sd['client_state'] == "challenge_accepted": if len(sd['client_buffer']) >= 4: if 'xor' in sd: sd['init_size'] = unpack( "<I", one_byte_xor(sd['client_buffer'][:4], sd['xor']))[0] else: sd['init_size'] = unpack("<I", sd['client_buffer'][:4])[0] sd['client_state'] = "init_code_collection" sd['client_buffer'] = sd['client_buffer'][4:] if sd['client_state'] == "init_code_collection": if sd['init_size'] <= len(sd['client_buffer']): sd['client_state'] = "init_code_collected" #decrypted = CamelliaDecrypt(sd['client_buffer'][:sd['init_size']], md['camcrypt']) if md['debug']: chop.tsprnt("init code size: %08X" % sd['init_size']) sd['client_buffer'] = sd['client_buffer'][sd['init_size']:] if sd['client_state'] == "init_code_collected": if len(sd['client_buffer']) >= 4: if 'xor' in sd: sd['version'] = unpack( "<I", one_byte_xor(sd['client_buffer'][:4], sd['xor']))[0] else: sd['version'] = unpack("<I", sd['client_buffer'][:4])[0] sd['client_buffer'] = sd['client_buffer'][4:] sd['client_state'] = "version_collected" chop.tsprnt("Poison Ivy Version: %0.2f" % (sd['version'] / 100.00)) if sd['client_state'] == "version_collected": if len(sd['client_buffer']) >= 4: if 'xor' in sd: sd['init_size'] = unpack( "<I", one_byte_xor(sd['client_buffer'][:4], sd['xor']))[0] else: sd['init_size'] = unpack("<I", sd['client_buffer'][:4])[0] sd['client_buffer'] = sd['client_buffer'][4:] sd['client_state'] = "stub_code_collection" if md['debug']: chop.tsprnt("stub code size: %08X" % sd['init_size']) if sd['client_state'] == "stub_code_collection": if sd['init_size'] <= len(sd['client_buffer']): sd['client_state'] = "stub_code_collected" if md['debug']: chop.tsprnt("stub code collected..") sd['client_buffer'] = sd['client_buffer'][sd['init_size']:] if sd['client_state'] == "stub_code_collected": #initialization complete if md['debug']: chop.tsprnt("init complete..") sd['client_state'] = "read_header" sd['server_state'] = "read_header" sd['server_buffer'] = "" if sd['client_state'] == "read_header": listid = sd['client_cur_listid'] if len(sd['client_buffer']) >= 32: (sd['client_cur_listid'], newstream) = getHeaders("in", sd['client_buffer'][:32], tcp) listid = sd['client_cur_listid'] sd['client_state'] = "recv_chunk" sd['client_buffer'] = sd['client_buffer'][32:] if newstream: if sd['inbound_type'].get(listid) == 6: #handle file data decrypted = CamelliaDecrypt( sd['client_buffer'] [:sd['inbound_chunk_size'].get(listid)], md['camcrypt'], sd.get('xor', None)) sd['client_buffer'] = sd['client_buffer'][ sd['inbound_chunk_size'].get(listid):] if sd['inbound_unpadded_chunk_size'].get(listid) != sd[ 'inbound_decompressed_chunk_size'].get(listid): buf = lznt1.dCompressBuf( decrypted[:sd['inbound_unpadded_chunk_size']. get(listid)]) if buf == None: chop.tsprnt("decompression error:\n%s" % hexdump(decrypted)) tcp.stop() else: buf = decrypted[:sd['inbound_unpadded_chunk_size']. get(listid)] #decompressed = lznt1.dCompressBuf(decrypted[:sd['inbound_unpadded_chunk_size']]) filename = string.strip(buf, "\x00") sd['inbound_filename'][ listid] = "PI-extracted-inbound-file-%d-%s" % ( md['filecount'], filename[string.rfind(filename, "\\") + 1:]) md['filecount'] += 1 chop.tsprnt("inbound file %s " % filename) sd['client_state'] = "read_header" sd['inbound_size_left'][listid] = sd[ 'inbound_total_size'].get(listid) if sd['inbound_size_left'].get(listid) == 0: sd['inbound_size_left'][listid] = sd['inbound_total_size'].get( listid) if sd['client_state'] == "recv_chunk": listid = sd['client_cur_listid'] if sd['inbound_chunk_size'].get(listid) <= len( sd['client_buffer']): if md['debug']: chop.tsprnt("handling inbound chunk.. %d bytes to go" % sd['inbound_size_left'].get(listid)) sd['client_state'] = "read_header" decrypted = CamelliaDecrypt( sd['client_buffer'][:sd['inbound_chunk_size'].get(listid)], md['camcrypt'], sd.get('xor', None)) decrypted = decrypted[:sd['inbound_unpadded_chunk_size']. get(listid)] buf = decrypted if sd['inbound_unpadded_chunk_size'].get(listid) != sd[ 'inbound_decompressed_chunk_size'].get(listid): buf = lznt1.dCompressBuf(decrypted) if buf == None: chop.tsprnt("decompression error:\n%s" % hexdump(decrypted)) tcp.stop() sd['client_collect_buffer'][listid] += buf sd['client_buffer'] = sd['client_buffer'][ sd['inbound_chunk_size'].get(listid):] sd['inbound_size_left'][listid] -= sd[ 'inbound_decompressed_chunk_size'].get(listid) if sd['inbound_type'].get(listid) == 6 and md['savefiles']: #inbound file chop.savefile(sd['inbound_filename'].get(listid), buf, False) if sd['inbound_size_left'].get(listid) == 0: if sd['inbound_type'].get(listid) == 6: if md['savefiles']: #inbound file chop.finalizefile( sd['inbound_filename'].get(listid)) chop.tsprnt("saved %s.." % sd['inbound_filename'].get(listid)) else: analyzeCode(sd['client_collect_buffer'].get(listid), sd['inbound_type'].get(listid), tcp) if md['debug']: chop.tsprnt("analyzing code..") sd['client_collect_buffer'][listid] = "" #chop.tsprnt("to client:%d" % tcp.client.count_new) tcp.discard(tcp.client.count_new) return elif tcp.server.count_new > 0: sd['server_buffer'] += tcp.server.data[:tcp.server.count_new] if sd['client_state'] == "unauthenticated": if len(sd['server_buffer']) >= 256: sd['client_state'] = "challenged" #chop.tsprnt(hexdump(tcp.server.data[:tcp.server.count_new])) sd['challenge'] = sd['server_buffer'][:256] sd['server_buffer'] = sd['server_buffer'][256:] #chop.tsprnt(hexdump(sd['challenge'])) elif sd['client_state'] == "challenged": if len(sd['server_buffer']) >= 256: sd['client_state'] = "double_challenged" sd['challenge'] = sd['server_buffer'][:256] sd['server_buffer'] = sd['server_buffer'][256:] elif sd['client_state'] == "double_challenged": if md['verbose'] or md['debug']: chop.tsprnt("PI challenge not found, skipping stream..") tcp.stop() if sd['server_state'] == "read_header": listid = sd['server_cur_listid'] if len(sd['server_buffer']) >= 32: (sd['server_cur_listid'], newstream) = getHeaders("out", sd['server_buffer'][:32], tcp) listid = sd['server_cur_listid'] sd['server_state'] = "recv_chunk" sd['server_buffer'] = sd['server_buffer'][32:] if newstream: if sd['outbound_type'].get(listid) == 4: #handle file data decrypted = CamelliaDecrypt( sd['server_buffer'] [:sd['outbound_chunk_size'].get(listid)], md['camcrypt'], sd.get('xor', None)) if sd['outbound_unpadded_chunk_size'].get( listid ) != sd['outbound_decompressed_chunk_size'].get( listid): buf = lznt1.dCompressBuf( decrypted[:sd['outbound_unpadded_chunk_size']. get(listid)]) if buf == None: chop.tsprnt("decompression error:\n%s" % hexdump(decrypted)) tcp.stop() else: buf = decrypted[:sd['outbound_unpadded_chunk_size'] .get(listid)] sd['server_buffer'] = sd['server_buffer'][ sd['outbound_chunk_size'].get(listid):] filename = string.strip(buf, "\x00") sd['outbound_filename'][ listid] = "PI-extracted-outbound-file-%d-%s" % ( md['filecount'], filename[string.rfind(filename, "\\") + 1:]) md['filecount'] += 1 chop.tsprnt("outbound file %s " % filename) sd['server_state'] = "read_header" sd['outbound_size_left'][listid] = sd[ 'outbound_total_size'].get(listid) if sd['outbound_size_left'].get(listid) == 0: sd['outbound_size_left'][listid] = sd[ 'outbound_total_size'].get(listid) if sd['server_state'] == "recv_chunk": listid = sd['server_cur_listid'] if sd['outbound_chunk_size'].get(listid) <= len( sd['server_buffer']): if md['debug']: chop.tsprnt("handling outbound chunk.. %d bytes to go" % sd['outbound_size_left'].get(listid)) sd['server_state'] = "read_header" decrypted = CamelliaDecrypt( sd['server_buffer'] [:sd['outbound_chunk_size'].get(listid)], md['camcrypt'], sd.get('xor', None)) decrypted = decrypted[:sd['outbound_unpadded_chunk_size']. get(listid)] buf = decrypted if sd['outbound_unpadded_chunk_size'].get(listid) != sd[ 'outbound_decompressed_chunk_size'].get(listid): buf = lznt1.dCompressBuf(decrypted) if buf == None: chop.tsprnt("decompression error:\n%s" % hexdump(decrypted)) tcp.stop() sd['server_collect_buffer'][listid] += buf sd['server_buffer'] = sd['server_buffer'][ sd['outbound_chunk_size'].get(listid):] sd['outbound_size_left'][listid] -= sd[ 'outbound_decompressed_chunk_size'].get(listid) if sd['outbound_type'].get(listid) == 4 and md['savefiles']: #outbound file chop.savefile(sd['outbound_filename'].get(listid), buf, False) if sd['outbound_size_left'].get(listid) == 0: if sd['outbound_type'].get(listid) == 4: if md['savefiles']: #outbound file chop.finalizefile( sd['outbound_filename'].get(listid)) chop.tsprnt("saved %s.." % sd['outbound_filename'].get(listid)) else: if md['debug']: chop.tsprnt("outbound data:\n%s" % hexdump( sd['server_collect_buffer'].get(listid))) try: if sd['outbound_type'].get(listid) == 0x5c: md['cmdhandler'][sd['outbound_type'].get( listid)](sd['server_collect_buffer'].get( listid), tcp) else: md['cmdhandler'][sd['outbound_type'].get( listid)](sd['server_collect_buffer'].get( listid)) except: if md['verbose'] or md['debug']: chop.tsprnt("unrecognized command..") sd['server_collect_buffer'][listid] = "" tcp.discard(tcp.server.count_new) return tcp.discard(tcp.server.count_new) return
def handleStream(tcp): def decrypt_packed_string(__src): global key global src global size src = __src key = unpack("<I", __src[0:4])[0] if key == 0x54534f50 or key == 0x50545448: return src, 0xffff, '' #chop.tsprnt(hex(key)) size = 16 stage1 = decrypt() #chop.tsprnt(repr(hex(unpack("<I", stage1[0:4])[0])),repr(hex(unpack("<I",stage1[4:8])[0])),repr(hex(unpack("H",stage1[8:10])[0])),repr(hex(unpack("H",stage1[10:12])[0]))) flags = unpack("<I", stage1[4:8])[0] #chop.tsprnt(hex(flags)) #chop.tsprnt(repr(stage1[8:10])) if flags & 0x2000000: #do not decrypt payload separately if tcp.module_data['verbose']: chop.tsprnt("do not decrypt separately") size = len(src) stage1 = decrypt() # consider removing the verbosity filter here if you're seeing the do not decrypt message and not data. if tcp.module_data['verbose']: chop.tsprnt(unpack("H", stage1[8:10])[0]) else: size = len(src[16:]) src = __src[16:] stage1 = stage1 + decrypt() if flags & 0x1000000: #do not decompress payload if tcp.module_data['verbose']: chop.tsprnt("do not decompress") return stage1, flags, '' else: if flags in module_data['flags'].keys(): comp = stage1[16:] if tcp.module_data['verbose']: chop.tsprnt("len of payload: %d len in header: %d" % (len(comp), unpack("H", stage1[8:10])[0])) if len(comp) == unpack("H", stage1[8:10]): return stage1[:16], flags, comp return stage1[:16], flags, comp[:unpack("H", stage1[8:10])[0]] decomp = '' #chop.tsprnt(repr(stage1[:16]),repr(stage1[16:])) return stage1[:16] + decomp, flags, '' def decrypt(): global key global new_key global key0 global key1 global key2 global key3 global src global dst global res global i global size key0 = key key1 = key key2 = key key3 = key dst = b'' i = 0 if size > 0: while i < size: if tcp.module_data['protocol'] == 0: key0 = (key0 + (((key0 >> 3) & 0xFFFFFFFF) - 0x11111111) & 0xFFFFFFFF) & 0xFFFFFFFF key1 = (key1 + (((key1 >> 5) & 0xFFFFFFFF) - 0x22222222) & 0xFFFFFFFF) & 0xFFFFFFFF key2 = (key2 + (0x44444444 - ( (key2 << 9) & 0xFFFFFFFF)) & 0xFFFFFFFF) & 0xFFFFFFFF key3 = (key3 + (0x33333333 - ( (key3 << 7) & 0xFFFFFFFF)) & 0xFFFFFFFF) & 0xFFFFFFFF new_key = (((key2 & 0xFF) + (key3 & 0xFF) + (key1 & 0xFF) + (key0 & 0xFF)) & 0xFF) elif tcp.module_data['protocol'] == 1: key0 = (key0 + ((key0 >> 3) + 3) & 0xFFFFFFFF) & 0xFFFFFFFF key1 = (key1 + (((key1 >> 5) & 0xFFFFFFFF) + 5) & 0xFFFFFFFF) & 0xFFFFFFFF key2 = (0xFFFFFF81 * (key2 & 0xFFFFFFFF) - 7) & 0xFFFFFFFF key3 = (0xFFFFFE01 * (key3 & 0xFFFFFFFF) - 9) & 0xFFFFFFFF new_key = (((key2 & 0xFF) + (key3 & 0xFF) + (key1 & 0xFF) + (key0 & 0xFF)) & 0xFF) else: new_key = 0xFF if tcp.module_data['verbose']: chop.tsprnt(hex(new_key), hex(key0), hex(key1), hex(key2), hex(key3)) res = unpack("<B", src[i:i + 1])[0] ^ new_key dst = dst + pack("<B", res) i = i + 1 return dst data = '' # collect time and IP metadata ((src, sport), (dst, dport)) = tcp.addr # handle client system packets if tcp.server.count_new > 0: data = tcp.server.data[:tcp.server.count_new] if not data[:6] == 'POST /' and not data[:6] == 'HTTP/1': if tcp.module_data['verbose']: chop.tsprnt(repr(data[:16])) if len(data) < 16: #wtf, but it happens tcp.stream_data['server_buf'] += data return (decrypted, flags, comp) = decrypt_packed_string(data) if tcp.module_data['verbose']: chop.tsprnt( "c2_side - precrypt - key:%s flag:%s szComp:%s szDeComp:%s" % (repr(hex(unpack("<I", data[0:4])[0])), repr(hex(unpack("<I", data[4:8])[0])), repr(hex(unpack("H", data[8:10])[0])), repr(hex(unpack("H", data[10:12])[0])))) chop.tsprnt( "c2_side - postcrypt - key:%s flag:%s szComp:%s szDeComp:%s" % (repr(hex(unpack("<I", decrypted[0:4])[0])), repr(hex(unpack("<I", decrypted[4:8])[0])), repr(hex(unpack("H", decrypted[8:10])[0])), repr(hex(unpack("H", decrypted[10:12])[0])))) if flags != 0xffff: if tcp.module_data['verbose']: chop.tsprnt("c2 decrypted header: %s flag: %s" % (repr(decrypted), hex(flags))) if comp: chop.tsprnt("printable chars sent to c2", repr(lznt1.dCompressBuf(comp).replace("\x00", "")), module_data['flags'][flags]) if tcp.module_data['verbose']: chop.tsprnt("full dump of data sent to c2 %s" % repr(lznt1.dCompressBuf(comp))) tcp.discard(tcp.server.count_new) # handle server system packets if tcp.client.count_new > 0: data = tcp.client.data[:tcp.client.count_new] if not data[:6] == 'POST /' and not data[:6] == 'HTTP/1': if tcp.module_data['verbose']: chop.tsprnt(repr(data[:16])) if len(data) < 16: #wtf, but it happens tcp.stream_data['server_buf'] += data return (decrypted, flags, comp) = decrypt_packed_string(data) if tcp.module_data['verbose']: chop.tsprnt("bot_side - precrypt: %s %s %s %s" % (repr(hex(unpack("<I", data[0:4])[0])), repr(hex(unpack("<I", data[4:8])[0])), repr(hex(unpack("H", data[8:10])[0])), repr(hex(unpack("H", data[10:12])[0])))) chop.tsprnt("bot_side - postcrypt: %s %s %s %s" % (repr(hex(unpack("<I", decrypted[0:4])[0])), repr(hex(unpack("<I", decrypted[4:8])[0])), repr(hex(unpack("H", decrypted[8:10])[0])), repr(hex(unpack("H", decrypted[10:12])[0])))) chop.tsprnt(comp) if flags != 0xffff: if tcp.module_data['verbose']: chop.tsprnt("bot decrypted header: %s flag: %s" % (repr(decrypted), hex(flags))) if comp: chop.tsprnt("printable chars sent to bot", repr(lznt1.dCompressBuf(comp).replace("\x00", "")), module_data['flags'][flags]) if tcp.module_data['verbose']: chop.tsprnt("full dump of data sent to bot message %s" % repr(lznt1.dCompressBuf(comp))) tcp.discard(tcp.client.count_new) if tcp.stream_data['flag']: while data: #stuff! break #chop.tsprnt("Finding flag: %s:%i->%s:%i (%i)" % (src, sport, dst, dport, len(data))) # Sometimes our data isn't all in one packet? I'm not sure why I am fighting this bug #if not tcp.stream_data['flag']: #chop.tsprnt("No flag found, skipping stream.") #tcp.stop() return
def handleStream(tcp): data = '' # collect time and IP metadata ((src, sport), (dst, dport)) = tcp.addr # handle client system packets if tcp.server.count_new > 0: data = tcp.server.data[:tcp.server.count_new] if not data[:6] == 'POST /' and not data[:6] == 'HTTP/1': if tcp.module_data['verbose']: chop.tsprnt(repr(data[:16])) if len(data) < 16: #wtf, but it happens tcp.stream_data['server_buf'] += data return (decrypted, flags, comp) = decrypt_packed_string(data) if tcp.module_data['verbose']: chop.tsprnt( "c2_side - precrypt - key:%s flag:%s szComp:%s szDeComp:%s" % (repr(hex(unpack("<I", data[0:4])[0])), repr(hex(unpack("<I", data[4:8])[0])), repr(hex(unpack("H", data[8:10])[0])), repr(hex(unpack("H", data[10:12])[0])))) chop.tsprnt( "c2_side - postcrypt - key:%s flag:%s szComp:%s szDeComp:%s" % (repr(hex(unpack("<I", decrypted[0:4])[0])), repr(hex(unpack("<I", decrypted[4:8])[0])), repr(hex(unpack("H", decrypted[8:10])[0])), repr(hex(unpack("H", decrypted[10:12])[0])))) if flags != 0xffff: if tcp.module_data['verbose']: chop.tsprnt("c2 decrypted header: %s flag: %s" % (repr(decrypted), hex(flags))) if comp: chop.tsprnt("printable chars sent to c2", repr(lznt1.dCompressBuf(comp).replace("\x00", "")), module_data['flags'][flags]) if tcp.module_data['verbose']: chop.tsprnt("full dump of data sent to c2 %s" % repr(lznt1.dCompressBuf(comp))) tcp.discard(tcp.server.count_new) # handle server system packets if tcp.client.count_new > 0: data = tcp.client.data[:tcp.client.count_new] if not data[:6] == 'POST /' and not data[:6] == 'HTTP/1': if tcp.module_data['verbose']: chop.tsprnt(repr(data[:16])) if len(data) < 16: #wtf, but it happens tcp.stream_data['server_buf'] += data return (decrypted, flags, comp) = decrypt_packed_string(data) if tcp.module_data['verbose']: chop.tsprnt("bot_side - precrypt: %s %s %s %s" % (repr(hex(unpack("<I", data[0:4])[0])), repr(hex(unpack("<I", data[4:8])[0])), repr(hex(unpack("H", data[8:10])[0])), repr(hex(unpack("H", data[10:12])[0])))) chop.tsprnt("bot_side - postcrypt: %s %s %s %s" % (repr(hex(unpack("<I", decrypted[0:4])[0])), repr(hex(unpack("<I", decrypted[4:8])[0])), repr(hex(unpack("H", decrypted[8:10])[0])), repr(hex(unpack("H", decrypted[10:12])[0])))) chop.tsprnt(comp) if flags != 0xffff: if tcp.module_data['verbose']: chop.tsprnt("bot decrypted header: %s flag: %s" % (repr(decrypted), hex(flags))) if comp: chop.tsprnt("printable chars sent to bot", repr(lznt1.dCompressBuf(comp).replace("\x00", "")), module_data['flags'][flags]) if tcp.module_data['verbose']: chop.tsprnt("full dump of data sent to bot message %s" % repr(lznt1.dCompressBuf(comp))) tcp.discard(tcp.client.count_new) if tcp.stream_data['flag']: while data: #stuff! break #chop.tsprnt("Finding flag: %s:%i->%s:%i (%i)" % (src, sport, dst, dport, len(data))) # Sometimes our data isn't all in one packet? I'm not sure why I am fighting this bug #if not tcp.stream_data['flag']: #chop.tsprnt("No flag found, skipping stream.") #tcp.stop() return
def handleStream(tcp): sd = tcp.stream_data md = tcp.module_data if tcp.client.count_new > 0: sd['client_buffer'] += tcp.client.data[:tcp.client.count_new] if sd['client_state'] == "challenged": if len(sd['client_buffer']) >= 256: challenge_resp = sd['client_buffer'][:256] sd['client_buffer'] = sd['client_buffer'][256:] if md['pwlist']: if TryKeyList(md['pwlist'], sd['challenge'], challenge_resp, md['camcrypt']): if md['verbose'] or md['debug']: chop.tsprnt("PI challenge response accepted..") sd['client_state'] = "challenge_accepted" tcp.discard(tcp.client.count_new) return if challenge_resp == CamelliaEncrypt(sd['challenge'], md['camcrypt']): if md['verbose'] or md['debug']: chop.tsprnt("PI challenge response accepted..") sd['client_state'] = "challenge_accepted" tcp.discard(tcp.client.count_new) return else: sd['client_state'] = "challenge_failed" if md['verbose'] or md['debug']: chop.tsprnt("PI challenge response not valid for supplied passwords(s), skipping stream..") #sd['challenge_accepted'] = True tcp.stop() return if sd['client_state'] == "double_challenged": if len(sd['client_buffer']) >= 260: challenge_resp = sd['client_buffer'][:256] sd['client_buffer'] = sd['client_buffer'][256:] (a, b) = struct.unpack('>HH', tcp.client.data[:4]) a ^= 0xd015 if a != b: sd['client_state'] = "challenge_failed" if md['verbose'] or md['debug']: chop.tsprnt("PI challenge not valid, skipping stream..") tcp.stop() return sd['xor'] = a & 0xFF chop.tsprnt("PI double nonce xor variant, xor key: %02X" % sd['xor']) if md['pwlist']: if TryKeyList(md['pwlist'], sd['challenge'], challenge_resp, md['camcrypt'], sd['xor']): if md['verbose'] or md['debug']: chop.tsprnt("PI challenge response accepted..") sd['client_state'] = "challenge_accepted" tcp.discard(tcp.client.count_new) return if challenge_resp == CamelliaEncrypt(one_byte_xor(sd['challenge'], sd['xor']), md['camcrypt'], sd['xor']): if md['verbose'] or md['debug']: chop.tsprnt("PI challenge response accepted..") sd['client_state'] = "challenge_accepted" tcp.discard(tcp.client.count_new) return else: sd['client_state'] = "challenge_failed" if md['verbose'] or md['debug']: chop.tsprnt("PI double challenge response not valid for supplied passwords(s), skipping stream..") #sd['challenge_accepted'] = True tcp.stop() return if sd['client_state'] == "challenge_accepted": if len(sd['client_buffer']) >= 4: if 'xor' in sd: sd['init_size'] = unpack("<I", one_byte_xor(sd['client_buffer'][:4], sd['xor']))[0] else: sd['init_size'] = unpack("<I", sd['client_buffer'][:4])[0] sd['client_state'] = "init_code_collection" sd['client_buffer'] = sd['client_buffer'][4:] if sd['client_state'] == "init_code_collection": if sd['init_size'] <= len(sd['client_buffer']): sd['client_state'] = "init_code_collected" #decrypted = CamelliaDecrypt(sd['client_buffer'][:sd['init_size']], md['camcrypt']) if md['debug']: chop.tsprnt("init code size: %08X" % sd['init_size']) sd['client_buffer'] = sd['client_buffer'][sd['init_size']:] if sd['client_state'] == "init_code_collected": if len(sd['client_buffer']) >= 4: if 'xor' in sd: sd['version'] = unpack("<I", one_byte_xor(sd['client_buffer'][:4], sd['xor']))[0] else: sd['version'] = unpack("<I", sd['client_buffer'][:4])[0] sd['client_buffer'] = sd['client_buffer'][4:] sd['client_state'] = "version_collected" chop.tsprnt("Poison Ivy Version: %0.2f" % (sd['version'] / 100.00)) if sd['client_state'] == "version_collected": if len(sd['client_buffer']) >= 4: if 'xor' in sd: sd['init_size'] = unpack("<I", one_byte_xor(sd['client_buffer'][:4], sd['xor']))[0] else: sd['init_size'] = unpack("<I", sd['client_buffer'][:4])[0] sd['client_buffer'] = sd['client_buffer'][4:] sd['client_state'] = "stub_code_collection" if md['debug']: chop.tsprnt("stub code size: %08X" % sd['init_size']) if sd['client_state'] == "stub_code_collection": if sd['init_size'] <= len(sd['client_buffer']): sd['client_state'] = "stub_code_collected" if md['debug']: chop.tsprnt("stub code collected..") sd['client_buffer'] = sd['client_buffer'][sd['init_size']:] if sd['client_state'] == "stub_code_collected": #initialization complete if md['debug']: chop.tsprnt("init complete..") sd['client_state'] = "read_header" sd['server_state'] = "read_header" sd['server_buffer'] = "" if sd['client_state'] == "read_header": listid = sd['client_cur_listid'] if len(sd['client_buffer']) >= 32: (sd['client_cur_listid'], newstream) = getHeaders("in", sd['client_buffer'][:32], tcp) listid = sd['client_cur_listid'] sd['client_state'] = "recv_chunk" sd['client_buffer'] = sd['client_buffer'][32:] if newstream: if sd['inbound_type'].get(listid) == 6: #handle file data decrypted = CamelliaDecrypt(sd['client_buffer'][:sd['inbound_chunk_size'].get(listid)], md['camcrypt'], sd.get('xor', None)) sd['client_buffer'] = sd['client_buffer'][sd['inbound_chunk_size'].get(listid):] if sd['inbound_unpadded_chunk_size'].get(listid) != sd['inbound_decompressed_chunk_size'].get(listid): buf = lznt1.dCompressBuf(decrypted[:sd['inbound_unpadded_chunk_size'].get(listid)]) if buf == None: chop.tsprnt("decompression error:\n%s" % hexdump(decrypted)) tcp.stop() else: buf = decrypted[:sd['inbound_unpadded_chunk_size'].get(listid)] #decompressed = lznt1.dCompressBuf(decrypted[:sd['inbound_unpadded_chunk_size']]) filename = string.strip(buf, "\x00") sd['inbound_filename'][listid] = "PI-extracted-inbound-file-%d-%s" % (md['filecount'], filename[string.rfind(filename, "\\")+1:]) md['filecount'] += 1 chop.tsprnt("inbound file %s " % filename) sd['client_state'] = "read_header" sd['inbound_size_left'][listid] = sd['inbound_total_size'].get(listid) if sd['inbound_size_left'].get(listid) == 0: sd['inbound_size_left'][listid] = sd['inbound_total_size'].get(listid) if sd['client_state'] == "recv_chunk": listid = sd['client_cur_listid'] if sd['inbound_chunk_size'].get(listid) <= len(sd['client_buffer']): if md['debug']: chop.tsprnt("handling inbound chunk.. %d bytes to go" % sd['inbound_size_left'].get(listid)) sd['client_state'] = "read_header" decrypted = CamelliaDecrypt(sd['client_buffer'][:sd['inbound_chunk_size'].get(listid)], md['camcrypt'], sd.get('xor', None)) decrypted = decrypted[:sd['inbound_unpadded_chunk_size'].get(listid)] buf = decrypted if sd['inbound_unpadded_chunk_size'].get(listid) != sd['inbound_decompressed_chunk_size'].get(listid): buf = lznt1.dCompressBuf(decrypted) if buf == None: chop.tsprnt("decompression error:\n%s" % hexdump(decrypted)) tcp.stop() sd['client_collect_buffer'][listid] += buf sd['client_buffer'] = sd['client_buffer'][sd['inbound_chunk_size'].get(listid):] sd['inbound_size_left'][listid] -= sd['inbound_decompressed_chunk_size'].get(listid) if sd['inbound_type'].get(listid) == 6 and md['savefiles']: #inbound file chop.savefile(sd['inbound_filename'].get(listid), buf, False) if sd['inbound_size_left'].get(listid) == 0: if sd['inbound_type'].get(listid) == 6: if md['savefiles']: #inbound file chop.finalizefile(sd['inbound_filename'].get(listid)) chop.tsprnt("saved %s.." % sd['inbound_filename'].get(listid)) else: analyzeCode(sd['client_collect_buffer'].get(listid), sd['inbound_type'].get(listid), tcp) if md['debug']: chop.tsprnt("analyzing code..") sd['client_collect_buffer'][listid] = "" #chop.tsprnt("to client:%d" % tcp.client.count_new) tcp.discard(tcp.client.count_new) return elif tcp.server.count_new > 0: sd['server_buffer'] += tcp.server.data[:tcp.server.count_new] if sd['client_state'] == "unauthenticated": if len(sd['server_buffer']) >= 256: sd['client_state'] = "challenged" #chop.tsprnt(hexdump(tcp.server.data[:tcp.server.count_new])) sd['challenge'] = sd['server_buffer'][:256] sd['server_buffer'] = sd['server_buffer'][256:] #chop.tsprnt(hexdump(sd['challenge'])) elif sd['client_state'] == "challenged": if len(sd['server_buffer']) >= 256: sd['client_state'] = "double_challenged" sd['challenge'] = sd['server_buffer'][:256] sd['server_buffer'] = sd['server_buffer'][256:] elif sd['client_state'] == "double_challenged": if md['verbose'] or md['debug']: chop.tsprnt("PI challenge not found, skipping stream..") tcp.stop() if sd['server_state'] == "read_header": listid = sd['server_cur_listid'] if len(sd['server_buffer']) >= 32: (sd['server_cur_listid'], newstream) = getHeaders("out", sd['server_buffer'][:32], tcp) listid = sd['server_cur_listid'] sd['server_state'] = "recv_chunk" sd['server_buffer'] = sd['server_buffer'][32:] if newstream: if sd['outbound_type'].get(listid) == 4: #handle file data decrypted = CamelliaDecrypt(sd['server_buffer'][:sd['outbound_chunk_size'].get(listid)], md['camcrypt'], sd.get('xor', None)) if sd['outbound_unpadded_chunk_size'].get(listid) != sd['outbound_decompressed_chunk_size'].get(listid): buf = lznt1.dCompressBuf(decrypted[:sd['outbound_unpadded_chunk_size'].get(listid)]) if buf == None: chop.tsprnt("decompression error:\n%s" % hexdump(decrypted)) tcp.stop() else: buf = decrypted[:sd['outbound_unpadded_chunk_size'].get(listid)] sd['server_buffer'] = sd['server_buffer'][sd['outbound_chunk_size'].get(listid):] filename = string.strip(buf, "\x00") sd['outbound_filename'][listid] = "PI-extracted-outbound-file-%d-%s" % (md['filecount'], filename[string.rfind(filename, "\\")+1:]) md['filecount'] += 1 chop.tsprnt("outbound file %s " % filename) sd['server_state'] = "read_header" sd['outbound_size_left'][listid] = sd['outbound_total_size'].get(listid) if sd['outbound_size_left'].get(listid) == 0: sd['outbound_size_left'][listid] = sd['outbound_total_size'].get(listid) if sd['server_state'] == "recv_chunk": listid = sd['server_cur_listid'] if sd['outbound_chunk_size'].get(listid) <= len(sd['server_buffer']): if md['debug']: chop.tsprnt("handling outbound chunk.. %d bytes to go" % sd['outbound_size_left'].get(listid)) sd['server_state'] = "read_header" decrypted = CamelliaDecrypt(sd['server_buffer'][:sd['outbound_chunk_size'].get(listid)], md['camcrypt'], sd.get('xor', None)) decrypted = decrypted[:sd['outbound_unpadded_chunk_size'].get(listid)] buf = decrypted if sd['outbound_unpadded_chunk_size'].get(listid) != sd['outbound_decompressed_chunk_size'].get(listid): buf = lznt1.dCompressBuf(decrypted) if buf == None: chop.tsprnt("decompression error:\n%s" % hexdump(decrypted)) tcp.stop() sd['server_collect_buffer'][listid] += buf sd['server_buffer'] = sd['server_buffer'][sd['outbound_chunk_size'].get(listid):] sd['outbound_size_left'][listid] -= sd['outbound_decompressed_chunk_size'].get(listid) if sd['outbound_type'].get(listid) == 4 and md['savefiles']: #outbound file chop.savefile(sd['outbound_filename'].get(listid), buf, False) if sd['outbound_size_left'].get(listid) == 0: if sd['outbound_type'].get(listid) == 4: if md['savefiles']: #outbound file chop.finalizefile(sd['outbound_filename'].get(listid)) chop.tsprnt("saved %s.." % sd['outbound_filename'].get(listid)) else: if md['debug']: chop.tsprnt("outbound data:\n%s" % hexdump(sd['server_collect_buffer'].get(listid))) try: md['cmdhandler'][sd['outbound_type'].get(listid)](sd['server_collect_buffer'].get(listid), tcp) except: if md['verbose'] or md['debug']: chop.tsprnt("unrecognized command..") sd['server_collect_buffer'][listid] = "" tcp.discard(tcp.server.count_new) return tcp.discard(tcp.server.count_new) return