Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add kernelCTF CVE-2024-26808_cos #113

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

st424204
Copy link
Contributor

No description provided.

Copy link
Collaborator

@koczkatamas koczkatamas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey!

Thank you for the submission!

I've requested a few changes below. There are 11 comments, the Github UI will only slow 10 by default and will hide 1 additional comment, please make sure to click on the "Load more..." button to see all comments.

Thanks!


mnl_attr_put_str(nlh, IFLA_IFNAME, link_name);
linkinfo = mnl_attr_nest_start(nlh, IFLA_LINKINFO);
mnl_attr_put_str(nlh, IFLA_INFO_KIND, "dummy");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain what this "dummy" info_kind does here? I see guess the call stack is __rtnl_newlink -> rtnl_newlink_create -> rtnl_create_link, but I did not find the "dummy" kind in the code and it seems the interface tries to call request_module("rtnl-link-%s", kind), but I did not find rtnl-link-dummy either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We create dummy network interfaces to reach vulnerable code path without affect other devices. (We just followed the example in rtnl source code). CONFIG_DUMMY is enabled by default in most of linux distro, it will register "dummy" kind at function dummy_init_module, please refer to drivers\net\dummy.c

With these two limited primitive, our exploit is in the following steps:

1. Cross cache to overwrite `net_device` object in kmalloc-cg-4k to `packet_fanout` object in kmalloc-4k.
2. `packet_fanout` object has `net` address in the first 8 byte, we leak this using UAF leak primitive.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explain here, that you are doing this, because you need to know the right net pointer to pass the dev_net(dev) != net check. Any the only reason to use packet_fanout because that's an object which contains this (namespace-specific?) pointer in the first 8 bytes as your primitive can only leak the first 8 bytes.

(You are describing what you are doing, which is great, but it is easier to understand the exploit, if you also explain WHY you are doing this. I know this was mentioned that you have to leak the net address before, but it's better to mention again, so the reader can "connect the dots".)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I add in step 2: we leak net using UAF leak primitive to satisfy condition dev_net(dev) != net when perform arbitrary free at step 6.. the first 8 bytes leak already explained at UAF Leak section

1. Cross cache to overwrite `net_device` object in kmalloc-cg-4k to `packet_fanout` object in kmalloc-4k.
2. `packet_fanout` object has `net` address in the first 8 byte, we leak this using UAF leak primitive.
3. Using another UAF object, this time we overwrite `net_device` with `msg_msg` object, no need cross cache we use same kmalloc-cg-4k.
4. We fill `msg_msg.next` (first 8 byte offset) with kmalloc-cg-192 by send `msg_msg` with size 0x90 for every `msg_msg` that already placed in kmalloc-cg-4k.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both msg_msg and pipe_buffer are elastic objects, seemingly you could've chosen any cache, why did you choose kmalloc-cg-192? Was there any particular reason for that? Less noise?

Please also document this in the writeup.

Did you choose the size of 0x90 because that's bigger than 128, but less than 192, so it will be kmalloc-cg-192, and any other value between 129 - 192 would've worked or was there any other condition which required you to choose exactly 0x90?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I add this in the step: kmalloc-cg-192 guarantee that the first byte doesn't contain null byte, so we can leak kmalloc-cg-192 msg_msg addr using UAF leak.

8. Reclaim it as another `pipe_buffer` B.
9. Free pipe_buffer B and reclaim it as `msg_msgseg`.
10. Call syscall vmsplice to vDSO memory page into `pipe_buffer` A, this method gives `struct page` addr of kernel text to `pipe_buffer`.
11. Read from `msg_msgseg` to read content of `pipe_buffer` A.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be to mention that you are doing this to leak the page field of the pipe_buffer struct (at offset 0x00 - 0x08) because it contains the address of vdso_image_64.data aka. raw_data symbol.

You can add this to the writeup or code too (probably most useful near to the code where you use0x863000):

gef➤  p vdso_image_64.data
$5 = (void *) 0xffffffff82d3b000 <raw_data>
gef➤  p &core_pattern
$6 = (char (*)[128]) 0xffffffff8359e7a0 <core_pattern>
gef➤  p 0xffffffff8359e000-0xffffffff82d3b000
$7 = 0x863000

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

10. Call syscall vmsplice to vDSO memory page into `pipe_buffer` A, this method gives `struct page` addr of kernel text to `pipe_buffer`.
11. Read from `msg_msgseg` to read content of `pipe_buffer` A.
12. Modify the `page` value so it will point to the `core_pattern` `page`'s addr and modify `PIPE_BUF_FLAG_CAN_MERGE`.
13. Free this `msg_msgseg`/`pipe_buffer` A and overwrite again with new content.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention that in this step you want to spray the fake pipe_buffer you prepared in the previous step (step 12) but you cannot modify msg_msgseg, so you have to free the current one and spray the new content to reallocate your fake pipe_buffer object at the same memory address.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

msgrcv(msqid[i + 0x2000], &msg,
0x1000 - 0x30 + 0xc0 - 0x8, 6, 0);
// modify struct page that will represent of `core_pattern`'s page
p->page += (0x863000 >> 6);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment here that p->page was originally pointing vdso_image_64.data (aka. raw_data, defined in arch/x86/entry/vdso/vdso-image-64.c)'s vmemmap address (which contains struct page*).

Explain how this calculation work: 0x863000 >> 6.

gef➤  p vdso_image_64.data
$5 = (void *) 0xffffffff82d3b000 <raw_data>
gef➤  p &core_pattern
$6 = (char (*)[128]) 0xffffffff8359e7a0 <core_pattern>
gef➤  p 0xffffffff8359e000-0xffffffff82d3b000
$7 = 0x863000

The >> 6 comes from that the difference between the address of the pages of vdso_image_64.data and core_pattern is 0x863000, but for every 4096 byte page, there is a 64 byte struct page* stored in vmemmap, so to calculate the difference between struct page* addresses, you have to do: 0x863000 / 4096 * 64 which equals to 0x863000 >> 6.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

0x1000 - 0x30 + 0xc0 - 0x8, 6, 0);
// modify struct page that will represent of `core_pattern`'s page
p->page += (0x863000 >> 6);
p->offset = 0x7a0 - 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention that the address of core_pattern is 0xffffffff8359e7a0, so 0x7a0 is the offset within core_pattern's page. Also mention that - 1 is because p->len == 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

printf("msgget01 Failed 0x%x\n", i);
}

struct fanout_args fa = {.max_num_members = 0x100};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does max_num_members = 0x100 do? Why do you use this value?

Copy link
Contributor Author

@st424204 st424204 Aug 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max_num_members == 0x100 to make struct packet_fanout in kmalloc-4096 cache

match = kvzalloc(struct_size(match, arr, args->max_num_members),
				 GFP_KERNEL);
gef➤  p sizeof(struct packet_fanout) + sizeof(struct sock*)*0x100
$4 = 0x8c0

5. Leak kmalloc-cg-192 `msg_msg` addr using UAF leak.
6. Use arbitrary free to free a chunk in kmalloc-cg-192 we got before.
7. Reclaim it as `pipe_buffer` A and use arbitrary free again at same address.
8. Reclaim it as another `pipe_buffer` B.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this step if not needed (from the documentation and from the exploit).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

6. Use arbitrary free to free a chunk in kmalloc-cg-192 we got before.
7. Reclaim it as `pipe_buffer` A and use arbitrary free again at same address.
8. Reclaim it as another `pipe_buffer` B.
9. Free pipe_buffer B and reclaim it as `msg_msgseg`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention that you are using msg_msgseg because you a structure which cannot be easily corrupted by the pipe_buffer which will take its place (you can also mention that you cannot use the previous msg_msg you used before, because pipe_buffer would overwrite its important fields and it would crash).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants