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!

The Discovery and Exploitation of CVE-2022-25636- 805

Nick GregoryPosted 4 Years Ago
  • One night, the author of this post decided to look at the netfilter subsystem of the Linux kernel. Netfilter enables packet filtering, NAT translation, packet logging and many other things. In particular, they chose to review the protocol parsers for two reasons. First, protocol parsing of non-trivial data tends to be error prone. Secondly, the configuration input came from userland with good control.
  • While digging through the source code of this functionality, they ran across the following line:
    entry = &flow->rule->action.entries[ctx->num_actions++];
    
    This line caught their eye because it was incrementing ctx->num_actions and using it as an index without any bounds checks. Secondly, the struct flow->rule->action.entries had seemingly not obvious relationship with ctx->num_actions. Not a bug yet but a code smell that prompted for more review!
  • How do we determine if an overflow of some sort is occurring here? The hacker asked themselves what is the length of the action.entries array, what are the controls on this function and how are things initialized? All great questions to get to the point of figuring out if this is a bug or not. While diving into this, they found that the path for how things were called. Additionally, they found that the num_actions counter is ONLY incremented when the NFT_OFFLOAD_F_ACTION flag is set on the offload flags.
  • When reviewing the code that called this functionality, none of them were setting NFT_OFFLOAD_F_ACTION. The problem is that the actions array is allocated based on the number of immediate expressions types and does NOT take into consideration that this index could be incremented. As a result, the MAX index would be different than the original allocation size.
  • Finding a supposed vulnerability in the Linux kernel and trigger it are two different things. The author had to learn how to trigger the code for nftables. After playing around with the CLI for a while, they noticed it was adding arbitrary restrictions, which screwed up his exploitation attempts. Because of this, they use the GoLang implementation with custom overwrites. After playing with binary settings and tracing code, they were able to trigger the bug with a kernel panic. Exploitation time :)
  • When starting to hit exploitation, it is crucial to know what the bug provides. The dereference code above gave as an entry struct to play with. In particular, the fields id and dev were being written. To find the offsets of these structs, they used pahole. The id field writes a value of 4 or 5; dev was writing a pointer 24 bytes past the end of the array.
  • The size of the allocation depending on the amount of immediate rules that were inside of the filter. With no rules, it was 32 bytes; with one rule it's 112 and two rules it is 192. The allocation location is important, since it changes the heap (slab) location that is used. While hunting for target objects in this slab, they hit a road block, since no good targets were around.
  • After going through this post on a recent kernel bug, they realized something: this bug can be triggered multiple times! This allows hitting different offsets as a result and chain multiple objects together. The post by Alexander Popov uses the security pointer of msg_msg at offset 40 (which is hittable offset by us) to land an arbitrary kfree.
  • Using the security pointer inside of msg_msg as a target, it will eventually be freed. Since the write will have our net_device pointer (dev in the code above), we have manufactured a use after free from this. In order to make this viable, a ton of heap spraying was done to get tihs to line up JUST right for the attack.
  • Once the free has occurred, the goal is to get this heap chunk back to overwrite information within the field. In particular, we can set the net_device.netdev_ops function pointer to get code execution in the kernel when this object is overlapped. The exploit chain above has a few nice quirks:
    • The overwritten pointer for list_head.prev inside of the msg_msg can be FOUND by calling MSG_CPY. This allows us to know which one is corrupted.
    • Besides being able to tell if it's corrupted or not, it contains a kernel heap pointer for us!
    • Checking the /code> of a security pointer tells us WHICH one was corrupted with the ID value from above. Boom, code execution!
  • The exploit was the opposite of consistent though; they kept getting crashes and had no idea why. They hypothesize it had to do with freelist randomization or the other sporadic allocations within the 128 slab (they tried to rewrite this in a different slab even). This was working 30% of the time, but they never figured out what was making it fail. The exploit code is on Github.
  • Overall, a super interesting post about the bug discovery, primitive gathering and exploitation strategizing. I absolutely LOVE Nick's blog posts since they show the nitty-gritty details of finding and exploiting bugs in the Linux kernel. Good work!