Note: this is a draft version of our style guide. Feel free to give feedback and improvement ideas on Discord.
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
drr_class
) and in which cache (e.g. kmalloc-cg-1k
).race_
- functions helping to win a race (e.g. epoll)Spraying or grooming the heap.
spray_
- spraying related functions
spray_msg_msg_kmalloc_cg_1k
.kmalloc-cg-1k
).fake_
- for creating fake objects which can be sprayed later
fake_msg_msg_for_oob_read
or fake_msg_msg
in a more generic case.Executing cross-cache attack.
spray_cross_cache_
- cross-cache related functionsLeaking information (e.g. heap pointer, kASLR base address).
leak_
- functions related for leakingleak_kaslr_
- leaking kASLRleak_heap_
- leaking heap addressGetting RIP control.
rop_
- ROP-related functions (e.g. which create a ROP chain)rip_
- other RIP control primitivesGeneric 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
).
msgrcv
wrapper to make it easier to call the kernel API, but if you are using for spraying (e.g. in case of msg_msg
just putting inside a for loop), then we would prefer it be prefixed with spray_
instead.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.
For structures which are used for spraying and for “fake objects” which are created in buffers, we recommend two approaches:
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.
❌ Code to be improved | ✅ Expected code quality |
|
|
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 |
|
|
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 #define
s for those values which could change depending on the target or which may require finetuning (including but not limited to):
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 #define
s if it is impractical to use #define
.)
❌ Code to be improved | ✅ Expected code quality |
|
|
❌ Code to be improved | ✅ Expected code quality |
|
|
❌ Code to be improved | ✅ Expected code quality |
|
|
❌ Code to be improved | ✅ Expected code quality |
|
|
❌ Code to be improved | ✅ Expected code quality |
|
|
❌ Code to be improved | ✅ Expected code quality |
|
|
Use descriptive names for including but not limited to: variables, functions, defines.
Make sure that the name is not misleading.
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 |
|
|
Avoid using one-character variable names (except for the iterator in a non-nested for loop).
❌ Code to be improved | ✅ Expected code quality |
|
|
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 |
|
|
❌ Code to be improved | ✅ Expected code quality |
|
|
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 |
|
|
We prefer collecting target related details like symbol, ROP gadget and stack pivot offsets and structure sizes as #define
s 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 |
|
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.
|
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.
❌ Code to be improved | ✅ Expected code quality |
|
|
❌ Code to be improved | ✅ Expected code quality |
|
|
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:
|
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 |
|
|
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 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 |
|
|
❌ Code to be improved | ✅ Expected code quality |
|
|
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 |
|
Use English in your exploits (including but not limited to comments, variable names and strings).
❌ Code to be improved | ✅ Expected code quality |
|
|
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 |
|
|
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 -
|
We prefer 4 spaces.