Full Report
In the first part we talked about the basics of Qiling, you can find it here.
Analysis Summary
# Qiling for Malware Analysis: Part 2
=====================================
## Key Points
- **Qiling**: A great project for malware analysis and binary emulation.
- **Dynamic Imports**: Malware samples use dynamic imports to make static analysis harder.
- **KSLØT Keylogger**: The malware sample used in this example is a keylogger that uses dynamic imports.
## Threat Actors
- **Attribution**: Not provided, but attributed to the KSLØT campaign.
## TTPs
- **Dynamic Imports**: Malware samples use `LoadLibrary()` and `GetProcAddress()` to make static analysis harder.
- **Hooking `GetProcAddress()`**: The script hooks `GetProcAddress()` on exit to dump the second argument (function name) each time it's called.
## Affected Systems
- **Windows 64-bit**: The malware sample is for Windows 64-bit.
## Mitigations
- **Dynamic Import Detection**: Monitor dynamic imports and hook `GetProcAddress()` to analyze them.
- **String Analysis**: Use Qiling to decrypt strings and add them as IDA comments.
## Conclusion
- **Qiling Capabilities**: Great project for malware analysis and binary emulation, with lots of capabilities and potential improvements.
### Code Snippets
#### Hooking `GetProcAddress()`
python
DLL_MAIN = 0x1800019a0 # Adress of DLLMain function
hinstDLL ql.reg.rcx = 0x180000000 # Address where Qiling loads the DLL
fdwReason ql.reg.rdx = 0x1 # DLL_PROCESS_DETACH
lpvReserved ql.reg.r8 = 0x0
# FARPROC GetProcAddress(
# HMODULE hModule,
# LPCSTR lpProcName
#)
def hook_GetProcAddress(ql, addr, params):
print("[*] Import: {}".format(params["lpProcName"]))
# hook GetProcAddress() on exit
ql.set_api("GetProcAddress", hook_GetProcAddress, QL_INTERCEPT.EXIT)
# disable logging
ql.filter = []
# start emulation
ql.run(begin=DLL_MAIN)
#### Decrypting QBot Strings
python
# initialize emulator (x86 windows)
ql = Qiling(["qbot.exe"], rootfs="qiling/examples/rootfs/x86_windows")
DEC_START = 0x4065B7 # xrefs to the decryption function
DEC_END = 0x406655 # xrefs to the decryption function
xrefs = idautils.CodeRefsTo(DEC_START, 0)
indexes = {}
for x in xrefs:
ea = idc.prev_head(x)
t = idc.get_operand_type(ea, 1)
if t == idc.o_imm:
idx = idc.get_operand_value(ea, 1)
indexes[ea] = idx
# loop through collected indexes
for ea, idx in indexes.items():
ql.reg.eax = idx
ql.run(begin=0x4065B7, end=0x406654)
# set decrypted string as ida comment
idc.set_cmt(ea, readString(ql, ql.reg.eax), 1)
# read string from memory address
def readString(ql, addr):
res = ""
while True:
c = ql.mem.read(addr, 1).decode()
if c == '\x00':
break
res += c
addr += 1
return res