allows a debugger to single-step on control transfers caused by branches. What does this imply to defense against control-flow hijacking attacks (e.g. ROP or JOP) ?
Control-flow Transfer Instructions
Control-flow hijacking attacks allow an attacker to overwrite a value that is loaded into the program counter (EIP) of a running program, typically redirecting execution to his own injected code or existing ROP/JOP gadget chains for executing arbitrary malicious code. In general, the value that is subverted could a jump target address, function pointer, or return address in a user-controlled stack.
Call/Jmp/Ret instructions, called as control-transfer branch instructions, are used by control-flow hijacking attacks to redirect CPU execution. There are many software tools that can perform binary analysis on those instructions, for example, by dynamically instrumenting control-flow graph (CFG) for control-flow integrity (CFI) enforcement. So it would be good if the hardware processor can generate an exception on (or after) those control-transfer instructions.
Single-stepping on Branches
In x86/Intel processor architecture, there is a bit (Trap Flag, TF) in EFLAGS register as below. It is set to enable single-step mode for debugging, clear to disable single-step mode.
In single-step mode, the processor generates a debug exception (#DB) after each instruction. This allows the execution state of a program to be inspected after each instruction.
However, things are changed under a special condition as indicated below. When BTF (single-step on branches) flag in IA32_DEBUGCTL MSR is set, the processor treats the TF flag in the EFLAGS register as a “single-step on branches” flag rather than a “single-step on instructions” flag. This mechanism allows single-stepping the processor on taken branches. Note that the exception is a trap-class exception, which means the exception is generated after the branch instruction (call/ret/jmp) is executed.
So now we can make processor generate an exception (#DB) on (^after^, actually) every call/jmp/ret instruction.
We might have some usages with this capability, for example:
- Build dynamical CFG (Control Flow Graph) without changes to software binary or source code.
- Detect unknown control-flow hijacking vulnerabilities by using dynamic taint analysis, e.g. when a tainted value loaded into the program counter (EIP) has been influenced by data from the untrusted inputs.
- Perform software-invisible hooks for function calling (target of "call" instruction).
However, there are some limitations:
- Performance overhead !!! (unless we use it under some environment where performance is not a big concern).
- It cannot control jmp/ret/call individually, for example, trigger exceptions only on CALL instructions, or RET instructions, or even only on "indirect" jmp/call instructions (because normally code with direct-jmp/call is trusted due to W^X on code section).
- It also has no CPL (user or kernel) controls, but we can control it through EFLAGS.TF bit crossing system call/ret.
- Because this #DB is controlled by EFLAGS bit, it can be easy to be disabled by using a "popf" instruction if the stack is controlled by an attacker:( .
- It obviously requires OS kernel changes (Does OS provide legitimate #DB handler registration?)
Please let me know if you have any comments.
Intel IA32 architecture software development manual: