Collab with @BoymoderRE

This is part of an ongoing collaboration with BoymoderRE she has been streaming her work over on her twitch channel, and we have been sharing IDBs using the free open source IDA collaboration tool IDArling.

Overview

Brute Ratel is a pentesting framework what was recently leaked and has been showing up in the hands of ransomware operators. We are going to focus on the releases before 1.3 where many of the weaknesses in the implant were fixed. To date we have only seen the older versions used by ransomware operators.

Some of the weaknesses in the older version.

  • Default Rc4 key used bYXJm/3#M?:XyMBF
  • Using ror13 string hashes
  • Strings in the badger's memory
  • Config is base64 encoded in stage 1

Sample

The sample we are analyzing in not public.

References

Analysis

Our sample comes as a 64-bit shellcode blob, the first order of business is to unpack it!

b64_data = bytes.fromhex('42 53 4B 50 36 52 5A 38 61 66 62 4F 6E 48 47 38 4E 54 76 50 66 6B 59 42 51 35 67 42 42 67 67 68 43 6B 71 6E 2F 43 35 51 62 68 72 55 51 53 39 75 4B 4C 4C 37 33 57 50 31 4A 68 46 77 51 66 35 6E 55 39 38 50 4E 53 70 36 70 6D 2B 69 55 55 63 46 70 64 45 4C 58 79 38 79 64 6A 67 71 49 4C 4C 78 61 6F 55 4A 57 33 49 6D 49 6C 48 74 64 31 48 34 51 70 4F 37 6E 2B 56 4D 62 56 45 77 36 77 75 32 47 7A 68 6B 4B 68 51 77 72 4B 31 32 51 35 4F 78 61 6F 6A 57 31 30 2F 42 70 39 31 72 77 68 49 4B 4C 37 4E 51 73 62 38 75 57 66 34 46 62 52 42 69 70 6D 73 37 33 37 65 4E 41 71 47 4E 51 58 78 44 34 59 41 6E 51 41 65 68 47 71 49 47 39 6A 4A 36 2B 7A 4F 78 34 6F 6A 63 30 70 77 57 70 42 48 53 79 63 37 2F 57 73 46 53 46 69 77 37 36 43 53 6C 6D 38 43 43 6E 4D 45 6F 54 56 30 56 41 57 6E 6A 41 4F 31 33 75 72 79 43 6C 77 4E 38 77 4C 2F 73 63 45 62 5A 37 30 73 77 67 67 73 54 45 42 32 34 64 73 6B 78 6A 6F 41 55 55 6C 7A 70 6E 6F 31 5A 6F 57 57 4B 5A 67 4E 6E 7A 72 65 67 64 41 4E 2B 6B 49 53 71 31 4F 2F 48 43 44 70 47 62 54 42 42 43 67 58 34 36 48 34 4A 38 47 4F 59 42 70 43 55 53 66 66 56 58 6A 68 53 49 31 32 65 55 55 65 48 41 37 79 57 38 63 46 5A 00'.replace(' ',''))
b64_data
b'BSKP6RZ8afbOnHG8NTvPfkYBQ5gBBgghCkqn/C5QbhrUQS9uKLL73WP1JhFwQf5nU98PNSp6pm+iUUcFpdELXy8ydjgqILLxaoUJW3ImIlHtd1H4QpO7n+VMbVEw6wu2GzhkKhQwrK12Q5OxaojW10/Bp91rwhIKL7NQsb8uWf4FbRBipms737eNAqGNQXxD4YAnQAehGqIG9jJ6+zOx4ojc0pwWpBHSyc7/WsFSFiw76CSlm8CCnMEoTV0VAWnjAO13uryClwN8wL/scEbZ70swggsTEB24dskxjoAUUlzpno1ZoWWKZgNnzregdAN+kISq1O/HCDpGbTBBCgX46H4J8GOYBpCUSffVXjhSI12eUUeHA7yW8cFZ\x00'

Stage 1 Unpacker

  • The shellcode payload contains an RC4 encrypted PE with the "badger" payload. The RC4 key is 8 bytes which is appended directly to the end of the encrypted payload.

  • This encrypted blob is moved on the the stack in blocks of 8 bytes. The stack blob and size are then copied on the heap.

  • The same approach is used for the (config?) but this is base64 encoded.

  • The heap allocations containing the encrypted payload, the base64 encoded config, and the lenght of both data allocations are passed to the initialization function in the shellcode.

def rc4crypt(data, key):
    #If the input is a string convert to byte arrays
    if type(data) == str:
        data = data.encode('utf-8')
    if type(key) == str:
        key = key.encode('utf-8')
    x = 0
    box = list(range(256))
    for i in range(256):
        x = (x + box[i] + key[i % len(key)]) % 256
        box[i], box[x] = box[x], box[i]
    x = 0
    y = 0
    out = []
    for c in data:
        x = (x + 1) % 256
        y = (y + box[x]) % 256
        box[x], box[y] = box[y], box[x]
        out.append(c ^ box[(box[x] + box[y]) % 256])
    return bytes(out)
payload_enc = open('/tmp/brute_enc_shellcode.bin','rb').read()
payload_key = payload_enc[-8:]
out = rc4crypt(payload_enc[:-8],payload_key)
out[:0x400]
b'\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\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x0e\x1f\xba\x0e\x00\xb4\t\xcd!\xb8\x01L\xcd\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\r\r\n$\x00\x00\x00\x00\x00\x00\x00PE\x00\x00d\x86\t\x00\xd9\xb4\x03c\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x00."\x0b\x02\x02"\x00\xb4\x02\x00\x00\xdc\x00\x00\x00\x1e\x00\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x10\x00\x00\x00\x02\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x05\x00\x02\x00\x00\x00\x00\x00\x00\x10\x04\x00\x00\x04\x00\x00\xf0\xd3\x03\x00\x03\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\xe0\x03\x006\x00\x00\x00\x00\xf0\x03\x00\x80\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x03\x00\x8c\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00l\x04\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\x00\x00\x00\x00L\xf1\x03\x00\x10\x01\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.text\x00\x00\x00\xb0\xb3\x02\x00\x00\x10\x00\x00\x00\xb4\x02\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00P`.data\x00\x00\x000\x1b\x00\x00\x00\xd0\x02\x00\x00\x1c\x00\x00\x00\xb8\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00`\xc0.rdata\x00\x00P\x83\x00\x00\x00\xf0\x02\x00\x00\x84\x00\x00\x00\xd4\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00`@.pdata\x00\x00\x8c\x13\x00\x00\x00\x80\x03\x00\x00\x14\x00\x00\x00X\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x000@.xdata\x00\x00h\x18\x00\x00\x00\xa0\x03\x00\x00\x1a\x00\x00\x00l\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x000@.bss\x00\x00\x00\x00\xa6\x1d\x00\x00\x00\xc0\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00`\xc0.edata\x00\x006\x00\x00\x00\x00\xe0\x03\x00\x00\x02\x00\x00\x00\x86\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x000@.idata\x00\x00\x80\x04\x00\x00\x00\xf0\x03\x00\x00\x06\x00\x00\x00\x88\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x000\xc0.reloc\x00\x00l\x04\x00\x00\x00\x00\x04\x00\x00\x06\x00\x00\x00\x8e\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x000B\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\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\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\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\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\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'
stage1_data = open('/tmp/stage1.bin','rb').read()
from ctypes import *
import unicorn as uc

stage1_data = open('/tmp/stage1.bin','rb').read()

call_state = 0

def hook_call(uc_engine, mem_type, address, size):
    global call_state
    ptr_data = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RCX)
    data_size = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RDX)
    print(f"Hook: blob ptr: {hex(ptr_data)} size:{hex(data_size)}")
    buf = uc_engine.mem_read(ptr_data, data_size)
    if call_state  == 0:
        print("Found first blob")
        print(buf[:100])
        name = "first.bin"
    else:
        print("Found second blob")
        print(buf[:100])
        name = "second.bin" 
    call_state += 1
    rip = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RIP)
    print(f"RIP: {hex(rip)}")
    rip += 5
    uc_engine.reg_write(uc.x86_const.UC_X86_REG_RIP, rip)
    open(f'/tmp/{name}', 'wb').write(buf)
    if call_state == 2:
        print("End emulation")
        uc_engine.reg_write(uc.x86_const.UC_X86_REG_RIP, 0x99999999999)
    return True

def main(buf):
    uc_engine = uc.Uc(uc.UC_ARCH_X86, uc.UC_MODE_64)
    STACK_ADDR = 0x4400000
    CODE_ADDR = 0x1400000
    uc_engine.mem_map(CODE_ADDR, 0x100000, uc.UC_PROT_ALL)
    uc_engine.mem_map(STACK_ADDR - 0x100000, 0x200000, uc.UC_PROT_ALL)
    uc_engine.mem_write(CODE_ADDR, buf)
    uc_engine.reg_write(uc.x86_const.UC_X86_REG_RIP, CODE_ADDR)
    uc_engine.reg_write(uc.x86_const.UC_X86_REG_RSP, STACK_ADDR)
    hook1 = uc_engine.hook_add(uc.UC_HOOK_CODE, hook_call, None, CODE_ADDR + 0x0536B1, CODE_ADDR + 0x0536B2)
    hook2 = uc_engine.hook_add(uc.UC_HOOK_CODE, hook_call, None, CODE_ADDR + 0x536CB, CODE_ADDR + 0x536CC)
    uc_engine.emu_start(CODE_ADDR, CODE_ADDR + 0x10000, 0, 0)
    out_rip = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RIP)
    
main(stage1_data)
Hook: blob ptr: 0x43c69f0 size:0x39410
Found first blob
bytearray(b';\xbc\xca\x9f\xfa\x95aX,\xb4\xdc\x98\xfb\x87\x13"\x88x\x05\xff2\xc4\xcc\xbd\xdc,\xb6\xa5$.\xe5\xe4\xf2r\xec\xea]/\xec6\xae\xc3\xd9\xc4\x91@A\x14\xf8\xbd\xa4t\x95\xb6\xa6[\xb6\x9a~\xaf<\xf7\xc598\x0b\xd2\xa9\x8df\xaf\xa4\x00\xeb\xcc\xa4w\xb0\xa8i#u\x0b\xf0\xec\x89\xde\xf6\xe7e\x8a\xca\x06m\x1dl#\x9f\xd4a')
RIP: 0x14536b1
Hook: blob ptr: 0x43ffe00 size:0x178
Found second blob
bytearray(b'BSKP6RZ8afbOnHG8NTvPfkYBQ5gBBgghCkqn/C5QbhrUQS9uKLL73WP1JhFwQf5nU98PNSp6pm+iUUcFpdELXy8ydjgqILLxaoUJ')
RIP: 0x14536cb
End emulation
---------------------------------------------------------------------------
UcError                                   Traceback (most recent call last)
<ipython-input-7-7940ea9dc380> in <module>
     45     out_rip = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RIP)
     46 
---> 47 main(stage1_data)

<ipython-input-7-7940ea9dc380> in main(buf)
     42     hook1 = uc_engine.hook_add(uc.UC_HOOK_CODE, hook_call, None, CODE_ADDR + 0x0536B1, CODE_ADDR + 0x0536B2)
     43     hook2 = uc_engine.hook_add(uc.UC_HOOK_CODE, hook_call, None, CODE_ADDR + 0x536CB, CODE_ADDR + 0x536CC)
---> 44     uc_engine.emu_start(CODE_ADDR, CODE_ADDR + 0x10000, 0, 0)
     45     out_rip = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RIP)
     46 

~/.pyenv/versions/3.9.5/lib/python3.9/site-packages/unicorn/unicorn.py in emu_start(self, begin, until, timeout, count)
    339         status = _uc.uc_emu_start(self._uch, begin, until, timeout, count)
    340         if status != uc.UC_ERR_OK:
--> 341             raise UcError(status)
    342 
    343         if self._hook_exception is not None:

UcError: Invalid memory fetch (UC_ERR_FETCH_UNMAPPED)
from ctypes import *
import unicorn as uc

stage1_data = open('/tmp/stage1.bin','rb').read()

call_state = 0

def hook_call(uc_engine, mem_type, address, size):
    global call_state
    rip = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RIP)
    rip_byte = uc_engine.mem_read(rip, 1)
    if rip_byte ==  b'\xe8':
        print(f"Call hook at RIP: {hex(rip)}")
        if call_state  == 0:
            call_state += 1
            return True
        ptr_data = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RCX)
        data_size = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RDX)
        print(f"Hook: blob ptr: {hex(ptr_data)} size:{hex(data_size)}")
        buf = uc_engine.mem_read(ptr_data, data_size)
        if call_state  == 1:
            print("Found first blob")
            print(buf[:100])
            name = "first.bin"
        else:
            print("Found second blob")
            print(buf[:100])
            name = "second.bin" 
        call_state += 1
        rip += 5
        uc_engine.reg_write(uc.x86_const.UC_X86_REG_RIP, rip)
        open(f'/tmp/{name}', 'wb').write(buf)
        if call_state == 3:
            print("End emulation")
            uc_engine.reg_write(uc.x86_const.UC_X86_REG_RIP, 0x99999999999)
    return True

def main(buf):
    uc_engine = uc.Uc(uc.UC_ARCH_X86, uc.UC_MODE_64)
    STACK_ADDR = 0x4400000
    CODE_ADDR = 0x1400000
    uc_engine.mem_map(CODE_ADDR, 0x100000, uc.UC_PROT_ALL)
    uc_engine.mem_map(STACK_ADDR - 0x100000, 0x200000, uc.UC_PROT_ALL)
    uc_engine.mem_write(CODE_ADDR, buf)
    uc_engine.reg_write(uc.x86_const.UC_X86_REG_RIP, CODE_ADDR)
    uc_engine.reg_write(uc.x86_const.UC_X86_REG_RSP, STACK_ADDR)
    hook1 = uc_engine.hook_add(uc.UC_HOOK_CODE, hook_call, None)
    uc_engine.emu_start(CODE_ADDR, CODE_ADDR + 0x10000, 0, 0)
    out_rip = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RIP)
    
main(stage1_data)
Call hook at RIP: 0x1400000
Call hook at RIP: 0x14536b1
Hook: blob ptr: 0x43c69f0 size:0x39410
Found first blob
bytearray(b';\xbc\xca\x9f\xfa\x95aX,\xb4\xdc\x98\xfb\x87\x13"\x88x\x05\xff2\xc4\xcc\xbd\xdc,\xb6\xa5$.\xe5\xe4\xf2r\xec\xea]/\xec6\xae\xc3\xd9\xc4\x91@A\x14\xf8\xbd\xa4t\x95\xb6\xa6[\xb6\x9a~\xaf<\xf7\xc598\x0b\xd2\xa9\x8df\xaf\xa4\x00\xeb\xcc\xa4w\xb0\xa8i#u\x0b\xf0\xec\x89\xde\xf6\xe7e\x8a\xca\x06m\x1dl#\x9f\xd4a')
Call hook at RIP: 0x14536cb
Hook: blob ptr: 0x43ffe00 size:0x178
Found second blob
bytearray(b'BSKP6RZ8afbOnHG8NTvPfkYBQ5gBBgghCkqn/C5QbhrUQS9uKLL73WP1JhFwQf5nU98PNSp6pm+iUUcFpdELXy8ydjgqILLxaoUJ')
End emulation
---------------------------------------------------------------------------
UcError                                   Traceback (most recent call last)
<ipython-input-2-47735b0fbcb1> in <module>
     49     out_rip = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RIP)
     50 
---> 51 main(stage1_data)

<ipython-input-2-47735b0fbcb1> in main(buf)
     46     uc_engine.reg_write(uc.x86_const.UC_X86_REG_RSP, STACK_ADDR)
     47     hook1 = uc_engine.hook_add(uc.UC_HOOK_CODE, hook_call, None)
---> 48     uc_engine.emu_start(CODE_ADDR, CODE_ADDR + 0x10000, 0, 0)
     49     out_rip = uc_engine.reg_read(uc.x86_const.UC_X86_REG_RIP)
     50 

~/.pyenv/versions/3.9.5/lib/python3.9/site-packages/unicorn/unicorn.py in emu_start(self, begin, until, timeout, count)
    339         status = _uc.uc_emu_start(self._uch, begin, until, timeout, count)
    340         if status != uc.UC_ERR_OK:
--> 341             raise UcError(status)
    342 
    343         if self._hook_exception is not None:

UcError: Invalid memory fetch (UC_ERR_FETCH_UNMAPPED)