-
Notifications
You must be signed in to change notification settings - Fork 749
Tutorial Unpacking Malware
Many malware samples are "packed" to hinder analysis. A packer is a tool that compresses or encrypts an executable. When the packed program is run, a small piece of code called the "stub" first unpacks the original code into memory and then transfers execution to it. This is a common obfuscation technique.
This tutorial will guide you through using Qiling to defeat a simple packer.
The core idea is to let the malware do the hard work for us. We will use Qiling to emulate the packed binary, allow the unpacking stub to run, and then, at the right moment, dump the unpacked, clean code from memory.
The key is to find the Original Entry Point (OEP)—the address of the first instruction of the original, unpacked code.
UPX is a popular, open-source packer. We will use it to create a simple packed binary for this tutorial.
-
Create a simple C program (
hello.c
):#include <stdio.h> int main() { printf("Hello from the original, unpacked program!\n"); return 0; }
-
Compile it:
gcc -o hello hello.c
-
Pack it with UPX:
upx -o hello_packed hello
Now, hello_packed
is our target.
Our goal is to find the OEP. A common technique used by packers is to allocate a new region of memory, write the unpacked code into it, and then jump to it. The jump instruction that takes us from the unpacking stub to the original code is often called the "tail jump."
We can find this jump by looking for instructions that transfer control to a different memory section, particularly one that was recently made executable.
Here is the Qiling script to automate this:
from qiling import Qiling
from qiling.const import QL_VERBOSE
def find_oep_and_dump(ql):
"""Hook to be called at every instruction."""
try:
# Get the current instruction mnemonic and operands
insn = ql.arch.disassembler.disasm(ql.mem.read(ql.reg.arch_pc, 16), ql.reg.arch_pc)[0]
mnemonic = insn.mnemonic
# We are looking for a jump or call instruction
if mnemonic.startswith('j') or mnemonic == 'call':
# Get the destination address of the jump/call
dest_addr = int(insn.op_str, 16)
# Get the memory map of the current and destination addresses
current_map = ql.mem.get_map_containing(ql.reg.arch_pc)
dest_map = ql.mem.get_map_containing(dest_addr)
# A jump from a non-executable section to an executable one is a strong indicator of OEP
if dest_map and 'x' in dest_map.prot and 'x' not in current_map.prot:
print(f"\n[+] OEP likely found! Jumping from {ql.reg.arch_pc:#x} to {dest_addr:#x}")
print(f" > Destination section has permissions: {dest_map.prot}")
# We found it. Dump the memory region of the destination.
start, end, perms, label = dest_map
unpacked_code = ql.mem.read(start, end - start)
with open("unpacked_binary.bin", "wb") as f:
f.write(unpacked_code)
print(f"[+] Dumped {len(unpacked_code)} bytes to unpacked_binary.bin")
# Stop the emulation
ql.emu_stop()
except Exception:
# Ignore disassembly errors
pass
if __name__ == "__main__":
# Path to the packed binary and its rootfs
packed_file = 'hello_packed'
rootfs = 'path/to/your/rootfs/x8664_linux'
ql = Qiling([packed_file], rootfs, verbose=QL_VERBOSE.OFF)
# Hook every instruction. This is slow but necessary for this heuristic.
ql.hook_code(find_oep_and_dump)
print("[*] Starting emulation to find OEP...")
ql.run()
print("[*] Emulation finished.")
-
find_oep_and_dump
Hook: This function is executed for every single instruction. - Instruction Check: It checks if the current instruction is a jump or a call.
- Memory Region Analysis: It compares the memory permissions of the current instruction's region with the destination's region.
- OEP Heuristic: The key heuristic is that the unpacking stub often resides in a non-executable section, and it jumps to the newly unpacked (and now executable) section. When we find such a jump, we declare the destination as the OEP.
- Dump and Stop: Once the likely OEP is found, we dump the entire memory region containing it to a file and stop the emulation.
This tutorial provides a basic but powerful template for using Qiling to automate the unpacking of malware, a critical first step in any analysis.
- Home
- Getting Started
- Core Concepts
- Usage
- Features
- Tutorials
- Development
- Resources