Deep Analysis of Mars Stealer
Introduction
Mars Stealer is an improved copy of Oski Stealer. I saw alot of tweets recently about it so i decided to write an analysis of the newer version V8. Enjoy reading!
- Diffrences from the previous version:
- Anti analysis technique
- Diffrent encryption algoithm
- Introudcing new anti debug technique
- New configuration format
- External dlls are in one zip file
Overview
Anti-Analysis
Opening mars stealer in ida we can see an anti-analysis trick called Opaque Predicates it’s a commonly used technique in program obfuscation, intended to add complexity to the control flow.
This obfuscation simply takes an absolute jump (JMP) and transforms it into two conditional jumps (JZ/JNZ). Depending on the value of the Zero flag (ZF), the execution will follow the first or second branch.
However, disassemblers are tricked into thinking that there is a fall-through branch if the second jump is not taken (which is impossible as one of them must be taken) and tries to disassemble the unreachable instructions (often invalid) resulting in garbage code.
the deobfuscation is simple, we just need to patch the first conditional jump to an absolute jump and nop out the second jump, we can use IDAPython to achieve this:
import idc
ea = 0
while True:
ea = min(ida_search.find_binary(ea,idc.BADADDR, "74 ? 75 ?",16 ,idc.SEARCH_NEXT | idc.SEARCH_DOWN), # JZ / JNZ
ida_search.find_binary(ea,idc.BADADDR, "75 ? 74 ?",16, idc.SEARCH_NEXT | idc.SEARCH_DOWN)) # JNZ / JZ
if ea == idc.BADADDR:
break
idc.patch_byte(ea, 0xEB)
idc.patch_byte(ea+2, 0x90)
idc.patch_byte(ea+3, 0x90)
idc.patch_byte(ea+4, 0x90)
now we can see a clear view , after reversing and renaming
First Mars get a handle to kernel32.dll by parsing InLoadOrderModuleList
then it passes the handle to a fucntion that loops over the exported functions of the DLL to get the address of the LocalAlloc()
and VirtualProtect()
functions.
String Encryption
After that it decrypts some strings used for some checks , the decryption is a simple xor function
We can although see that the xor function is refrenced in another function which i renamed as Decrypt_String_2 if the malware passes the checks which we will see soon it decrypt those string which contanis strings needed for the malware to steal sensitive data .
We use idapython script to get those strings and rename the variables to make reversing easier
import string
def sanitize_string(name):
return "".join([c for c in name if c in string.ascii_letters])[:20].capitalize()
def X0r(key, data, length):
res = ""
for i in range(length):
res += chr(key[i] ^ data[i])
return res
start_Addrs = [0x00401770,0x00401990 ]
end_Addrs = [0x00401967,0x0405444 ]
string_list = []
dectypred_data = b''
addrs = []
for i in range(len(start_Addrs)):
ea = start_Addrs[i]
end = end_Addrs[i]
while ea <= end:
if idc.get_operand_type(ea, 0) == idc.o_imm:
addrs.append((idc.get_operand_value(ea, 0)))
if len(addrs) == 3:
length = addrs[0]
data = idc.get_bytes(addrs[1], length)
key = idc.get_bytes(addrs[2], length)
dectypred_data = X0r(key, data, length)
string_list.append(dectypred_data)
addrs = []
if idc.print_insn_mnem(ea) == "call":
idc.set_cmt(ea, dectypred_data, 1)
if idc.print_insn_mnem(ea) == "mov" and (idc.get_operand_type(ea, 0) == idc.o_mem) and (
idc.get_operand_type(ea, 1) == idc.o_reg):
global_var = idc.get_operand_value(ea, 0)
idc.set_name(global_var, "Str" + sanitize_string(dectypred_data), SN_NOWARN)
ea = idc.next_head(ea, end)
Here is a list of the decrypted strings :
Expand to see more
LoadLibraryA
GetProcAddress
ExitProcess
advapi32.dll
crypt32.dll
GetTickCount
Sleep
GetUserDefaultLangID
CreateMutexA
GetLastError
HeapAllocGetProcessHeap
GetComputerNameA
VirtualProtect
GetCurrentProcess
VirtualAllocExNuma
GetUserNameA
CryptStringToBinaryA
HAL9TH
JohnDoe
6ʧÈ/2022 20:00:00
http://
194.87.218.39
92550737836278980100
/RyC66VfSGP.php
Default
%hu/%hu/%hu %hu:%hu:%hu
open
sqlite3.dll
C:\ProgramData\sqlite3.dll
freebl3.dll
C:\ProgramData\freebl3.dll
mozglue.dll
C:\ProgramData\mozglue.dll
msvcp140.dll
C:\ProgramData\msvcp140.dll
nss3.dll
C:\ProgramData\nss3.dll
softokn3.dll
C:\ProgramData\softokn3.dll
vcruntime140.dll
C:\ProgramData\vcruntime140.dll
.zip
Tag:
IP: IP?
Country: Country?
Working Path:
Local Time:
TimeZone:
Display Language:
Keyboard Languages:
Is Laptop:
Processor:
Installed RAM:
OS:
(
Bit)
Videocard:
Display Resolution:
PC name:
User name:
Domain name:
MachineID:
GUID:
Installed Software:
system.txt
Grabber\%s.zip
%APPDATA%
%LOCALAPPDATA%
%USERPROFILE%
%DESKTOP%
Wallets\
Ethereum
\Ethereum\
keystore
Electrum
\Electrum\wallets\
*.*
ElectrumLTC
\Electrum-LTC\wallets\
Exodus
\Exodus\
exodus.conf.json
window-state.json
\Exodus\exodus.wallet\
passphrase.json
seed.seco
info.seco
ElectronCash
\ElectronCash\wallets\
default_wallet
MultiDoge
\MultiDoge\
multidoge.wallet
JAXX
\jaxx\Local Storage\
file__0.localstorage
Atomic
\atomic\Local Storage\leveldb\
000003.log
CURRENT
LOCK
LOG
MANIFEST-000001
0000*
Binance
\Binance\
app-store.json
Coinomi
\Coinomi\Coinomi\wallets\
*.wallet
*.config
*wallet*.dat
GetSystemTime
lstrcatA
SystemTimeToFileTime
ntdll.dll
sscanf
memset
memcpy
wininet.dll
user32.dll
gdi32.dll
netapi32.dll
psapi.dll
bcrypt.dll
vaultcli.dll
shlwapi.dll
shell32.dll
gdiplus.dll
ole32.dll
dbghelp.dll
CreateFileA
WriteFile
CloseHandle
GetFileSize
lstrlenA
LocalAlloc
GlobalFree
ReadFile
OpenProcess
SetFilePointer
SetEndOfFile
GetCurrentProcessId
GetLocalTime
GetTimeZoneInformation
GetUserDefaultLocaleName
LocalFree
GetSystemPowerStatus
GetSystemInfo
GlobalMemoryStatusEx
IsWow64Process
GetTempPathA
GetLocaleInfoA
GetFileSizeEx
GetFileAttributesA
FindFirstFileA
FindNextFileA
FindClose
GetCurrentDirectoryA
CopyFileA
DeleteFileA
lstrcmpW
GlobalAlloc
FreeLibrary
SetCurrentDirectoryA
CreateFileMappingA
MapViewOfFile
UnmapViewOfFile
FileTimeToSystemTime
GetFileInformationByHandle
GlobalLock
GlobalSize
WideCharToMultiByte
GetWindowsDirectoryA
GetVolumeInformationA
GetVersionExA
GetModuleFileNameA
CreateFileW
CreateFileMappingW
MultiByteToWideChar
CreateThread
GetEnvironmentVariableA
SetEnvironmentVariableA
lstrcpyA
lstrcpynA
InternetOpenA
InternetConnectA
HttpOpenRequestA
HttpSendRequestA
HttpQueryInfoA
InternetCloseHandle
InternetReadFile
InternetSetOptionA
InternetOpenUrlA
InternetCrackUrlA
wsprintfA
CharToOemW
GetKeyboardLayoutList
EnumDisplayDevicesA
ReleaseDC
GetDC
GetSystemMetrics
GetDesktopWindow
GetWindowRect
GetWindowDC
CloseWindow
RegOpenKeyExA
RegQueryValueExA
RegCloseKey
GetCurrentHwProfileA
RegEnumKeyExA
RegGetValueA
CreateDCA
GetDeviceCaps
CreateCompatibleDC
CreateCompatibleBitmap
SelectObject
BitBlt
DeleteObject
StretchBlt
GetObjectW
GetDIBits
SaveDC
CreateDIBSection
DeleteDC
RestoreDC
DsRoleGetPrimaryDomainInformation
GetModuleFileNameExA
CryptUnprotectData
BCryptCloseAlgorithmProvider
BCryptDestroyKey
BCryptOpenAlgorithmProvider
BCryptSetProperty
BCryptGenerateSymmetricKey
BCryptDecrypt
VaultOpenVault
VaultCloseVault
VaultEnumerateItems
VaultGetItemWin8
VaultGetItemWin7
VaultFree
StrCmpCA
StrStrA
PathMatchSpecA
SHGetFolderPathA
ShellExecuteExA
GdipGetImageEncodersSize
GdipGetImageEncoders
GdipCreateBitmapFromHBITMAP
GdiplusStartup
GdiplusShutdown
GdipSaveImageToStream
GdipDisposeImage
GdipFree
CreateStreamOnHGlobal
GetHGlobalFromStream
SymMatchString
HEAD
HTTP/1.1
GET
POST
file
Content-Type: multipart/form-data; boundary=----
Content-Disposition: form-data; name="
Content-Disposition: form-data; name="file"; filename="
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
SOFT:
PROF: ?
PROF:
HOST:
USER:
PASS:
sqlite3_open
sqlite3_prepare_v2
sqlite3_step
sqlite3_column_text
sqlite3_finalize
sqlite3_close
sqlite3_column_bytes
sqlite3_column_blob
encrypted_key
"}
PATH
PATH=
NSS_Init
NSS_Shutdown
PK11_GetInternalKeySlot
PK11_FreeSlot
PK11_Authenticate
PK11SDR_Decrypt
SELECT origin_url, username_value, password_value FROM logins
Cookies\%s_%s.txt
SELECT HOST_KEY , is_httponly , path , is_secure , (expires_utc/1000000)-11644480800 , name , encrypted_value from cookies
TRUE
FALSE
Autofill\%s_%s.txt
SELECT name, value FROM autofill
CC\%s_%s.txt
SELECT name_on_card, expiration_month, expiration_year, card_number_encrypted FROM credit_cards
Card number:
Name on card:
Expiration date:
History\%s_%s.txt
SELECT url FROM urls
Downloads\%s_%s.txt
SELECT target_path, tab_url from downloads
Login Data
Cookies
Web Data
History
SELECT host, isHttpOnly, path, isSecure, expiry, name, value FROM moz_cookies
logins.json
formSubmitURL
usernameField
encryptedUsername
encryptedPassword
guid
SELECT fieldname, value FROM moz_formhistory
SELECT url FROM moz_places
cookies.sqlite
formhistory.sqlite
places.sqlite
\Local State
..\profiles.ini
C:\ProgramData\
Chrome
\Google\Chrome\User Data
ChromeBeta
\Google\Chrome Beta\User Data
ChromeCanary
\Google\Chrome SxS\User Data
Chromium
\Chromium\User Data
Edge_Chromium
\Microsoft\Edge\User Data
Kometa
\Kometa\User Data
Amigo
\Amigo\User Data
Torch
\Torch\User Data
Orbitum
\Orbitum\User Data
Comodo
\Comodo\Dragon\User Data
Nichrome
\Nichrome\User Data
Maxthon5
\Maxthon5\Users
Sputnik
\Sputnik\User Data
EPB
\Epic Privacy Browser\User Data
Vivaldi
\Vivaldi\User Data
CocCoc
\CocCoc\Browser\User Data
Uran
\uCozMedia\Uran\User Data
QIP
\QIP Surf\User Data
Cent
\CentBrowser\User Data
Elements
\Elements Browser\User Data
TorBro
\TorBro\Profile
CryptoTab
\CryptoTab Browser\User Data
Brave
\BraveSoftware\Brave-Browser\User Data
Opera
\Opera Software\Opera Stable\
OperaGX
\Opera Software\Opera GX Stable\
OperaNeon
\Opera Software\Opera Neon\User Data
Firefox
\Mozilla\Firefox\Profiles\
SlimBrowser
\FlashPeak\SlimBrowser\Profiles\
PaleMoon
\Moonchild Productions\Pale Moon\Profiles\
Waterfox
\Waterfox\Profiles\
Cyberfox
\8pecxstudios\Cyberfox\Profiles\
BlackHawk
\NETGATE Technologies\BlackHawk\Profiles\
IceCat
\Mozilla\icecat\Profiles\
KMeleon
\K-Meleon\
Thunderbird
\Thunderbird\Profiles\
passwords.txt
ibnejdfjmmkpcnlpebklmnkoeoihofec
TronLink
nkbihfbeogaeaoehlefnkodbefgpgknn
MetaMask
fhbohimaelbohpjbbldcngcnapndodjp
Binance Chain Wallet
ffnbelfdoeiohenkjibnmadjiehjhajb
Yoroi
jbdaocneiiinmjbjlgalhcelgbejmnid
Nifty Wallet
afbcbjpbpfadlkmhmclhkeeodmamcflc
Math Wallet
hnfanknocfeofbddgcijnmhnfnkdnaad
Coinbase Wallet
hpglfhgfnhbgpjdenjgmdgoeiappafln
Guarda
blnieiiffboillknjnepogjhkgnoapac
EQUAL Wallet
cjelfplplebdjjenllpjcblmjkfcffne
Jaxx Liberty
fihkakfobkmkjojpchpfgcmhfjnmnfpi
BitApp Wallet
kncchdigobghenbbaddojjnnaogfppfj
iWallet
amkmjjmmflddogmhpjloimipbofnfjih
Wombat
nlbmnnijcnlegkjjpcfjclmcfggfefdm
MEW CX
nanjmdknhkinifnkgdcggcfnhdaammmj
GuildWallet
nkddgncdjgjfcddamfgcmfnlhccnimig
Saturn Wallet
fnjhmkhhmkbjkkabndcnnogagogbneec
Ronin Wallet
cphhlgmgameodnhkjdmkpanlelnlohao
NeoLine
nhnkbkgjikgcigadomkphalanndcapjk
Clover Wallet
kpfopkelmapcoipemfendmdcghnegimn
Liquality Wallet
aiifbnbfobpmeekipheeijimdpnlpgpp
Terra Station
dmkamcknogkgcdfhhbddcghachkejeap
Keplr
fhmfendgdocmcbmfikdcogofphimnkno
Sollet
cnmamaachppnkjgnildpdmkaakejnhae
Auro Wallet
jojhfeoedkpkglbfimdfabpdfjaoolaf
Polymesh Wallet
flpiciilemghbmfalicajoolhkkenfel
ICONex
nknhiehlklippafakaeklbeglecifhad
Nabox Wallet
hcflpincpppdclinealmandijcmnkbgn
KHC
ookjlbkiijinhpmnjffcofjonbfbgaoc
Temple
mnfifefkajgofkcjkemidiaecocnkjeh
TezBox
dkdedlpgdmmkkfjabffeganieamfklkm
Cyano Wallet
nlgbhdfgdhgbiamfdfmbikcdghidoadd
Byone
infeboajgfhgbjpjbeppbkgnabfdkdaf
OneKey
cihmoadaighcejopammfbmddcmdekcje
LeafWallet
lodccjjbdhfakaekdiahmedfbieldgik
DAppPlay
ijmpgkjfkbfhoebgogflfebnmejmfbml
BitClip
lkcjlnjfpbikmcmbachjpdbijejflpcm
Steem Keychain
onofpnbbkehpmmoabgpcpmigafmmnjhl
Nash Extension
bcopgchhojmggmffilplmbdicgaihlkp
Hycon Lite Client
klnaejjgbibmhlephnhpmaofohgkpgkd
ZilPay
aeachknmefphepccionboohckonoeemg
Coin98 Wallet
bfnaelmomeimhlpmgjnjophhpkkoljpa
Phantom
hifafgmccdpekplomjjkcfgodnhcellj
Crypto.com
dngmlblcodfobpdpecaadgfbcggfjfnm
Maiar DeFi Wallet
ppdadbejkmjnefldpcdjhnkpbjkikoip
Oasis
hpbgcgmiemanfelegbndmhieiigkackl
MonstaWallet
fcckkdbjnoikooededlapcalpionmalo
MOBOX
jccapkebeeiajkkdemacblkjhhhboiek
Crust Wallet
mgffkfbidihjpoaomajlbgchddlicgpn
Pali Wallet
nphplpgoakhhjchkkhmiggakijnkhfnd
TON Wallet
ldinpeekobnhjjdofggfgjlcehhmanlj
Hiro Wallet
pocmplpaccanhmnllbbkpgfliimjljgo
Slope Wallet
bhhhlbepdkbapadjdnnojkbgioiodbic
Solflare Wallet
pgiaagfkgcbnmiiolekcfmljdagdhlcm
Stargazer Wallet
cgeeodpfagjceefieflmdfphplkenlfk
EVER Wallet
gjkdbeaiifkpoencioahhcilildpjhgh
partisia-wallet
bgjogpoidejdemgoochpnkmdjpocgkha
Ecto Wallet
ifckdpamphokdglkkdomedpdegcjhjdp
ONTO Wallet
agechnindjilpccclelhlbjphbgnobpf
Fractal Wallet
algblmhagnobbnmakepomicmfljlbehg
ADS Wallet
imijjbmbnebfnbmonjeileijahaipglj
Moonlet Wallet
kpjdchaapjheajadlaakiiigcbhoppda
ZEBEDEE
dlcobpjiigpikoobohmabehhmhfoodbb
Argent X StarkNet Wallet
bofddndhbegljegmpmnlbhcejofmjgbn
X-Wallet
mapbhaebnddapnmifbbkgeedkeplgjmf
Biport Wallet
kfdniefadaanbjodldohaedphafoffoh
Typhon Wallet
jaooiolkmfcmloonphpiiogkfckgciom
Twetch Wallet
aijcbedoijmgnlmjeegjaglmepbmpkpi
Leap Wallet
fhfffofbcgbjjojdnpcfompojdjjhdim
Lamden Wallet
agkfnefiabmfpanochlcakggnkdfmmjd
Earth Wallet
lpfcbjknijpeeillifnkikgncikgfhdo
Nami
fecfflganphcinpahcklgahckeohalog
Coin Wallet
ilhaljfiglknggcoegeknjghdgampffk
Beam Web Wallet
dklmlehijiaepdijfnbbhncfpcoeeljf
FShares Wallet
fkhebcilafocjhnlcngogekljmllgdhd
WAGMIswap.io Wallet
laphpbhjhhgigmjoflgcchgodbbclahk
BLUE - Worlds Safest and Simplest Wallet
mkjjflkhdddfjhonakofipfojoepfndk
Unification Web Wallet
jnldfbidonfeldmalbflbmlebbipcnle
Infinity Wallet
ellkdbaphhldpeajbepobaecooaoafpg
Fetch.ai Network Wallet
iokeahhehimjnekafflcihljlcjccdbe
Alby Wallet
omajpeaffjgmlpmhbfdjepdejoemifpe
xBull Wallet
pgojdfajgcjjpjnbpfaelnpnjocakldb
Sugarchain Wallet
pnndplcbkakcplkjnolgbkdgjikjednm
Tronium
fnnegphlobjdpkhecapkijjdkgcjhkib
Harmony
fhilaheimglignddkjgofkcbgekhenbh
Oxygen
cmbagcoinhmacpcgmbiniijboejgiahi
JustLiquidity Wallet
kmmolakhbgdlpkjkcjkebenjheonagdm
AlgoSigner
fnabdmcgpkkjjegokfcnfbpneacddpfh
Goldmint Lite Wallet
bgpipimickeadkjlklgciifhnalhdjhe
GeroWallet
hoighigmnhgkkdaenafgnefkcmipfjon
EO.Finance
nlgnepoeokdfodgjkjiblkadkjbdfmgd
Multi Wallet
nhihjlnjgibefgjhobhcphmnckoogdea
Waves Enterprise Wallet
ehibhohmlpipbaogcknmpmiibbllplph
Bluehelix Wallet
magbanejlegnbcppjljfhnmfmghialkl
Nebulas Wallet
fgkaeeikaoeiiggggbgdcjchmdfmamla
Vtimes
pnlfjmlcjdjgkddecgincndfgegkecke
Crocobit Wallet
bhghoamapcdpbohphigoooaddinpkbai
Authenticator
gaedmjdfmmahhbjefcbgaolhhanlaolb
Authy
oeljdldpnmdbchonielidgobddffflal
EOS Authenticator
ilgcnhelpchnceeipipijaljkblbcobl
GAuth Authenticator
imloifkgjagghnncjkhggdhalmcnfklk
Trezor Password Manager
%s\%s\Local Extension Settings\%s
%s\CURRENT
%s\%s\Sync Extension Settings\%s
%s\%s\IndexedDB\chrome-extension_%s_0.indexeddb.leveldb
Plugins\
HARDWARE\DESCRIPTION\System\CentralProcessor\0
ProcessorNameString
SOFTWARE\Microsoft\Windows NT\CurrentVersion
ProductName
x64
x86
DISPLAY
SOFTWARE\Microsoft\Cryptography
MachineGuid
SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
DisplayName
DisplayVersion
screenshot.jpg
ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890
/c timeout /t 5 & del /f /q "%s" & exit
C:\Windows\System32\cmd.exe
Dynamic linking
The adress of GetProcAddress()
and LoadLibraryA()
is retrieved by the same method in Dynamic_Linking_1 looping over the exported functions of the kernel32.DLL , then it uses LoadLibraryA()
to Load the specified module into the address space and get a handle that get passed to GetProcAddress()
to retrieve the address of an exported function from the specified dynamic-link library.
Dynamic_Linking_2 is loading the APIs only needed to do some checks if it passes it will load others needed for stealing functionality.
dword_42774 is GetProcAddress()
it is called in other function which is Dynamic_Linking_3 that will load other APIs needed for stealing functionality.
We use idapython to rename the global variables with the api name to make reversing easier
import idc
start_Addrs = [0x00415F86,0x00415FC0 ,0x004161A0 ]
end_Addrs = [0x00415FB7,0x00416176,0x00417034]
string_list = []
for i in range(len(start_Addrs)):
ea = start_Addrs[i]
end = end_Addrs[i]
while ea <= end:
if (idc.print_insn_mnem(ea) == "push" )and (idc.get_operand_type(ea, 0) == idc.o_imm):
name = idc.get_strlit_contents(idc.get_operand_value(ea, 0)).decode()
if (idc.print_insn_mnem(ea) == "mov" and (idc.get_operand_type(ea, 0) == idc.o_reg)and (idc.get_operand_type(ea, 1) == idc.o_mem)) :
temp_name = idc.get_name(idc.get_operand_value(ea, 1))
if "Str_" == temp_name[0:4]:
name = temp_name[4::]
if (idc.print_insn_mnem(ea) == "mov") and (idc.get_operand_type(ea, 0) == idc.o_mem) and (idc.get_operand_type(ea, 1) == idc.o_reg):
global_var = idc.get_operand_value(ea, 0)
idc.set_name(global_var, name, SN_NOWARN)
ea = idc.next_head(ea, end)
Anti-Sandbox
Since a lot of sandboxes hook and bypass Sleep()
preventing malware being idle over their execution time.
The malware first calls GetTickCount()
function that retrieves the number of milliseconds that have elapsed since the system was started, up to 49.7 days, that is our first timestamp. Then calls the Sleep()
to suspend itself for 16 seconds. calling GetTickCount()
again gets our second timestamp . The malware checks if at least 12 seconds diffrence between the 2 timestampes . If the function returns flase it means that the Sleep()
hasn’t been skipped the malware assumes that it is running in a sandbox and exits immediately.
Anti-CIS
This is one of the easy tricks to check if the malware is not infected users from specific countries.
Mars checks the user language to determine if it’s part of the Commonwealth of Independent States (CIS) countrie it gets the user language ID by using GetUserDefaultLangID and it compares the user language ID to:
Language ID | Country |
---|---|
0x43F | Kazakhstan |
0x443 | Uzbekistan |
0x82C | Azerbaijan |
0x43Fu | Kazakhstan |
0x419u | Russia |
0x423u | Belarus |
If the user language ID matches one of the IDs above, it will exit.
Anti-Emulation
If the malware is executed with the computer name HAL9TH
and the username with JohnDoe
it will exit . This check is done because it is the name given to the Windows Defender Emulator, this technique is used by malware to prevent itself from running in an emulated environment.
Mutex
The malware creates a mutex object using CreateMutexA()
to avoid having more than one instance running. Then calls GetLastError()
which gets the last error, and if the error code is equal to 183 (ERROR_ALREADY_EXIST) it means that mutex already exists and an instance of the malware is already running therefore malware exits.
Anti-Debug
The malware create thread that checks BeingDebugged
flag which is Special flag in system tables, which dwell in process memory and which an operation system sets, can be used to indicate that the process is being debugged. The states of these flags can be verified either by using specific API functions or examining the system tables in memory.
If the malware is being debugged it exits .
The thread is going to keep running until the malware finishes excution or the thread end the malware excution if its being debugged .
Expiration check
The Expiration date variable contains the date 26/04/2022 20:00:00.
Mars uses GetSystemTime() to get current system date and time as SYSTEMTIME structe, then calls sscanf()
to parse the Expiration date to a SYSTEMTIME structe . SystemTimeToFileTime() take SYSTEMTIME structe as argument then convert it to file time and Expiration date although is converted to file time.
If the current time exceedes the Expiration time, the malware calls ExitProcess()
to exit immediately.
Main Functionality
Mars generate random string that will be the name of the zip file contains stolen data.
The communications between c2 and the malware is described as:
- sends a GET request to the C2 URL on the
/RyC66VfSGP.php
endpoint to grab its configuration . - fetches all DLLs on the
/request
endpoint, the libraries are zipped - Stolen data are posted to the C2 on the same URL used in step 1.
Dlls retrieved:
DLL Name | Description | Save path |
---|---|---|
sqlite3.dll | Enables SQLite related operations | none (mars doesnt write it on disk, parsed from memory) |
freebl3.dll | Library for the NSS (Gecko-based browsers) | C:\ProgramData\freebl3.dll |
mozglue.dll | Mozilla Browser Library | C:\ProgramData\mozglue.dll |
msvcp140.dll | Visual C++ Runtime 2015 | C:\ProgramData\msvcp140.dll |
nss3.dll | Network System Services Library (Gecko-based browsers) | C:\ProgramData\nss3.dll |
softokn3.dll | Mozilla Browser Library | C:\ProgramData\softokn3.dll |
vcruntime140.dll | Visual C++ Runtime 2015 | C:\ProgramData\vcruntime140.dll |
Another diffrence from the last version is that sqlite3 isnt written on disk, it just get parsed and passed to another function to get handle to it and start loading needed function , the other dll are written .
Since the C2 was down i got the pcap from Hatching sandbox.
Understanding Configuration Format
configuration is base64 encoded
MXwxfDF8MXwxfDVxRGxQdVZLb1J8RGlzY29yZHwwfCVBUFBEQVRBJVxkaXNjb3JkXExvY2FsIFN0b3JhZ2VcfCp8MXwwfDB8VGVsZWdyYW1
8MHwlQVBQREFUQSVcVGVsZWdyYW0gRGVza3RvcFx0ZGF0YVx8KkQ4NzdGNzgzRDVEM0VGOEMqLCptYXAqLCpjb25maWdzKnwxfDB8MHw=
1|1|1|1|1|5qDlPuVKoR|Discord|0|%APPDATA%\discord\Local Storage\ |*|1|0|0|Telegram|0|%APPDATA%\Telegram Desktop\tdata\ |*D877F783D5D3EF8C*,*map*,*configs*|1|0|0|
import base64
config = base64.b64decode("MXwxfDF8MXwxfDVxRGxQdVZLb1J8RGlzY29yZHwwfCVBUFBEQVRBJVxkaXNjb3JkXExvY2FsIFN0b3JhZ2VcfCp8MXwwfDB8VGVsZWdyYW18MHwlQVBQREFUQSVcVGVsZWdyYW0gRGVza3RvcFx0ZGF0YVx8KkQ4NzdGNzgzRDVEM0VGOEMqLCptYXAqLCpjb25maWdzKnwxfDB8MHw=").decode()
config = config.split("|")
print("First Part : \n" ,config[0:6])
print("Second Part :" )
for i in range(6,len(config),7):
print(config[i:i+7])
First Part :
['1', '1', '1', '1', '1', '5qDlPuVKoR']
Second Part :
['Discord', '0', '%APPDATA%\\discord\\Local Storage\\', '*', '1', '0', '0']
['Telegram', '0', '%APPDATA%\\Telegram Desktop\tdata\\', '*D877F783D5D3EF8C*,*map*,*configs*', '1', '0', '0']
- First part
Config | Meaning |
---|---|
1 | Downloads_history_Flag |
1 | Browser_History_Flag |
1 | Autofill_Flag |
1 | ScreenShoot_Flag |
1 | Self_Deletion_Flag |
5qDlPuVKoR | Explorer Credentials FileName |
- Second part
Config | Meaning |
---|---|
Discord | name for the zip file – will contain all the stolen files that related to the current task.so the name for the zip will be name.zip. |
0 | maybe max size (no indecation of use) |
%APPDATA%\discord\Local Storage\ | An environment variable name and folder name – a starting point for the recursive Grabber. |
* | A regex list – contains multiply parameters that are separated by “,” each one of them is a regex that represents a file type. |
1 | is_Recursive |
0 | Write to zip enabled if 0 |
0 | Exclusion List |
Grabber
lets dig into Config_Grabber
function to see how you it works
after receiving the config we can see the it has a lot of |
so it split the config with |
delimiter and loop through the splited config.
the first part enables/disable some of the stealer functionality then it starts in part 2 which start grapping files wanted.
as example
[‘Discord’, ‘0’, ‘%APPDATA%\discord\Local Storage\’, ‘*’, ‘1’, ‘0’, ‘0’]
it start recurseively grabbing all files in discord\\Local Storage\\
under %APPDATA%
and put them in discord.zip
If there is more than one regex as in
[‘Telegram’, ‘0’, ‘%APPDATA%\Telegram Desktop\tdata\’, ‘D877F783D5D3EF8C,map,configs’, ‘1’, ‘0’, ‘0’]
it loops through them and call Recursive_Grabber
with each regex .
Browsers
Mars steals credentials from browsers by static paths. It has four different methods to steal data from different types of browses, like Gecko-based browsers, Opera, Internet Explorer and Chromium-based browsers.
Data Extraction
All the extraction functions have the same scheme:
- The malware saves the addresses of the functions from sqlite3.dll
- sqlite3_open
- sqlite3_prepare_v2
- sqlite3_step
- sqlite3_column_bytes
- sqlite3_column_blob
- sqlite3_column_text
- sqlite3_column_finalize
- sqlite3_column_close
- It generates a random string (length of 8 characters) and copies the DB file to a temp folder named like the random string – all the extractions methods will be on the copied DB. In order to extract the data from the DB, the malware has to create the SQL query and query the DB using sqlite3.dll functions.
- The malware opens the DB by using sqlite3_open and passes the DB path.
- It calls to sqlite3_prepare_v2, the function gets a handle to DB and the SQL query and returns a statement handle.
- By using sqlite3_column_bytes/sqlite3_column_blob/sqlite3_column_text, the malware can get the results from the queries
- The Credentials in Chromium-based browsers DB are encrypted by DPAPI and, therefore, the malware uses the function CryptUnprotectData to decrypt the Credentials.
Mars steals information from the Windows Vault, which is the default storage vault for the credential manager information. This is done through the use of Vaultcli.dll, which encapsulates the necessary functions to access the Vault. The malware loops through its items using:
- VaultEnumerateVaults
- VaultOpenVault
- VaultEnumerateItems
- VaultGetItem
- VaultFree
Targeted DB Files
File Name | Affected Software |
---|---|
History | Chromium-based browsers |
Login Data | Chromium-based browsers |
Cookies | Chromium-based browsers |
Web Data | Chromium-based browsers |
formhistory.sqlite | Gecko-based browsers |
cookies.sqlite | Gecko-based browsers |
signongs.sqlite | Gecko-based browsers |
places.sqlite | Gecko-based browsers |
Queries Used
Query | Target Browser | Enabled |
---|---|---|
SELECT target_path, tab_url from downloads | chromium , opera | by default this feature is disabled, enabled if Downloads_history_Flag is set to 1 |
SELECT name, value FROM autofill | chromium , opera | by default this feature is disabled, enabled if Autofill_Flag is set to 1 |
SELECT url FROM urls | chromium , opera | by default this feature is disabled,enabled if Browser_History_Flag is set to 1 |
SELECT action_url, username_value, password_value FROM logins | chromium , opera | enabled by default |
SELECT HOST_KEY, is_httponly, path, is_secure, (expires_utc/1000000)-11644480800, name, encrypted_value from cookies | chromium , opera | enabled by default |
SELECT name_on_card, expiration_month, expiration_year, card_number_encrypted FROM credit_cards | chromium , opera | enabled by default |
SELECT host, isHttpOnly, path, isSecure, expiry, name, value FROM moz_cookies | gecko | enabled by default |
SELECT url FROM moz_places | gecko | by default this feature is disabled,enabled if Browser_History_Flag is set to 1 |
SELECT fieldname, value FROM moz_formhistory | gecko | enabled by default |
Cryptocurrency Wallets via browser extensions
Mars appears to also target additional Chrome-based browser extensions related to two-factor authentication (2FA) .
Mars steal files from 3 folders :
- \Local Extension Settings\Extension ID from Google Store
- \Sync Extension Settings\ Extension ID from Google Store
- \IndexedDB\Domain Name.indexeddb.leveldb
as example if the victim uses Google Chrome with a crypto browser wallet extension, the extension files will be stored in:
C:\Users\Username\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\Extension ID from Google Store C:\Users\Username\AppData\Local\Google\Chrome\User Data\Default\Sync Extension Settings\ Extension ID from Google Store C:\Users\Username\AppData\Local\Google\Chrome\User Data\Default\IndexedDB\Domain Name.indexeddb.leveldb
Type | Extension name | Extension id |
---|---|---|
Crypto | TronLink | ibnejdfjmmkpcnlpebklmnkoeoihofec |
Crypto | MetaMask | nkbihfbeogaeaoehlefnkodbefgpgknn |
Crypto | Binance Chain Wallet | fhbohimaelbohpjbbldcngcnapndodjp |
Crypto | Yoroi | ffnbelfdoeiohenkjibnmadjiehjhajb |
Crypto | Nifty Wallet | jbdaocneiiinmjbjlgalhcelgbejmnid |
Crypto | Math Wallet | afbcbjpbpfadlkmhmclhkeeodmamcflc |
Crypto | Coinbase Wallet | hnfanknocfeofbddgcijnmhnfnkdnaad |
Crypto | Guarda | hpglfhgfnhbgpjdenjgmdgoeiappafln |
Crypto | EQUAL Wallet | blnieiiffboillknjnepogjhkgnoapac |
Crypto | Jaxx Liberty | cjelfplplebdjjenllpjcblmjkfcffne |
Crypto | BitApp Wallet | fihkakfobkmkjojpchpfgcmhfjnmnfpi |
Crypto | iWallet | kncchdigobghenbbaddojjnnaogfppfj |
Crypto | Wombat | amkmjjmmflddogmhpjloimipbofnfjih |
Crypto | MEW CX | nlbmnnijcnlegkjjpcfjclmcfggfefdm |
Crypto | GuildWallet | nanjmdknhkinifnkgdcggcfnhdaammmj |
Crypto | Saturn Wallet | nkddgncdjgjfcddamfgcmfnlhccnimig |
Crypto | Ronin Wallet | fnjhmkhhmkbjkkabndcnnogagogbneec |
Crypto | NeoLine | cphhlgmgameodnhkjdmkpanlelnlohao |
Crypto | Clover Wallet | nhnkbkgjikgcigadomkphalanndcapjk |
Crypto | Liquality Wallet | kpfopkelmapcoipemfendmdcghnegimn |
Crypto | Terra Station | aiifbnbfobpmeekipheeijimdpnlpgpp |
Crypto | Keplr | dmkamcknogkgcdfhhbddcghachkejeap |
Crypto | Sollet | fhmfendgdocmcbmfikdcogofphimnkno |
Crypto | Auro Wallet | cnmamaachppnkjgnildpdmkaakejnhae |
Crypto | Polymesh Wallet | jojhfeoedkpkglbfimdfabpdfjaoolaf |
Crypto | ICONex | flpiciilemghbmfalicajoolhkkenfel |
Crypto | Nabox Wallet | nknhiehlklippafakaeklbeglecifhad |
Crypto | KHC | hcflpincpppdclinealmandijcmnkbgn |
Crypto | Temple | ookjlbkiijinhpmnjffcofjonbfbgaoc |
Crypto | TezBox | mnfifefkajgofkcjkemidiaecocnkjeh |
Crypto | Cyano Wallet | dkdedlpgdmmkkfjabffeganieamfklkm |
Crypto | Byone | nlgbhdfgdhgbiamfdfmbikcdghidoadd |
Crypto | OneKey | infeboajgfhgbjpjbeppbkgnabfdkdaf |
Crypto | LeafWallet | cihmoadaighcejopammfbmddcmdekcje |
Crypto | DAppPlay | lodccjjbdhfakaekdiahmedfbieldgik |
Crypto | BitClip | ijmpgkjfkbfhoebgogflfebnmejmfbml |
Crypto | Steem Keychain | lkcjlnjfpbikmcmbachjpdbijejflpcm |
Crypto | Nash Extension | onofpnbbkehpmmoabgpcpmigafmmnjhl |
Crypto | Hycon Lite Client | bcopgchhojmggmffilplmbdicgaihlkp |
Crypto | ZilPay | klnaejjgbibmhlephnhpmaofohgkpgkd |
Crypto | Coin98 Wallet | aeachknmefphepccionboohckonoeemg |
2FA | Authenticator | bhghoamapcdpbohphigoooaddinpkbai |
2FA | Authy | gaedmjdfmmahhbjefcbgaolhhanlaolb |
2FA | EOS Authenticator | oeljdldpnmdbchonielidgobddffflal |
2FA | GAuth Authenticator | ilgcnhelpchnceeipipijaljkblbcobl |
2FA | Trezor Password Manager | imloifkgjagghnncjkhggdhalmcnfklk |
Crypto Wallets
Mars does not just stop at targeting crypto currencies via browser extensions. Many people prefer not to use third-party applications and services to store their digital currency. Mars will go through various folders looking for specific files related to cryptocurrency.
The first paramter detmerines the path if 0 then it’s under %appdata% if 1 it’s under %localappdata%
then it search for other wallets with regex *wallet*.dat
under %appdata%
Mars have dedicated functionality to target the following crypto wallets:
Wallet name | Wallet folder | Regex |
---|---|---|
Ethereum | %appdata%\Ethereum\ | keystore |
Electrum | %appdata%\Electrum\wallets\ | . |
Electrum LTC | %appdata%\Electrum-LTC\wallets\ | . |
Exodus | %appdata%\Exodus\ | exodus.conf.json, window-state.json, \Exodus\exodus.wallet\, passphrase.json, seed.seco, info.seco |
Electron Cash | %appdata%\ElectronCash\wallets\ | default_wallet |
MultiDoge | %appdata%\MultiDoge\ | multidoge.wallet |
Jaxx | %appdata%\jaxx\Local Storage\ | file__0.localstorage |
Atomic | %appdata%\atomic\Local Storage\leveldb\ | 000003.log, CURRENT, LOCK, LOG, MANIFEST.000001, 0000* |
Binance | %appdata%\Binance\ | app-store.json |
Coinomi | %localappdata%\Coinomi\Coinomi\wallets\ | *.wallet, *.config |
Other wallets | %appdata% | *wallet*.dat |
System info
The malware grabs system info and store it in system.txt file
- IP and country
- Working path to EXE file
- Local time and time zone
- Language system
- Language keyboard layout
- Notebook or desktop
- Processor model
- Computer name
- User name
- Domain computer name
- Machine ID
- GUID
- Installed software and their versions
Mars althouge takes screenshot and then add all stolen files to a zip file which it will exfiltrate back to the c2 and get loader config.
Loader
Malware gets loader config as a response after exfiltrating data. This config looks like download_URL|An environment variable name and folder name |startup_parameter| .
After pasring the config Mars calls download_file()
function with the url and a path which the file will be saved in . Then calls ShellExecuteExA()
to execute executable with give paramters retrieved from the config.
Self Deletion
Malware gets the path to itself by using GetModuleFileName()
and calls ShellExecuteExA()
which executes the following command
"C:/Windows/System32/cmd.exe" /c timeout /t 5 & del /f / path_To_file & exit
After 5 seconds the executable will be deleted.
Generalized idapython Script using patterns
import idautils , idc, idaapi, ida_search, ida_bytes, ida_auto
import string
seg_mapping = {idaapi.getseg(x).name: (idaapi.getseg(x).start_ea, idaapi.getseg(x).end_ea) for x in
idautils.Segments()}
start = seg_mapping[0x1][0]
end = seg_mapping[0x1][1]
def sanitize_string(name):
return "".join([c for c in name if c in string.ascii_letters])[:20].capitalize()
def Xor(key, data, length):
res = ""
for i in range(length):
res += chr(key[i] ^ data[i])
return res
def getData (addr):
key_addr = idc.prev_head(addr)
data_addr = idc.prev_head(key_addr)
key_length_addr = idc.prev_head(data_addr)
length = idc.get_operand_value(key_length_addr, 0)
key = idc.get_bytes(idc.get_operand_value(key_addr,0),length)
data = idc.get_bytes(idc.get_operand_value(data_addr,0),length)
return key , data ,length
def rename_APIs(ea,end):
func_addr = ea
for i in range(20):
if (idc.print_insn_mnem(ea) == "push" )and (idc.get_operand_type(ea, 0) == idc.o_imm):
name = idc.get_strlit_contents(idc.get_operand_value(ea, 0)).decode()
break
if (idc.print_insn_mnem(ea) == "mov" and (idc.get_operand_type(ea, 0) == idc.o_reg)and (idc.get_operand_type(ea, 1) == idc.o_mem)) :
temp_name = idc.get_name(idc.get_operand_value(ea, 1))
if "Str_" == temp_name[0:4]:
name = temp_name[4::]
break
ea = idc.prev_head(ea)
ea = func_addr
for i in range(20):
if (idc.print_insn_mnem(ea) == "mov") and (idc.get_operand_type(ea, 0) == idc.o_mem) and (idc.get_operand_type(ea, 1) == idc.o_reg):
global_var = idc.get_operand_value(ea, 0)
idc.set_name(global_var, name, SN_NOWARN)
return name
ea = idc.next_head(ea, end)
def API_resolve(start,end):
Loadlibrarya_addr = 0x0
GetProcAddress_pattern = "8B 55 ?? 52 8B 45 ?? 8B 4D ?? 8B 55 ?? 03 14 ?? 52 E8 ?? ?? ?? ?? 83 C4 ?? 85 C0 75 ??"
GetProcAddress_addr = ida_search.find_binary(start, end, GetProcAddress_pattern, 16, idc.SEARCH_DOWN)
GetProcAddress_addr = idaapi.get_func(GetProcAddress_addr).start_ea
print('[*] Traget fucntion found at {}'.format(hex(GetProcAddress_addr)))
for ref in idautils.XrefsTo(GetProcAddress_addr):
addr = ref.frm
x = rename_APIs(addr, end)
if "Loadlibrarya" in x:
Loadlibrarya_addr = idc.get_operand_value(idc.next_head(idc.next_head(addr, end), end), 0)
new_GetProcAddress_addr = idc.get_operand_value(idc.next_head(idc.next_head(addr, end), end), 0)
for ref in idautils.XrefsTo(new_GetProcAddress_addr):
addr = ref.frm
rename_APIs(addr, end)
for ref in idautils.XrefsTo(Loadlibrarya_addr):
addr = ref.frm
rename_APIs(addr, end)
def Strings_resolve(start,end):
xor_pattern = "8b 4d ?? 03 4d ?? 0f be 19 8b 55 ?? 52 e8 ?? ?? ?? ?? 83 c4 ?? 8b c8 8b 45 ?? 33 d2 f7 f1 8b 45 ?? 0f be 0c 10 33 d9 8b 55 ?? 03 55 ?? 88 1a eb be"
xor_fun_addr = ida_search.find_binary(start, end, xor_pattern, 16, idc.SEARCH_DOWN)
xor_fun_addr = idaapi.get_func(xor_fun_addr).start_ea
print('[*] Traget fucntion found at {}'.format(hex(xor_fun_addr)))
for ref in idautils.XrefsTo(xor_fun_addr):
addr = ref.frm
key, data, length = getData(addr)
decrypt_string = Xor(key, data, length)
idc.set_cmt(addr, decrypt_string, 1)
ea = idc.next_head(idc.next_head(addr, end),end)
global_var = idc.get_operand_value(ea, 0)
idc.set_name(global_var, "Str_" + sanitize_string(decrypt_string), SN_NOWARN)
def Anit_Reverse():
ea = 0
while True:
ea = min(ida_search.find_binary(ea, idc.BADADDR, "74 ? 75 ?", 16, idc.SEARCH_NEXT | idc.SEARCH_DOWN),
# JZ / JNZ
ida_search.find_binary(ea, idc.BADADDR, "75 ? 74 ?", 16,
idc.SEARCH_NEXT | idc.SEARCH_DOWN)) # JNZ / JZ
if ea == idc.BADADDR:
break
idc.patch_byte(ea, 0xEB)
idc.patch_byte(ea + 2, 0x90)
idc.patch_byte(ea + 3, 0x90)
idc.patch_byte(ea + 4, 0x90)
def main():
Anit_Reverse()
Strings_resolve(start,end)
API_resolve(start,end)
main()
for more Idapython scripts check my repo .
IOCs
- Hashes:
- md5 : 880924E5583978C615DD03FF89648093
- sha1 : EF759F6ECA63D6B05A7B6E395DF3571C9703278B
- sha256 : 4bcff4386ce8fadce358ef0dbe90f8d5aa7b4c7aec93fca2e605ca2cbc52218b
- imphash : 4E06C011D59529BFF8E1F1C88254B928
- ssdeep : 3072:U/E8k9fjpIg+zNch12KbAwSaSMtmSu4/bVBt4b8EG:U/E8k9bwz6/tJc/4xM8EG
- Mutex : 92550737836278980100
- Files:
- C:\ProgramData\freebl3.dll
- C:\ProgramData\mozglue.dll
- C:\ProgramData\msvcp140.dll
- C:\ProgramData\nss3.dll
- C:\ProgramData\softokn3.dll
- C:\ProgramData\vcruntime140.dll
- C2 Server : 194.87.218.39
- C2 Domains:
- http://194[.]87[.]218[.]39/request
- http://194[.]87[.]218[.]39/RyC66VfSGP[.]php
YARA
rule Mars_Stealer: Mars Stealer
{
meta:
Author = "X__Junior"
Description = "Mars Stealer v8 Detection"
strings:
$xor ={8b 4d ?? 03 4d ?? 0f be 19 8b 55 ?? 52 e8 ?? ?? ?? ?? 83 c4 ?? 8b c8 8b 45 ?? 33 d2 f7 f1 8b 45 ?? 0f be 0c 10 33 d9 8b 55 ?? 03 55 ?? 88 1a eb be}
$debug = {64 A1 30 00 00 00 80 78 02 00}
$thread_func = {B8 01 00 00 00 85 ?? 74 ?? E8 ?? ?? ?? ?? 85 ?? 74 ?? 6A 00 FF ?? ?? ?? ?? ?? 6A ?? FF ?? ?? ?? ?? ?? EB ??}
$api1 = "LocalAlloc" ascii
$api2 = "VirtualProtect" ascii
$api3 = "SetFileTime" ascii
$api4 = "LocalFileTimeToFileTime" ascii
$api5 = "HeapFree" ascii
$api6 = "VirtualFree" ascii
$api7 = "VirtualAlloc" ascii
$s1 = "DPAPI" ascii
$s2 = "memset" ascii
$s3 = "msvcrt.dll" ascii
$s4 = "_mbsnbcpy" ascii
$s5 = "_mbsstr" ascii
condition:
uint16(0) == 0x5A4D and 2 of($api*) and 3 of($s*) and $debug and $xor and $thread_func
}
Conclusion
The last sample of mars i saw came packed with custom packer , easy to unpack with x32dbg by just setting a breakpoint on VirtualAlloc()
, nothing else was changed except for the C2 .
References
- Great analysis of the previous version https://3xp0rt.com/posts/mars-stealer
- https://lp.cyberark.com/rs/316-CZP-275/images/CyberArk-Labs-Racoon-Malware-wp.pdf