Recent variants of Ryuk have had their code cleaned up. They removed non-referenced strings that are relics from the HERMES source code days. One interesting part of the code clean-up is a new string decoder. The string decoder is the first MD5 brute forcer that I have observed in malware. It's an interesting technique because it is a computational attack that delays execution of Ryuk before the strings are decoded in memory. The decoding of strings happens in two phases. The first phase uses a hardcoded lookup table that is to decode API names. Once the API names are decrypted, they are dynamically imported and then used to recover the original string from an MD5 hash. After the original string is discovered, each byte of the string is hashed and then the hash is MD5ed, then the hexdigest contents are appended to a string. Each byte within the appended MD5 strings is used to create a second lookup table which is then used to decrypt strings.
Example Python code of the MD5 Bruteforcer and the creation of the lookup table.
import hashlib
import struct
import hexdump
def brute_hash(md5_hash):
temp = ""
ii = 0
while True:
temp = b"%x" % ii
hh = hashlib.md5(temp).hexdigest()
if hh == md5_hash:
return temp
ii += 1
def expand(str_og):
v4 = 0
temp = b""
for bb in str_og:
for cc in str_og:
v7 = v4 + cc
v4 = v7 % 0x64
v4 *= bb
_byte = b"%x" % v4
temp += hashlib.md5(_byte).hexdigest().encode()
return temp
def create_lookup_table(hashes):
lookup = b""
for cc, bb in enumerate(hashes):
tt = bb % 3
if tt == 0:
if cc != 0:
if lookup[(cc*4)-4] == 0:
tt = 1
lookup += struct.pack('i', tt)
return lookup
# 0b9684754061b1ff963114a60d3ceb69
hh = brute_hash("28210726827e070417fa9361bf595742")
print(hh)
yy = expand(hh)
print(yy)
xxx = create_lookup_table(yy)
hexdump.hexdump(xxx)
Bruteforced string 13d960f
followed by MD5 hashes of each byte and then the hexdump of the lookup table.
b'13d960f'
b'bae60998ffe4923b131e3d6e4c19993e78f726c0acf2c8bd020d122e315d061cb05ef1f72e4bbd5cbefdc8b5d95e0b3b2d0735738d27b99d0f58b010309f895adc5689792e08eb2e219dce49e64c885b39059724f73a9969845dfe4146c5660e11ed44bdce52060ea46e1bcd5e0cc5da'
00000000: 02 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00 ................
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 ................
00000020: 00 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 ................
00000030: 00 00 00 00 02 00 00 00 00 00 00 00 02 00 00 00 ................
00000040: 01 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 ................
00000050: 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 ................
00000060: 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 ................
00000080: 01 00 00 00 02 00 00 00 00 00 00 00 01 00 00 00 ................
00000090: 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0: 01 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 ................
000000B0: 00 00 00 00 02 00 00 00 02 00 00 00 01 00 00 00 ................
000000C0: 00 00 00 00 02 00 00 00 00 00 00 00 01 00 00 00 ................
000000D0: 01 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 ................
000000E0: 00 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 ................
000000F0: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
00000100: 02 00 00 00 00 00 00 00 02 00 00 00 02 00 00 00 ................
00000110: 00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 ................
00000120: 02 00 00 00 02 00 00 00 01 00 00 00 02 00 00 00 ................
00000130: 02 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00 ................
00000140: 02 00 00 00 02 00 00 00 00 00 00 00 01 00 00 00 ................
00000150: 00 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 ................
00000160: 01 00 00 00 00 00 00 00 02 00 00 00 02 00 00 00 ................
00000170: 00 00 00 00 02 00 00 00 00 00 00 00 02 00 00 00 ................
00000180: 02 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 ................
00000190: 00 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00 ................
000001A0: 02 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 ................
000001B0: 02 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 ................
000001C0: 00 00 00 00 00 00 00 00 02 00 00 00 02 00 00 00 ................
000001D0: 02 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
000001E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001F0: 02 00 00 00 00 00 00 00 02 00 00 00 01 00 00 00 ................
00000200: 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 ................
00000210: 02 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
00000220: 02 00 00 00 02 00 00 00 00 00 00 00 02 00 00 00 ................
00000230: 02 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 ................
00000240: 02 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 ................
00000250: 00 00 00 00 02 00 00 00 01 00 00 00 00 00 00 00 ................
00000260: 02 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................
00000270: 02 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 ................
00000280: 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 ................
00000290: 00 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 ................
000002A0: 00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 ................
000002B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000002C0: 02 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 ................
000002D0: 00 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 ................
000002E0: 01 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 ................
000002F0: 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 ................
00000300: 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 ................
00000310: 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 ................
00000320: 00 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 ................
00000330: 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 ................
00000340: 01 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 ................
00000350: 01 00 00 00 02 00 00 00 00 00 00 00 01 00 00 00 ................
00000360: 02 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 ................
00000370: 00 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 ................
IDAPython code for patching strings. The lookup table offset will first need to be labeled with "Stage1LookUpTable". The following code is an example from IDA.
.text:35002A4F push 32h ; '2'
.text:35002A51 xor esi, esi
.text:35002A53 lea eax, [ebp+var_808]
.text:35002A59 push esi
.text:35002A5A push eax
.text:35002A5B call MemAlloc
.text:35002A60 push offset Stage1LookUpTable
.text:35002A65 call ReiatDecodeGPA
.text:35002A6A push 32h ; '2' ; int
.text:35002A6C lea eax, [ebp+var_808]
.text:35002A72 push eax ; char *
.text:35002A73 push offset a28210726827e07 ; "28210726827e070417fa9361bf595742"
.text:35002A78 call MD5BruteString
.text:35002A7D push 7D0h
import hashlib
import struct
import hexdump
MD5_HASH = "0b9684754061b1ff963114a60d3ceb69"
VERBOSE = True
MD5_LOOKUP = ""
def get_to_xrefs(ea):
xref_set = set([])
for xref in idautils.XrefsTo(ea, 1):
xref_set.add(xref)
return xref_set
def get_from_xrefs(ea):
xref_set = set([])
for xref in idautils.XrefsTo(ea, 1):
xref_set.add(xref)
return xref_set
def get_next_xref(ea):
ss = 0
cur_addr = idc.next_head(ea)
while True:
if get_to_xrefs(cur_addr):
return cur_addr - 1
elif get_from_xrefs(cur_addr):
return cur_addr - 1
cur_addr = idc.next_head(cur_addr)
if cur_addr == idc.BADADDR:
return ea
def calculate_unknown_size(ea):
return get_next_xref(ea) - ea
def get_lookup_table(ea, size):
return idc.get_bytes(ea, size)
def decode_str_lookup(ea, size, lookup):
for ii in range(0, size):
cc = idc.get_bytes(ea + ii, 1)
bb = ord(cc) - lookup[ii*4]
idc.patch_byte(ea + ii, bb)
def get_basic_block(ea):
f = idaapi.get_func(ea)
fc = idaapi.FlowChart(f)
for block in fc:
if block.start_ea <= ea:
if block.end_ea > ea:
return block.start_ea, block.end_ea
return None, None
def get_block_attributes(start_ea, end_ea):
size = 0
offset = 0
cur_addr = start_ea
for _ in range(0, end_ea - start_ea):
if idc.print_insn_mnem(cur_addr) == "sub":
offset = idc.get_operand_value(cur_addr, 0)
if idc.print_insn_mnem(cur_addr) == "cmp":
size = idc.get_operand_value(cur_addr, 1)
cur_addr = idc.next_head(cur_addr)
if cur_addr == end_ea:
break
return size, offset
def get_table():
ea_lookup = idc.get_name_ea_simple("Stage1LookUpTable")
if ea_lookup:
lookup_size = calculate_unknown_size(ea_lookup)
lookup_table = get_lookup_table(ea_lookup, lookup_size)
return lookup_table
else:
print("label `lookup_table` offset")
return None
def brute_hash(md5_hash):
lookup = "0123456789abcdef"
temp = ""
ii = 0
while True:
temp = b"%x" % ii
hh = hashlib.md5(temp).hexdigest()
if hh == md5_hash:
return temp
ii += 1
def expand(str_og):
v4 = 0
temp = b""
for bb in str_og:
for cc in str_og:
v7 = v4 + cc
v4 = v7 % 0x64
v4 *= bb
_byte = b"%x" % v4
temp += hashlib.md5(_byte).hexdigest().encode()
return temp
def create_lookup_table(hashes):
lookup = b""
for cc, bb in enumerate(hashes):
tt = bb % 3
if tt == 0:
if cc != 0:
if lookup[(cc*4)-4] == 0:
tt = 1
lookup += struct.pack('i', tt)
return lookup
def build_hash_lookup_table():
hashed_string = brute_hash(MD5_HASH)
if VERBOSE:
print(hashed_string)
hashes = expand(hashed_string)
if VERBOSE:
print(hashes)
md5_lookup_table = create_lookup_table(hashes)
if VERBOSE:
hexdump.hexdump(md5_lookup_table)
return md5_lookup_table
STAGE1 = False
if STAGE1:
lookup_table = get_table()
if lookup_table:
start, end = get_basic_block(here())
if start:
size, offset = get_block_attributes(start, end)
if size:
decode_str_lookup(offset, size, lookup_table)
else:
MD5_LOOKUP = build_hash_lookup_table()