Security builder & leader

Tips for Reverse-Engineering Malicious Code

Reversing malicious Windows executables involves examining static properties, identifying suspicious strings and API calls, performing behavioral analysis, and using disassemblers and debuggers. Key areas include understanding x86/x64 registers, common assembly instructions, risky API calls for code injection, DLL loading, and keylogging.

Tips for Reverse-Engineering Malicious Code - illustration

This cheat sheet outlines tips for reversing malicious Windows executables via static and dynamic code analysis with the help of a debugger and a disassembler. To print it, use the one-page PDF version; you can also edit the Word version to customize it for you own needs.

Overview of the Code Analysis Process

  1. Examine static properties of the Windows executable for initial assessment and triage.
  2. Identify strings and API calls that highlight the program’s suspicious or malicious capabilities.
  3. Perform automated and manual behavioral analysis to gather additional details.
  4. Emulate code execution to identify characteristics and areas for further analysis.
  5. Use a disassembler and decompiler to statically examine code related to risky strings and APIs calls.
  6. Use a debugger for dynamic analysis to examine how risky strings and API calls are used.
  7. If appropriate, unpack the code and its artifacts.
  8. As your understanding of the code increases, add comments, labels; rename functions, variables.
  9. Progress to examine the code that references or depends upon the code you’ve already analyzed.
  10. Repeat steps 5-9 above as necessary (the order may vary) until analysis objectives are met.

Common 32-Bit Registers and Uses

RegisterUse
EAXAddition, multiplication, function results
ECXCounter; used by LOOP and others
EBPBaseline/frame pointer for referencing function arguments (EBP+offset) and local variables (EBP-offset)
ESPPoints to the current “top” of the stack; changes via PUSH, POP, and others
EIPInstruction pointer; points to the next instruction; shellcode gets it via call/pop
EFLAGSContains flags that store outcomes of computations (e.g., Zero and Carry flags)
FSF segment register; FS:[0] points to SEH chain, FS:[0x30] points to the PEB.

Common x86 Assembly Instructions

InstructionDescription
mov EAX,0xB8Put the value 0xB8 in EAX.
push EAXPut EAX contents on the stack.
pop EAXRemove contents from top of the stack and put them in EAX .
lea EAX,[EBP-4]Put the address of variable EBP-4 in EAX.
call EAXCall the function whose address resides in the EAX register.
add esp,8Increase ESP by 8 to shrink the stack by two 4-byte arguments.
sub esp,0x54Shift ESP by 0x54 to make room on the stack for local variable(s).
xor EAX,EAXSet EAX contents to zero.
test EAX,EAXCheck whether EAX contains zero, set the appropriate EFLAGS bits.
cmp EAX,0xB8Compare EAX to 0xB8, set the appropriate EFLAGS bits.

Understanding 64-Bit Registers

|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| R8 (64 bits)
________________________________|||||||||||||||||||||||||||||||| R8D (32 bits)
________________________________________________|||||||||||||||| R8W (16 bits)
________________________________________________________|||||||| R8B (8 bits)

Passing Parameters to Functions on Windows

ArgumentLocation
arg0[EBP+8] on 32-bit, RCX on 64-bit
arg1[EBP+0xC] on 32-bit, RDX on 64-bit
arg2[EBP+0x10] on 32-bit, R8 on 64-bit
arg3[EBP+0x14] on 32-bit, R9 on 64-bit

Decoding Conditional Jumps

InstructionDescription
JA / JGJump if above/jump if greater.
JB / JLJump if below/jump if less.
JE / JZJump if equal; same as jump if zero.
JNE / JNZJump if not equal; same as jump if not zero.
JGE/ JNLJump if greater or equal; same as jump if not less.

Some Risky Windows API Calls

Additional Code Analysis Tips

Post-Scriptum

Authored by Lenny Zeltser with feedback from Anuj Soni. Malicious code analysis and related topics are covered in the SANS Institute course FOR610: Reverse-Engineering Malware, which they’ve co-authored. This cheat sheet, version 1.1, is released under the Creative Commons v3 “Attribution” License.

About the Author

Lenny Zeltser is a cybersecurity executive with deep technical roots, product management experience, and a business mindset. As CISO at Axonius, he leads the security and IT program, focusing on trust and growth. He is also a Faculty Fellow at SANS Institute and the creator of REMnux, a popular Linux toolkit for malware analysis. Lenny shares his perspectives on security leadership and technology at zeltser.com.

Learn more →