Vidar's Anti-Disassembly Trick
Understand how Vidar stealer uses opaque predicates and shield bytes to confuse IDA Pro, and how to fix it.
When analyzing modern malware, the first few instructions often set the tone for the entire session. Vidar stealer doesn’t use complex virtual machines or custom packers for its initial entry point, but rather a sequence of simple assembly-level tricks that exploit how disassemblers interpret code.
The Setup
Here’s what IDA hands you when you first open the binary:
00414E60 74 03 jz short loc_414E64+1
00414E62 75 01 jnz short loc_414E64+1
00414E64 B8 E8 26 C2 FE mov eax, 0FEC226E8h ; <-- IDA's view
00414E69 FF 74 03 75 push dword ptr [ebx+eax+75h]
...
Looks like a conditional jump followed by some bogus arithmetic. None of it makes sense. That’s the point.
Trick #1: The Opaque Predicate
The jz/jnz pair is the classic opaque predicate. The Zero Flag has exactly two possible states: set or clear. So:
- ZF = 1 →
jzfires → jumps to0x414E65 - ZF = 0 →
jnzfires → jumps to0x414E65
Both branches always land on the same address. This is just an unconditional jump dressed up as two conditional ones. The byte at 0x414E64 is never executed.
Trick #2: The Shield Byte
Both jumps target loc_414E64+1 — that’s 0x414E65, one byte past the label, but IDA starts disassembling at 0x414E64.
0x414E64: B8 E8 26 C2 FE → mov eax, 0FEC226E8h
The B8 at 0x414E64 is a shield byte that’s never executed, but it causes the disassembler to swallow the E8 byte that follows as part of an immediate operand. The CPU doesn’t care about IDA’s labeling. It jumps directly to 0x414E65 and sees:
0x414E65: E8 26 C2 FE FF → call 0x401090
E8 is the CALL rel32 opcode. The 4-byte relative offset 0xFFFEC226 resolves to:
0x414E6A + 0xFFFEC226 = 0x00401090
The real first instruction of WinMain is call 0x401090. Every push, add, and mov IDA showed you? Disassembler artifacts, bytes that are actually part of call targets and opaque predicate sequences being misread as instruction bodies.
It’s Chained
After call 0x401090 returns, the next bytes at 0x414E6A are 74 03 75 01, which is another jz/jnz opaque predicate pointing into the middle of yet another mov-as-shield-byte. The pattern repeats at least three times across the block. IDA’s entire view of this region is wrong from the first byte.
Fixing It in IDA
It’s a two-step manual fix per sequence:
- Press
Uto undefine the instruction at the shield byte address (e.g.0x414E64) - Move your cursor one byte forward to
0x414E65and pressCto create code
IDA will now decode the real call. Repeat for each chained predicate.
For automation, an IDAPython script scanning for the 74 ?? 75 ?? byte pattern (where both targets resolve to the same address) can patch the shield byte to a NOP and reanalyze.
The Takeaway
| What it is | What it does to the disassembler |
|---|---|
jz X+1 / jnz X+1 |
Forces execution to bypass the “real” label |
Shield byte (B8) |
Makes the key E8 opcode invisible as a CALL |
Neither technique is sophisticated in isolation. The point is that chaining them repeatedly across the function entry turns a straightforward call dispatch into something that looks like corrupted data at a glance.