Skip to content

Instantly share code, notes, and snippets.

@alexander-hanel
Last active March 26, 2020 18:26
Show Gist options
  • Save alexander-hanel/74755e22d2cfe14ec0f6580237851ff9 to your computer and use it in GitHub Desktop.
Save alexander-hanel/74755e22d2cfe14ec0f6580237851ff9 to your computer and use it in GitHub Desktop.
Ryuk String Decoder Notes

RYUK STRING DECODER NOTES

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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment