Skip to content

Instantly share code, notes, and snippets.

@msm-code
Created December 7, 2016 10:20
Show Gist options
  • Save msm-code/5c98c80ed1694e2d853c7a640c8daf3d to your computer and use it in GitHub Desktop.
Save msm-code/5c98c80ed1694e2d853c7a640c8daf3d to your computer and use it in GitHub Desktop.
import socket, struct, binascii, random, hashlib
import time, os, errno, glob
import pprint
import libs.tofsee.parse as parse
import libs.tofsee.decompress as decompress
import libs.tofsee.parse_mail as parse_mail
import libs.log as log
from libs.remote import HTTPBot
log = log.get_logger(__name__)
# This is the main file implementing communication with Tofsee.
def recv_exactly(s, n):
r = ""
while len(r) < n:
resp = s.recv(n - len(r))
if resp == "":
raise EOFError("EOF...")
r += resp
return r
def receive_greet(s):
return parse.parse_greet(recv_exactly(s, 200))
def send_encrypted(s, hdr, payload, key):
hdr["crc"] = binascii.crc32(payload)
hdr["size"] = len(payload)
hdr["size_decomp"] = len(payload)
hdr["flag_compr_2"] = 1 # No compression.
hdrstr = ""
hdrstr += struct.pack("<I", hdr["size"])
hdrstr += struct.pack("<I", hdr["size_decomp"])
hdrstr += struct.pack("<i", hdr["crc"])
hdrstr += struct.pack("<B", hdr["flag_compr_2"])
hdrstr += struct.pack("<I", hdr["op"])
hdrstr += struct.pack("<I", hdr["subop1"])
hdrstr += struct.pack("<I", hdr["subop2"])
msg = hdrstr + payload
msg = parse.xor_deencrypt(msg, key)
s.sendall(msg)
def receive_encrypted(s, key):
hdr = parse.xor_deencrypt(recv_exactly(s, 25), key)
hdr = parse.parse_header(hdr)
payload = parse.xor_deencrypt(recv_exactly(s, hdr["size"]), key)
if hdr["flag_compr_2"] & 2:
payload = decompress.deco(payload, hdr["size_decomp"])
return hdr, payload
def parse_message2(payload):
type = struct.unpack("<I", payload[:4])[0]
name = payload[4:20].strip("\x00").replace("\x00", "_")
length = struct.unpack("<I", payload[0x18:0x1c])[0]
buf = payload[0x24:]
assert len(buf) == length
return type, name, buf
def parse_ips_list(payload):
assert len(payload) % 0x45 == 0
ips = []
for i in range(len(payload) / 0x45):
part = payload[i * 0x45:(i + 1) * 0x45]
ip = part[1:0x41].strip("\x00")
port = struct.unpack("<I", part[0x41:])[0]
ips.append((ip, port))
return ips
def send_list_of_resources(s, key, hdr, resources):
# Send resources - commented out to simulate lack of resources.
# hdr["op"]=2
# payload=""
# for res in resources:
# payload+=struct.pack("<I", res[0]) # Type
# name=res[1]
# payload+=name+"\x00"*(16-len(name))
# payload+="\x00"*16
# sendEncrypted(s, hdr, payload, key)
# Finish transmission.
hdr["op"] = 0
send_encrypted(s, hdr, "", key)
def save_resource(type, name, content):
return # NO SAVING. Comment this line out to save all resources to local folders.
if name.startswith("work_srv"):
ips = parse_ips_list(content)
for ip in ips:
save_resource(type, "work_srv/" + ip[0] + ":" + str(ip[1]), "")
return
elif type == 2 and name.startswith("ID"):
return # Don't save IDs, that's spammy.
elif type == 7 and (name.startswith("%EVA_AUTOURL") or name.startswith("#EVA_URL")):
return
directory = "resources/" + str(type)
try:
os.makedirs(directory)
except OSError as e:
if e.errno == errno.EEXIST:
pass
else:
raise
files = glob.glob(directory + "/*")
name = directory + "/" + name + (16 - len(name)) * "_"
if content != "":
name += "." + hashlib.md5(content).hexdigest()
if name not in files:
print repr(name)
open(name, "wb").write(content)
class tofsee(HTTPBot):
@classmethod
def create(cls, el):
yield cls(el)
def init_bot(self, el):
self.start_ips = []
self.resources = {}
self.url = 'my legs are dangling off the edge'
self._raw_cfg = None
for ipport in el["urls"]:
self.start_ips.append((ipport["ip"], ipport["port"]))
def parse(self):
return True
def ppr(self):
return self._raw_cfg
def get_cfg(self):
ips = self.start_ips
for tries in range(10):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(30)
# Choose random IP from C&C list (initially from static config,
# later from the initial C&C).
ip = random.choice(ips)
log.info("Trying IP: " + str(ip) + "...")
s.connect(ip)
log.info("Connected.")
greet = receive_greet(s)
log.info("Greeting received.")
botID = 0xdeadcafebabe1337
botinfo_struct = {
"flags_upd": struct.pack("<I", 0),
"dl": struct.pack("<I", botID >> 32),
"id": struct.pack("<I", botID & 0xffffFFFF),
"unk0": struct.pack("<I", 72),
"unk1": struct.pack("<I", 0),
"net_type_flags": struct.pack("<I", 0x1f),
"VM": struct.pack("<I", 0),
"unk2": struct.pack("<I", 10),
"unk3": struct.pack("<I", 0),
"unk4": struct.pack("<I", 0),
"lid_file_upd": struct.pack("<I", 0),
"tickcount": struct.pack("<I", 0x1000000),
"tickcount_delta": struct.pack("<I", 84),
"born_timestamp": struct.pack("<I", int(time.time())),
"localIP": struct.pack("<I", 0x0f02000a),
"host_flags": struct.pack("<I", 0),
"unk5": struct.pack("<I", 0),
"unk6": struct.pack("<B", 0),
"os": struct.pack("<B", 0x61),
"unkrest": '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
}
botinfo = ""
botinfo += botinfo_struct["flags_upd"]
botinfo += botinfo_struct["dl"]
botinfo += botinfo_struct["id"]
botinfo += botinfo_struct["unk0"]
botinfo += botinfo_struct["unk1"]
botinfo += botinfo_struct["net_type_flags"]
botinfo += botinfo_struct["VM"]
botinfo += botinfo_struct["unk2"]
botinfo += botinfo_struct["unk3"]
botinfo += botinfo_struct["unk4"]
botinfo += botinfo_struct["lid_file_upd"]
botinfo += botinfo_struct["tickcount"]
botinfo += botinfo_struct["tickcount_delta"]
botinfo += botinfo_struct["born_timestamp"]
botinfo += botinfo_struct["localIP"]
botinfo += botinfo_struct["host_flags"]
botinfo += botinfo_struct["unk5"]
botinfo += botinfo_struct["unk6"]
botinfo += botinfo_struct["os"]
botinfo += botinfo_struct["unkrest"]
hdr = {'subop2': 0, 'subop1': 0, 'op': 1}
# Send the first message (general bot information).
send_encrypted(s, hdr, botinfo, greet["skey"])
log.info("Botinfo sent.")
# Now, as long as server sends anything, try to keep up conversation with it.
while True:
hdr, payload = receive_encrypted(s, greet["rkey"])
if hdr["op"] == 1:
# Server wants to know which resources we already have.
flags = struct.unpack("<I", payload[:4])[0]
if flags == 4 and (hdr["subop2"] & 1) == 0:
log.info("Sending list of resources...")
# We answer "we have no resources yet".
send_list_of_resources(s, greet["skey"], hdr, self.resources)
elif flags == 0x1bb and hdr["subop2"] == 0:
# No idea what this message means, but VM sent zeroes
# in response.
hdr["op"] = 3
send_encrypted(s, hdr, "\x00" * 40, greet["skey"])
hdr["op"] = 0
send_encrypted(s, hdr, "", greet["skey"])
log.info("Sent some zeroes, meaning unknown.")
else:
log.warning("Unknown operation " + hex(flags) + ".")
log.debug(repr(payload))
elif hdr["op"] == 2:
# New resource has been received - add it to result.
type, name, content = parse_message2(payload)
self.resources[(type, name)] = content
save_resource(type, name, content)
if type == 1 and name == "work_srv":
ips = parse_ips_list(content)
log.info("New IPs: " + repr(ips))
else:
log.info("New resource (type=" + str(type) + ") received: " + name)
else:
log.warning("Unknown operation.")
log.info(repr(hdr))
log.debug(repr(payload))
except EOFError as e:
log.info("EOF received.")
# We already have all the resources server wanted to send us.
# Let's parse some emails then.
vars11 = {} # Type 11: email templates.
vars7 = {} # Type 7 : global substitutions.
vars8 = {} # Type 8 : local substitutions.
for var in self.resources:
if var[0] == 11:
vars11[var[1]] = self.resources[var]
elif var[0] == 7:
vars7[var[1]] = self.resources[var]
elif var[0] == 8:
vars8[var[1]] = self.resources[var]
result = {"sample_mails": {}}
for num in vars11:
# For each template, create a sample email using global
# variables and those local to this particular script.
variables = vars7.copy()
for var in vars8:
if var.startswith(num + "%"):
newvarname = var.lstrip(num)
variables[newvarname] = vars8[var]
template = vars11[num]
parsed = parse_mail.parse(template, variables)
result["sample_mails"][num] = parsed
# This "if" is necessary, because the first server doesn't return
# any resources, so we need to retry communication - this time
# with new C&Cs.
if len(self.resources) > 10:
s.close()
rsrc = {}
# Make keys strings (so far, they were tuples (type, name)).
for key in self.resources:
s = str(key[0])
if s not in rsrc:
rsrc[s] = {}
rsrc[s][key[1]] = self.resources[key]
result["resources"] = rsrc
self._raw_cfg = pprint.pformat(result)
return result
if ip not in ips:
s.close()
ip = random.choice(ips)
except Exception as e:
log.warning("Exception caught: " + repr(e))
self._raw_cfg = 'no config'
return self._raw_cfg
# Returned dict has the following form (obviously edited for brevity):
# {
# 'resources': {
# '5': {
# '1': 'MZ\x90\x00...',
# '10': 'MZ\x90\x00...',
# '11': 'MZ\x90\x00...',
# '12': 'MZ\x90\x00...'
# },
# '7': {
# '%ACCFN': 'substitutions',
# '%ACCLN': 'substitutions',
# '%ACCNAME': 'substitutions'
# },
# '8': {
# '1835%FROM_EMAIL': 'S%FROM_USE',
# '1835%RNDRFONT': 'SVerdana',
# '1835%RNDRSIZE': 'S2\n3\n4'
# },
# },
# 'sample_mails': {
# '1835': 'parsed email',
# '1906': 'parsed email'
# }
# }
# Some of the more interesting (from our perspective) resources:
# js["resources"]["7"]["%JS_EXPL_00"] - contains base64-encoded
# zip attachment (also JS_EXPL_01, 02 and so on)
# js["sample_mails"] - contains parsed mails
# js["resources"]["1"]["work_srv"] - contains C&C addresses
# js["resources"]["1"]["start_srv"] - some more C&C
# js["resources"]["5"] - all the submodules of Tofsee (DDoS etc.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment