LockBit Encryption
Introduction
In this article, we will talk briefly about the LockBit features and focus on the different parts that have not been fully covered by other analysts like encryption. The LockBit sample used in this analysis has been extracted during an Incident Response. The attack was conducted manually by humans, with the help of Cobalt Strike.
Finally, in the Annexes we will present a way to recover light encrypted stack strings using IDA and the miasm symbolic engine.
General description
The sample was packed, strings "encrypted" using XOR operation with the first byte as key. The sample checks the process privileges and sidestep Windows UAC using the bypass methods in hfiref0x’s UACME #43.
LockBit stopped undesirable services by checking their names against a skip list using OpenSCManagerA, enumerate dependent services using EnumDependentServicesA and terminate them using ControlService with the SERVICE_CONTROL_STOP control code.
Processes are enumerated using CreateToolhelp32Snapshot, Process32First, Process32Next and OpenProcess. If their names appear on the skip list, the process is killed using TerminateProcess.
The ransomware can enumerate shares on the current /24 subnet using EnumShare and networks resources using WNetAddConnection2W and NetShareEnum.
As usual, Windows snapshots are deleted using the following commands:
cmd.exe /c vssadmin delete shadows /all /quiet & wmic shadowcopy delete & bcdedit /set {default} bootstatuspolicy ignoreallfailures & bcdedit /set {default} recoveryenabled No & wbadmin delete catalog -quiet
Below, we will take a closer look at some of its features.
IOCP
To encrypt quickly and efficiently, LockBit uses the Windows I/O Completion Ports mechanisms. This provides an efficient threading model for processing multiple asynchronous I/O requests on a multiprocessor system. Instead of using the traditional API CreateIoCompletionPort and GetQueuedCompletionStatus, they use NtCreateIoCompletion, NtSetInformationFile and NtRemoveIoCompletion.
LockBit starts by creating an I/O completion Port using NtCreateIoCompletion API:
Then, for each file that does not match entries on the folder and file blacklist, it associates the file handle with the I/O completion port using NtSetInformationFile with the information class that is set to FileCompletionInformation:
Then, reading the file using the OVERLAPPED structure will create an I/O completion packet that is queued in first-in-first-out (FIFO) order to the associated I/O completion port:
Later on (in the decryption_thread), instead of calling the “GetQueuedCompletionStatus” to dequeue an I/O completion packet from the specified I/O completion Port, it calls NtRemoveIoCompletion:
Encryption
Introduction
The encryption is based on two algorithms: RSA and AES. First, an RSA session key pair is generated on the infected workstations. This key pair is encrypted using the embedded attacker's public key and saved on the registry SOFTWARE\LockBit\full
. An AES key is generated randomly for each file to encrypt. Once the file is encrypted, the AES key is encrypted using the RSA public session key and appended to the end of the file with the previously encrypted session key.
Encryption detection
To detect encryption algorithms, public crypto Yara rules can be used. On the unpack binary, you will get the following results:
- Big_Numbers1
- Prime_Constants_long
- Crypt32_CryptBinaryToString_API
- RijnDael_AES_CHAR
- RijnDael_AES_LONG
As you can see, the RSA algorithm has not been detected. Reading codes close to the AES functions make me grepping the constant -0x4480 using grep.app application which leads me to the library mbedtls.
With the source code (or one close to the original one), we can try to load C Header files in IDA, by doing File -> Load file -> Parse C header files
. You may have to set properly the Include directories and the Calling convention in the option compiler box:
Also, it may be needed to delete external includes in the header files like stddef.h to avoid errors Can't open include file stddef.h.
This allows the analysts to easily import structure:
And maybe, provide hints by comparing the structure size (0x140):
Encryption Preparation
Like many ransomware, LockBit uses session keys to encrypt the symmetric key that is used to encrypt files. The function that generates session keys is easy to find because it is just after the debug message "Generate session keys" and just before the encryption_thread :)
Below the function that generates the RSA session keys:
Because the private session key needs to be kept secret, LockBit RSA encrypts it with his embedded public key (named Attacker_Modulus in the code):
Finally, the full encrypted session keys are stored in the registry SOFTWARE\LockBit\full
while in the SOFTWARE\LockBit\Public
the public session key (Modulus and Exponent, respectively 0x100 and 0x3 bytes):
Files Encryption
As we mentioned in the IOCP chapter, each file marked for encryption is passed to the encryption thread using ReadFile and the OVERLAPPED structure. They added to the original structure a random AES 128 bits key that is generated just before using BCryptGenRandom from bcrypt.dll library:
In the decryption thread, the packet is dequeued from the specified I/O completion Port, then the AES key is set using aesni_setkey_enc_128 if the processor supports the Advanced Encryption Standard New Instructions (AES-NI) otherwise with the mbedtls_setkey_enc_128 function:
The codes that check if the processor supports AES-NI are done earlier, before generating the RSA session keys:
Finally, the file content is encrypted using AES and written back into the file:
Files end data
At the end of each file, 0x610 bytes are appended. This data structure contains the required information for decryption:
Offset | Description | Size (bytes) |
---|---|---|
0x0 | Encrypted AES key | 0x100 |
0x100 | Encrypted RSA session keys | 0x500 |
0x600 | First 0x10 bytes of the attacker's RSA public key | 0x10 |
Decryptor and Decryption
To decrypt a file, the Decryptor needs to:
- Import the attacker’s RSA keys
- Get and decrypt the session key pair (placed at the end of the file) using the attackers' private key.
- Get and decrypt the AES key (placed at the end of the file) used for encryption using the decrypted session key pair.
- Decrypt the file using AES and the previous AES key.
The decryptor includes in its resource the full RSA attacker's key (0x483 bytes):
Imports the RSA key:
Gets the last 0x510 file bytes and decrypts the first 0x500 to get the RSA session keys:
From this, it can decrypt the AES key (stored at the end of the file) with the RSA session key and finally the file.
Annexes
Dynamic Stack Strings
LockBit builds his strings in the stack dynamically. For example, the function that compares file names to a blacklist uses stack strings to build the blacklist:
Because there are many strings, and their length can be as big as a ransom note and also because it is a classical feature that you may find again in other malware, automating this task may be useful. One solution would have been to create a script using IDA that records write operation on the stack but as we said in the introduction, strings are also built in addition to a XOR routine:
or:
In many malware, there is often a single function that is used to decrypt the strings. Most of the time, the solution is to get the cross references to this function with its parameter using IDA, and execute the decrypt function by using one of them: + Python implementation + Debugger conditional breakpoint + IDA appcall + x86 Emulation (unicorm, miasm, etc.)
But because stack strings or often inline we won’t be able to use cross references.
One way to do it is by doing static symbolic emulation on a portion of the code by using IDA selection. I did use this way because with dynamic emulation (symbolic or not) you may have to handle corner cases when an instruction is doing read/write memory operation in an undefined memory zone or API call, etc.
I used miasm framework, because it is easy to install (copy the miasm directory to C:\...\IDA\python\), and they already had a good example on their git.
Original miasm example
Taking a simple case:
By running the original miasm script, we get the following output:
IRDst = loc_key_2
@32[EBP_init + 0xFFFFFC2C] = 0x730077
@32[EBP_init + 0xFFFFFC20] = 0x770024
@32[EBP_init + 0xFFFFFC34] = 0x740062
@32[EBP_init + 0xFFFFFC28] = 0x6F0064
@32[EBP_init + 0xFFFFFC30] = 0x7E002E
@16[EBP_init + 0xFFFFFC38] = (EAX_init)[0:16]
@32[EBP_init + 0xFFFFFC24] = 0x6E0069
This gives us a good starting point to implement more features on it.
Modified version
The idea was to modify the script to automatically comment the instruction with the right strings and rename the local variables (see picture above). Below the steps:
1 - Symbolic execution
Do symbolic execution on each instruction and for each write operation in memory, stored:
- The destination memory expr (@32[EBP_init + 0xFFFFFC2C])
- The instruction address (EIP)
- The stack position (to handle case where a local variable is accessed using ESP and not EBP)
def run_symb_exec(symb, start, end):
""" Execute symbolic execution and record memory writes
:param symb : SymbolicEngine
:param start (int) : start address
:param end (int) : end address
return data (dict) : Dictionnary with information needed (instruction offset, data_xrefs, source memory expression, spd value)
"""
data = dict()
while True:
irblock = ircfg.get_block(start)
if irblock is None:
break
for assignblk in irblock:
if assignblk.instr:
LOGGER.debug("0x%x : %s" % (assignblk.instr.offset, assignblk.instr))
if assignblk.instr.args:
if assignblk.instr.args[0].is_mem() is True: # write mem operation
data[assignblk.instr.args[0]] = {'to': assignblk.instr.offset, 'd_ref_type': 2, 'expr': assignblk.instr.args[0], 'spd': idc.get_spd(assignblk.instr.offset)}
if assignblk.instr.offset == end:
break
symb.eval_updt_assignblk(assignblk)
start = symb.eval_expr(symb.ir_arch.IRDst)
return data
2 - Concretize destination memory
Replace destination memory expression (@32[EBP_init + 0xFFFFFC2C]) with concrete value (0x0 for EBP and the stack pointer (spd) for ESP), to get a concrete value.
def concretizes_stack_ptr(phrase_mem):
""" replace ESP or EBP with a concrete value
:param phrase_mem (dict)
"""
stack = dict()
for dst, src in phrase_mem.items():
ptr_expr = dst.ptr
spd = src['spd'] + 0x4
real = expr_simp(ptr_expr.replace_expr({ExprId('ESP', 32): ExprInt(spd, 32), ExprId('EBP', 32): ExprInt(0x0, 32)}))
if real.is_int():
stack[real.arg] = src
return stack
3 - Evaluate the expression
Evaluate the destination memory expression to get the result expression. For example:
@128[ESP + 0x488] = {@8[0x423EE0] 0 8, @8[0x423EE0] ^ @128[0x423EE0][8:16] 8 16, @128[0x423EE0][16:128] 16 128}
4 - Translate the resulting expression in python
Using the miasm Translator we can convert the expression in Python code:
{@8[0x423EE0] 0 8, @8[0x423EE0] ^ @128[0x423EE0][8:16] 8 16, @128[0x423EE0][16:128] 16 128} -> (((memory(0x423EE0, 0x1) & 0xff) << 0) | ((((memory(0x423EE0, 0x1) ^ ((memory(0x423EE0, 0x10) >> 8) & 0xff)) & 0xff) & 0xff) << 8) | ((((memory(0x423EE0, 0x10) >> 16) & 0xffffffffffffffffffffffffffff) & 0xffffffffffffffffffffffffffff) << 16))
This will allow us to evaluate the Python code to get the content.
5 - Comment IDA and rename local var
I reproduce the stack frame using a list, and for each string found, I comment IDA to the right offset using the previously stored information and rename the local variable with the right size.
def set_data_to_ida(start, data):
""" Add comment with the strings, and redefine stack variables in IDA
"""
size_local_var = idc.get_func_attr(start, idc.FUNCATTR_FRSIZE)
frame_high_offset = 0x100000000
frame_id = idc.get_func_attr(start, idc.FUNCATTR_FRAME)
l_xref = dict()
if size_local_var == 0: # TODO : handle when stack size is undefined
size_local_var = 0x1000 # set stack size to arbitrary value 0x1000
# represent the stack as a list (initialization)
stack = [0 for i in range(0, size_local_var)]
# fill the stack
for offset, value in data.items():
if (offset >> 31) == 1: # EBP based frame
if offset <= frame_high_offset:
stack_offset = twos_complement(offset) # Ex: 0xfffffe00 -> 0x200
frame_offset = stack_to_frame_offset(stack_offset, size_local_var) # frame_offset = member_offset = positive offset in the stack frame
l_xref[frame_offset] = value
stack[frame_offset] = value['value']
else: # ESP based frame
l_xref[offset] = value
stack_offset = frame_to_stack_offset(offset, size_local_var)
frame_offset = offset
stack[offset] = value['value']
LOGGER.debug("offset 0x%x, stack_offset 0x%x, frame_offset 0x%x, value %s" % (offset, stack_offset, frame_offset, chr(value['value'])))
index = 0
while index < len(stack):
if stack[index] != 0: # skip null bytes
buff = get_bytes_until_delimiter(stack[index::], [0x0, 0x0]) # TODO: adjust auto ascii/utf16 ?
if buff:
LOGGER.debug("raw output = %s" % repr(buff))
ascii_buff = zeroes_out(buff) # transform to ascii, deletes null bytes
if buff[1] == 0x0: # check if strings is utf16 TODO: check if really usefull
STRTYPE = idc.STRTYPE_C
else:
STRTYPE = idc.STRTYPE_C
var_offset = index
var_name = idc.get_member_name(frame_id, var_offset)
s_string = "".join(map(chr, ascii_buff))
LOGGER.info("[+] Index 0x%x, var_offset 0x%x, frame_id = 0x%x, var_name %s, strings = %s, clean_string = %s, len_strings = 0x%x, size_local_var 0x%x, xref to 0x%x" % (index, var_offset, frame_id, var_name, s_string, replace_forbidden_char(s_string), len(buff), size_local_var, l_xref[index]['to']))
# delete struct member, to create array of char
LOGGER.info("[+] Delete structure member")
for a_var_offset in range(var_offset, var_offset + len(buff)):
# if idc.get_member_name(frame_id, a_var_offset):
ret = idc.del_struc_member(frame_id, a_var_offset)
LOGGER.debug("ret del_struc_member = %d" % ret)
# re add structure member with good size and proper name
LOGGER.info("[+] Add structure member")
ret = idc.add_struc_member(frame_id, replace_forbidden_char(s_string)[:0x10], var_offset, idc.FF_STRLIT, STRTYPE, len(buff))
LOGGER.debug("ret add_struc_member = %d" % ret)
# Comment instruction using the xref key
LOGGER.info("[+] Comment instruction")
if l_xref[index]['d_ref_type'] == 2:
if not idc.get_cmt(l_xref[index]['to'], 0):
idc.set_cmt(l_xref[index]['to'], s_string, 0)
index = index + len(buff)
else:
index = index + 1
else:
index = index + 1
LOGGER.info("[+] end")
return stack
Script Output Example
By selecting instructions and running the script, we get instructions automatically commented and local variable renamed:
Yara
rule malware_first_unpacking_routine {
strings :
$h1 = { 81 [1-5] A9 0F 00 00 75 ?? C7 ?? ?? ?? ?? ?? 40 2E EB ED }
condition :
$h1
}
IOC
Commands
/c vssadmin Delete Shadows /All /Quiet
/c bcdedit /set {default} recoveryenabled No
/c bcdedit /set {default} bootstatuspolicy ignoreallfailures
/c wbadmin DELETE SYSTEMSTATEBACKUP
/c wbadmin DELETE SYSTEMSTATEBACKUP -deleteOldest
/c wmic SHADOWCOPY /nointeractive
/c wevtutil cl security
/c wevtutil cl system
/c wevtutil cl application
/c vssadmin delete shadows /all /quiet & wmic shadowcopy delete & bcdedit /set {default} bootstatuspolicy ignoreallfailures & bcdedit /set {default} recoveryenabled no & wbadmin delete catalog -quiet
/C ping 127.0.0.7 -n 3 > Nul & fsutil file setZeroData offset=0 length=524288 "%s" & Del /f /q "%s"
Mutex
Global\{BEF590BE-11A6-442A-A85B-656C1081E04C}
Services
- wrapper
- DefWatch
- ccEvtMgr
- ccSetMgr
- SavRoam
- Sqlservr
- sqlagent
- sqladhlp
- Culserver
- RTVscan
- sqlbrowser
- SQLADHLP
- QBIDPService
- Intuit.QuickBooks.FCS
- QBCFMonitorService
- sqlwriter
- msmdsrv
- tomcat6
- zhudongfangyu
- vmware-usbarbitator64
- vmware-converter
- dbsrv12
- dbeng8
- MSSQL$MICROSOFT##WID
- MSSQL$VEEAMSQL2012
- SQLAgent$VEEAMSQL2012
- SQLBrowser
- SQLWriter
- FishbowlMySQL
- MSSQL$MICROSOFT##WID
- MySQL57
- MSSQL$KAV_CS_ADMIN_KIT
- MSSQLServerADHelper100
- SQLAgent$KAV_CS_ADMIN_KIT
- msftesql-Exchange
- MSSQL$MICROSOFT##SSEE
- MSSQL$SBSMONITORING
- MSSQL$SHAREPOINT
- MSSQLFDLauncher$SBSMONITORING
- MSSQLFDLauncher$SHAREPOINT
- SQLAgent$SBSMONITORING
- SQLAgent$SHAREPOINT
- QBFCService
- QBVSS
- YooBackup
- YooIT
- svc$
- MSSQL
- MSSQL$
- memtas
- mepocs
- sophos
- veeam
- backup
- bedbg
- PDVFSService
- BackupExecVSSProvider
- BackupExecAgentAccelerator
- BackupExecAgentBrowser
- BackupExecDiveciMediaService
- BackupExecJobEngine
- BackupExecManagementService
- BackupExecRPCService
- MVArmor
- MVarmor64
- stc_raw_agent
- VSNAPVSS
- VeeamTransportSvc
- VeeamDeploymentService
- VeeamNFSSvc
- AcronisAgent
- ARSM
- AcrSch2Svc
- CASAD2DWebSvc
- CAARCUpdateSvc
- WSBExchange
- MSExchange
- MSExchange$
Process
- wxServer
- wxServerView
- sqlmangr
- RAgui
- supervise
- Culture
- Defwatch
- winword
- QBW32
- QBDBMgr
- qbupdate
- axlbridge
- httpd
- fdlauncher
- MsDtSrvr
- java
- 360se
- 360doctor
- wdswfsafe
- fdhost
- GDscan
- ZhuDongFangYu
- QBDBMgrN
- mysqld
- AutodeskDesktopApp
- acwebbrowser
- Creative Cloud
- Adobe Desktop Service
- CoreSync
- Adobe CEF Helper
- node
- AdobeIPCBroker
- sync-taskbar
- sync-worker
- InputPersonalization
- AdobeCollabSync
- BrCtrlCntr
- BrCcUxSys
- SimplyConnectionManager
- Simply.SystemTrayIcon
- fbguard
- fbserver
- ONENOTEM
- wsa_service
- koaly-exp-engine-service
- TeamViewer_Service
- TeamViewer
- tv_w32
- tv_x64
- TitanV
- Ssms
- notepad
- RdrCEF
- oracle
- ocssd
- dbsnmp
- synctime
- agntsvc
- isqlplussvc
- xfssvccon
- mydesktopservice
- ocautoupds
- encsvc
- firefox
- tbirdconfig
- mydesktopqos
- ocomm
- dbeng50
- sqbcoreservice
- excel
- infopath
- msaccess
- mspub
- onenote
- outlook
- powerpnt
- steam
- thebat
- thunderbird
- visio
- wordpad
- bedbh
- vxmon
- benetns
- bengien
- pvlsvr
- beserver
- raw_agent_svc
- vsnapvss
- CagService
- DellSystemDetect
- EnterpriseClient
- VeeamDeploymentSvc
Registry
SOFTWARE\LockBit
SOFTWARE\LockBit\full
SOFTWARE\LockBit\Public
Persistance
HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\XO1XADpO01
UAC bypass CLSID
- {3E5FC7F9-9A51-4367-9063-A120244FBEC7}
- {D2E7041B-2927-42fb-8E9F-7CE93B6DC937}
Full Script
import idaapi
import idc
import logging
from math import log
def unpack_arbitrary(data, word_size=None):
""" (modified pwntools) unpack arbitrary-sized integer
:param data : String to convert
:word_size : Word size of the converted integer or the string “all” (in bits).
:return : The unpacked number
"""
number = 0
data = reversed(data)
data = bytearray(data)
for c in data:
number = (number << 8) + c
number = number & ((1 << word_size) - 1)
signbit = number & (1 << (word_size-1))
return int(number - 2*signbit)
def memory(ea, size):
""" Read memory from IDA
:param ea (int) : Start address
:param size (int) : size of buffer in normal 8-bit bytes
: return int/long
"""
return unpack_arbitrary(idc.get_bytes(ea, size), size * 8)
def twos_complement(esp_offset):
return 0x100000000 - esp_offset
def stack_to_frame_offset(stack_offset, size_local_var):
"""
stack_offset <-> frame_offset
"""
return size_local_var - abs(stack_offset)
def frame_to_stack_offset(frame_offset, size_local_var):
"""
stack_offset <-> frame_offset
"""
return size_local_var - abs(frame_offset)
def get_bytes_until_delimiter(bytes_list, delimiter):
"""Get bytes until it reach the delimiter
:param bytes_list (list) : List of bytes
:param delimiter (list) : Delimiter to reach (e.g. [0x0, 0x0])
:return a list of bytes
"""
len_delimiter = len(delimiter)
iterables = list()
for x in range(len_delimiter):
iterables.append(bytes_list[x:])
abc = zip(*iterables)
if tuple(delimiter) in abc:
index = abc.index(tuple(delimiter))
return (bytes_list[0:index]) + delimiter
else:
return None
def zeroes_out(bytes_list):
""" delete null bytes
:param bytes_list (list) : list of bytes
:return : list of bytes without zeros
"""
return [x for x in bytes_list if x != 0]
def replace_forbidden_char(my_strings):
""" replace IDA forbidden char
"""
clean_string = list()
for x in my_strings:
if x.isalnum():
clean_string.append(x)
elif x.isspace():
clean_string.append('_')
else:
clean_string.append('?')
return "k_" + "".join(clean_string)
def bytes_needed(n):
""" get the number of bytes that compose the number
https://stackoverflow.com/questions/14329794/get-size-of-integer-in-python
:pararm n (int) : number
:return (int) : number of bytes
"""
if n == 0:
return 1
return int(log(n, 256)) + 1
def extract_byte_x(num, position):
""" Get bytes from position
:param num (int) : original number
:param position (int) : desired byte position
: return (int) : byte x
"""
offset = 0x8 * (position - 1)
and_mask = (0xFF << offset)
return (num & and_mask) >> offset
def symbolic_exec(start, end):
""" Symbolic execution engine (modified of original https://github.com/cea-sec/miasm/blob/master/example/ida/symbol_exec.py)
Takes start and end address from IDA selection, do symbolic execution on instructions
Replace stacks registers (ESP, EBP) variable by concrete value 0x0 or current spd instructions
"""
from miasm.ir.symbexec import SymbolicExecutionEngine
from miasm.expression.expression import ExprId, ExprInt
from miasm.expression.simplifications import expr_simp
from miasm.core.bin_stream_ida import bin_stream_ida
from miasm.ir.translators import Translator
from miasm.analysis.machine import Machine
def run_symb_exec(symb, start, end):
""" Execute symbolic execution and record memory writes
:param symb : SymbolicEngine
:param start (int) : start address
:param end (int) : end address
return data (dict) : Dictionnary with information needed (instruction offset, data_xrefs, source memory expression, spd value)
"""
data = dict()
while True:
irblock = ircfg.get_block(start)
if irblock is None:
break
for assignblk in irblock:
if assignblk.instr:
LOGGER.debug("0x%x : %s" % (assignblk.instr.offset, assignblk.instr))
if assignblk.instr.args:
if assignblk.instr.args[0].is_mem() is True: # write mem operation
data[assignblk.instr.args[0]] = {'to': assignblk.instr.offset, 'd_ref_type': 2, 'expr': assignblk.instr.args[0], 'spd': idc.get_spd(assignblk.instr.offset)}
if assignblk.instr.offset == end:
break
symb.eval_updt_assignblk(assignblk)
start = symb.eval_expr(symb.ir_arch.IRDst)
return data
def concretizes_stack_ptr(phrase_mem):
""" replace ESP or EBP with a concrete value
:param phrase_mem (dict)
"""
stack = dict()
for dst, src in phrase_mem.items():
ptr_expr = dst.ptr
spd = src['spd'] + 0x4
real = expr_simp(ptr_expr.replace_expr({ExprId('ESP', 32): ExprInt(spd, 32), ExprId('EBP', 32): ExprInt(0x0, 32)}))
if real.is_int():
stack[real.arg] = src
return stack
def eval_src_expr(symb, data):
""" evaluate/resolve the source expression trying to get a concrete value.
Translate to python anbd eval if necessary
Evaluation:
@128[ESP + 0x488] = {@8[0x423EE0] 0 8, @8[0x423EE0] ^ @128[0x423EE0][8:16] 8 16, @128[0x423EE0][16:128] 16 128}
Translation:
{@8[0x423EE0] 0 8, @8[0x423EE0] ^ @128[0x423EE0][8:16] 8 16, @128[0x423EE0][16:128] 16 128} = (((memory(0x423EE0, 0x1) & 0xff) << 0) | ((((memory(0x423EE0, 0x1) ^ ((memory(0x423EE0, 0x10) >> 8) & 0xff)) & 0xff) & 0xff) << 8) | ((((memory(0x423EE0, 0x10) >> 16) & 0xffffffffffffffffffffffffffff) & 0xffffffffffffffffffffffffffff) << 16))
"""
tmp = dict()
for dst, src in data.items():
tmp_val = symb.eval_expr(src['expr'])
if tmp_val.is_int(): # Case where the source expression is a int
tmp_val = int(tmp_val.arg)
else: # The source expression may by a data reference (read from mem)
# Translate expression to python
py_expr = Translator.to_language('Python').from_expr(tmp_val)
try:
tmp_val = eval(py_expr) # TODO better solution (need to add memory function) ?
except Exception as e:
LOGGER.info("[-] Python Translator Exception")
LOGGER.info(py_expr)
raise e
if isinstance(tmp_val, int) or isinstance(tmp_val, long):
# Set one byte per address
for i in range(0, bytes_needed(tmp_val)):
tmp[dst + i] = {'to': src['to'], 'd_ref_type': 2, 'value': extract_byte_x(tmp_val, i + 1), 'spd': src['spd']}
return tmp
bs = bin_stream_ida()
machine = Machine("x86_32")
mdis = machine.dis_engine(bs, dont_dis_nulstart_bloc=True)
ir_arch = machine.ira(mdis.loc_db)
# Disassemble the targeted function until end
mdis.dont_dis = [end]
asmcfg = mdis.dis_multiblock(start)
# Generate IR
ircfg = ir_arch.new_ircfg_from_asmcfg(asmcfg)
# Replace ExprID
regs = machine.mn.regs.regs_init
# regs[ExprId('ESP', 32)] = ExprId('ESP', 32)
# regs[ExprId('ESP', 32)] = ExprId('EBP', 32)
# regs[ExprId('EAX', 32)] = ExprId('EAX', 32)
# regs[ExprId('EBX', 32)] = ExprId('EBX', 32)
# regs[ExprId('ECX', 32)] = ExprId('ECX', 32)
# regs[ExprId('EDX', 32)] = ExprId('EDX', 32)
# regs[ExprId('XMM0_init', 32)] = ExprId('XMM0', 32)
LOGGER.info("[+] Get symbolic engine")
symb = SymbolicExecutionEngine(ir_arch, regs)
LOGGER.info("[+] Run symbolic execution")
data = run_symb_exec(symb, start, end)
LOGGER.info("[+] Concretize stack pointer")
data = concretizes_stack_ptr(data)
LOGGER.info("[+] Resolves source expression")
data = eval_src_expr(symb, data)
return data
def set_data_to_ida(start, data):
""" Add comment with the strings, and redefine stack variables in IDA
"""
size_local_var = idc.get_func_attr(start, idc.FUNCATTR_FRSIZE)
frame_high_offset = 0x100000000
frame_id = idc.get_func_attr(start, idc.FUNCATTR_FRAME)
l_xref = dict()
if size_local_var == 0: # TODO : handle when stack size is undefined
size_local_var = 0x1000 # set stack size to arbitrary value 0x1000
# represent the stack as a list (initialization)
stack = [0 for i in range(0, size_local_var)]
# fill the stack
for offset, value in data.items():
if (offset >> 31) == 1: # EBP based frame
if offset <= frame_high_offset:
stack_offset = twos_complement(offset) # Ex: 0xfffffe00 -> 0x200
frame_offset = stack_to_frame_offset(stack_offset, size_local_var) # frame_offset = member_offset = positive offset in the stack frame
l_xref[frame_offset] = value
stack[frame_offset] = value['value']
else: # ESP based frame
l_xref[offset] = value
stack_offset = frame_to_stack_offset(offset, size_local_var)
frame_offset = offset
stack[offset] = value['value']
LOGGER.debug("offset 0x%x, stack_offset 0x%x, frame_offset 0x%x, value %s" % (offset, stack_offset, frame_offset, chr(value['value'])))
index = 0
while index < len(stack):
if stack[index] != 0: # skip null bytes
buff = get_bytes_until_delimiter(stack[index::], [0x0, 0x0]) # TODO: adjust auto ascii/utf16 ?
if buff:
LOGGER.debug("raw output = %s" % repr(buff))
ascii_buff = zeroes_out(buff) # transform to ascii, deletes null bytes
if buff[1] == 0x0: # check if strings is utf16 TODO: check if really usefull
STRTYPE = idc.STRTYPE_C_16
else:
STRTYPE = idc.STRTYPE_C
var_offset = index
var_name = idc.get_member_name(frame_id, var_offset)
s_string = "".join(map(chr, ascii_buff))
LOGGER.info("[+] Index 0x%x, var_offset 0x%x, frame_id = 0x%x, var_name %s, strings = %s, clean_string = %s, len_strings = 0x%x, size_local_var 0x%x, xref to 0x%x" % (index, var_offset, frame_id, var_name, s_string, replace_forbidden_char(s_string), len(buff), size_local_var, l_xref[index]['to']))
# delete struct member, to create array of char
LOGGER.info("[+] Delete structure member")
for a_var_offset in range(var_offset, var_offset + len(buff)):
# if idc.get_member_name(frame_id, a_var_offset):
ret = idc.del_struc_member(frame_id, a_var_offset)
LOGGER.debug("ret del_struc_member = %d" % ret)
# re add structure member with good size and proper name
LOGGER.info("[+] Add structure member")
ret = idc.add_struc_member(frame_id, replace_forbidden_char(s_string)[:0x10], var_offset, idc.FF_STRLIT, STRTYPE, len(buff))
LOGGER.debug("ret add_struc_member = %d" % ret)
# Comment instruction using the xref key
LOGGER.info("[+] Comment instruction")
if l_xref[index]['d_ref_type'] == 2:
if not idc.get_cmt(l_xref[index]['to'], 0):
idc.set_cmt(l_xref[index]['to'], s_string, 0)
index = index + len(buff)
else:
index = index + 1
else:
index = index + 1
LOGGER.info("[+] end")
return stack
def main():
start, end = idc.read_selection_start(), idc.read_selection_end()
if start != 0xFFFFFFFF and end != 0xFFFFFFFF:
data = symbolic_exec(start, end)
LOGGER.info("[+] Set and comment data to IDA")
stack = set_data_to_ida(start, data)
else:
print("Error: Select instructions")
stack = -0x1
return stack
if __name__ == '__main__':
idaapi.CompileLine('static key_F3() { RunPythonStatement("main()"); }')
idc.add_idc_hotkey("F3", "key_F3")
LOGGER = logging.getLogger(__name__)
if not LOGGER.handlers: # Avoid duplicate handler
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
LOGGER.addHandler(console_handler)
LOGGER.setLevel(logging.DEBUG)
print("=" * 50)
print("""Available commands:
main() - F3: Symbolic execution of current selection
""")