1406 words
7 minutes
Write-ups: System Security (Microarchitecture Exploitation) series

Examples#

下面给出一些我在学习过程中写的 demo,就不详细说明作用了,只贴一下代码(怕自己弄丢了

用到的 axium 是我自己写的内核 / 侧信道框架,点点 star 支持一下谢谢 :P

CuB3y0nd
/
axium
Waiting for api.github.com...
00K
0K
0K
Waiting...

对于侧信道,还提供了缓存噪声分析的 dashboard xD

每个 chart 都可以单独导出为 PNG,方便嵌入到文档中之类的……

当然,TUI 的可视化 report 也是必不可少的:

探测当前程序内内存映射有没有进缓存#

#include <axium/axium.h>
#define ARRAY_SIZE 32
#define PAGE_SIZE 0x1000
#define TOTAL_SIZE (ARRAY_SIZE * PAGE_SIZE)
int main(int argc, char *argv[]) {
set_log_level(DEBUG);
if (argc < 2) {
log_info("Usage: %s <IDX>", argv[0]);
return 1;
}
int target_idx = atoi(argv[1]);
if (target_idx < 0 || target_idx >= ARRAY_SIZE) {
log_error("Index must be between 0 and %d", ARRAY_SIZE - 1);
}
uint8_t *target = mmap(NULL, TOTAL_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);
if (target == MAP_FAILED) {
log_exception("mmap");
}
uint64_t threshold = cache_calibrate_threshold(target);
log_info("Calibrated threshold (context-aware): %lu cycles", threshold);
cache_flush_range(target, TOTAL_SIZE);
maccess(&target[target_idx * PAGE_SIZE]);
uint64_t results[ARRAY_SIZE];
for (size_t i = 0; i < ARRAY_SIZE; i++) {
size_t mixed_idx = MIXED_IDX(i, ARRAY_SIZE - 1);
uint64_t start = probe_start();
maccess(&target[mixed_idx * PAGE_SIZE]);
uint64_t end = probe_end();
results[mixed_idx] = end - start;
}
cache_report_t report;
cache_analyze(&report, results, ARRAY_SIZE, threshold);
cache_report(&report);
if (cache_export_report(&report, "report.json") == 0) {
log_success("Report exported!");
cache_view_report("report.json");
} else {
log_error("Report export failed!");
}
munmap(target, TOTAL_SIZE);
return 0;
}

Shared Memory 1.0#

Information#

  • Category: Pwn

Description#

Get started with interacting with processes via shared memory.

Write-up#

前两道题不记分,估计是用来让我们熟悉一下共享内存的。

话不多说,直接逆。

main 先调用这个函数,把 flag 读到 bss 了,没啥说的。

int read_flag_to_global()
{
int v1; // [rsp+8h] [rbp-8h]
int fd; // [rsp+Ch] [rbp-4h]
fd = open("/flag", 0);
v1 = read(fd, &flag_val, 0x100u);
if ( !fd || v1 <= 0 )
{
printf("Failed to read flag file. Exiting");
exit(1);
}
return close(fd);
}

然后是核心逻辑:

必须要 argc 大于 1,也就是除了 argv[0] 外我们起码还得多传一个参数,那具体要几个,以及,这个参数是干啥的呢?

我们注意到 argv[1] 被复制到了 dest,所以这个参数是必要的,没有溢出,设置了 NULL terminate,然后又创建了一个 argva 参数列表,列表里只有一个参数,即 dest

if ( argc > 1 )
{
strncpy(dest, argv[1], 0x12Bu);
dest[299] = 0;
argva[0] = dest;
argva[1] = 0;
shared_page = make_shared_page(&p_sem);
v12 = (int)time(0) % 13;
pin_cpu(v12);
printf("Pinning processes to CPU %d\n", v12);
v14 = fork();
if ( v14 )
{
printf("Launching your code as PID: %d!\n", v14);
puts("----------------");
wait(0);
v11 = inject_open_shm(v14);
inject_mmap(v14, v11);
inject_drop_privs(v14);
sem = p_sem;
sem_init(p_sem, 1, 0);
v9 = sem + 1;
*(_DWORD *)sem[1].__size = 0;
fd = open("/flag", 0);
buf = (char *)&p_sem[1].__align + 4;
read(fd, (char *)&p_sem[1].__align + 4, 0x50u);
printf("The flag is now in memory at %p\n", buf);
ptrace_detatch(v14);
shm_unlink("/pwncollege");
puts("### Goodbye!");
}
else
{
ptrace(PTRACE_TRACEME, 0, 0, 0);
execv(dest, argva);
}
return 0;
}
else
{
puts("ERROR: argc < 2!");
return 1;
}

接着创建共享页,并设置大小为 0x101000,然后映射到 0x1337000,权限是 PROT_READ | PROT_WRITE,flag 是 MAP_FIXED | MAP_SHARED,即必须映射到 addr 指定的地址且映射和 fd 背后的对象共享,即访问这个映射地址就是访问共享内存。

此外,0x1337000 这个地址也将用作全局变量 p_sem,根据定义,这是一个 semaphore。然后将共享内存的 fd 返回,给到 shared_page

__int64 __fastcall make_shared_page(sem_t **p_sem)
{
unsigned int fd; // [rsp+1Ch] [rbp-4h]
puts("Creating Shared memory");
shm_unlink("/pwncollege");
fd = shm_open("/pwncollege", 66, 0x1C7u);
ftruncate(fd, 0x101000);
*p_sem = (sem_t *)mmap((void *)0x1337000, 0x101000u, 3, 32769, fd, 0);
if ( *p_sem != (sem_t *)0x1337000 )
__assert_fail("*addr == (char *) mmap_addr", "babyarchmemflag.c", 0x169u, "make_shared_page");
return fd;
}

接着用当前的时间作为 seed 随机将程序绑定到 [0,13)[0, 13) 的一个 CPU Core 上运行。

int __fastcall pin_cpu(int n0x3FF)
{
cpu_set_t cpuset; // [rsp+10h] [rbp-90h] BYREF
unsigned __int64 n0x3FF_1; // [rsp+98h] [rbp-8h]
memset(&cpuset, 0, sizeof(cpuset));
n0x3FF_1 = n0x3FF;
if ( (unsigned __int64)n0x3FF <= 1023 )
cpuset.__bits[n0x3FF_1 >> 6] |= 1LL << (n0x3FF_1 & 63);
return sched_setaffinity(0, 0x80u, &cpuset);
}

然后创建子进程,子进程会执行 dest,因此,我们知道 argv[1] 其实需要传递一个程序路径。此外,由于子进程设置了 PTRACE_TRACEME,所以父进程可以修改子进程的 stats 。

一旦 execv 执行成功,内核会 自动 SIGTRAP,子进程暂停,父进程开始 ptrace 操作子进程。

ptrace(PTRACE_TRACEME, 0, 0, 0);
execv(dest, argva);

父进程通过 wait(0) 确保子进程发出 SIGTRAP 信号后,开始 ptrace,进行一些操作,具体内容自己分析太恶心了,丢给 AI 就好了,我就不写了。

然后将 0x1337000,也就是 p_sem 信号量联合体初始化为 0,之后 open flag,将 flag 读到 (char *)&p_sem[1].__align + 4 的位置,这个可以看一下 sem_t 的定义:

#if __WORDSIZE == 64
# define __SIZEOF_SEM_T 32
#else
# define __SIZEOF_SEM_T 16
#endif
typedef union
{
char __size[__SIZEOF_SEM_T];
long int __align;
} sem_t;

因此它其实就是将 flag 读到了第二个联合体的地址加四的位置。之后 ptrace_detatch 让子进程继续跑。

所以我们只要写一个程序去输出 flag 被加载到的位置就好了。

Exploit#

#include <semaphore.h>
#include <unistd.h>
#define SHM_BASE 0x1337000
#define SEM_SIZE sizeof(sem_t)
#define OFFSET (SEM_SIZE + 4)
#define FLAG_ADDR (char *)(SHM_BASE + OFFSET)
int main(int argc, char *argv[]) {
write(1, FLAG_ADDR, 0x100);
return 0;
}

Shared Memory 2.0#

Information#

  • Category: Pwn

Description#

Get started with interacting with processes via shared memory.

Write-up#

和上题差不多,区别是父进程的实现:

if ( v14 )
{
printf("Launching your code as PID: %d!\n", v14);
puts("----------------");
wait(0);
v11 = inject_open_shm(v14);
inject_mmap(v14, v11);
inject_drop_privs(v14);
sem = p_sem;
sem_init(p_sem, 1, 0);
v9 = sem + 1;
*(_DWORD *)sem[1].__size = 0;
printf("This challenge will read the flag into memory when the semaphore located at %p is incremented.", sem);
ptrace_detatch(v14);
sem_wait(sem);
fd = open("/flag", 0);
buf = (char *)&p_sem[1].__align + 4;
read(fd, (char *)&p_sem[1].__align + 4, 0x50u);
printf("The flag is now in memory at %p\n", buf);
shm_unlink("/pwncollege");
puts("### Goodbye!");
}

ptrace_detatch 之后 sem_wait(sem) 等待信号量被消耗,然后才会去 open, read flag 。但是由于这个信号量被 sem_init(p_sem, 1, 0) 初始化为 0,所以我们的子进程应该使用 sem_post 增加一下信号量,否则程序会一直等待。

Exploit#

#include <semaphore.h>
#include <unistd.h>
#define SHM_BASE 0x1337000
#define SEM_SIZE sizeof(sem_t)
#define OFFSET (SEM_SIZE + 4)
#define FLAG_ADDR (char *)(SHM_BASE + OFFSET)
#define SEM_ADDR (sem_t *)SHM_BASE
int main(int argc, char *argv[]) {
sem_post(SEM_ADDR);
write(1, FLAG_ADDR, 0x100);
return 0;
}
Write-ups: System Security (Microarchitecture Exploitation) series
https://cubeyond.net/posts/write-ups/pwncollege-microarchitecture-exploitation/
Author
CuB3y0nd
Published at
2026-01-30
License
CC BY-NC-SA 4.0