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.


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

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.

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

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


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