Background
CryptBot is an Info-Stealer malware that has been making its rounds this year, 2022, distributed via cracked software and sketchy websites. Its capabilities range from stealing browser credentials, cookies, and cryptocurrency wallets, and enumerating system information sent to its C2 Server.
As reported by Mandiant, the group UNC3512 is currently using the bot to steal sensitive information with capabilities of taking screenshots, cookie and password stealing, and crypto wallet money stealing. Mandiant also claims that this bot has also been tied to installing additional botnets; DANABOT Mandiant Trending Threats.
This analysis aims to confirm previous findings, report tactics and techniques that were formerly undocumented and contribute this new information to the cybersecurity industry.
Stage 1
Sha256: af0403b7c12d7b7fa9c487eb4a6e68705e9247abf7bc542f77168bd4ed3408fb
Anti-Debugging
Using the PEB structure to determine if the sample is being debugged. It causes an exception if it is. This can be bypassed by using the x64dbg feature Hide Debugger.
API Hashing
Obfuscation through API hashing. From my analysis, I found that the hashing algorithm was just an offshoot of an already discovered hashing algorithm. It differs by adding 10 to the end to make automatic hashing lookup slower. Itβs possible that as CryptBot evolves, it will produce different algorithms by just adding additional values to the hash result.
# Round roate functions:
# https://gist.github.com/trietptm/5cd60ed6add5adad6a34098ce255949a
rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
# Original Work
def hash(function_name):
GENERATED_HASH = 0
for character in function_name:
segment_1 = ror(GENERATED_HASH, 13, 32)
segment_2 = ord(character)
if segment_2 >= 97:
segment_2 -= 32
GENERATED_HASH = segment_1 + segment_2
return GENERATED_HASH
VirtualAlloc, resolved from API hashing, allocated space for the encrypted data, all of which is XOR encrypted. The two buffers produced consist of stage 2 shellcode and the final stage CryptBot executable. For the rest of the analysis, I will be focusing on the final stage executable.
Final Stage Executable
Sha256: f158d132733c7bc9f696a2b626f1324e868030513c12dae8d1273c7c841ad899
API Hashing
This stage relies heavily on API hashing to resolve most of its essential APIs, such as InternetOpenA, RegQueryValueA, and RegOpenKeyA. The hashing algorithm was not included in HashDBand is a new incorporation that I have named cryptbot_ror13_add_10.
# Round roate functions:
# https://gist.github.com/trietptm/5cd60ed6add5adad6a34098ce255949a
rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))
ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))
# Original Work
def hash(function_name):
GENERATED_HASH = 0
for character in function_name:
segment_1 = ror(GENERATED_HASH, 13, 32)
segment_2 = ord(character)
if segment_2 >= 97:
segment_2 -= 32
GENERATED_HASH = segment_1 + segment_2
return GENERATED_HASH + 10
String Encryption
In the .rdata section there isa long list of encrypted strings, in the following format:
- First Byte - Data Length
- Next 5 Bytes - XOR Key
- Rest of Bytes - Encrypted Data
The contents of these strings are path strings for browsers, wallets, crypto wallet browser extensions and IDs. Itβs tedious to decrypt these manually, so I created an IDA Script to handle the decryption.
#MAIN FUNCTION TO BE USED
def string_decryption_ida_v2(start, end):
while start < end:
encrypted_string_size = get_item_size(start)
string_decryption_ida(start)
start += encrypted_string_size
#HELPER FUNCTION
def replace_data(ea, new_string, offset):
ea_start = ea
for s in new_string:
patch_byte(ea, ord(s))
ea+=1
while ea < ea_start+offset:
patch_byte(ea, 0)
ea+=1
create_strlit(ea_start, idc.BADADDR)
#HELPER FUNCTION
def string_decryption_ida(ea):
item_size = get_item_size(ea)
original_ea = ea
data_size = get_bytes(ea, 1)
data_size = int.from_bytes(data_size, byteorder='big')
ea = ea+1
xor_key = get_bytes(ea, 5)
ea = ea+5
output = []
counter = 0
while counter < data_size:
encrypted_byte = get_bytes(ea, 1)
output.append(chr(encrypted_byte[0] ^ xor_key[counter % 5]))
counter+=1
ea = ea+1
data = "".join(output)
print(data)
replace_data(original_ea, data, item_size)
Decrypting the strings also revealed SQL queries for credentials and cookie stealing.
They also showed capabilities to steal system statistics and capabilities
Targeted Crypto Wallets, Password Managers and Authentication
Name | ID |
---|---|
Guarda | hpglfhgfnhbgpjdenjgmdgoeiappafln |
Coin98 | aeachknmefphepccionboohckonoeemg |
Math | afbcbjpbpfadlkmhmclhkeeodmamcflc |
TronLink | ibnejdfjmmkpcnlpebklmnkoeoihofec |
Keplr | dmkamcknogkgcdfhhbddcghachkejeap |
Tezos | ookjlbkiijinhpmnjffcofjonbfbgaoc |
Atomic | fhilaheimglignddkjgofkcbgekhenbh |
Yoroi | ffnbelfdoeiohenkjibnmadjiehjhajb |
Jaxx | cjelfplplebdjjenllpjcblmjkfcffne |
EOS_Authenticator | oeljdldpnmdbchonielidgobddffflal |
Gauth_Authenticator | ilgcnhelpchnceeipipijaljkblbcobl |
Trezor_Password_Manager | imloifkgjagghnncjkhggdhalmcnfklk |
TRUST | egjidjbpglichdcondbcbdnbeeppgdp |
metamask | ejbalbakoplchlghecdalmeeeajnimhm |
Mask Wallet | dfeccadlilpndjjohbjdblepmjeahlmm |
Yoroi | akoiaibnepcedcplijmiamnaigbepmcb |
JAXX Wallet | dmdimapfghaakeibppbfeokhgoikeoci |
Contacted URLs
- dixuip12.top
- luedil01.top
As the creation of this report both URLs are dead
Conclusion
My work confirms previous tactics and techniques documented about the CryptBot malware. It also provides insight into their hashing algorithms and how their implementation slows automatic static analysis. CryptBot, while a standard bot stealer, appears to be very effective because of its prevalence.
π¬ Comment: