Paprer CJ Usenix03
Paprer CJ Usenix03
Mihai Christodorescu
Somesh Jha
mihai@cs.wisc.edu
jha@cs.wisc.edu
Introduction
0000
4C24
005B
FE5B
8D4B
83C3
4251
1CFA
5050
8B2B
This corresponds to the following IA-32 instruction sequence, which constitutes part of the virus body:
E8 00000000
5B
8D 4B 42
51
50
50
0F01 4C 24 FE
5B
83 C3 1C
FA
8B 2B
call 0h
pop ebx
lea ecx, [ebx + 42h]
push ecx
push eax
push eax
sidt [esp - 02h]
pop ebx
add ebx, 1Ch
cli
mov ebp, [ebx]
Related Work
showed that in general the problem of virus detection is undecidable. Similarly, several important static
analysis problems are undecidable or computationally
hard [28, 35].
However, the problem considered in this paper is
slightly different than the one considered by Cohen [10]
and Chess-White [9]. Assume that we are given a vanilla
virus V which contains a malicious sequence of instructions . Next we are given an obfuscated version O(V )
of the virus. The problem is to find whether there exists a sequence of instructions 0 in O(V ) which is semantically equivalent to . A recent result by Vadhan
et al. [3] proves that in general program obfuscation is
impossible. This leads us to believe that a computationally bounded adversary will not be able to obfuscate a
virus to completely hide its malicious behavior. We will
further explore these theoretical issues in the future.
2.2
Other Obfuscators
Original code
E8 00000000
5B
8D 4B 42
51
50
50
0F01 4C 24 FE
5B
83 C3 1C
FA
8B 2B
call 0h
pop ebx
lea ecx, [ebx + 42h]
push ecx
push eax
push eax
sidt [esp - 02h]
pop ebx
add ebx, 1Ch
cli
mov ebp, [ebx]
Signature
E800 0000 005B 8D4B 4251 5050
0F01 4C24 FE5B 83C3 1CFA 8B2B
Obfuscated code
E8 00000000
5B
8D 4B 42
90
51
50
50
90
0F01 4C 24 FE
5B
83 C3 1C
90
FA
8B 2B
call 0h
pop ebx
lea ecx, [ebx + 45h]
nop
push ecx
push eax
push eax
nop
sidt [esp - 02h]
pop ebx
add ebx, 1Ch
nop
cli
mov ebp, [ebx]
New signature
E800 0000 005B 8D4B 4290 5150
5090 0F01 4C24 FE5B 83C3 1C90
FA8B 2B
Figure 1: Original code and obfuscated code from Chernobyl/CIH, and their corresponding signatures. Newly added
instructions are highlighted.
E800
8D4B
50(90)*
5B(90)*
8B2B
0000
42(90)*
0F01
83C3
00(90)*
51(90)*
4C24
1C(90)*
5B(90)*
50(90)*
FE(90)*
FA(90)*
Hare
Finally, the Hare virus infects the bootloader sectors
of floppy disks and hard drives, as well as executable
programs. When the payload is triggered, the virus
overwrites random sectors on the hard disk, making the
data inaccessible. The virus spreads by polymorphically
changing its decryption routine and encrypting its main
body.
The Hare and Chernobyl/CIH viruses are well known
in the antivirus community, with their presence in the
wild peaking in 1996 and 1998, respectively. In spite
of this, we discovered that current commercial virus
scanners could not detect slightly obfuscated versions
of these viruses.
Architecture
Original code
call 0h
pop ebx
lea ecx, [ebx+42h]
push ecx
push eax
push eax
sidt [esp - 02h]
pop ebx
add ebx, 1Ch
cli
mov ebp, [ebx]
call 0h
pop ebx
lea ecx, [ebx+42h]
nop
nop
push ecx
push eax
inc eax
push eax
dec [esp - 0h]
dec eax
sidt [esp - 02h]
pop ebx
add ebx, 1Ch
cli
mov ebp, [ebx]
(*)
(*)
S3:
(**)
(**)
(**)
S2:
S4:
S5:
call 0h
pop ebx
jmp S2
push eax
push eax
sidt [esp - 02h]
jmp S4
add ebx, 1Ch
jmp S6
lea ecx, [ebx+42h ]
push ecx
jmp S3
pop ebx
cli
jmp S5
mov ebp, [ebx]
call 0h
pop ebx
lea ecx, [ebx+42h]
sub esp, 03h
sidt [esp - 02h]
add [esp], 1Ch
mov ebx, [esp]
inc esp
cli
mov ebp, [ebx]
Figure 3: Examples of obfuscation through dead-code insertion, code transposition, and instruction substitution. Newly added
instructions are highlighted.
Chernobyl
z0mbie-6.b
f0sf0r0
Hare
original
obfuscated
original
obfuscated
original
obfuscated
original
obfuscated
Obfuscations considered:
Norton
Antivirus
7.0
[1]
[1,2]
[1,2]
[1,2]
[1]
[2]
McAfee
VirusScan
6.01
[1,2]
[1,2]
[1,2]
[1,2]
Command
Antivirus
4.61.2
[1,2]
[1,2]
[1,2]
[1,2]
SAFE
Binary
Executable
Pattern
Definition
Loader
Intermediate Form
for the Patterns
Executable
Loader
Annotated
Annotator
CFG
Malicious
Code
Automaton
Detector
No
Executable Loader:
CodeSurfer
IDA Pro
Connector
mov
mov
mov
eax, dr1
ebx, [eax+10h]
edi, [eax]
pop
jecxz
mov
mov
pop
pop
call
jmp
ecx
SFMM
esi, ecx
eax, 0d601h
edx
ecx
edi
LOWVCTF
pop
pop
stc
pushf
ebx
eax
LOWVCTF:
Figure 5: Implementation of executable loader
module.
The detector
This component computes whether the malicious code
(represented by the malicious code automaton) appears
in the abstract representation of the executable (created
by the annotator). This component uses an algorithm
based upon language containment and unification. Details can be found in Section 7.
Throughout the rest of the paper, the malicious code
fragment shown in Figure 6 is used as a running example. This code fragment was extracted from the Chernobyl virus version 1.4.
To obtain the obfuscated code fragment depicted (Figure 7), we applied the following obfuscation transformations: dead-code insertion, code transposition, and register reassignment. Incidentally, the three commercial
antivirus software (Norton, McAfee, and Command) detected the original code fragment shown. However, the
obfuscated version was not detected by any of the three
commercial antivirus software.
Program Annotator
SFMM:
Obfuscated code
WVCTF:
mov
jmp
Loc2:
mov
LOWVCTF:
pop
jecxz
nop
mov
nop
nop
mov
jmp
Loc1:
mov
jmp
Loc3:
pop
pop
nop
call
jmp
SFMM:
pop
pop
push
pop
stc
pushf
eax, dr1
Loc1
edi, [eax]
ecx
SFMM
esi, ecx
eax, 0d601h
Loc3
ebx, [eax+10h]
Loc2
edx
ecx
edi
LOWVCTF
ebx
eax
eax
eax
6.1
Basic Definitions
Assign(eax,dr1)
jmp n_11
IrrelevantJump
Assign(ebx,[eax+10h])
jmp n_02
jecxz n_18
Loop: Pop(ecx)
If(ecx==0)
jecxz n_18
(F)
pop ebx
pop eax
nop
push eax
nop
pop eax
stc
jmp n_13
IrrelevantInstr
Assign(esi,ecx)
nop
Assign(Carry,1)
jmp n_13
Push(flags)
Pop(ecx)
call edi
jmp Loop
pop ebx
pop eax
push eax
stc
pushf
pop edx
pop ecx
IrrelevantInstr
nop
(T)
pop eax
Pop(edx)
pop ecx
IrrelevantInstr
nop
IrrelevantJump
pop edx
Pop(eax)
IrrelevantInstr
Assign(eax,0d601h)
pushf
Pop(ebx)
nop
jmp n_02
Assign(edi,[eax])
(T)
nop
jmp n_11
IrrelevantJump
(F)
IndirectCall(edi)
nop
call edi
GoTo(Loop)
jmp Loop
Dominators(B)
P ostDominators(B)
P red(B)
Succ(B)
F irst(B)
Last(B)
P revious(I)
N ext(I)
Kills(p, a)
U ses(p, a)
Alias(p, x, y)
LiveRangeStart(p, a)
LiveRangeEnd(p, a)
Delta(p, m, n)
Delta(m, p1 , p2 )
P ointsT o(p, x, a)
::
|
|
|
|
|
|
|
|
ground
[n]
(n]
ptr
s{1 , . . . , k }
u{1 , . . . , k }
1 k
>(n)
(n)
Ground types
Pointer to the base of an array of type and of size n
Pointer into the middle of an array of type and of size n
Pointer to
Structure (product of types of i )
Union
Function
Top type of n bits
Bottom type of n bits (type any of n bits)
::
(l, , i)
ground
::
int(g:s:v) | uint(g:s:v) | . . .
Table 3: A simple type system.
Code
call 0h
pop ebx
lea ecx, [ebx + 42h]
push ecx
push eax
push eax
sidt [esp - 02h]
pop ebx
add ebx, 1Ch
cli
mov ebp, [ebx]
Type
ebx :
ecx :
ebx :
ecx :
eax :
eax :
(32)
(32),
ptr (32)
(32)
(32)
(32)
eax : (32)
ebx : int(0:1:31)
ebp : (32),
ebx : ptr (32)
IA-32 logical address is a combination of a 16-bit segment selector and a 32-bit segment offset, thus its type
is the cross product of a 16-bit unsigned integer and a
32-bit pointer.
6.2
Abstraction Patterns
=
=
=
{ x1 : 1 , . . . , xk : k }
h I(v1 , . . . , vm ) | I : 1 m i
boolean expression involving static
analysis predicates and logical operators
This pattern represents two instructions that pop a register X off the stack and then add a constant value to
it (0x03AF). Note the use of uninterpreted symbol X
in the pattern. Use of the uninterpreted symbols in a
pattern allows it to match multiple sequences of instructions, e.g., the patterns shown above matches any instantiation of the pattern where X is assigned a specific register. The type int(0 : 1 : 31) of X represents an integer
with 31 bits of storage and one sign bit.
Compatible(B1 , B2 ) =
x V.( [x, y1 ] B1 [x, y2 ] B2 )
(y1 = y2 )
The union of two compatible bindings B1 and B2 includes all the pairs from both bindings. For incompatible
bindings, the union operation returns an empty binding.
if Compatible(B1 , B2 )
def
B1 B2 =
if Compatible(B1 , B2 )
When matching an abstraction pattern against a sequence of instructions, we use unification to bind the
free variables of to actual values. The function
Unify ( h. . . , opi (xi,1 , . . . , xi,ni ), . . . i1im , )
returns a most general binding B if the instruction sequence h. . . , opi (xi,1 , . . . , xi,ni ), . . . i1im can be unified with the sequence of instructions O specified in
the pattern . If the two instruction sequences cannot be unified, Unify returns false. Definitions and algorithms related to unification are standard and can be
found in [20].3
6.3
Annotator Operation
IA-32 Datatype
Type Expression
uint(0:0:8)
uint(0:0:16)
uint(0:0:32)
uint(0:0:64)
uint(0:0:128)
int(0:1:7)
int(0:1:15)
int(0:1:31)
int(0:1:63)
int(0:1:127)
float(0:1:31)
float(0:1:63)
float(0:1:79)
(32)
uint(0:0:16) uint(0:0:32) (48)
(32)
(32)
int(0:1:31)
(16)
(16)
(8)
(8)
Table 4: IA-32 datatypes and their corresponding expression in the type system from Table 3.
Detector
Intuitively, the malicious code automaton is a generalization of the vanilla virus, i.e., the malicious code automaton also represents obfuscated strains of the virus.
Formally, a malicious code automaton (or MCA) A is a
6-tuple (V, , S, , S0 , F ), where
V = {v1 : 1 , . . . , vk : k } is a set of typed variables,
= {1 , . . . , n } is a finite alphabet of patterns
parametrized by variables from V , for 1 i n,
Pi = (Vi , Oi , Ci ) where Vi V ,
S is a finite set of states,
: S 2S is a transition function,
S0 S is a non-empty set of initial states,
F S is a non-empty set of final states.
An MCA is a generalization of an ordinary finite-state
automaton in which the alphabets are a finite set of patterns defined over a set of typed variables. Given a binding B for the variables V = {v1 , . . . , vk }, the finite-state
automaton obtained by substituting B(vi ) for vi for all
1 i k in A is denoted by B(A). Note that B(A) is
a vanilla finite-state automaton. We explain this using
mov
esi, ecx
mov
eax, 0d601h
pop
edx
pop
ecx
B1 = { [A, esi],
[B, ecx],
[C, eax],
[D, edx] }
S2
Pop(D)
S3
Pop(B)
S4
mov
esi, eax
mov
ebx, 0d601h
pop
ecx
pop
eax
B2 = { [A, esi],
[B, eax],
[C, ebx],
[D, ecx] }
S0
IrrelevantJump()
Move(A,dr1)
S1
IrrelevantJump()
Move(B,[A+10h])
S2
IrrelevantJump()
Move(E,[A])
S3
IrrelevantJump()
Pop(C)
S4
IrrelevantJump()
JumpIfECXIsZero() JumpIfECXIsZero()
S5
IrrelevantJump()
S11
Move(F,C)
S6
IrrelevantJump()
Pop(B)
S12
Move(A,0d601h)
S7
IrrelevantJump()
IrrelevantJump()
S13
IrrelevantJump()
Jump()
IrrelevantJump()
SetCarryFlag()
S14
Pop(C)
S9
IrrelevantJump()
Pop(A)
Pop(D)
S8
IrrelevantJump()
IrrelevantJump()
PushEFLAGS()
S15
IrrelevantJump()
IndirectCall(E)
S10
IrrelevantJump()
Figure 11: Malicious code automaton corresponding to code fragment from Figure 6.
associated with the start node is the list of all pairs [s, ],
where s is an initial state of the MCA A, and the post
list associated with the start node is empty.
The do-until loop: The do-until loop updates the pre
and post lists of all the nodes. At the end of the loop, the
worklist WS contains the set of nodes whose pre or post
information has changed. The loop executes until the pre
and post information associated with the nodes does not
change, and a fixed point is reached. The join operation
that computes Lpre
takes the list of state-binding pairs
i
from all of the Lpost
sets for program points preceding
j
i and copies them to Lpre
only if there are no repeated
i
states. In case of repeated states, the conflicting pairs
are merged into a single pair only if the bindings are
compatible. If the bindings are incompatible, both pairs
are thrown out.
Diagnostic feedback: Suppose our algorithm returns
a non-empty set, meaning a malicious pattern is common to the annotated CFG P and MCA A. In this
case, we return the sequence of instructions in the executable corresponding to the malicious pattern. This is
achieved by keeping an additional structure with the algorithm. Every time the post list for a node n is updated
by taking a transition in A (see the statement 14 in Figure 12), we store the predecessor of the added state, i.e.,
if [(s, ), Bs B] is added to Lpost
n , then we add an edge
from s to (s, ) (along with the binding Bs B) in the
conassociated structure. Suppose we detect that Lpost
n
tains a state [s, Bs ], where s is a final state of the MCA
A. Then we traceback the associated structure from s
until we reach an initial state of A (storing the instructions occurring along the way).
Experimental Data
Testing Environment
Chernobyl
z0mbie-6.b
f0sf0r0
Hare
Annotator
avg.
(std. dev.)
1.444 s (0.497 s)
4.600 s (2.059 s)
4.900 s (2.844 s)
9.142 s (1.551 s)
Detector
avg.
(std. dev.)
0.535 s (0.043 s)
1.149 s (0.041 s)
0.923 s (0.192 s)
1.604 s (0.104 s)
Table 5: SAFE performance when checking obfuscated viruses for false negatives.
z0mbie-6.b
f0sf0r0
Hare
Annotator
avg.
(std. dev.)
3.400 s (1.428 s)
4.900 s (1.136 s)
1.000 s (0.000 s)
Detector
avg.
(std. dev.)
1.400 s (0.420 s)
0.840 s (0.082 s)
0.220 s (0.019 s)
Table 6: SAFE performance when checking obfuscated viruses for false positives against the Chernobyl/CIH virus.
n
(4)
WS
(5)
do
(6)
WS old WS
(7)
WS
(8)
foreach n N
// update pre information
S
post
(9)
if Lpre
=
6
L
n
mP revious(n) m
S
post
(10)
Lpre
n
mP revious(n) Lm
(11)
WS WS {n}
(12)
foreach n N
// update post information
(13)
N ewLpost
n
(14)
foreach [s, Bs ] Lpre
n
(15)
foreach [, B] Annotation(n)
// follow a transition
(16)
Compatible(Bs , B)
(17)
add [ (s, ), Bs B ] to N ewLpost
n
post
(18)
if Ln 6= N ewLpost
n
N ewLpost
(19)
Lpost
n
n
(20)
WS WS {n}
(21)
until WS =
(22)
return n N . [s, Bs ] Lpost
.sF
n
Figure 12: Algorithm to check a program model against a malicious code specification.
We presented a unique view of malicious code detection as a obfuscation-deobfuscation game. We used this
viewpoint to explore obfuscation attacks on commercial
virus scanners, and found that three popular virus scanners were susceptible to these attacks. We presented a
static analysis framework for detecting malicious code
patterns in executables. Based upon our framework, we
have implemented SAFE, a static analyzer for executables that detects malicious patterns in executables and is
resilient to common obfuscation transformations.
For future work, we will investigate the use of theorem provers during the construction of the annotated
CFG. For instance, SLAM [2] uses the theorem prover
Simplify [16] for predicate abstraction of C programs.
Our detection algorithm is context insensitive and does
References
[1] K. Ashcraft and D. Engler. Using programmer-written
compiler extensions to catch security holes. In 2002
tiffdither.exe
winmine.exe
spyxx.exe
QuickTimePlayer.exe
Executable
size
9,216 B
96,528 B
499,768 B
1,043,968 B
.text
size
6,656 B
12,120 B
307,200 B
499,712 B
Procedure
count
29
85
1,765
4,767
Annotator
avg.
(std. dev.)
6.333 s (0.471 s)
15.667 s (1.700 s)
193.667 s (11.557 s)
799.333 s (5.437 s)
Detector
avg.
(std. dev.)
1.030 s (0.043 s)
2.283 s (0.131 s)
30.917 s (6.625 s)
160.580 s (4.455 s)
Table 7: SAFE performance in seconds when checking clean programs against the Chernobyl/CIH virus.
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
[10]
[11]
[12]
[13]
[14] P. Cousot and N. Halbwachs. Automatic discovery of linear restraints among variables of a program. In Proceedings of the 5th ACM Symposium on Principles of Programming Languages (POPL78), pages 84 96. ACM
Press, January 1978.
[15] D. W. Currie, A. J. Hu, and S. Rajan. Automatic formal
verification of dsp software. In Proceedings of the 37th
ACM IEEE Conference on Design Automation (DAC00),
pages 130135. ACM Press, 2000.
[16] D. Detlefs, G. Nelson, and J. Saxe. The simplify theorem prover. http://research.compaq.com/SRC/
esc/simplify.html .
[17] U. Erlingsson and F. B. Schneider. IRM enforcement of
Java stack inspection. In 2000 IEEE Symposium on Security and Privacy (Oakland00), pages 246255, May
2000.
[18] J. Esparza, D. Hansel, P. Rossmanith, and S. Schwoon.
Efficient algorithms for model checking pushdown systems. In Proceedings of the 12th International Conference on Computer-Aided Verification (CAV00), volume
1855 of Lecture Notes in Computer Science, pages 232
247. Springer-Verlag, July 2000.
[19] X. Feng and Alan J. Hu. Automatic formal verification
for scheduled VLIW code. In Proceedings of the Joint
Conference on Languages, Compilers and Tools for Embedded Systems - Software and Compilers for Embedded Systems (LCTES/SCOPES02), pages 8592. ACM
Press, 2002.
[20] M. Fitting. First-Order Logic and Automated Theorem
Proving. Springer-Verlag, 1996.
[21] J. T. Giffin, S. Jha, and B. P. Miller. Detecting manipulated remote call streams. In Proceedings of the 11th
USENIX Security Symposium (Security02). USENIX
Association, August 2002.
[22] J.E. Hopcroft, R. Motwani, and J.D. Ullman. Introduction to Automata Theory, Languages, and Computation.
Addison Wesley, 2001.
[23] S. Horwitz, T. Reps, and D. Binkley. Interprocedural slicing using dependence graphs. ACM Transactions on Programming Languages and Systems (TOPLAS), 12(1):26
60, January 1990.
[24] GrammaTech Inc. Codesurfer code analysis and
understanding tool. http://www.grammatech.com/
products/codesurfer/index.html (Last accessed: 3
February 2003).
[40] T. Reps, S. Horwitz, and M. Sagiv. Precise interprocedural dataflow analysis via graph reachability. In Proceedings of the 22th ACM SIGPLAN-SIGACT Symposium
on Principles of Programming Languages (POPL95),
pages 4961. ACM Press, January 1995.
[41] M. Samamura. Expanded Threat List and Virus Encyclopaedia, chapter W95.CIH. Symantec Antivirus
Research Center, 1998. http://securityresponse.
//www.viruslist.com/eng/viruslistbooks.
asp?id=32&key=0000100007000020000100003
(Last accessed: 3 February 2003).
symantec.com/avcenter/venc/data/cih.html
(Last accessed: 3 February 2003).
[29] R.W. Lo, K.N. Levitt, and R.A. Olsson. MCF: A malicious code filter. Computers & Society, 14(6):541566,
1995.
(Last
[32] G. Morrisett, K. Crary, N. Glew, and D. Walker. Stackbased Typed Assembly Language. In Xavier Leroy and
Atsushi Ohori, editors, 1998 Workshop on Types in Compilation, volume 1473 of Lecture Notes in Computer Science, pages 28 52. Springer-Verlag, March 1998.
[33] G. Morrisett, D. Walker, K. Crary, and N. Glew. From
System F to Typed Assembly Language. In Proceedings
of the 25th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL98), pages 85
97. ACM Press, January 1998.
[34] S.S. Muchnick. Advanced Compiler Design and Implementation. Morgan Kaufmann, 1997.
[35] E.M. Myers. A precise interprocedural data flow algorithm. In Conference Record of the 8th Annual ACM
Symposium on Principles of Programming Languages
(POPL81), pages 219 230. ACM Press, January 1981.
[36] C. Nachenberg. Polymorphic virus detection module.
United States Patent # 5,696,822, December 9, 1997.
[37] C. Nachenberg. Polymorphic virus detection module.
United States Patent # 5,826,013, October 20, 1998.
[38] G. C. Necula. Translation validation for an optimizing
compiler. In Proceedings of the ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI00), pages 8394. ACM Press, June 2000.
[39] S. Owre, S. Rajan, J. Rushby, N. Shankar, and M. Srivas. PVS: Combining specification, proof checking, and
model checking. In Proceedings of the 8th International
Conference on Computer-Aided Verification (CAV96),
volume 1102 of Lecture Notes in Computer Science,
pages 411414. Springer-Verlag, August 1996.
[44] P. Szor and P. Ferrie. Hunting for metamorphic. In Proceedings of Virus Bulletin Conference, pages 123 144,
September 2001.
[45] TESO.
Notes
1 Note that the subroutine address computation had to be updated to
take into account the new nops. This is a trivial computation and can
be implemented by adding the number of inserted nops to the initial
offset hard-coded in the virus-morphing code.
2 Most executable formats require that the various sections of the
executable file start at certain aligned addresses, to respect the target
platforms idiosyncrasies. The extra space between the end of one
section and the beginning of the next is usually padded with nulls.
3 We use one-way matching which is simpler than full unification.
Note that the instruction sequence does not contain any variables. We
instantiate variables in the pattern so that they match the corresponding
terms in the instruction sequence.