Anti-analysis
Goal is to make analysis as difficult as possible.
Anti-static
Control flow obfuscation
Transformations that make it hard to analyze flow of control of program
- confuse about function boundaries, if statements/loops, jump targets
Opaque predicate: expression whose value know to programmer but difficult for analyst
- e.g.
(x*x)+x % 2
, which always results in 0
Control flow flattening: aims to obscure control flow logic
- remove control flow structure from function, hide nesting of loops and conditionals
- put each block of code as case inside switch statement, wrap switch inside infinite loop
- control flow guided by dispatcher variable
Return address patching: ret
pops value from stack and jumps to place pointed by it, if function modifies return value then disassemblers get confused
- so you can overwrite return address
- to make it easier to analyze,
nop
the bytes that are skipped, which gives you actual instructions
Data obfuscation
i.e. convert variable to representation that’s hard for analyst to understand or is unexpected
Encryption:
xor
bytewise with known constant value
- convert to function that computes its value at runtime
Variable splitting:
- split
x
into x1
and x2
, never compute the real value
- define encoding and decoding functions
- you may need to still compute the actual value of
x
, such as in system calls
- can also add variable merging, e.g. splitting two variables and merging parts of them
Anti-dynamic
Anti-debugging
different flags (PEB, Heap flags, NtGlobalFlag)
- NtGlobalFlag is value in Process Environment Block, value is 0x70 when debugging
detect breakpoints
- hardware
- hardware breakpoint fires when instruction tries to access specific address
- check debug hardware registers (but can only be accessed at level 0, in Windows you can register exception handlers and use that to check)
- software
int 0x3
(opcode 0xcc
) is software breakpoint, debugger overwrites original instruction with this
- to detect, just look for these instructions
exception traps
- program intentionally calls
int 0x3
- if debugger not attached, program raises an exception, so check for it
self debugging
- only one debugger at a time can be attached
- so make program try to debug itself (
ptrace()
), and if it fails, another debugger already attached
time-based detection
- if process executed in single-step mode, it’s very slow
- almost any timing mechanism can be used
How do you counter?
- reduce debugger visibility
- intercept API functions and return fake results
- single step through problematic part and disable anti-debugging checks
Anti-VM
Malware analysis often uses VMs to run samples, so samples refuse to run in VMs.
Look for VM-specific artifacts
- names/strings/processes/configurations typical of a VM (e.g.
VMwareService.exe
)
- but can easily be fooled by patching the OS to hide artifacts
- critical OS tables are relocated in VM
- Interrupt Descriptor Table (DT) usually higher in memory on guest machines than on host
- similarly for Global and Local Descriptor Tables (GDT, LDT)
- look for differences in memory structures using
sidt
, sgdt
, sldt
(can be done from userspace)
- virtualised hardware may have distinct fingerprints
- illegal opcodes that are supported by VM (e.g. communication with host)
- instructions that are buggy or have side-effects with some CPUs but not VM, and vice-versa
- e.g. VMware modifies functionality of
in
instruction
Anti-monitoring in general
- user interaction (see if you’re a robot)
- only do bad things after reboot
- run before/after specific date
- execute after initial call to
NtTerminateProcess
- sleep for a while, since analysis has time-outs (but some sandboxes have anti-sleep-acceleration)
- could still be avoided by introducing race condition that involves sleeping
- use environment details as key to decrypt actual payload