Blog | G5 Cyber Security

Decoding Assembly: Jmp-Pop-Call

TL;DR

This guide explains how to understand and defeat a common anti-analysis technique in malware called “jmp-pop-call”. It involves using indirect jumps, popping values from the stack, and then calling those addresses. We’ll cover how it works, why attackers use it, and practical steps for reversing it.

Understanding Jmp-Pop-Call

The jmp-pop-call technique is a way to hide where code actually goes when a program runs. Instead of directly calling functions or jumping to specific locations, the code does this:

  1. Push Addresses: It pushes addresses onto the stack. These are usually addresses of important functions or code blocks.
  2. Pop Address: It pops an address off the stack into a register (like EIP on x86).
  3. Call/Jump: It then calls or jumps to that popped address.

This makes static analysis harder because you don’t immediately see where the code is going; it depends on what’s been pushed onto the stack.

Why Attackers Use It

Decoding Jmp-Pop-Call – Step-by-Step

  1. Identify the Pattern: Look for sequences like push address, followed by pop register, and then call/jmp register. Disassemblers (like IDA Pro or Ghidra) can help highlight these patterns.
  2. Trace Stack Operations: The key is to understand what’s being pushed onto the stack *before* the pop instruction. Use a debugger to step through the code and observe the stack contents.
  3. Find Push Locations: Locate all instances of push address that feed into the jmp-pop-call sequence. These are where the target addresses are stored.
  4. Dynamic Analysis (Debugging):
    • Set a breakpoint before the pop register instruction.
    • Step over the pop register to see what address is loaded into the register.
    • Step into the call/jmp register to follow the execution flow.
  5. Static Analysis – Address Resolution: Once you know the addresses being pushed, try to resolve them in your disassembler.
    • If the address is within the same executable, the disassembler should automatically link it.
    • If it’s an external library function, ensure your disassembler has the correct symbol files (PDB for Windows).
    • Sometimes addresses are calculated dynamically; you may need to find where these calculations happen and apply them statically.
  6. Patching (Optional): If you want to simplify analysis, you can patch the code to directly call/jump to the resolved addresses.
    // Example: Replace jmp-pop-call with a direct jump
    ; Original Code
    push 0x12345678  ; Address of function A
    pop eax          ; Pop address into EAX
    jmp eax          ; Jump to the popped address
    ; Patched Code
    jmp 0x12345678  ; Direct jump to function A
  7. Scripting (Advanced): For complex jmp-pop-call chains, consider writing a script (e.g., using IDA Python) to automatically resolve addresses and patch the code.
    # Example IDA Python snippet (very basic)
    for func in Functions():
      for xref in XrefsTo(func):
        if xref.type == XREF_TYPE_CALL:
          print("Potential jmp-pop-call target: %s" % func)
    

Tools

Exit mobile version