Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

Kernel Pwning with eBPF: a Love Story- 580

https://www.graplsecurity.com/post/kernel-pwning-with-ebpf-a-love-storyPosted 4 Years Ago
  • Extended Berkeley Packet Filter (eBPF) is a way for a user mode applicatin to run code in the kernel without needing to install a kernel module. This is important as the kernel is fast and allowing anybody to add kernel modules is a security hazzard. eBPF is used for tracing, instrumentation, hooking of system calls, debugging and packet capturing/filtering.
  • eBPF is programmed in a higher level language that is compiled into eBPF bytecode. The VM has a simple instruction set with twelve 64-bit registers, a program counter and a 512 byte sized stack. Prior to loading the bytecode, the verifier runs to ensure the security of the code being added to the kernel. Once loaded, the program specifies a hook point, which will be acted upon with event driven programming.
  • The sysctl knob kernel.unprivileged_bpf_disabled determines whether users can run the programs within the context of the kernel. If this is set to true (like in most Linux distros), then this is a great attack surface for local privilege escalation.
  • The verifier, as you can imagine, is extremely complicated. How do you even begin to determine if code is safe or not? For more information on the verifier, read this post yourself or read it at ZDI. But, in general, the verifier validates the following:
    • No back edges, loops or unreachable instructions.
    • No pointer comparisons. Only scalar values can be added or subtracted from the pointer.
    • pointer arithmetic cannot leave the safe bounds of the memory allocated to it. This is done by verifying the upper and lower boudns of the values of each register.
    • No pointers can be stored in maps or stored as a return value, in order to avoid leaking kernel addresses to user space.
  • When operating on the logical operations (AND, OR and XOR), the 32 bit tracking function has a small flaw in it. When updating the expected bounds of a program there is code there is code that defers it to the 64-bit operation while in the 32-bit operation. The validation on whether to do this update or not comes from two different variables!
  • The 32-bit operation uses the function tnum_subreg_is_const and the 64-bit code uses the function tnum_is_const. The difference is that the 32-bit function returns true if the the lower 32 bits of the register are known constants, and the latter returns true only if the entire 64 bits are constant. This becomes an issue if the operation involves registers where the lower 32 bits are known but the upper 32 bits are unknown. This breaks the assumption mentioned in the comments!
  • The article contains a large amount of information not related to the exploit, such as debugging tricks with runtime debugging, logging and many other things. If you are looking into digging into eBPF, this would be a wonderful article to read to help you out with this with an insane amount of reference articles as well.
  • The authors goal was to lengthen the registers with invalid boundaries in order widen the safe range for pointer arithmetic. By doing this, we can read out of bounds is possible to leak a kernel address. It should be noted that this was possible because of the bug above having unexpected consequences allowing for the converting of a pointer type into a scalar type to skip all sanity checks.
  • With an initial leak, we want to get an arbitrary read/write primitive by an out of bounds access on an array via eBPF map pointers. Actually making this happen with the bug comes down to tricking the verifier that the value is 0 but is actually equal to 0 at runtime. With this, the author defers to the ZDI article above for getting an arbitrary read/write primitive (which is game over).
  • The exploitation is insanely complicated and relies upon a deep understanding of how eBPF works to exploit. To make matters even crazier, the eBPF does dynamic patching of the bytecode in order to make it faster. Above is an overview of how it works but there is much more to look with how eBPF works internally.
  • At the end of the article, the author makes an interesting point. In a recent exploit by Qualys, they used the a vulnerability to corrupt eBPF code after it had been verified in order to achieve a read/write primitive in the kernel. In order to prevent this exploit method from being usable in the future, it would be a good idea to mark the region read-only before verification.