Skip to content

Commit 5191862

Browse files
committed
pythom/smb - let dionaea talk to WannaCry, MS17-010 honeypot and collect Double Pulsar payload
Merged from gento/dionaea@d17ebf3
1 parent 2285b5d commit 5191862

3 files changed

Lines changed: 145 additions & 1 deletion

File tree

modules/python/dionaea/smb/include/smbfields.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,14 @@ class SMB_Trans_Response(Packet):
13081308
ByteField("Reserved2",0),
13091309
]
13101310

1311+
class SMB_Trans_Response_Simple(Packet):
1312+
name = "SMB Trans Response Simple"
1313+
smb_cmd = SMB_COM_TRANSACTION #0x25
1314+
fields_desc = [
1315+
ByteField("WordCount",0),
1316+
LEShortField("ByteCount",0),
1317+
]
1318+
13111319
# page 45
13121320
class SMB_Trans2_Request(Packet):
13131321
name = "SMB Trans2 Request"
@@ -1378,6 +1386,54 @@ class SMB_Trans2_Response(Packet):
13781386
LEShortField("ByteCount",0),
13791387
]
13801388

1389+
class SMB_Trans2_Secondary_Request(Packet):
1390+
name = "SMB Trans2 Secondary Request"
1391+
smb_cmd = SMB_COM_TRANSACTION2_SECONDARY # 0x33
1392+
fields_desc = [
1393+
ByteField("WordCount",0),
1394+
LEShortField("TotalParamCount",0),
1395+
LEShortField("TotalDataCount",0),
1396+
LEShortField("MaxParamCount",0),
1397+
LEShortField("MaxDataCount",0),
1398+
ByteField("MaxSetupCount",0),
1399+
ByteField("Reserved",0),
1400+
XLEShortField("Flags",0),
1401+
LEIntField("Timeout",0),
1402+
ShortField("Reserved2",0),
1403+
FieldLenField("ParamCount", 0, fmt='<H', count_of="Data"),
1404+
StrFixedLenField("Data", "", length_from=lambda pkt: pkt.ParamCount),
1405+
]
1406+
1407+
class SMB_NT_Trans_Request(Packet):
1408+
name = "SMB NT Trans Request"
1409+
smb_cmd = SMB_COM_NT_TRANSACT #0xa0
1410+
fields_desc = [
1411+
ByteField("WordCount",0),
1412+
ByteField("MaxSetupCount",0),
1413+
ShortField("Reserved",0),
1414+
LEIntField("TotalParamCount",0),
1415+
LEIntField("TotalDataCount",0),
1416+
LEIntField("MaxParamCount",0),
1417+
LEIntField("MaxDataCount",0),
1418+
FieldLenField("ParamCount", 0, fmt='<I', count_of="Params"),
1419+
LEIntField("ParamOffset",0),
1420+
LEIntField("DataCount",0),
1421+
LEIntField("DataOffset",0),
1422+
FieldLenField("SetupCount", 0, fmt='B', count_of="Setup"),
1423+
ShortField("Function",0),
1424+
#TODO: need more work on this part
1425+
FieldListField("Param", 0, XByteField("", 0), count_from = lambda pkt: pkt.ParamCount),
1426+
StrFixedLenField("Data", b"", length_from=lambda pkt: pkt.DataCount),
1427+
]
1428+
1429+
class SMB_NT_Trans_Response(Packet):
1430+
name = "SMB NT Trans Response"
1431+
smb_cmd = SMB_COM_NT_TRANSACT #0xa0
1432+
fields_desc = [
1433+
ByteField("WordCount",0),
1434+
LEShortField("ByteCount",0),
1435+
]
1436+
13811437
# [MS-CIFS].pdf - 2.2.5 Transaction Subcommands
13821438
# http://msdn.microsoft.com/en-us/library/ee441557%28v=PROT.13%29.aspx
13831439
TRANS_NMPIPE_SET_STATE = 0x0001
@@ -1659,6 +1715,7 @@ class RAP_Response(Packet):
16591715
Command=lambda x: x==0x25, Flags=lambda x: not x&0x80)
16601716
bind_bottom_up(SMB_Header, SMB_Trans2_Request,
16611717
Command=lambda x: x==0x32, Flags=lambda x: not x&0x80)
1718+
bind_bottom_up(SMB_Header, SMB_Trans2_Secondary_Request, Command=lambda x: x==0x33, Flags=lambda x: not x&0x80)
16621719

16631720
bind_bottom_up(SMB_Header, SMB_Write_AndX_Request,
16641721
Command=lambda x: x==0x2f, Flags=lambda x: not x&0x80)
@@ -1715,6 +1772,7 @@ class RAP_Response(Packet):
17151772
bind_top_down(SMB_Header, SMB_Read_AndX_Response, Command=0x2e)
17161773
bind_top_down(SMB_Header, SMB_Trans_Request, Command=0x25)
17171774
bind_top_down(SMB_Header, SMB_Trans2_Request, Command=0x32)
1775+
bind_top_down(SMB_Header, SMB_Trans2_Secondary_Request, Command=0x33)
17181776
bind_top_down(SMB_Header, SMB_Open_AndX_Request, Command=0x2d)
17191777
bind_top_down(SMB_Read_AndX_Response, SMB_Data)
17201778

modules/python/dionaea/smb/smb.py

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from dionaea.core import incident, connection, g_dionaea
3030

3131
import traceback
32+
import hashlib
3233
import logging
3334
import tempfile
3435
from uuid import UUID
@@ -41,6 +42,7 @@
4142
from .include.asn1.ber import BER_len_dec, BER_len_enc, BER_identifier_dec
4243
from .include.asn1.ber import BER_CLASS_APP, BER_CLASS_CON,BER_identifier_enc
4344
from .include.asn1.ber import BER_Exception
45+
from dionaea.util import xor
4446

4547

4648
smblog = logging.getLogger('SMB')
@@ -72,6 +74,7 @@ def __init__ (self, proto="tcp", config=None):
7274
'stop': False,
7375
}
7476
self.buf = b''
77+
self.buf2 = b'' # ms17-010 SMB_COM_TRANSACTION2
7578
self.outbuf = None
7679
self.fids = {}
7780
self.printer = b'' # spoolss file "queue"
@@ -340,6 +343,8 @@ def process(self, p):
340343
if Service.startswith('\\\\'):
341344
Service = Service[1:]
342345
Service = Service.split('\\')[-1]
346+
if Service[-1] == '\x00':
347+
Service = Service[:-1]
343348
if Service[-1] == '$':
344349
Service = Service[:-1]
345350
r.Service = Service + '\x00'
@@ -600,14 +605,80 @@ def process(self, p):
600605
rdata.ByteCount = dceplen
601606
rdata.Bytes = self.outbuf
602607

608+
if socket.htons(h.Setup[0]) == TRANS_NMPIPE_PEEK:
609+
SetupCount = h.SetupCount
610+
if SetupCount > 0:
611+
smblog.info('MS17-010 - SMB RCE exploit scanning..')
612+
r = SMB_Trans_Response_Simple()
613+
# returned #STATUS_INSUFF_SERVER_RESOURCE as we not being patched
614+
rstatus = 0xc0000205 # STATUS_INSUFF_SERVER_RESOURCES
615+
603616
r /= rdata
604-
elif p.getlayer(SMB_Header).Command == SMB_COM_TRANSACTION2:
617+
elif Command == SMB_COM_TRANSACTION2:
618+
h = p.getlayer(SMB_Trans2_Request)
619+
if h.Setup[0] == SMB_TRANS2_SESSION_SETUP:
620+
smblog.info('Possible DoublePulsar connection attempts..')
621+
# make sure the payload size not larger than 10MB
622+
if len(self.buf2) > 10485760:
623+
self.buf2 = ''
624+
elif len(self.buf2) == 0 and h.DataCount == 4096:
625+
self.buf2 = self.buf2 + h.Data
626+
elif len(self.buf2) != 0 and h.DataCount == 4096:
627+
self.buf2 = self.buf2 + h.Data
628+
elif len(self.buf2) != 0 and h.DataCount < 4096:
629+
smblog.info('DoublePulsar payload receiving..')
630+
self.buf2 = self.buf2 + h.Data
631+
key = bytearray([0x52, 0x73, 0x36, 0x5E])
632+
xor_output = xor(self.buf2, key)
633+
hash_buf2 = hashlib.md5(self.buf2);
634+
smblog.info('DoublePulsar payload - MD5 (before XOR decryption): %s' % (hash_buf2.hexdigest()))
635+
hash_xor_output = hashlib.md5(xor_output);
636+
smblog.info(
637+
'DoublePulsar payload - MD5 (after XOR decryption ): %s' % (hash_xor_output.hexdigest()))
638+
639+
# f = tempfile.NamedTemporaryFile(delete=False, prefix="xorfull-"+hash_xor_output.hexdigest()+"-", suffix="", dir=g_dionaea.config()['downloads']['dir'])
640+
dir = g_dionaea.config()['downloads']['dir'] + "/"
641+
f = open(dir + hash_xor_output.hexdigest(), 'wb')
642+
f.write(xor_output)
643+
f.close
644+
645+
offset = 0
646+
for i, c in enumerate(xor_output):
647+
if ((xor_output[i] == 0x4d and xor_output[i + 1] == 0x5a) and xor_output[i + 2] == 0x90):
648+
offset = i
649+
smblog.info('DoublePulsar payload - MZ header found...')
650+
break
651+
652+
hash_xor_output_mz = hashlib.md5(xor_output[offset:]);
653+
smblog.info('DoublePulsar payload - MD5 final: %s. Save to disk' % (hash_xor_output_mz.hexdigest()))
654+
# f1 = tempfile.NamedTemporaryFile(delete=False, prefix=hash_xor_output_mz.hexdigest()+"-", suffix="", dir=g_dionaea.config()['downloads']['dir'])
655+
f1 = open(dir + hash_xor_output_mz.hexdigest(), 'wb')
656+
f1.write(xor_output[offset:])
657+
f1.close
658+
self.buf2 = b''
659+
xor_output = b''
660+
661+
icd = incident("dionaea.download.complete")
662+
icd.path = dir + hash_xor_output_mz.hexdigest()
663+
icd.url = self.remote.host
664+
icd.con = self
665+
icd.report()
605666
r = SMB_Trans2_Response()
667+
rstatus = 0xc0000002 # STATUS_NOT_IMPLEMENTED
668+
606669
elif Command == SMB_COM_DELETE:
607670
# specific for NMAP smb-enum-shares.nse support
608671
h = p.getlayer(SMB_Delete_Request)
609672
if h.FileName == b'nmap-test-file\0':
610673
r = SMB_Delete_Response()
674+
elif Command == SMB_COM_TRANSACTION2_SECONDARY:
675+
h = p.getlayer(SMB_Trans2_Secondary_Request)
676+
# TODO: need some extra works
677+
pass
678+
elif Command == SMB_COM_NT_TRANSACT:
679+
h = p.getlayer(SMB_NT_Trans_Request)
680+
r = SMB_NT_Trans_Response()
681+
rstatus = 0x00000000 # STATUS_SUCCESS
611682
else:
612683
smblog.error('...unknown SMB Command. bailing out.')
613684
p.show()
@@ -616,9 +687,18 @@ def process(self, p):
616687
smbh = SMB_Header(Status=rstatus)
617688
smbh.Command = r.smb_cmd
618689
smbh.Flags2 = p.getlayer(SMB_Header).Flags2
690+
if Command == SMB_COM_TRANSACTION:
691+
smbh.Flags2 = p.getlayer(SMB_Header).Flags2 | SMB_FLAGS2_ERR_STATUS
692+
smblog.info('MS17-010 - responded with legitimate SMB with #STATUS_INSUFF_SERVER_RESOURCES')
693+
smblog.info('MS17-010 - waiting for next incoming request.. ')
619694
# smbh.Flags2 = p.getlayer(SMB_Header).Flags2 & ~SMB_FLAGS2_EXT_SEC
620695
smbh.MID = p.getlayer(SMB_Header).MID
621696
smbh.PID = p.getlayer(SMB_Header).PID
697+
# Deception for DoublePulsar, we fix the XOR key first as 0x5273365E
698+
# WannaCry will use the XOR key to encrypt and deliver next payload, so we can decode easily later
699+
if Command == SMB_COM_TRANSACTION2:
700+
smbh.MID = p.getlayer(SMB_Header).MID + 16
701+
smbh.Signature = 0x000000009cf9c567
622702
rp = NBTSession()/smbh/r
623703

624704
if Command in SMB_Commands:

modules/python/dionaea/util.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,9 @@ def find_shell_download(connection, data, report_incidents=True):
131131
i.report()
132132

133133
return urls
134+
135+
def xor(data, key):
136+
l = len(key)
137+
return bytearray((
138+
(data[i] ^ key[i % l]) for i in range(0, len(data))
139+
))

0 commit comments

Comments
 (0)