Last week, I find that Google CTF Quals take PICFI as a pwn chanllenge. Since this paper is also mentioned in The Dynamics of Innocent Flesh on the Bone: Code Reuse Ten Years Later, I decide to take simple note no this paper and take the pwn challenge to solve.
In this paper, the author propose a more fine-grained CFI compared with conventional CFI proposed by Abadi.
CFI extracts a Control-Flow Graph (CFG) from a program and instruments the program by adding checks before indirect branch instructions.
A static, all-input CFG. This is typically computed by static analysis. We call this the Static CFG, abbreviated to SCFG.
The CFG that is currently enforced. Checks are inserted before indirect branches to consult this CFG to decide whether indirect branches are allowed. We call this the Enforced CFG, abbreviated to ECFG.
Take the code in above picture as an example. In conventional CFI, the valid return address of foo will be L1 and L2. PICFI aims at elimination such overestimation to raise the bar of exploit.
Edge addition is issued by untrusted code. To prevent PICFI from arbitrarily adding edges, it should be able to identify those edges that shall never be added. To address this challenge, PICFI reuses previous CFI methods to first compute an all-input CFG statically.
PICFI dynamically activates indirect branch targets. When a target address is submitted to PICFI’s runtime for activation, it consults the encoded SCFG to check if the address is a valid target address; if so, the runtime activates the address (by enabling it in the ECFG) so that future indirect branches can jump to it. Address activation is divided into three cased: (1)Return address, (2)Function pointer, (3)Virtual function method and (4) Exception handler.
Case of return address of direct call The foostub is replaced with patchstub at loading time. When line 4 is reached after the program starts execution, the control transfers to patchstub. It firstly pops the return address L from the stack to %r11, which can be used as a scratch register thanks to the calling convention of x86-64 Linux. It then invokes return_address_activate provided by PICFI’s runtime.
The runtime, once entered, saves the context and activates L by updating the ECFG. PICFI reuses MCFI’s tables for encoding an SCFG. There is a table called Tary in MCFI that lists all valid target addresses. So activating an address means an update to the Tary table to enable the address.
Case of return address of indirect call The cfi-check at line 3 is an operation that performs CFI checks and can be implemented using any conventional CFI. At load time, a direct call to the patchstub can be inserted. Note that in this case when patchstub gets called its stack pop instruction does not load L to %r11, but the runtime can straightforwardly calculate L by rounding %r11 to the next 8-byte aligned address. After the return address is activated by the runtime, the patchstub call is patched back to the 5-byte no-op.
PICFI activates the address of a function at the place when the function’s address is taken.
Virtual Function Method
PICFI activates a virtual method’s address when the first object of the virtual method’s class is instantiated. In πCFI, the address-activation operations for virtual method addresses are actually inserted into the corresponding classes’ constructors so that, when a constructor gets first executed, all virtual methods in its virtual table are activated.
PICFI considers an exception handler’s address activated when the function where the exception handler resides gets executed for the first time. Therefore, same as how πCFI instruments and patches C++ constructors, πCFI also instruments those functions that have exception handlers when loading the code into memory and patches the code back to its original bytes when such functions are executed for the first time.