CryptBot
Another C++ bot
Overview
This is another C++ bot! According to Malpedia...
A typical infostealer, capable of obtaining credentials for browsers, crypto currency wallets, browser cookies, credit cards, and creates screenshots of the infected system. All stolen data is bundled into a zip-file that is uploaded to the c2.
Samples
Samples available on UnpacMe- Packed 7ccda59528c0151bc9f11b7f25f8291d99bcf541488c009ef14e2a104e6f0c5d
- Unpacked
cfbecf45c083efffff6d3000972a66cddb2f26d5c1845a697351b132e65049e0
References
Analysis
This is a some in-memory string that was captured in a joe sandbox run
Content-Length: Content-Type: multipart/form-data;boundary=httphttpstrue<>S-1-5-18[<apis.google.com>]/[<443>][<www.google.com>][<"facebook">][<www.facebook.com>][<TEMP>][<APPDATA>][<LOCALAPPDATA>][<USERPROFILE>];[<ExternalDownload>][<Anti>][<true>][<UserAgent>][<UAC>]runas[<NTFS>][<Prefix>][<UID: >][<UserName: >][<ComputerName: >][<Info>][<OS: >][<DateTime: >][<UserAgent: >][<Keyboard Languages: >][<Display Resolution: >][<CPU: >][<RAM: >][<GPU: >][<isGodMod: yes>][<isGodMod: no>][<isAdmin: yes>][<isAdmin: no>][<Installed software:>][<Disk:>][<Process:>][<Screenshot>][<InfoFile>][<ScreenFile>][<PasswordFile>][<ChromeDBFolder>][<ChromeExt>][<WalletFolder>][<_Chrome_profile>][<EdgeDB>][<EdgeDBFolder>][<EdgeExt>][<_Edge_profile>][<Desktop>][<DesktopFolder>][<.txt>][<Wallet>]_test.err://[<80>][<OK>][< /c >][<cmd>][<open>][<MessageAfterEnd>][<System Error>][<The application was unable to start correctly (0xc000007b). Click OK to close the application.>][<DeleteAfterEnd>][< /c timeout -t 5 && del ">]
Yara Rule
Some of these strings exist in the binary unencrypted so we can use them for a yara rule
rule cryptbot {
strings:
$s1 = "UID:"
$s2 = "UserName:"
$s3 = "ComputerName:"
$s4 = "DateTime:"
$s5 = "UserAgent:"
$s6 = "Keyboard Languages:"
$s7 = "Display Resolution:"
$s8 = "CPU:"
$s9 = "RAM:"
$s10 = "GPU:"
$s11 = "isGodMod: yes"
$s12 = "isGodMod: no"
$s13 = "isAdmin: yes"
$s14 = "isAdmin: no"
$s15 = "Installed software:"
condition:
all of them
}
Config
Finding a ref to the config in the code.
80 3D ?? ?? ?? ?? 09 cmp byte ptr ptr_config, 9 ; "PSJigdSdi8"
B9 00 01 00 00 mov ecx, 100h
B9 00 01 00 00 mov ecx, 100h
80 3D ?? ?? ?? ?? 09 cmp byte ptr a7m1fqxrljy, 9 ; "7m1fqXrLJy"
import re
import pefile
import struct
file_data = open('/tmp/cryptbot.bin', 'rb').read()
pe = pefile.PE(data = file_data)
image_base = pe.OPTIONAL_HEADER.ImageBase
text_data = None
for s in pe.sections:
if b'.text' == s.Name[:5]:
text_data = s.get_data()
break
assert text_data is not None
eggs = [
rb'\x80\x3D(....)\x09\xB9\x00\x01\x00\x00',
rb'\xB9\x00\x01\x00\x00\x80\x3D(....)\x09'
]
candidate_offsets = []
for egg in eggs:
for m in re.finditer(egg, text_data, re.DOTALL):
try:
candidate_va = struct.unpack('<I', m.group(1))[0]
candidate_offset = pe.get_offset_from_rva(candidate_va - image_base)
candidate_offsets.append(candidate_offset)
except:
print(f"failed for group {m.group(1)}")
pass
def xor_decrypt(data, key):
out = []
for i in range(len(data)):
out.append(data[i] ^ key[i % len(key)])
return bytes(out)
def get_config(data, offset):
key = data[offset:].split(b'\x00')[0]
assert 5 < len(key) < 20
config_data_enc = data[offset + len(key) + 1:]
return xor_decrypt(config_data_enc, key)
config_data = None
for candidate_offset in candidate_offsets:
try:
tmp_config = get_config(file_data, candidate_offset)
if tmp_config[:4] == b'http':
config_data = tmp_config
break
except:
pass
assert config_data is not None
# config parse
config_array = []
for a in config_data.split(b'\x00'):
if not a.isascii():
break
config_array.append(a)
c2 = config_array[0].decode('utf-8')
settings = []
for config_entries in config_array[1:]:
for entry in config_entries.split(b'<>\r\n'):
if len(entry) == 0:
continue
settings.append({'key':entry.split(b'<>_<>')[0].decode('utf-8'), 'value':entry.split(b'<>_<>')[1].decode('utf-8')})
print(c2)
print(settings)
def get_config_from_file(file_path):
file_data = open(file_path, 'rb').read()
pe = pefile.PE(data = file_data)
image_base = pe.OPTIONAL_HEADER.ImageBase
text_data = None
for s in pe.sections:
if b'.text' == s.Name[:5]:
text_data = s.get_data()
break
assert text_data is not None
eggs = [
rb'\x80\x3D(....)\x09\xB9\x00\x01\x00\x00',
rb'\xB9\x00\x01\x00\x00\x80\x3D(....)\x09'
]
candidate_offsets = []
for egg in eggs:
for m in re.finditer(egg, text_data, re.DOTALL):
try:
candidate_va = struct.unpack('<I', m.group(1))[0]
candidate_offset = pe.get_offset_from_rva(candidate_va - image_base)
candidate_offsets.append(candidate_offset)
except:
print(f"failed for group {m.group(1)}")
pass
assert len(candidate_offsets) != 0
config_data = None
for candidate_offset in candidate_offsets:
try:
tmp_config = get_config(file_data, candidate_offset)
if tmp_config[:4] == b'http':
config_data = tmp_config
break
except:
pass
assert config_data is not None
# config parse
config_array = []
for a in config_data.split(b'\x00'):
if not a.isascii():
break
config_array.append(a)
c2 = config_array[0].decode('utf-8')
settings = []
for config_entries in config_array[1:]:
for entry in config_entries.split(b'<>\r\n'):
if len(entry) == 0:
continue
settings.append({'key':entry.split(b'<>_<>')[0].decode('utf-8'), 'value':entry.split(b'<>_<>')[1].decode('utf-8')})
assert len(settings) != 0
final_config = {'c2':c2, 'settings':settings}
return final_config
get_config_from_file('/tmp/cryptbot.bin')
from pathlib import Path
for file in Path('/tmp/samples/').glob('*'):
print(file)
try:
config = get_config_from_file(file)
print(config.get('c2'), 'None')
except Exception as e:
print("ERROR")
print(e)