Intro

In this blog I’ll be covering a recent phishing campaign that was targeting content creators while impersonating to a brand offering a collaboration offer to those creators.

The Phish

The email that the user receives includes a short explanation that the company wants to be his partner, they explain to him when and for how long to put the promo video and of course how much money he will receive as a payment.

At the bottom of the email the user will find a link to the promotion materials and his personal password:

image.png

The promotion materials link leads to Google Drive, there the User will need to download an archive with the name of: H&M Corporation Advertising Contract.zip

The archive contains inside of it several decoy files that are associated with H&M, and a 600MB .scr file with the name: H&M Advertising contract and Payment information.pdf.scr

image-2.png

.NET Loader

Opening the loader in DiE,we can see that the loader is 32bit .NET assembly protected with Smart Assembly:

image.png

I’ve opened the loader in DnSpy to further analyze it. The first thing I see is the confirmation that the loader is protected with Smart Assembly, I can see the PoweredBy section in the static information fields:

image-2.png

Looking at the entry point we can understand that working with the loader in this state won’t be efficient:

image-3.png

I will be using SAE (Simple Assembly Explorer) in order to deobfuscate the code, we can use the deobfuscator feature in SAE:

image-4.png

I’m using the default settings as it’s fits my needs:

image-5.png

Opening the deoubfuscated output file in Dnspy, we can now see a clearer code:

image-6.png

Payload Extraction

There are several interesting actions that happens in the loader:

  1. c000009 instance creation with internal field that will contain a path to the injected process.

image.png

  1. The instance then will be passed to the method c000066.m000022. this method will have several things in it, the first one being a call to the method: c000066.m00007b, passing the string: fInckSommmenn twice.

  2. The method c000066.m00007b will simply fetch resource content from the binary resources:

image-2.png

  1. Then a call to the method c000066.m000019 will be invoked passing the extracted resource content, the string: fInckSommmenn and the instance of c000009

  2. This method will be in charge of decrypting the payload with some Xor routine and it will return the decrypted binary.

image-3.png

  1. After the decryption was done the decrypted binary will be passed alongside with the full path to the injected process to c000066.m00002a method which will do a process injection to the desired process with the decrypted binary content.

image-4.png

I’ve created a powershell script that extract the decrypted binary by invoking the necessery methods:

# Load the file.
$assembly = [System.Reflection.Assembly]::LoadFile("C:\Users\igal\Desktop\loader.exe")

#Initialize "NS005.c000009" object.
$ini = [Activator]::CreateInstance($assembly.Modules[0].GetType("NS005.c000009"),@())

#Retrieve the resource fetching method and invoke it.
$classType2 = $assembly.GetType("NS004.c000066")
$array = $classType2.GetMethod("m00007b").Invoke($null,@("fInckSommmenn", "fInckSommmenn"))

#Invoke the decryption method with the necessary arguments.
$fixedArray = $classType2.GetMethod("m000019").Invoke($null,@($array, "fInckSommmenn", $ini))

#Write the output to a file.
[io.file]::WriteAllBytes('C:\Users\igal\Desktop\payload.bin',$fixedArray)

Vidar Payload

In this part of the blog I will be going through some of the Vidar stealer capabilities, evasion techniques and some anti analysis tricks. Opening the payload in DiE we can see that it’s a 32bit C/C++ binary:

image-2.png

Anti-Analysis Nightmare

I’ve opened the payload in IDA and the first thing that happend is that WinMain was not recognized as a function and rather as instruction:

image-4.png

I’ve tried to convert it to function by pressing P but this wasn’t helpful, so I’ve scrolled a bit down and found out a chunk of data that wasn’t convered as supposed:

image-5.png

Then I pressed C to convert that data to code and now that we have instructions instead of data I’ve marked all the instruction from the beginning of WinMain until the relevent mov - pop - return instructions that marks the end of a function (in my case the instructions range was 0x4156B0 - 0x415891)

Now I start to work with the decompiler view, I’ve noticed that the decompilation process is a bit broken:

image-6.png

One thing that was done here to confuse the decompiler is Opaque Predicate.

“Opaque predicate is a term used in programming to refer to decision making where there is only one possible outcome. This can be achieved through the use of complex or hard-to-understand logic, such as calculating a value that will always return True. Opaque predicates are often used as anti-disassembling techniques, as they can make it difficult for an analyst to understand the code and determine its intent. By using opaque predicates, malware authors can make their code more difficult to reverse engineer, which can help to evade detection and analysis.” (Unprotect Project definition)

image-7.png

We can use @_n1ghtw0lf script for it:

import idc

ea = 0
while True:
    ea =  min(idc.find_binary(ea, idc.SEARCH_NEXT | idc.SEARCH_DOWN, "74 ? 75 ?"),  # JZ / JNZ
              idc.find_binary(ea, idc.SEARCH_NEXT | idc.SEARCH_DOWN, "75 ? 74 ?"))  # JNZ / JZ
    if ea == idc.BADADDR:
    	break
    idc.patch_byte(ea, 0xEB)	# JMP
    idc.patch_byte(ea+2, 0x90)	# NOP
    idc.patch_byte(ea+3, 0x90)	# NOP

After running the script the Decomplier looks a bit better:

image.png

But there is still some code missing because we can see a JUMPOUT instruction, looking at the referenced address in the instruction, we can see that the instruction is:

mov     eax, 0FEB912E8h

clearly that’s wrong and nothing to do with the actual code (and this is caused because the convertation of all the data to code), it can be repaired by simply undefining the instruction. But after that we still can see a unclear jumpout:

image-2.png

again same strange mov instrcution to eax:

mov     eax, 0FEB9C8E8h

it can be fixed by the same approach as before.

After clearing the code we have a “clear” function:

image-4.png

The Author added a lot of junk calls to the code to make our life a bit harder but we can just ignore them and follow the function calls.

Self Termination Triggers

This Vidar payload has several triggers that can occur and lead to self termination of the payload.

  • The first one being usage of VirtualAllocExNuma which is a way for the payload to understand whether he runs on a system with one or more physical CPU:

image-2.png

  • The second check the payload does is checking the physical memory of the computer (whether it’s above 769MB or not) if it’s less then the defined size the payload will terminate:

image-3.png

  • The last check will occur after the strings and api resolving functions(which will be covered in a moment), it will retrieve the computer name and compare it to HAL9TH, it will also retrieve the user name and compare it to JohnDoe. if one of the retrieved values matches one of the strings the payload will terminate itself:

image-4.png

Strings Decryption

As most variants of Vidar, the strings are simply xor’ed. The function receives 3 parameters:

  1. Length
  2. Xor key
  3. Encrypted string

image.png

I’ve used the script written by @eln0ty and modified it abit to fit my needs:

import idc

START = 0x401190
END = 0x40134D
TEMP = 0x0
FLAG = True
'''
[0] = Encrypted String.
[1] = Xor Key.
[2] = Length.
'''
VALUES = [] 

ea = START

# XOR decryption helper function.
def xorDecrypt(encString, xorKey, keyLen):
    decoded = []
    for i in range(0,len(encString)):
        decoded.append(encString[i] ^ xorKey[i % keyLen])
    return bytes(decoded)



while ea <= END:
    # get argument values 
    if idc.get_operand_type(ea, 0) == idc.o_imm:
        VALUES.append(idc.get_operand_value(ea, 0))
    
    if len(VALUES) == 2:
        if idc.get_operand_type(ea, 0) == idc.o_reg:
            VALUES.append(idc.get_operand_value(ea, 1))
    
    if idc.print_insn_mnem(ea) == "call":
        length = VALUES[2]
        data = idc.get_bytes(VALUES[0], length)
        key = idc.get_bytes(VALUES[1], length)
        VALUES = []
        TEMP = ea
        while FLAG:
            ea = idc.next_head(ea, END)
            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):
                dec = xorDecrypt(data, key, length).decode('ISO-8859-1')
                print(f'current location:{hex(ea)}, value will be: {dec}')
                dwordVar = idc.get_operand_value(ea, 0)
                idc.set_cmt(ea, dec, 1)
                idc.set_name(dwordVar, "STR_" + dec, SN_NOWARN)
                FLAG = False
                ea = TEMP
                break


    # move to next instruction
    FLAG = True
    ea = idc.next_head(ea, END)

quick note: some of the names wont be assigned properly due to IDA syntax, so I’ve added the plain string as comment in the dissembler. For example:

image.png

Decoded strings output:

image-3.png

Dynamic API Resolving:

Vidar will user LoadLibraryA and GetProcAddress to resolve the necessery API’s alongside with the strings it decrypted:

image.png

Once again I used the script written by @eln0ty to replace the name of the variables for easier analysis:

import idc

start = 0x420874
end = 0x420901
ea = start

api_names = []

while ea <= end:
    # get GetProcAddress API name
    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):
        addr = idc.get_operand_value(ea, 1)
        name = idc.get_name(addr)
        if name.startswith("STR_"):
            api_names.append(name)

    # assign GetProcAddress result to global var
    if (idc.print_insn_mnem(ea) == "mov") and (idc.get_operand_type(ea, 0) == idc.o_mem) and (idc.print_operand(ea, 1) == "eax"):
        addr = idc.get_operand_value(ea, 0)
        name = api_names.pop(0)
        idc.set_name(addr, "API_" + name[4:])

    # move to next instruction
    ea = idc.next_head(ea, end)

C2 Communication - Init Communication

In order to harvest all the data Vidar looking for, Vidar will need to utilize some DLL’s which it will fetch from a C2 server, below is a short explanation of the DLL’s Vidar will retrieve from the C2:

DLL Name Description
freebl3.dll Network Security Services (NSS) from Mozilla Foundation
mozglue.dll Memory management for Mozilla applications
msvcp140.dll Microsoft Visual C++ library for C++ programming
nss3.dll Network security services for SSL/TLS encryption
softokn3.dll Cryptographic library for key management and encryption/decryption
sqlite3.dll Accessing and managing SQLite databases
vcruntime140.dll Microsoft Visual C++ library for memory management and I/O

In my case the Vidar C2 was hosted on 2 different sites:

  • Telegram:

image.png

image-2.png

  • Steam:

image-3.png

image-4.png

And in case both of them are down, a plain C2 is presented as a backup:

image-5.png

After retrieving the C2 Vidar will send a POST request to the URI:

{C2}/{BOT_ID}

In my case the bot id is: 907 which is also assigned a plain string:

image-6.png

After that first request was made the client will receive a response from the server that looks like that:

1,1,1,1,1,b36abae611984b4404a903d57724b39e,1,1,1,1,0,123;%DOCUMENTS%\;*.txt;50;true;movies:music:mp3:exe;

Each operation is splitted with ; delimiter

C2 Communication - Operations Configuration

As mentioned, each operation is splitted by ; delimiter. First Section:

1,1,1,1,1,b36abae611984b4404a903d57724b39e,1,1,1,1,0,123

Most of those values are flags that says what data should be harvested: |Index|Flag|Description| | — | — | — | |1|1|Local Passwords| |2|1|Cookies| |3|1|Crypto Wallets| |4|1|Browser History| |5|1|Telegram Data| |6|b36abae611984b4404a903d57724b39e|Exfil Token| |7|1|Steam Data| |8|1|Discord Data| |9|1|Screenshot| |10|1|Possible Grabber| |11|0|File Size Limit| |12|123|Profile ID|

Second Section:

%DOCUMENTS%\

The grabber activity folder.

Third Section:

*.txt

Files extensions the grabber will harvest.

Fourth Section:

50

File size limit in KB.

Fifth Section:

true

Recursive harvesting.

Sixth Section:

movies:music:mp3:exe

Excluded file extensions.

Additionally Vidar will create a profile for the user by harvesting the OS info, RAM, CPU, active processes etc… and will send out infromation.txt alongside with the harvested data:

Version: 2.4

Date: 12/2/2023 11:15:46
MachineID: 4cfb5922-b036-4c14-9ed1-03c0dad19fbd
GUID: {d6dc608d-2a27-11ed-a0e3-806e6f6e6963}
HWID: 12ac9eab3d083674480464-4cfb5922-b036-4c14-9ed1-a0e3-806e6f6e6963

Path: C:\Windows\Microsoft.NET\Framework\v4.0.30319\vbc.exe
Work Dir: In memory

Windows: Windows 10 Pro [x64]
Install date: 8/12/2021 0:18:31
AV: Unknown
Computer Name: IYMUGYHL
User Name: Admin
Display Resolution: 1280x720
Display Language: en-US
Keyboard Languages: English (United States)
Local Time: 12/2/2023 11:15:47
TimeZone: UTC-0

[Hardware]
Processor: Intel Core Processor (Broadwell)
Cores: 2
Threads: 2
RAM: 4095 MB
VideoCard: Microsoft Basic Display Adapter

[Processes]
- System [4]
- Registry [92]
- smss.exe [348]
- csrss.exe [436]
- wininit.exe [512]
- csrss.exe [520]
- winlogon.exe [604]
- services.exe [644]
- lsass.exe [656]
- fontdrvhost.exe [764]
- fontdrvhost.exe [772]
- svchost.exe [780]
- svchost.exe [884]
- svchost.exe [932]
- dwm.exe [1016]
- svchost.exe [60]
- svchost.exe [720]
- svchost.exe [640]
- svchost.exe [1044]
- svchost.exe [1052]
- svchost.exe [1140]
- svchost.exe [1192]
- svchost.exe [1208]
- svchost.exe [1232]
- svchost.exe [1316]
- svchost.exe [1384]
- svchost.exe [1432]
- svchost.exe [1452]
- svchost.exe [1504]
- svchost.exe [1572]
- svchost.exe [1604]
- svchost.exe [1616]
- svchost.exe [1712]
- svchost.exe [1740]
- svchost.exe [1840]
- svchost.exe [1876]
- svchost.exe [1900]
- svchost.exe [1952]
- svchost.exe [1968]
- spoolsv.exe [1296]
- svchost.exe [1944]
- svchost.exe [2064]
- svchost.exe [2100]
- sihost.exe [2288]
- svchost.exe [2296]
- taskhostw.exe [2436]
- svchost.exe [2488]
- svchost.exe [2496]
- OfficeClickToRun.exe [2552]
- svchost.exe [2560]
- svchost.exe [2616]
- svchost.exe [2656]
- svchost.exe [2668]
- svchost.exe [2676]
- svchost.exe [2976]
- explorer.exe [3048]
- svchost.exe [2832]
- dllhost.exe [3248]
- StartMenuExperienceHost.exe [3356]
- RuntimeBroker.exe [3416]
- dllhost.exe [3456]
- SearchApp.exe [3568]
- RuntimeBroker.exe [3688]
- RuntimeBroker.exe [4652]
- svchost.exe [4340]
- svchost.exe [1892]
- svchost.exe [3392]
- svchost.exe [4424]
- svchost.exe [4680]
- sppsvc.exe [1096]
- svchost.exe [1260]
- svchost.exe [2544]
- WmiPrvSE.exe [1348]
- SppExtComObj.Exe [2532]
- svchost.exe [2596]
- svchost.exe [3020]
- upfc.exe [4400]
- svchost.exe [1632]
- H&M Advertising contract and Payment information.pdf.scr [4396]
- vbc.exe [1684]

[Software]
Google Chrome [89.0.4389.114]
Microsoft Edge [92.0.902.67]
Microsoft Edge Update [1.3.167.21]
Microsoft Visual C++ 2012 Redistributable (x86) - 11.0.61030 [11.0.61030.0]
Java Auto Updater [2.8.66.17]
Microsoft Visual C++ 2015-2022 Redistributable (x86) - 14.30.30704 [14.30.30704.0]
Microsoft Visual C++ 2015-2022 Redistributable (x64) - 14.30.30704 [14.30.30704.0]
Microsoft Visual C++ 2013 Redistributable (x86) - 12.0.40660 [12.0.40660.0]
Microsoft Visual C++ 2013 x86 Additional Runtime - 12.0.40660 [12.0.40660]
Microsoft Visual C++ 2008 Redistributable - x86 9.0.30729.6161 [9.0.30729.6161]
Adobe Acrobat Reader DC [19.010.20069]
Microsoft Visual C++ 2012 x86 Additional Runtime - 11.0.61030 [11.0.61030]
Microsoft Visual C++ 2012 x86 Minimum Runtime - 11.0.61030 [11.0.61030]
Microsoft Visual C++ 2022 X86 Additional Runtime - 14.30.30704 [14.30.30704]
Microsoft Visual C++ 2012 Redistributable (x64) - 11.0.61030 [11.0.61030.0]
Microsoft Visual C++ 2013 x86 Minimum Runtime - 12.0.40660 [12.0.40660]
Microsoft Visual C++ 2013 Redistributable (x64) - 12.0.40660 [12.0.40660.0]
Microsoft Visual C++ 2010  x86 Redistributable - 10.0.40219 [10.0.40219]
Microsoft Visual C++ 2022 X86 Minimum Runtime - 14.30.30704 [14.30.30704]

C2 Communication - Data Exfiltration

After harvesting all the data Vidar will compress all harvested data to as a zip encode it to base64 and send it out alongside with some more data in the next format:

------{random_generated_delimiter}
Content-Disposition: form-data; name="profile"

{BOT_ID}
------{random_generated_delimiter}
Content-Disposition: form-data; name="profile_id"

{PERSONAL_ID}
------{random_generated_delimiter}
Content-Disposition: form-data; name="hwid"

{COMPUTER_HWID}
------{random_generated_delimiter}
Content-Disposition: form-data; name="token"

{EXFIL_TOKEN}
------{random_generated_delimiter}
Content-Disposition: form-data; name="file"
{BASE64_ENCODED_ARCHIVE}

image.png

Post Exfiltration Self Termination

After Vidar exfiltrated the data it will create a self termination task using cmd command and by this will end the execution of itself:

"C:\Windows\System32\cmd.exe" /c timeout /t 6 & del /f /q Vidar.exe & exit

image.png

Summary

Vidar is a well known stealer that was active for the past years and keeps on constantly updated by its developers.

In this blog we’ve covered most Vidars functions and how it was delivered to it’s victims.

Quick note that it’s my first “In Depth” writeup for a malware so any feedback would be appreciated, you can always PM me on twitter (0xToxin)

Yara Rule

The rule is updated up to version 2.4 which was recently revamped from version 5X.X (more info can be found here)

rule win_Vidar
{
	meta:
        author = "0xToxin"
        description = "Vidar stealer strings and functions"
        Date = "20-02-2022"
		
	strings:
		$dll1 = "vcruntime140.dll" ascii wide
		$dll2 = "softokn3.dll" ascii wide
		$dll3 = "nss3.dll" ascii wide
		$dll4 = "msvcp140.dll" ascii wide
		$dll5 = "mozglue.dll" ascii wide
		$dll6 = "freebl3.dll" ascii wide
		$dll7 = "sqlite3.dll" ascii wide
		$c2Fetch1 = "t.me" ascii wide
		$c2Fetch2 = "steamcommunity.com" ascii wide
		$stringDec = {
			68 ?? ?? ?? 00
			68 ?? ?? ?? 00
			B9 ?? ?? 00 00
			E8 ?? ?? ?? ??
			68 ?? ?? ?? 00
			68 ?? ?? ?? 00
			B9 ?? ?? 00 00
			A3 ?? ?? ?? ??
		}
	condition:
		uint16(0) == 0x5a4d and 3 of ($dll*) and 1 of ($c2Fetch*) and #stringDec >= 15
}

You can see also the Yara Hunt result on UnpackMe.

IOC’s

References