Bandit Stealer Garbled
Garble GO obfuscation analysis
Overview
This is a new infostealer written in GO that primarily targets browser credentials and crypto wallets. The collected information is uploaded to Telegram with the operator's telegram ID and channel ID hard coded in the binary. Some variants of the stealer use Garble an open source GO obfuscator.
References
- Technical Analysis of Bandit Stealer
- New Info Stealer Bandit Stealer Targets Browsers, Wallets
- Breaking into the Bandit Stealer Malware Infrastructure
- GO IDA parser (works well!)
Samples
According to CloudSek the stealer panel has security flaws that allow some of the data to be accessed without authentication. This example shows the main panel for the stealer http[:]//142.202.240[.]84:8080/csetayukhv.html
Binary Analysis
Early builds of the stealer did not use obfuscation, and contained plaintext strings. These versions were simple to reverse engineer once the method names were recovered using GO IDA parser (works well!). Later versions of the stealer attempted to slightly obfuscate the method names and ultimately moved to using Garble a GO obfuscator.
Garble
Garble is able to obfuscate GO method names, obfuscate strings, and modify control flow. Each option can be enabled separately. We will be analyzing the following Bandit Stealer sample obfuscated with Garble 623a5f4c57cf5b3feb6775508cd6492f89d55ce11f62e0b6fb1020fd730b2e8f
.
Method Name Obfuscation
The method names are obfuscated using a hash which is then base64 encoded. The method metadata recovery process is not possible using our favorite IDA script but we can use GoReSym. When this is run we can see method names like...
{
"Start": 5369065792,
"End": 5369065824,
"PackageName": "h20dLQEZPVaM",
"FullName": "h20dLQEZPVaM.CvGFbRy"
},
{
"Start": 5369072416,
"End": 5369072512,
"PackageName": "h20dLQEZPVaM",
"FullName": "h20dLQEZPVaM.IRlPxsFX"
},
{
"Start": 5369072512,
"End": 5369072608,
"PackageName": "h20dLQEZPVaM",
"FullName": "h20dLQEZPVaM.MRLxEQ"
},
Idea it might be possible to brute force these if you had access to an earlier version of the sample which has the full method names
String Obfuscation
The string obfuscation appears to result in dedicated functions for each obfuscated string which consist of some constants and an algorithm used to recreate the string. The function then converts the resulting byte string into a go string and returns it.
.text:00000001407BC661 mov ecx, 0Ah
.text:00000001407BC666 call runtime_slicebytetostring
.text:00000001407BC66B mov rbp, [rsp+38h+var_8]
.text:00000001407BC670 add rsp, 38h
.text:00000001407BC674 retn
It may be possible to attack this by identifying the dedicated string functions and emulating them!
from unicorn import *
from unicorn.x86_const import *
import struct
from capstone import *
from capstone.x86 import *
uc = Uc(UC_ARCH_X86, UC_MODE_64)
# Setup the stack
stack_base = 0x00100000
stack_size = 0x00100000
RSP = stack_base + (stack_size // 2)
uc.mem_map(stack_base, stack_size)
uc.mem_write(stack_base, b"\x00" * stack_size)
uc.reg_write(UC_X86_REG_RSP, RSP)
# Setup code
code = bytes.fromhex('49 3B 66 10 0F 86 C8 00 00 00 48 83 EC 38 48 89 6C 24 30 48 8D 6C 24 30 48 BA E5 B1 F0 56 65 EA 73 C9 48 89 54 24 18 66 C7 44 24 20 6F 6E 48 BA 02 00 01 05 01 05 05 07 48 89 54 24 22 48 BA 05 07 05 02 00 07 07 05 48 89 54 24 28 31 C0 EB 1D 44 0F B6 4C 34 18 41 29 D1 41 8D 51 E1 88 54 3C 18 41 8D 50 E1 88 54 34 18 48 83 C0 02 48 83 F8 0E 7D 27 0F B6 54 04 22 0F B6 74 04 23 89 D7 31 F2 01 C2 48 83 FF 0A 73 3C 44 0F B6 44 3C 18 41 29 D0 48 83 FE 0A 72 B8 EB 1B 31 C0 48 8D 5C 24 18 B9 0A 00 00 00 E8 D5 B8 88 FF 48 8B 6C 24 30 48 83 C4 38 C3 89 F0 B9 0A 00 00 00 0F 1F 40 00 E8 3B 02 8A FF 89 F8 B9 0A 00 00 00 E8 2F 02 8A FF 90 E8 A9 DB 89 FF E9 24 FF FF FF CC CC CC CC')
target_base = 0x00400000
target_size = 0x00100000
target_end = target_base + len(code)
uc.mem_map(target_base, target_size, UC_PROT_ALL)
uc.mem_write(target_base, b"\x00" * target_size)
uc.mem_write(target_base, code)
data_base = 0x00600000
data_size = 0x00100000
uc.mem_map(data_base, data_size, UC_PROT_ALL)
uc.mem_write(data_base, b"\x00" * data_size)
cs = Cs(CS_ARCH_X86, CS_MODE_64)
cs.detail = True
def trace(uc, address, size, user_data):
insn = next(cs.disasm(uc.mem_read(address, size), address))
#print(f"{address:#010x}:\t{insn.mnemonic}\t{insn.op_str}")
if insn.mnemonic == 'call':
print("Ending on a call!")
uc.emu_stop()
uc.reg_write(UC_X86_REG_R14, data_base)
uc.hook_add(UC_HOOK_CODE, trace, None)
uc.emu_start(target_base, target_end, 0, 0)
ptr_string = uc.reg_read(UC_X86_REG_RBX)
size = uc.reg_read(UC_X86_REG_RCX)
#print(hex(ptr_string))
string_data = uc.mem_read(ptr_string, size)
string = string_data.decode('utf-8')
print(string)
from unicorn import *
from unicorn.x86_const import *
import struct
from capstone import *
from capstone.x86 import *
def decrypt(code_string):
uc = Uc(UC_ARCH_X86, UC_MODE_64)
# Setup the stack
stack_base = 0x00100000
stack_size = 0x00100000
RSP = stack_base + (stack_size // 2)
uc.mem_map(stack_base, stack_size)
uc.mem_write(stack_base, b"\x00" * stack_size)
uc.reg_write(UC_X86_REG_RSP, RSP)
# Setup code
code = bytes.fromhex(code_string)
target_base = 0x00400000
target_size = 0x00100000
target_end = target_base + len(code)
uc.mem_map(target_base, target_size, UC_PROT_ALL)
uc.mem_write(target_base, b"\x00" * target_size)
uc.mem_write(target_base, code)
data_base = 0x00600000
data_size = 0x00100000
uc.mem_map(data_base, data_size, UC_PROT_ALL)
uc.mem_write(data_base, b"\x00" * data_size)
cs = Cs(CS_ARCH_X86, CS_MODE_64)
cs.detail = True
def trace(uc, address, size, user_data):
insn = next(cs.disasm(uc.mem_read(address, size), address))
#print(f"{address:#010x}:\t{insn.mnemonic}\t{insn.op_str}")
if insn.mnemonic == 'call':
#print("Ending on a call!")
uc.emu_stop()
uc.reg_write(UC_X86_REG_R14, data_base)
uc.hook_add(UC_HOOK_CODE, trace, None)
uc.emu_start(target_base, target_end, 0, 0)
#print(uc.mem_read(stack_base, stack_size).replace(b'\x00', b''))
ptr_string = uc.reg_read(UC_X86_REG_RBX)
size = uc.reg_read(UC_X86_REG_RCX)
string_data = uc.mem_read(ptr_string, size)
string = string_data.decode('utf-8')
return string
#print(decrypt('49 3B 66 10 0F 86 C8 00 00 00 48 83 EC 38 48 89 6C 24 30 48 8D 6C 24 30 48 BA E5 B1 F0 56 65 EA 73 C9 48 89 54 24 18 66 C7 44 24 20 6F 6E 48 BA 02 00 01 05 01 05 05 07 48 89 54 24 22 48 BA 05 07 05 02 00 07 07 05 48 89 54 24 28 31 C0 EB 1D 44 0F B6 4C 34 18 41 29 D1 41 8D 51 E1 88 54 3C 18 41 8D 50 E1 88 54 34 18 48 83 C0 02 48 83 F8 0E 7D 27 0F B6 54 04 22 0F B6 74 04 23 89 D7 31 F2 01 C2 48 83 FF 0A 73 3C 44 0F B6 44 3C 18 41 29 D0 48 83 FE 0A 72 B8 EB 1B 31 C0 48 8D 5C 24 18 B9 0A 00 00 00 E8 D5 B8 88 FF 48 8B 6C 24 30 48 83 C4 38 C3 89 F0 B9 0A 00 00 00 0F 1F 40 00 E8 3B 02 8A FF 89 F8 B9 0A 00 00 00 E8 2F 02 8A FF 90 E8 A9 DB 89 FF E9 24 FF FF FF CC CC CC CC'))
#print(decrypt('4C 8D 64 24 C8 4D 3B 66 10 0F 86 57 01 00 00 48 81 EC B8 00 00 00 48 89 AC 24 B0 00 00 00 48 8D AC 24 B0 00 00 00 48 BA 03 DC F5 44 2F 20 21 53 48 89 54 24 1D 48 BA AA 6D 47 01 6F 45 3A 04 48 89 54 24 25 48 BA 01 6F 45 3A 04 01 23 95 48 89 54 24 28 48 BA 72 19 F5 3B 01 EA 20 47 48 89 54 24 30 48 BA A6 18 DF 72 1B 69 53 4E 48 89 54 24 38 48 BA 4D 76 17 0A 06 E4 23 57 48 89 54 24 40 48 BA 09 85 2F E6 28 FC 36 02 48 89 54 24 48 48 BA B2 49 E8 14 19 4D 29 64 48 89 54 24 50 48 BA 2C 2F 26 12 27 1B 32 0B 48 89 54 24 58 48 8D 7C 24 60 48 8D 35 E7 00 A6 00 0F 1F 80 00 00 00 00 48 89 6C 24 F0 48 8D 6C 24 F0 E8 AB D9 FE FF 48 8B 6D 00 31 C0 EB 1A 44 0F B6 4C 34 1D 41 83 C1 E0 44 01 CA 88 54 3C 1D 44 88 44 34 1D 48 83 C0 02 48 83 F8 58 7D 31 0F B6 54 04 58 0F B6 74 04 59 89 D7 31 F2 01 C2 48 83 FF 3B 73 48 44 0F B6 44 3C 1D 41 83 C0 E0 41 01 D0 66 0F 1F 44 00 00 48 83 FE 3B 72 B1 EB 21 31 C0 48 8D 5C 24 1D B9 3B 00 00 00 E8 47 89 FD FF 48 8B AC 24 B0 00 00 00 48 81 C4 B8 00 00 00 C3 89 F0 B9 3B 00 00 00 E8 AB D2 FE FF 89 F8 B9 3B 00 00 00 0F 1F 40 00 E8 9B D2 FE FF 90 E8 15 AC FE FF E9 90 FE FF FF CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC'))
print(decrypt('49 3B 66 10 0F 86 EC 00 00 00 48 83 EC 48 48 89 6C 24 40 48 8D 6C 24 40 48 BA C8 61 12 01 F6 F6 69 3E 48 89 54 24 1A 48 BA 12 01 F6 F6 69 3E 6E 5F 48 89 54 24 1C 48 BA 41 6C 62 9B 2A 69 96 19 48 89 54 24 24 48 BA 00 10 0E 10 00 0E 0D 07 48 89 54 24 2C 48 BA 00 0E 0D 07 0D 10 03 10 48 89 54 24 30 48 BA 0D 00 02 11 07 05 04 04 48 89 54 24 38 31 C0 EB 1D 44 0F B6 4C 34 1A 41 29 D1 41 8D 51 7D 88 54 3C 1A 41 8D 50 7D 88 54 34 1A 48 83 C0 02 48 83 F8 14 7D 29 0F B6 54 04 2C 0F B6 74 04 2D 89 D7 31 F2 01 C2 48 83 FF 12 73 3A 44 0F B6 44 3C 1A 41 29 D0 48 83 FE 12 72 B8 66 90 EB 1B 31 C0 48 8D 5C 24 1A B9 12 00 00 00 E8 CD 0D FD FF 48 8B 6C 24 40 48 83 C4 48 C3 89 F0 B9 12 00 00 00 E8 37 57 FE FF 89 F8 B9 12 00 00 00 E8 2B 57 FE FF 90 E8 A5 30 FE FF 0F 1F 44 00 00 E9 FB FE FF FF CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC'))
TODO
- handle functions that have a call they need to execute
- check for decryption bugs
- write function identification algorithm based on prologue