32 KiB
kernelCTF exploit submission style guide
Note: this is a draft version of our style guide. Feel free to give feedback and improvement ideas on Discord.
High-level exploit structure
Functions and their naming conventions
Structure the exploit's source code into separate functions based on what they do (see categories below), especially if it is a complex exploit.
The goal of this is to make it easy for the reader get a good understanding on how the exploit works just by reading the main() function alone (which should not be too long). Then the separate functions can be read for further details if needed.
If implementation of a function would be trivial (1-4 lines) then you don't have to put them into a separate function, but it should be clear what they do or commented accordingly.
Feel free to choose a structure which you deem the most readable, but there are a recommendations below, which we think helps to make the exploit more structured, easier to read and faster to review:
Using the following prefixes for your functions:
-
Setting up the environment.
setup_- generic functions which setup the environment (e.g. userns, CPU affinity, network interfaces)
-
Triggering a vulnerability.
-
vuln_- vulnerability related functions -
vuln_setup- prepare victim structures before corruption -
vuln_trigger- triggering vulnerability, making the initial corruption- Comment what type of corruption is happening (UAF, OOB, etc) in which objects (e.g.
drr_class) and in which cache (e.g.kmalloc-cg-1k).
- Comment what type of corruption is happening (UAF, OOB, etc) in which objects (e.g.
-
race_- functions helping to win a race (e.g. epoll)
-
-
Spraying or grooming the heap.
-
spray_- spraying related functions- For example:
spray_msg_msg_kmalloc_cg_1k. - Make it clear what type object is sprayed and into which cache (e.g.
kmalloc-cg-1k).
- For example:
-
fake_- for creating fake objects which can be sprayed later- For example:
fake_msg_msg_for_oob_readorfake_msg_msgin a more generic case. - Make it clear what type object is created, which fields are sets and in a more specialized case, what side-effect is achieved via this (e.g. primitive transfer into OOB read, arbitrary free, RIP control, etc).
- For example:
-
-
Executing cross-cache attack.
spray_cross_cache_- cross-cache related functions
-
Leaking information (e.g. heap pointer, kASLR base address).
leak_- functions related for leakingleak_kaslr_- leaking kASLRleak_heap_- leaking heap address
-
Getting RIP control.
rop_- ROP-related functions (e.g. which create a ROP chain)rip_- other RIP control primitives
Generic utility functions, which are not strictly related to this specific exploit, do not have to prefixed, but if you prefer, you can use the util_ prefix or you can move them into separate .h files (e.g. util.h).
- This category can include e.g. a generic
msgrcvwrapper to make it easier to call the kernel API, but if you are using for spraying (e.g. in case ofmsg_msgjust putting inside a for loop), then we would prefer it be prefixed withspray_instead.
Exploit steps and connection to the writeup
Make sure that it's easy to understand which part of the exploit is described in which part of the writeup and vice versa.
You can make it very explicit by using one of the following comments:
// @step(1)- in case you are using numbered steps in your writeup// @step(name="Triggering the Vulnerability")- where the name parameter matches one of the Markdown header titles (the text after one of the#,##,###, ... headers)
Although we prefer the above (it's more declarative), you can also use free-text comments as long as it makes it easier to understand.
Sprayed and leaked structures
For structures which are used for spraying and for "fake objects" which are created in buffers, we recommend two approaches:
Approach #1
Declare the whole structure in your exploit and cast the buffer pointer to a structure pointer and set the fields.
This works better if the whole or majority of the structure is used and if the structure does not contain too much not used fields.
<style> pre { padding: 3px 0 !important; border: none } .highlight { background-color: initial !important; } .markdown-body .highlight { margin-bottom: 5px } .markdown-body table { display: table; } .markdown-body td { width: 50%; } .markdown-body pre { padding: 0; background:none; border:none } .highlight .err { color: initial; background-color: initial } td p { font-size: 0.9em; margin:5px 0 10px 0 !important } </style>| ❌ Code to be improved | ✅ Expected code quality |
|
|
Approach #2
If you need to only use a few fields from a structure, use #define for those offsets and name them as <struct_name>_OFFS_<field_name>. For example: #define PIPE_BUFFER_OFFS_OPS 0x10.
Make sure there are no typos in <struct_name> and <field_name>, so they can automatically parsed, and replaced if needed.
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Sizes should be defined as <struct_name>_SIZE.
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Name and/or comment numeric constants
Numeric constants should be named whenever possible and/or their purpose should be explained in a comment.
Use exported enum values for kernel APIs (e.g. syscalls, ioctls) by including the appropriate header files. Locally #define non-exported one in your exploit.
Also use #defines for those values which could change depending on the target or which may require finetuning (including but not limited to):
- structure sizes and field offsets
- ROP gadget, stack pivot offsets, kernel symbols
- memory addresses
- spraying rounds
Comment other non-trivial constants and explain their usage (including but not limited to):
-
If they are used to satisfy a check within the kernel (e.g. in case they are part of a fake object) then comment there what check they need to satisfy.
-
If their value influences which cache the object gets into.
(You can also use comments instead of #defines if it is impractical to use #define.)
Example: Kernel API enums
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example: Kernel symbols, structure field offsets
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example: Memory addresses
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example: Non-trivial constant values
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example: Fake object values
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example: Constant influences the used cache
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Naming conventions
Use descriptive names for including but not limited to: variables, functions, defines.
Make sure that the name is not misleading.
Example #1
Issue #1: Too generic function name (foo) which does not describe the purpose of the function which is saving data into the CPU Entry Area.
Issue #2: Misleading name (rop) from which the reader assumes that a ROP chain is written, while actually it contains a fake pipe_buffer_ops structure instead.
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example #2
Avoid using one-character variable names (except for the iterator in a non-nested for loop).
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example #3
Issue: too generic name (payment) which does not describe the purpose of the variable or where it will be used (it is a filter descriptor coded as bytes for setting up a ctnetlink_filter).
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example #4
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example #5
The name of two variables (buf and buffer) are too similar and they are also too generic, it's easy to confuse the two. Use names which more precisely express the purpose of the variable, function, etc.
| ❌ Code to be improved | ✅ Expected code quality |
|
|
ROP chains
We prefer collecting target related details like symbol, ROP gadget and stack pivot offsets and structure sizes as #defines at the top of the file with descriptive names.
The exact kernel symbols names should be used which could be found in the kernel.
The full ROP / stack pivot gadget (e.g. 0xffffffff815282e1 : cmp rdx, 1 ; jne 0xffffffff8152831d ; pop rbp ; ret) should be mentioned as a comment above or next to the gadget's #define.
In ROP chains, the *rop++ approach is prefered over absolute addressing (&rop[0x18]) which makes the code easier to read and makes the ROP chain easier to move if needed.
| ❌ Code to be improved | ✅ Expected code quality |
Note that |
|
Unused code
Remove unused code parts (including variables, functions, globals, defines, includes, etc) from the source code.
Compiling your code with -Wall or -Wunused can help you track down these issues.
Make sure that the code is actually useful, for example if you are just setting a variable, but never read it, then it can be removed.
If the code cannot be removed for some reason, for example due to a non-trivial side-effect, then this needs to be clearly commented.
| ❌ Code to be improved | ✅ Expected code quality |
|
Unused code is removed.
|
Variable shadowing
Make sure you are not shadowing existing variables (neither global or local ones), choose a unique name for your variable, so you are not confusing the reader which variable is being referenced.
Example #1
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example #2
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Commented out code
Remove code lines which are not used anymore from the source code or make them useful and optionally usable via command line arguments or #ifdef macros.
| ❌ Code to be improved | ✅ Expected code quality |
|
Unused lines are removed. If they were used for debugging then they can be converted to conditionally executed code: Comments like this explaining the context are okay and should not be removed: |
Sleeping & waiting
Add a comment to every sleep() and other non-trivial waiting functions (e.g. membarrier) what you are waiting for.
In case you are waiting for something to be happening in the kernel (GC, RCU, worker thread, etc), explicitly mention the name of the kernel function you are waiting for to be run.
Use the comment // @sleep(kernel_func="<name_of_the_function>", desc="...") if possible, which helps us automating this part later.
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Generated code (e.g. Syzkaller)
Convert tool generated, less readable code into an easily human readable source code you write otherwise (and which is compliant with the other parts of the style guide).
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Note: the code on the left and right-side are not equivalent, they are just demonstrating the different coding styles.
Explain duplicated lines
Example 1
Explain why netlink_write_noerr(nl_sock_fd, &new_qfq_qdisc) is called twice, otherwise it looks like a copy-paste mistake.
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Example 2
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Match iteration count for allocation, creation and usage
Make sure you are allocating arrays for the same amount of objects you are actually creating and then you are using all of those objects (or if you are not make a comment about why not).
This makes your intentions clear for readers.
| ❌ Code to be improved | ✅ Expected code quality |
The code above:
It's not clear for the reader whether these are typos and bugs in the exploit (hex |
|
Language
Use English in your exploits (including but not limited to comments, variable names and strings).
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Miscellaneous notes
Code duplication
Don't copy-paste big block of codes into separate places. Instead of that, create helper functions which contains the shared code blocks and call them instead.
Otherwise it can take a lot of time for the reader to understand the exact differences between the code duplicates.
You can always add arguments and simple branches to the helper functions if needed. Make them reusable.
| ❌ Code to be improved | ✅ Expected code quality |
|
|
Usage of global variables instead of local ones
Only use global variables if you really must to. Prefer using local variables instead. Otherwise the reader has to keep in mind that a global variable can chance at any time and it is harder to understand what a code does which uses a global variable. Questions like "Does the variable still contain the value which was set by another function?" or "Could another thread change this variable meanwhile?" can arise.
| ❌ Code to be improved | ✅ Expected code quality |
|
The (Also, the array size now matches actual usage - |
Indentation
We prefer 4 spaces.