I drafted this article long time ago, shortly after releasing an overview of Kpot, but I never followed-up on this, anyhow, recently I had some time and thought to polish it a bit and just publish.
I will jump straight to the point, but, for those of you that are not familiar with Qiling
just have a look at the repository and documentation portal, you won’t be disappointed!
Last but not least, if you want to follow along you will need ghidra_bridge, so to have Qiling and Ghidra on the same page, since the latter one “speaks” only Python2.7, whereas the former requires Python3 and as you guessed, the bridge will let you run - kind of - Python3 within the NSA’s tool.
Let it be Ghidra
I take as granted that you had a look at the previous analysis and have an idea how the decryption function was discovered and reversed, the whole point of working with Qiling in this scenario is - as lazy reverser - to let the tool do the heavy lifting and let us have the rest of the fun.
For this version of Kpot, two emulation approaches are possible, but the techniques introduced here, will serves you well for any malware that relies on the same code implementation.
The choice
Opt - 1. Since strings gets decrypted in bulk (within one huge single function), soon after the main entry in the code, emulating the wrapper function that hosts all calls to the mw_func_string_decrypt
, could be a quick win. Intercepting all MOV
operations, when the content of EAX
is moved into the global array, would grant access to the decrypted string.
In this case, it becomes trivial to check for the pattern {a3 ?? 5? 41 00 }
and build emulation around that, but we will go down another road instead…
Opt - 2. Emulate only the function in charge of the string decryption and craft all the expected parameters
We will investigate this 2nd method, since more precise and, as far as I could see, no one showed how to do this with Qiling, which is not the case for the first strategy.
First thing first, let’s do a quick recap about the decryption function, which uses __cdecl calling convention and it takes three parameters,
- Decryption key
- Encrypted string
- Length of the encrypted string
|
|
We do now a fast forward and assume the following (yet again, check the previous blog, which explains all steps how we reached this point):
- all xref calls to the string decryption function, were identified (via scripting) with Ghidra
- for every xref call, parameters (decryption key, encrypted string and length of the same) were extracted
- finally, everything was stored in a basic data structure, such as, a list of strings and tuples
buffer:list = [
key, (enc_str, enc_str_start_address)
]
after harvesting all the required details, the buffer
structure, would look like this
|
|
Maximum effort
At this point, we are just missing one single step, the emulation part, since all the details needed to accomplish Opt - 2
were collected and formatted in a way to be consumed by Qiling.
Main points, aka, what we need to make Qiling work for us:
1 Identify the decryption function, start and ending addresses, which in this case are, 0x405623
and 0x40567d
2 define, where in the malware code, a hook will be placed, e.g. 0x40567c
This is needed, since if you recall before exiting, the decrypt function will store the deobfuscated string in the EAX
register, and our goal is to extract this value before it gets overwritten by some other operation.
3 Since only one function is emulated and not the whole binary, and the same (function) uses __cdecl calling convention, the stack needs also to be prepared, which requires:
PUSH
length of the encrypted stringPUSH
encrypted stringPUSH
decryption keyPUSH
return address, to return once the decryption function has completed
let’s head to the code part
from qiling import *
# stores all printable and not printable decrypted strings
dec_string = list()
start_decryption_function = 0x405623
end_decryption_function = 0x40567d
def extract_eax(ql : core.Qiling):
"""
Hook code, which gets called every time the decryption
function is terminating, but before the EAX register
is modified by some other instructions
"""
decrypted_string = ql.mem.read(ql.reg.eax, 0x50).split(b"\x00")[0].decode()
print("Content of EAX: %s @ %s" % (decrypted_string, ql.enc_str_add))
if decrypted_string.isprintable():
try:
# wanna be pprint for crafted label, so that it can be
# displayed in Ghidra
label_decrypted_string = \
'_'.join(decrypted_string[:40].strip("\\").strip("\"").split())
start() # required to start a transaction (modification) - h/t @justfoxing
# update labels, so to leave untouched the encrypted raw bytes strings
createLabel(toAddr(ql.enc_str_add), label_decrypted_string, False)
end(True)
except Exceptions as err:
print(f"Error: {err}")
# keep all printable and not printable strings
# map the decrypted string and the start address of each.
# e.g. ( hxxp[:]//nkpotu.xyz/Kpot2/gate[.]ph, 0x411798 )
c = (decrypted_string, ql.enc_str_add)
dec_string.append(c)
def emulator():
ql = Qiling([
"kpotv1/soa_pdf_unpacked.bin"],
rootfs="qiling/examples/scripts/rootfs/x86_windows",
libcache=True)
# hook decrypt function before it exits
ql.hook_address(extract_eax, 0x40567c)
for key, (enc_str, enc_str_addr) in buffer:
key = bytes(key)
enc_str = bytes(enc_str.bytes)
# return address
ret = ql.stack_pop()
### push functions args ####
# 3rd arg: len of enc_str, int
ql.stack_push(len(enc_str))
# 2nd arg: enc_string, bytes
ptr = ql.os.heap.alloc(len(enc_str))
ql.mem.write(ptr, enc_str)
ql.stack_push(ptr)
# 1 arg: decryption key, bytes
ptr = ql.os.heap.alloc(len(key))
ql.mem.write(ptr, key)
ql.stack_push(ptr)
# finally, push the returns address,
ql.stack_push(ret)
# expand `ql` object, carring start address
# (that will be used within Ghidra) of the
# decrypted string
ql.enc_str_add = enc_str_addr
ql.run(begin=start_decryption_function, end=end_decryption_function)
emulator()
Below, how Ghidra’s listing view looks like after updating the labels with the above script.
One last note, in the example, the start and end addresses of the target function were hardcoded, but everything can be implemented more dynamically, leveraging a YARA rule to track down the function, something like the one below
rule kpotv1_decrypt_strings_function
{
meta:
author = "_raw_data_"
tlp = "WHITE"
version = "1.0"
created = "2020-04-13"
modified = "2020-04-13"
description = "Kpotv1 string decryption routine"
reference = "https://raw-data.gitlab.io/post/kpotv1/"
strings:
$dec_str_fun = { 5? 8b ?? 5? 5? 5? 8b 75 10 8d 46 01 57 50 e8 ?? ?? ?? ??
33 db 59 8b f8 85 f6 74 ?? 8b 45 08 2b c7 89 45 fc 8b 4d
0c 8d 34 3b e8 ?? ?? ?? ?? 8b c8 33 d2 8b c3 f7 f1 8b 45
0c 8b 4d fc 8a 04 02 32 04 31 43 88 06 3b 5d 10 72 ?? 8b
75 10 c6 44 37 ff 00 8d 4? }
condition:
$dec_str_fun
}
From here, calculating the max and min addresses within the function becomes trivial and could be implemented with a snippet like this
fn = getFunctionContaining("MEMORY-ADDRESS-WHERE-YARA-RETURNED-A-MATCH")
# get location of the first instruction within the function
start_decryption_function = fn.getBody().getMinAddress()
# get location of the last instruction within the function
end_decryption_function = fn.getBody().getMaxAddress()