IntroductionVidar is an information stealer that was first observed in 2018. Across its iterations, Vidar has continued to improve its string obfuscation to make detection and analysis more difficult by changing deobfuscation algorithms, constants, and primitives. From May through early September 2026, Zscaler ThreatLabz tracked Vidar’s string obfuscation as it evolved from basic XOR to ChaCha20, and more recently, to a custom virtual machine (VM), which is executed via a lightweight bytecode interpreter that is combined with a custom stream cipher that changes with each build.In this blog post, ThreatLabz covers Vidar’s string obfuscation methods from version 2.0 to the latest version 3.3. Key TakeawaysVidar is an information stealer that was first observed in 2018 that initially obfuscated strings with XOR-based encryption.The Vidar developer later migrated to more advanced string encryption algorithms based on ChaCha20.In June 2026, ThreatLabz observed Vidar introduce a virtual machine to protect the malware’s strings with different opcodes that change per build. In addition to the virtual machine, a new custom stream cipher algorithm was added with operations that vary across builds. These per-build string obfuscation techniques are designed to hinder static and automated analysis. Evolution TimelineEarlier versions of Vidar used single-byte XOR operations to obfuscate strings. Beginning with internal version 1.5, the developer adopted ChaCha20. Starting in versions 1.8, the ChaCha20 cipher was modified to make detection and decryption harder. Starting with internal version 2.0, Vidar shifted to a different approach that combines a custom virtual machine and custom stream cipher per build. The figure below shows a timeline of these changes.Figure 1: Evolution of Vidar’s string obfuscation from early May to early September 2026. ANALYST NOTE: The version number used in this analysis refers to internal versions identified by ThreatLabz. Technical AnalysisThe following sections examine Vidar’s string deobfuscation process, including a lightweight virtual machine and custom stream cipher.Virtual machine deobfuscationIn order to obfuscate the malware’s strings in versions 2.x and 3.x, the Vidar developer introduced a simple virtual machine that is executed via a bytecode interpreter. The bytecode interpreter consists of a fetch–decode–execute loop with the VM code provided as a byte array. Each opcode byte indexes a sparse 256-entry dispatch table. The matching handler mutates a one-byte accumulator and emits an output byte when a specific opcode is encountered. The dispatch table is initialized on first use and populated with 14 opcode handlers. All other slots in the dispatch table are null and terminate execution. The bytecode interpreter is relatively simple and does not implement a stack or use registers beyond an accumulator. Each handler uses simple primitives such as XOR, addition, subtraction, rotation, bitwise negation, multiplication, and substitution via a lookup table.The interpreter uses a context structure that is passed to each handler and executes until the bytecode or the output buffer is exhausted as shown in the code below.typedef struct bytecode_context {
uint8_t acc; // accumulator
uint8_t prev_out; // decoded byte
const uint8_t * code_ptr; // bytecode
uint32_t code_len; // bytecode_length
uint32_t index; // instruction_pointer
uint8_t* out_ptr; // output
uint32_t out_len; // output buffer size
uint32_t out_index; // output_position
uint32_t xor_key; // accumulator seed and used as an xor key
} bytecode_context;
void vidar_bytecode_intrepreter(
const uint8_t *input_bytes,
uint32_t input_len,
uint8_t *output_bytes,
uint32_t output_len)
{
bytecode_context ctx = {0};
init_vm_dispatch_table();
ctx.acc = get_seed_value();
ctx.prev_out = 0;
ctx.code_ptr = input_bytes;
ctx.code_len = input_len;
ctx.code_index = 0;
ctx.out_ptr = output_bytes;
ctx.out_len = output_len;
ctx.out_index = 0;
ctx.xor_key = get_seed_value();
while (ctx.code_index < ctx.code_len &&
ctx.out_index < ctx.out_len)
{
uint8_t opcode = ctx.code_ptr[ctx.code_index++];
void (*handler)(bytecode_context *) =
opcode_handlers[opcode];
if (handler == NULL)
break;
handler(&ctx);
}
}The hardcoded XOR key is a 4-byte value that changes with each build. The key also serves as a seed for initializing the bytecode interpreter’s accumulator. The opcodes, constants, and substitution tables change across builds, making automated analysis more difficult.The following table provides examples of Vidar’s VM opcodes and their corresponding operations.OpcodeDescriptionOperation0xF1XOR accumulator with a constant value.acc ^= 0x8F0xC5Add the next byte to the accumulator.acc += bytecode[code_index++]0xC7Rotate the accumulator right by 2 bits.acc = ROR8(acc, 2)0x58Subtract the next byte from the accumulator.acc -= bytecode[code_index++]0x16XOR the accumulator with the hardcoded XOR key (xor_key).acc ^= (xor_key >> ((out_index%4)*8)) & 0xFF0x39XOR the accumulator with the next byte.acc ^= bytecode[code_index++]0xE6Subtract a constant value from the accumulator.acc -= 0x750x51XOR the accumulator with the next byte,write the decoded byte to the output buffer,save the decoded byte (prev_out), and XOR the accumulator with the decoded byte.t = acc ^ bytecode[code_index++];out[out_index++] = t;prev_out = t;acc ^= t0x33XOR the accumulator with the previous decoded byte (prev_out).acc ^= prev_out0xB4Rotate the accumulator left by 3 bits.acc = ROL8(acc, 3)0x1APerform a bitwise NOT operation on the accumulator.acc = ~acc0x96XOR the accumulator with the output index (out_index).acc ^= out_index0x3FLoad a byte from the substitution table.acc = sbox[acc]0x3BMultiply the accumulator with a constant value.acc = (acc * 0x53) & 0xFFTable 1: Examples of VM opcodes and corresponding operations from a Vidar version 3.1 sample.Vidar’s VM is used in two different ways:For direct deobfuscation: The bytecode is interpreted to create the final deobfuscated string.To decrypt a key and nonce for a string encrypted with a custom stream cipher (described later): For Vidar versions 2.0 and 2.1, the first 9 bytes form the key. From version 2.2 onwards, the first 8 bytes are used as the key. In all versions of Vidar, the last 4 bytes are used as the nonce. The custom stream cipher then uses the key and nonce to decrypt an additional byte array containing the actual encrypted string.The table below provides examples of the VM bytecode that is interpreted to produce deobfuscated strings.BytecodeDeobfuscated stringc54fc7512439f1e6f151cfc55f161651fef1163f510a3b3396515ae6e6392e51de3931c5ce516d33e63b51e81ae65173b41ac7519a1a161a511bc71a518ec73351c41ab4516c1ac75135b4b451cd3b3f5171c5f51ac751d833393e5188f1163b5122f13b513739273b51e016c751d1e63b5104163b516f339696510fe616517239c1333b51ee3be61651c0e6c50851831a963b51733f1a51d81a33514ae61651dab41a519716c71651253333510596163b5188c54cc5dbc51651a1браузеров найдено: %de6c5a0c5cd5199c5d61a51b0169651afe639155140333fc751c2f1c7513716b45156c7b4f1515139e3b451af1633c751b23bb4516516b4512f3f3351e3c73b3b510dc556163351a71a1a3f519ec7e651b13f96f1511816b439f65108333b51243932335176b433c7511039911a51c5394b165169f1392051783bc54a51e31696c5c451ed399cb4515a333f51b816b4511b3b3b5143b43b51d9c79651863b16511db4c59c3f51961639919651ac3fc7e65108c73f51f7e6161651533b96514f3f33f1512016c73b512f3b33516f39283f3f51391a3351afc7f151e63f3f510916c73351d2169651031a3f51d3e6f1965190Loader: не удалось запустить %sb496b45150f11ac7512833398051ea3bf15123b4c5fa3f516bc7c751c1b4f151f29633c59751781639933b51ec3bb45157163b3f51f0b4f139fc51d41ab4c506517a963b51f133f11a51c533b4f15188f1c7c7515516b4b451c6e6165120e6c73351b83b1a512233c7e6510cf13fc5bf5148e6e63b510916b49651773316c5f2515fb43f1651b4e6b4e651f3b4e6514b3b96f151af3fc7e6515b96161a51e9e61651c13bc5e35105c7e6b4515c39bd1a513ec5a4165106f196c7514fc716b45134e6c545512c3bb496512f3bf1165109f139c139c751a0c541c73960516ae63b1651dbe6965127e6f1f151d71a3b518b331a512ef1c7b4516fc71a5104f1331651ec3fc751b6f1161a516cf13b51fe96b451273fc546517296c751a1169651f0e6c5c75162b4e651bbc5d9b451c0395a33b451d716f139405107c73b51b6c71a5136e63b3b5189b4c53851f3333f51273b396f165179397eb45154e6b4b4519233c5f95184f1b4b451e2161a5163f1392239895167c53a399ec5055161b43333516f161ae6516c163f16512eb433516dc50d3bb4510116b4c598517d969633517333c55f51171a1651443316b4511396b43b5129c7163b5131c56b165166e61651c4331651b796c73f51b6c5df3f519233f15114b4163f51d4161ae6515ce6398e5145e6f1517f33e6c751dd331651091ac751dc96c751ccc7c5c051913b1a3b51d139dac5ed9651e33be616514fe6c559335129b4c73979510ec7e6b45114browsers: %d (%d rules), wallets: %d (%d rules), plugins: %d (%d wallet / %d plugins / %d soft), grabber: %dTable 2: Examples of Vidar’s bytecode and the resulting deobfuscated strings.Deobfuscation using custom stream ciphersIn addition to the VM-based obfuscation, Vidar implements a custom stream cipher to decrypt some of the malware’s strings. Although the stream cipher interface remains consistent across builds, the underlying implementation varies. ThreatLabz identified two general custom stream cipher implementations: a modified ChaCha-based cipher and an add-rotate-XOR (ARX) based cipher.ChaCha-based cipherVidar versions 2.0 and 2.1 use a ChaCha-based cipher with a number of modifications that include a custom 128-bit initial state, 8-byte key, 4-byte nonce, and different quarter-round rotations that change between samples.ARX-based stream cipherVidar version 2.2 and later use a custom stream cipher that initializes the state, followed by ARX transformations to produce a keystream that is initialized using the previously decrypted key and nonce (from the VM). The initialization and decryption steps are described in the following subsections.State initialization with the key and FNV-1aThe cipher first incorporates the key material into a 32-bit state value using multiplication with the FNV-1a prime constant, as shown in the example below.state = SEED_CONSTANT; // Per-build 32-bit seed.
for (i = 0; i < key_length; i++) {
state ^= key[i];
state *= 0x01000193; // 32-bit FNV-1a prime.
}The per-build seed, SEED_CONSTANT, is the only element of this initialization step that changes between builds.State initialization with the nonce and golden ratio-related constantThe second phase incorporates the nonce material into the state using the fixed constant 0x9E3779B9 (the 32-bit representation of the fractional part of the golden ratio), as shown in the example below.for (i = 0; i < nonce_length; i++) {
state ^= nonce[i] * 256;
state += 0x9E3779B9;
}Keystream generation and decryptionEvery build uses a different set of ARX based transformations with unique constants to construct the keystream, which is then used to decrypt the ciphertext. The example below shows the algorithm observed in a Vidar version 2.4 sample, which applies various ARX operations. After these operations are executed, the state is folded into a keystream byte that is then used to decrypt the corresponding byte from the input array to produce the output byte, as shown in the example below.for (i = 0; i < input_length; i++) {
// Transform the 32-bit state.
state *= 0xC5C8C2C9;
state ^= state >> 16;
state += 0xE476B81E;
state ^= 0x907A30A0;
state *= 0xC967295D;
state ^= 0xA72A764F;
// Fold the 32-bit state into a single keystream byte.
keystream_byte =
(state ^ (state >> 8) ^ (state >> 16) ^ (state >> 24)) & 0xFF;
// XOR the keystream byte with the input.
output[i] = input[i] ^ keystream_byte;
}The example below shows a different implementation of the ARX algorithm that uses a different combination of operations and a new set of constants. This implementation was extracted from a Vidar version 2.5 sample.for (i = 0; i < input_length; i++) {
state -= 0x43ACCA67;
state += 0x871A3B01;
state += 0x1D24B66B;
state = ROL32(state, 22);
state ^= state >> 16;
state += 0x9EC4A92E;
state ^= state >> 16;
state += 0x9DDE39F6;
keystream_byte =
(state ^ (state >> 8) ^ (state >> 16) ^ (state >> 24)) & 0xFF;
output[i] = input[i] ^ keystream_byte;
}The table below provides examples of strings decrypted using Vidar’s VM together with one of Vidar’s custom stream ciphers from a version 3.1 sample.BytecodeCiphertextDecrypted stringe6c7514a33f19651d6331651f63935b41a51ce963ff151de333b1a51b2963351843ff1e65134f1b439b9512cf1c7c5475117393ac7c5c7516ff1f13b51b56925d83d2a99d43a726ebd063fa1b2c307f7c332ba24Loader: write failede63b51c0961ac52e51cc333fc53251d53be6518d16c52bb4513d1a1ac7513f1a1ab45178f13b51a7c5803f511ef13b1a51be1ab4511b169651d1a2eb729fb01a9739e20feb6a1605f56928aa3f568c66b8929a9e58a23b8259b7a46352d1c8a704388daa8080948b21d0308e10Не удалось сгенерировать HWIDc7c73b5133e69651b73f96513e1a1616510fc7c751f5e61ac75129963982f15153b416f15196963f5190c73f517016163f51a3f1163f51d719e22068ae2dc7519044263c46f643225cae82e9f8418a82fdbfd0beb7c5be9df605e9e215610fb6a72a6d6b37c21446cc771af0597f5d0c1d52950911d9120df050776dccae62777767581a0f1b57002c2abc493b3f18f6c13e040c8079c06a8e42861062ba8e9e898f46e954332c91a0b29c79eb0f302e7078a9f09d525b6d7b3fc727e93930d6240337834a1d700e149dAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rnTable 3: Example of strings decrypted using Vidar’s VM and custom stream cipher from a version 3.1 sample. ConclusionThe Vidar family is constantly evolving, with new changes and functionality along with additional obfuscation to make analysis and detection more difficult. The obfuscation is designed to look different across builds, while performing essentially the same function. Vidar keeps the algorithms simple and the interfaces stable while continuously randomizing the constants and micro-operations that static signatures rely on. This highlights the need for network and endpoint detection systems that can keep pace with these innovative techniques. Zscaler CoverageZscaler’s multilayered cloud security platform detects indicators related to Vidar at various levels. The figure below depicts the Zscaler Cloud Sandbox, showing detection details for Vidar.Figure 2: Zscaler Cloud Sandbox Report for Vidar.In addition to sandbox detections, Zscaler’s multilayered cloud security platform detects indicators related to Vidar at various levels with the following threat names:Win32.PWS.VidarZscaler MDR detects Vidar using these detection analytics:WIN-BIN-NETCONN-TO-TELEGRAM-SHORTENED-URLWIN-WEBBROWSER-UNUSUAL-PARENTWIN-STEALER-FILEMOD Indicators Of Compromise (IOCs)IOCDescription1628bb03db87f67661349e169d73ee14ed490bdbf22abfbda08ccc9ebe237974Vidar v2.0625a381981fc2d4c25c981d98b1d66bb2cf5da2dde2f590add0673a857d5b074Vidar v2.52d43d592630ad1e012da63ef7279f95dd4a8e94964e12ca2f996051875574fa6Vidar v3.1979048a749d8f28d877c7068b1b336ecd1e349869dfb1d7c68118f90e4099bc4Vidar v3.4
[#item_full_content] [[{“value”:”IntroductionVidar is an information stealer that was first observed in 2018. Across its iterations, Vidar has continued to improve its string obfuscation to make detection and analysis more difficult by changing deobfuscation algorithms, constants, and primitives. From May through early September 2026, Zscaler ThreatLabz tracked Vidar’s string obfuscation as it evolved from basic XOR to ChaCha20, and more recently, to a custom virtual machine (VM), which is executed via a lightweight bytecode interpreter that is combined with a custom stream cipher that changes with each build.In this blog post, ThreatLabz covers Vidar’s string obfuscation methods from version 2.0 to the latest version 3.3. Key TakeawaysVidar is an information stealer that was first observed in 2018 that initially obfuscated strings with XOR-based encryption.The Vidar developer later migrated to more advanced string encryption algorithms based on ChaCha20.In June 2026, ThreatLabz observed Vidar introduce a virtual machine to protect the malware’s strings with different opcodes that change per build. In addition to the virtual machine, a new custom stream cipher algorithm was added with operations that vary across builds. These per-build string obfuscation techniques are designed to hinder static and automated analysis. Evolution TimelineEarlier versions of Vidar used single-byte XOR operations to obfuscate strings. Beginning with internal version 1.5, the developer adopted ChaCha20. Starting in versions 1.8, the ChaCha20 cipher was modified to make detection and decryption harder. Starting with internal version 2.0, Vidar shifted to a different approach that combines a custom virtual machine and custom stream cipher per build. The figure below shows a timeline of these changes.Figure 1: Evolution of Vidar’s string obfuscation from early May to early September 2026. ANALYST NOTE: The version number used in this analysis refers to internal versions identified by ThreatLabz. Technical AnalysisThe following sections examine Vidar’s string deobfuscation process, including a lightweight virtual machine and custom stream cipher.Virtual machine deobfuscationIn order to obfuscate the malware’s strings in versions 2.x and 3.x, the Vidar developer introduced a simple virtual machine that is executed via a bytecode interpreter. The bytecode interpreter consists of a fetch–decode–execute loop with the VM code provided as a byte array. Each opcode byte indexes a sparse 256-entry dispatch table. The matching handler mutates a one-byte accumulator and emits an output byte when a specific opcode is encountered. The dispatch table is initialized on first use and populated with 14 opcode handlers. All other slots in the dispatch table are null and terminate execution. The bytecode interpreter is relatively simple and does not implement a stack or use registers beyond an accumulator. Each handler uses simple primitives such as XOR, addition, subtraction, rotation, bitwise negation, multiplication, and substitution via a lookup table.The interpreter uses a context structure that is passed to each handler and executes until the bytecode or the output buffer is exhausted as shown in the code below.typedef struct bytecode_context {
uint8_t acc; // accumulator
uint8_t prev_out; // decoded byte
const uint8_t * code_ptr; // bytecode
uint32_t code_len; // bytecode_length
uint32_t index; // instruction_pointer
uint8_t* out_ptr; // output
uint32_t out_len; // output buffer size
uint32_t out_index; // output_position
uint32_t xor_key; // accumulator seed and used as an xor key
} bytecode_context;
void vidar_bytecode_intrepreter(
const uint8_t *input_bytes,
uint32_t input_len,
uint8_t *output_bytes,
uint32_t output_len)
{
bytecode_context ctx = {0};
init_vm_dispatch_table();
ctx.acc = get_seed_value();
ctx.prev_out = 0;
ctx.code_ptr = input_bytes;
ctx.code_len = input_len;
ctx.code_index = 0;
ctx.out_ptr = output_bytes;
ctx.out_len = output_len;
ctx.out_index = 0;
ctx.xor_key = get_seed_value();
while (ctx.code_index < ctx.code_len &&
ctx.out_index < ctx.out_len)
{
uint8_t opcode = ctx.code_ptr[ctx.code_index++];
void (*handler)(bytecode_context *) =
opcode_handlers[opcode];
if (handler == NULL)
break;
handler(&ctx);
}
}The hardcoded XOR key is a 4-byte value that changes with each build. The key also serves as a seed for initializing the bytecode interpreter’s accumulator. The opcodes, constants, and substitution tables change across builds, making automated analysis more difficult.The following table provides examples of Vidar’s VM opcodes and their corresponding operations.OpcodeDescriptionOperation0xF1XOR accumulator with a constant value.acc ^= 0x8F0xC5Add the next byte to the accumulator.acc += bytecode[code_index++]0xC7Rotate the accumulator right by 2 bits.acc = ROR8(acc, 2)0x58Subtract the next byte from the accumulator.acc -= bytecode[code_index++]0x16XOR the accumulator with the hardcoded XOR key (xor_key).acc ^= (xor_key >> ((out_index%4)*8)) & 0xFF0x39XOR the accumulator with the next byte.acc ^= bytecode[code_index++]0xE6Subtract a constant value from the accumulator.acc -= 0x750x51XOR the accumulator with the next byte,write the decoded byte to the output buffer,save the decoded byte (prev_out), and XOR the accumulator with the decoded byte.t = acc ^ bytecode[code_index++];out[out_index++] = t;prev_out = t;acc ^= t0x33XOR the accumulator with the previous decoded byte (prev_out).acc ^= prev_out0xB4Rotate the accumulator left by 3 bits.acc = ROL8(acc, 3)0x1APerform a bitwise NOT operation on the accumulator.acc = ~acc0x96XOR the accumulator with the output index (out_index).acc ^= out_index0x3FLoad a byte from the substitution table.acc = sbox[acc]0x3BMultiply the accumulator with a constant value.acc = (acc * 0x53) & 0xFFTable 1: Examples of VM opcodes and corresponding operations from a Vidar version 3.1 sample.Vidar’s VM is used in two different ways:For direct deobfuscation: The bytecode is interpreted to create the final deobfuscated string.To decrypt a key and nonce for a string encrypted with a custom stream cipher (described later): For Vidar versions 2.0 and 2.1, the first 9 bytes form the key. From version 2.2 onwards, the first 8 bytes are used as the key. In all versions of Vidar, the last 4 bytes are used as the nonce. The custom stream cipher then uses the key and nonce to decrypt an additional byte array containing the actual encrypted string.The table below provides examples of the VM bytecode that is interpreted to produce deobfuscated strings.BytecodeDeobfuscated stringc54fc7512439f1e6f151cfc55f161651fef1163f510a3b3396515ae6e6392e51de3931c5ce516d33e63b51e81ae65173b41ac7519a1a161a511bc71a518ec73351c41ab4516c1ac75135b4b451cd3b3f5171c5f51ac751d833393e5188f1163b5122f13b513739273b51e016c751d1e63b5104163b516f339696510fe616517239c1333b51ee3be61651c0e6c50851831a963b51733f1a51d81a33514ae61651dab41a519716c71651253333510596163b5188c54cc5dbc51651a1браузеров найдено: %de6c5a0c5cd5199c5d61a51b0169651afe639155140333fc751c2f1c7513716b45156c7b4f1515139e3b451af1633c751b23bb4516516b4512f3f3351e3c73b3b510dc556163351a71a1a3f519ec7e651b13f96f1511816b439f65108333b51243932335176b433c7511039911a51c5394b165169f1392051783bc54a51e31696c5c451ed399cb4515a333f51b816b4511b3b3b5143b43b51d9c79651863b16511db4c59c3f51961639919651ac3fc7e65108c73f51f7e6161651533b96514f3f33f1512016c73b512f3b33516f39283f3f51391a3351afc7f151e63f3f510916c73351d2169651031a3f51d3e6f1965190Loader: не удалось запустить %sb496b45150f11ac7512833398051ea3bf15123b4c5fa3f516bc7c751c1b4f151f29633c59751781639933b51ec3bb45157163b3f51f0b4f139fc51d41ab4c506517a963b51f133f11a51c533b4f15188f1c7c7515516b4b451c6e6165120e6c73351b83b1a512233c7e6510cf13fc5bf5148e6e63b510916b49651773316c5f2515fb43f1651b4e6b4e651f3b4e6514b3b96f151af3fc7e6515b96161a51e9e61651c13bc5e35105c7e6b4515c39bd1a513ec5a4165106f196c7514fc716b45134e6c545512c3bb496512f3bf1165109f139c139c751a0c541c73960516ae63b1651dbe6965127e6f1f151d71a3b518b331a512ef1c7b4516fc71a5104f1331651ec3fc751b6f1161a516cf13b51fe96b451273fc546517296c751a1169651f0e6c5c75162b4e651bbc5d9b451c0395a33b451d716f139405107c73b51b6c71a5136e63b3b5189b4c53851f3333f51273b396f165179397eb45154e6b4b4519233c5f95184f1b4b451e2161a5163f1392239895167c53a399ec5055161b43333516f161ae6516c163f16512eb433516dc50d3bb4510116b4c598517d969633517333c55f51171a1651443316b4511396b43b5129c7163b5131c56b165166e61651c4331651b796c73f51b6c5df3f519233f15114b4163f51d4161ae6515ce6398e5145e6f1517f33e6c751dd331651091ac751dc96c751ccc7c5c051913b1a3b51d139dac5ed9651e33be616514fe6c559335129b4c73979510ec7e6b45114browsers: %d (%d rules), wallets: %d (%d rules), plugins: %d (%d wallet / %d plugins / %d soft), grabber: %dTable 2: Examples of Vidar’s bytecode and the resulting deobfuscated strings.Deobfuscation using custom stream ciphersIn addition to the VM-based obfuscation, Vidar implements a custom stream cipher to decrypt some of the malware’s strings. Although the stream cipher interface remains consistent across builds, the underlying implementation varies. ThreatLabz identified two general custom stream cipher implementations: a modified ChaCha-based cipher and an add-rotate-XOR (ARX) based cipher.ChaCha-based cipherVidar versions 2.0 and 2.1 use a ChaCha-based cipher with a number of modifications that include a custom 128-bit initial state, 8-byte key, 4-byte nonce, and different quarter-round rotations that change between samples.ARX-based stream cipherVidar version 2.2 and later use a custom stream cipher that initializes the state, followed by ARX transformations to produce a keystream that is initialized using the previously decrypted key and nonce (from the VM). The initialization and decryption steps are described in the following subsections.State initialization with the key and FNV-1aThe cipher first incorporates the key material into a 32-bit state value using multiplication with the FNV-1a prime constant, as shown in the example below.state = SEED_CONSTANT; // Per-build 32-bit seed.
for (i = 0; i < key_length; i++) {
state ^= key[i];
state *= 0x01000193; // 32-bit FNV-1a prime.
}The per-build seed, SEED_CONSTANT, is the only element of this initialization step that changes between builds.State initialization with the nonce and golden ratio-related constantThe second phase incorporates the nonce material into the state using the fixed constant 0x9E3779B9 (the 32-bit representation of the fractional part of the golden ratio), as shown in the example below.for (i = 0; i < nonce_length; i++) {
state ^= nonce[i] * 256;
state += 0x9E3779B9;
}Keystream generation and decryptionEvery build uses a different set of ARX based transformations with unique constants to construct the keystream, which is then used to decrypt the ciphertext. The example below shows the algorithm observed in a Vidar version 2.4 sample, which applies various ARX operations. After these operations are executed, the state is folded into a keystream byte that is then used to decrypt the corresponding byte from the input array to produce the output byte, as shown in the example below.for (i = 0; i < input_length; i++) {
// Transform the 32-bit state.
state *= 0xC5C8C2C9;
state ^= state >> 16;
state += 0xE476B81E;
state ^= 0x907A30A0;
state *= 0xC967295D;
state ^= 0xA72A764F;
// Fold the 32-bit state into a single keystream byte.
keystream_byte =
(state ^ (state >> 8) ^ (state >> 16) ^ (state >> 24)) & 0xFF;
// XOR the keystream byte with the input.
output[i] = input[i] ^ keystream_byte;
}The example below shows a different implementation of the ARX algorithm that uses a different combination of operations and a new set of constants. This implementation was extracted from a Vidar version 2.5 sample.for (i = 0; i < input_length; i++) {
state -= 0x43ACCA67;
state += 0x871A3B01;
state += 0x1D24B66B;
state = ROL32(state, 22);
state ^= state >> 16;
state += 0x9EC4A92E;
state ^= state >> 16;
state += 0x9DDE39F6;
keystream_byte =
(state ^ (state >> 8) ^ (state >> 16) ^ (state >> 24)) & 0xFF;
output[i] = input[i] ^ keystream_byte;
}The table below provides examples of strings decrypted using Vidar’s VM together with one of Vidar’s custom stream ciphers from a version 3.1 sample.BytecodeCiphertextDecrypted stringe6c7514a33f19651d6331651f63935b41a51ce963ff151de333b1a51b2963351843ff1e65134f1b439b9512cf1c7c5475117393ac7c5c7516ff1f13b51b56925d83d2a99d43a726ebd063fa1b2c307f7c332ba24Loader: write failede63b51c0961ac52e51cc333fc53251d53be6518d16c52bb4513d1a1ac7513f1a1ab45178f13b51a7c5803f511ef13b1a51be1ab4511b169651d1a2eb729fb01a9739e20feb6a1605f56928aa3f568c66b8929a9e58a23b8259b7a46352d1c8a704388daa8080948b21d0308e10Не удалось сгенерировать HWIDc7c73b5133e69651b73f96513e1a1616510fc7c751f5e61ac75129963982f15153b416f15196963f5190c73f517016163f51a3f1163f51d719e22068ae2dc7519044263c46f643225cae82e9f8418a82fdbfd0beb7c5be9df605e9e215610fb6a72a6d6b37c21446cc771af0597f5d0c1d52950911d9120df050776dccae62777767581a0f1b57002c2abc493b3f18f6c13e040c8079c06a8e42861062ba8e9e898f46e954332c91a0b29c79eb0f302e7078a9f09d525b6d7b3fc727e93930d6240337834a1d700e149dAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8rnTable 3: Example of strings decrypted using Vidar’s VM and custom stream cipher from a version 3.1 sample. ConclusionThe Vidar family is constantly evolving, with new changes and functionality along with additional obfuscation to make analysis and detection more difficult. The obfuscation is designed to look different across builds, while performing essentially the same function. Vidar keeps the algorithms simple and the interfaces stable while continuously randomizing the constants and micro-operations that static signatures rely on. This highlights the need for network and endpoint detection systems that can keep pace with these innovative techniques. Zscaler CoverageZscaler’s multilayered cloud security platform detects indicators related to Vidar at various levels. The figure below depicts the Zscaler Cloud Sandbox, showing detection details for Vidar.Figure 2: Zscaler Cloud Sandbox Report for Vidar.In addition to sandbox detections, Zscaler’s multilayered cloud security platform detects indicators related to Vidar at various levels with the following threat names:Win32.PWS.VidarZscaler MDR detects Vidar using these detection analytics:WIN-BIN-NETCONN-TO-TELEGRAM-SHORTENED-URLWIN-WEBBROWSER-UNUSUAL-PARENTWIN-STEALER-FILEMOD Indicators Of Compromise (IOCs)IOCDescription1628bb03db87f67661349e169d73ee14ed490bdbf22abfbda08ccc9ebe237974Vidar v2.0625a381981fc2d4c25c981d98b1d66bb2cf5da2dde2f590add0673a857d5b074Vidar v2.52d43d592630ad1e012da63ef7279f95dd4a8e94964e12ca2f996051875574fa6Vidar v3.1979048a749d8f28d877c7068b1b336ecd1e349869dfb1d7c68118f90e4099bc4Vidar v3.4″}]]