Stack String Decrypt con Ghidra Emulator (Orchard)

Stack String Decrypt con Ghidra Emulator (Orchard)

Introduzione

Ciao a tutti! Oggi vedremo come realizzare uno script sfruttando le API di Ghidra per decifrare le stringhe di Orchard. In particolare analizzeremo il sample V3 (MD5: cb442cbff066dfef2e3ff0c56610148f) sviluppato in C++. Questo malware sfrutta inoltre un’interessante tecnica DGA il cui seed non è deterministico e dipende dal balance del genesis block; l’analisi tecnica di questo aspetto si può trovare sui blog bin.re e 360 Netlab.

Orchard per la decryption delle stringhe memorizza nello stack l’offset del carattere all’interno dell’alfabeto; vedremo quindi come è possibile realizzare un semplice script che sfrutta l’EmulatorHelper di Ghidra per la decifratura.

Iniziamo!

Analisi

Il malware inserisce nello stack gli offset dell’alfabeto e viene costruito ottenendo degli int da variabili globali e valori immediati; successivamente vengono chiamate due funzioni, la prima che si occupa di creare un oggetto e la seconda di fare la decryption della stringa.

La tecnica utilizzata dal malware è molto semplice, tuttavia è possibile complicare maggiormente l’analisi facendo sì che i valori nello stack dipendando dal risultato di funzioni, come ad esempio spiegato qui; in questo caso non potremmo escludere dall’esecuzione tutte le chiamate a funzione come fatto successivamente per Orchard, in quanto non ci permetterebbe di ottenere il valore corretto della stringa.

Costruzione dello stack inserendo gli offset e chiamata delle due funzioni
Variabili globali che contengono gli offset

Interessante notare come floss non riesca a decifrare questo tipo di stringhe:

Risultato di floss

Per l’emulation successiva ci interessa come vengono utilizzati i registri e in particolare per la prima funzione:

  • EDX: puntatore al primo offset all’interno dello stack.
  • EAX: contiene il valore che sottratto all’indirizzo del primo offset e diviso per 4 permette di ottenere la lunghezza della stringa.
  • ECX: puntatore this.

Ad esempio in questo caso EDX punta a 0x19F8E4 e quindi 33,39,33,34,25,2D,24,32,29,36,25 che equivale alla stringa SYSTEMDRIVE. In EAX invece abbiamo 0x19F910, quindi (0x19F910 – 0x19F8E4)/ 4 = 11 che è il numero di offset presenti.

Utilizzo dei registri EAX, EDX e ECX

La prima funzione si occupa di creare l’oggetto allocando una nuova area di memoria e copiando gli offset dallo stack.

Creazione di un nuovo oggetto

La seconda funzione invece si occupa di decifrare la stringa, ottenendo gli offset che servono come indici per l’alfabeto:

Funzione di decifratura
Alfabeto utilizzato per la decryption

Emulation con Ghidra

Per effettuare Emulation ci sono diverse soluzioni come Unicorn, Flare-Emu, Qiling e Dumpulator. In questo caso utilizzeremo le API di Ghidra attraveso la classe EmulatorHelper . Per approfondire l’argomento: QUI, QUI e QUI.

In particolare, i passaggi effettuati per l’emulazione sono:

  • Ottenere tutte le chiamate alla funzione di Decryption
    • Ottenibile facilmente con le API Ghidra getReferencesTo e getFromAddress
  • Determinare quale istruzioni devono essere emulate
    • Funzione getStartAndEndAddress
    • Una stessa funzione può contenere più chiamate alla funzione di Decryption, quindi l’inizio dell’emulation è il prologo della funzione chiamante oppure l’indirizzo dell’istruzione successiva a una precedente chiamata di Decryption
  • Emulare solamente le istruzioni che ci interessano
    • Escludere chiamate che possono modificare il flusso (call, jmp, ecc)
    • L’API Ghidra getFlowType permette di ottenere il FlowType dell’istruzione
  • Leggere l’output dopo l’emulation
    • Indirizzo contenuto in EDX permette di ottenere il primo valore dell’offset nello stack
    • (Valore contenuto in EAX – valore contento in EDX) / 4 = size delle stringa
    • Le API Ghidra readRegister e readMemory permettono di leggere i valori memorizzati nei registri e nel range di memoria specificato

Il codice per effettuare l’emulation:

from ghidra.app.emulator import EmulatorHelper
from ghidra.program.model.symbol import SymbolUtilities
     
def decrypt(data):
    alphabet = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyzw"
  
    decrypted = ""
      
    for b in data:
       if(b != 0):  
            decrypted += alphabet[b-1]
    
    return decrypted


class Emulator(object):

    def __init__(self):
        self.emulator = EmulatorHelper(currentProgram)

    def run(self):

        references = getReferencesTo(toAddr("DecryptStackStrings"))
        
        decryptOk = 0
        numCall = len(references)
         
        # Ottengo l'indirizzo delle chiamate alla funzione di Decryption
        for ref in references:
        
            callAddr = ref.getFromAddress()
 
            # Ottengo l'indirizzo di partenza e finale per l'emulation
            start, end = self.getStartAndEndAddress(callAddr)
        
            print("--------------------------------------------------------------")
            print("Start: " + start.toString() )
            print("End: " + end.toString() )
            print("--------------------------------------------------------------")
            
            # Imposto i registri e avvio l'emulation con gli indirizzi precedentemente trovati
            self.setupRegister(start)
            self.runEmulation(start, end)

            # Ottengo i valori dei registri EBP, EDX, EAX per calcolare la size e l'indirizzo di partenza degli offset
            valueEBP = self.emulator.readRegister("EBP")
            valueEDX = self.emulator.readRegister("EDX")
            valueEAX = self.emulator.readRegister("EAX")

            # Calcolo la dimensione della stringa
            size = valueEAX - valueEDX

            addr = toAddr(self.emulator.readRegister("EDX"))
            code = bytes(self.emulator.readMemory(addr, size))

            decryptedString = decrypt(code)
             
            if(len(decryptedString) != 0):
                #hexdump.hexdump(code)
                #print("{%s} Decrypted: %s " % (callAddr, decryptedString) )
                
                print("{%s} - %s " % (callAddr, decryptedString) )
                
                decryptOk += 1
                
                # Imposto il commento su Ghidra
                codeUnit = currentProgram.getListing().getCodeUnitAt(callAddr)
                codeUnit.setComment(codeUnit.PLATE_COMMENT, decryptedString)


            
        print("Decryption done:" + str(decryptOk))
        print("Call number: " + str(numCall))
        
        self.emulator.dispose()

     
    def setEIPNextInstruction(self):
    
        nextInstr = getInstructionAt(self.emulator.getExecutionAddress()).getNext()
        self.emulator.writeRegister(self.emulator.getPCRegister(), nextInstr.getAddress().getOffset())
        return nextInstr
              
      
    # Funzione che permette di ottenere l'indirizzo di partenza e termine dell'emulation      
    def getStartAndEndAddress(self, fromAddr):

        # Ottengo il prologo della funzione attuale
        functionAddr = getFunctionContaining(fromAddr).getEntryPoint()

        while True:
        
            instr = getInstructionBefore(fromAddr)
            addr = instr.getAddress()

            mnemonic = instr.getMnemonicString().lower()
    
            if(len(instr.getOpObjects(0)) > 0):   
                op1 = instr.getOpObjects(0)[0]
                op1_str = op1.toString().lower()
            
                op2 = instr.getOpObjects(0)[1]
                op2_str = op2.toString().lower()
     
            # Chiamata funzione CreateObject, indirizzo dove l'emulation deve terminare
            if mnemonic == "call" and op1_str == "00703390":
                end = addr
                
            
            # Se siamo arrivati al prologo oppure a un'altra chiamata di decryption, quello è l'indirizzo di partenza dell'emulation
            if functionAddr == addr or (mnemonic == "call" and op1_str == "0070af90"):
                return addr, end
         
            fromAddr = instr.getAddress()

            

    def setupRegister(self, start):
    
        emulator = self.emulator

        # Ottengo la memoria per lo stack e inizializzo ESP e EBP
        stack_address = (start.getAddressSpace().getMaxAddress().getOffset() >> 1) - 0x7fff
        
        emulator.writeRegister("ESP", stack_address)
        emulator.writeRegister("EBP", stack_address)
        emulator.writeRegister(emulator.getPCRegister(), start.getOffset())


    def runEmulation(self, start, end):

        emulationDone = False
        
        while not monitor.isCancelled():
            
            executionAddr = self.emulator.getExecutionAddress()   
            currentInstruction = getInstructionAt(executionAddr)


            # Eseguo solamente istruzioni di tipo FALL_THROUGH escludendo quelle che possono modificare il flusso (call, jmp, ja, ecc)
            op = str(currentInstruction).lower()  
            flowType = currentInstruction.getFlowType().toString()
            prefixes = ["lock"]
            
            while(flowType != "FALL_THROUGH" or op in prefixes):
            
                newInstruction = self.setEIPNextInstruction()   
                flowType = newInstruction.getFlowType().toString()
                op = newInstruction.toString()
                
            if self.emulator.step(monitor) == False:
                raise Exception("Emulation Error: '{}'".format(self.emulator.getLastError()))


            if (executionAddr == end):
                emulationDone = True
                break   
                
                
        if not emulationDone:
            raise Exception("[EMULATION] Error Emulation! ")
    
        print("[EMULATION] Done!")


if __name__=="__main__":

    print(" [EMULATION] Starting.. ")

    Emulator().run()

Il risultato della decryption:

{0072087c} - Message 
{00720957} - Problem_Type 
{00720a34} - Message_Type 
{0071e62c} - .exe 
{0071c81e} - SYSTEMDRIVE 
{0071c8b2} - C:\ 
{0071cae8} - %08lX 
{0071d047} - psapi.dll 
{0071d176} - GetModuleFileNameExW 
{00720546} - Serial_Number 
{00720620} - Message_Type 
{0070c08d} - kernel32.dll 
{0070c18b} - Wow64DisableWow64FsRedirection 
{0070c263} - Wow64RevertWow64FsRedirection 
{0071d977} - nvcuda.dll 
{0071da4e} - cuDriverGetVersion 
{0071ed62} - Identity 
{0071eeb5} - Operating_System 
{0071f046} - System_Architecture 
{0071f19b} - Elevated 
{0071f292} - Threads 
{0071f3b1} - Camera 
{0071f49e} - Antivirus 
{0071f5b5} - Version 
{0071f72f} - Active_Window 
{0071f864} - CPU_Model 
{0071f9a1} - GPU_Models 
{0071fa8a} - Ram_Size 
{0071fb91} - Authenticate_Type 
{0071a8a9} - ntdll.dll 
{0071a9c9} - RtlGetVersion 
{0071a02d} - MajorVersion 
{0071a108} - MinorVersion 
{0071a1e7} - BuildNumber 
{0071a2e0} - ProductType 
{0071abf7} - ROOT\SecurityCenter2 
{0071ae48} - SELECTdisplayNameFROMAntiVirusProduct 
{0071aee8} - WQL 
{0071b1d4} - displayName 
{0070b3de} - Name 
{0070b4a2} - Type 
{00718915} - nvapi.dll 
{007189e1} - nvapi_QueryInterface 
{0071902e} - OpenCL.dll 
{007190ec} - clGetPlatformIDs 
{007191ac} - clGetDeviceIDs 
{00719278} - clGetDeviceInfo 
{00721710} - %04d-%02d-%02d 
{00721762} - .duckdns.org 
{0072178c} - .com 
{007217b6} - .net 
{007217e0} - .org 
{0072193d} - https://blockchain.info/balance?active=1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa 
{00722452} - CPU_Status 
{00722538} - GPU_Status 
{0072260b} - CPU_Hashrate 
{007226e8} - GPU_Hashrate 
{007227b2} - GPU_Type 
{007228ac} - GPU_Algorithm 
{0072297f} - Message_Type 
{007195b1} - Host 
{00719670} - Port 
{00719741} - File_Name 
{0071980e} - Handle 
{00722ce8} - Domain 
{00722dad} - Port 
{00722e8b} - Process_ID 
{00722f66} - Process_Name 
{00723041} - Process_Path 
{0072311f} - Is_Patched 
{007231f6} - In_Memory 
{007232d4} - Patch_Name 
{007233b2} - Install_Path 
{0072349a} - System_Idle 
{00723581} - System_Uptime 
{00723674} - Power_SaverMode 
{00723751} - Message_Type 
{00702716} - Required_Binary 
{007027ea} - Cuda_Version 
{0070c879} - *.exe 
{0072393e} - Buffer 
{007239cd} - Execute_Name 
{00723a62} - Binary_Source 
{00723af6} - Execute_Type 
{00723cd6} - Execute_Name 
{00723dbf} - Execute_Result 
{00723ec0} - Execute_Result_Type 
{00723f9d} - Message_Type 
{00724146} - Option 
{007241e8} - Message_Option 
{007242d8} - Type 
{00724373} - Transfer_Port 
{00724401} - Message_Type 
{00724539} - Buffer 
{007245c0} - Handle 
{0072465d} - Message_Option 

Grazie per l’ascolto! Per qualunque commento e migliorie (sicuramente ce ne sono tante 🙂 ) fatemi sapere nei commenti!! A presto 😀

Share this content:

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *