#!/usr/bin/env python
#
# LICENSE
# the GNU General Public License version 2
#
import sys
import pefile
import re
import argparse
from struct import unpack, unpack_from
# MZ Header
MZ_HEADER = b"\x4D\x5A\x90\x00"
# Resource pattern
RESOURCE_PATTERNS = [re.compile("\x50\x68(....)\x68(.)\x00\x00\x00(.)\xE8", re.DOTALL),
re.compile("(.)\x68(...)\x00\x68(.)\x00\x00\x00\x6A\x00\xE8(....)\x83(..)\xC3", re.DOTALL),
re.compile("(.)\x68(...)\x00\x68(.)\x00\x00\x00\x50\xE8(....)\x83(..)\xC3", re.DOTALL),
re.compile("\x04(.....)\x68(.)\x00\x00\x00\x6A\x00\xE8", re.DOTALL),
re.compile("\x56\xBE(....)\x56\x68(.)\x00\x00\x00\x6A\x00\xE8", re.DOTALL),
re.compile("\x53\x68(....)\x6A(.)\x56\xFF", re.DOTALL),
re.compile("(.)\x68(...)\x00\x68(.)\x00\x00\x00\x6A\x00\xE8(....)\x83\xc4\x0c", re.DOTALL),
]
# RC4 key pattern
RC4_KEY_PATTERNS = [re.compile("\x80\x68\x80\x00\x00\x00\x50\xC7\x40", re.DOTALL),
re.compile("\x80\x68\x80\x00\x00\x00(...)\x50\x52\x53\xC7\x40", re.DOTALL),
re.compile("\x8D(..)\x80\xC7\x43", re.DOTALL),
re.compile("\x1c\x68\x80\x00\x00\x00\x8d..\x8d(...)\x50\x52\x57\xC7\x40", re.DOTALL),
]
RC4_KEY_LENGTH = 0x80
# Config pattern -> (regexp, config_size, key_size)
CONFIG_PATTERNS = [(re.compile("\xC3\x90\x68(....)\xE8....\x59\x6A\x01\x58\xC3", re.DOTALL), 0x8D4, 4),
(re.compile("\x6A\x04\x68(....)\x8D.....\x56\x50\xE8", re.DOTALL), 0x8D4, 4),
(re.compile("\x68...\x00\x68(....)\xE8..\xFF\xFF", re.DOTALL), 0x1000, 128),
]
parser = argparse.ArgumentParser(description="TSCookie Config Parser")
parser.add_argument("file", type=str, metavar="FILE", help="TSCookie EXE file")
args = parser.parse_args()
# RC4
def rc4(data, key):
x = 0
box = range(256)
for i in range(256):
x = (x + box[i] + ord(key[i % len(key)])) % 256
box[i], box[x] = box[x], box[i]
x = 0
y = 0
out = []
for char in data:
x = (x + 1) % 256
y = (y + box[x]) % 256
box[x], box[y] = box[y], box[x]
out.append(chr(ord(char) ^ box[(box[x] + box[y]) % 256]))
return ''.join(out)
# helper function for formatting string
def __format_string(data):
return data.split("\x00")[0]
# Parse config
def parse_config(config):
print("\n[Config]")
print("{0}\n".format("-" * 50))
for i in xrange(4):
if config[0x10 + 0x100 * i] != "\x00":
print("Server name : {0}".format(__format_string(unpack_from("<240s", config, 0x10 + 0x100 * i)[0].decode("utf-16"))))
print(" port 1 : {0}".format(unpack_from("I", config, 0x604)[0]))
print("Sleep time : {0} (s)".format(unpack_from("I", key_end)[0]))
break
return key_end
# Find and load resource
def load_resource(pe, data):
for pattern in RESOURCE_PATTERNS:
mr = re.search(pattern, data)
if mr:
try:
(resource_name_rva, ) = unpack("=I", data[mr.start() + 2:mr.start() + 6])
rn_addr = pe.get_physical_by_rva(resource_name_rva - pe.NT_HEADERS.OPTIONAL_HEADER.ImageBase)
resource_name = data[rn_addr:rn_addr + 4]
resource_id = ord(unpack("c", data[mr.start() + 7])[0])
if resource_id > 200:
resource_id = ord(unpack("c", data[mr.start() + 8])[0])
if resource_id == 104:
resource_id = ord(unpack("c", data[mr.start() + 21])[0])
break
except:
sys.exit("[!] Faild to load resource id.")
if not mr:
sys.exit("[!] Resource id not found.")
for idx in pe.DIRECTORY_ENTRY_RESOURCE.entries:
if str(idx.name) in str(resource_name):
for entry in idx.directory.entries:
if entry.id == resource_id:
try:
data_rva = entry.directory.entries[0].data.struct.OffsetToData
size = entry.directory.entries[0].data.struct.Size
rc_data = pe.get_memory_mapped_image()[data_rva:data_rva + size]
print("[*] Found resource : {0}({1})".format(str(idx.name), entry.id))
except:
sys.exit("[!] Faild to load resource.")
return rc_data
def main():
with open(args.file, "rb") as fb:
data = fb.read()
if data[:2] != b'MZ':
print('[-] Not a PE file : %s' % args.file)
exit()
pe = pefile.PE(args.file)
rc_data = load_resource(pe, data)
key_end = load_rc4key(data)
dec_data = decode_resource(rc_data, key_end, args.file + ".decode")
dll_index = dec_data.find(MZ_HEADER)
if dll_index:
dll_data = dec_data[dll_index:]
dll = pefile.PE(data=dll_data)
print("[*] Found main DLL : 0x{0:X}".format(dll_index))
else:
sys.exit("[!] DLL data not found in decoded resource.")
for pattern, config_size, config_key_size in CONFIG_PATTERNS:
mc = re.search(pattern, dll_data)
if mc:
try:
(config_rva, ) = unpack("=I", mc.group(1))
config_addr = dll.get_physical_by_rva(config_rva - dll.NT_HEADERS.OPTIONAL_HEADER.ImageBase)
enc_config_data = dll_data[config_addr:config_addr + config_size]
enc_config_key_size = config_key_size
print("[*] Found config data : 0x{0:X}".format(config_rva))
except:
sys.exit("[!] Config data not found in DLL.")
for pattern in RESOURCE_PATTERNS:
mr2 = re.search(pattern, dll_data)
if mr2:
print("[*] Found resource in main DLL.")
rc2_data = load_resource(dll, dll_data)
key_end = load_rc4key(dll_data)
decode_resource(rc2_data, key_end, args.file + ".2nd.decode")
try:
enc_config = enc_config_data[enc_config_key_size:]
rc4key = enc_config_data[:enc_config_key_size]
config = rc4(enc_config, rc4key)
open(args.file + ".config", "wb").write(config)
print("[*] Successful decoding config: {0}".format(args.file + ".config"))
except:
sys.exit("[!] Faild to decoding config data.")
parse_config(config)
print("\n[*] Done.")
if __name__ == "__main__":
main()