crewCTF 2025 -- WASM Vault
从 js 中看出,先把输入的 flag 存到 wasm 的内存为 0 的位置,然后调用 vault.wasm 导出的 unlock() 函数判断
这里用开源项目 wabt 把 wasm 转成 c 文件,再用 gcc 编译成二进制文件,这样就可以拖到 ida 里分析了
wasm2c vault.wasm -o vault.c
gcc -c vault.c -o vault.o
ida里看到,里面用了很多 w2c_env_x() 混淆,这个函数在 js 里。简单分析可知,对于一个常数,函数的返回结果是固定的
为了去除 w2c_env_x() 的混淆,可以用 mov eax,0/1 替换 call w2c_env_x,刚好都是5个字节
import idautils
import ida_idaapi
import ida_bytes
import idc
import idaapi
import ctypes
def x(n):
r = 0
while n!=0:
r^=(n&1)
n>>=1
return r
func_x_addr = 0x0000000000007988
for addr in idautils.Functions():
if 'w2c_vault' in idc.get_func_name(addr):
for ea in idautils.FuncItems(addr):
insn = idaapi.insn_t()
if idaapi.decode_insn(insn, ea) == 0:
continue
# call w2c_env_x
if insn.itype == idaapi.NN_call and insn.ops[0].addr == func_x_addr:
result = 0
# 找到传参指令 mov
mov_ea = ea
for i in range(6): mov_ea = idc.prev_head(mov_ea)
insn = idaapi.insn_t()
idaapi.decode_insn(insn, mov_ea)
if insn.itype == idaapi.NN_mov and insn.ops[0].type == idaapi.o_displ and insn.ops[1].type == idaapi.o_imm:
result = x(insn.ops[1].value)
else:
raise Exception('error')
ida_bytes.patch_byte(ea, 0xb8) # mov eax
ida_bytes.patch_dword(ea+1, result) # 0/1
去混淆前
去混淆后
接下来花了亿点时间分析。它根据输入的 flag 构建了一个 Huffman 树,flag 每个字符用根到叶子节点的路径编码表示。
最后把序列化后的 Huffman 树和 flag 的编码和一个固定的字符串比较。
#include <stdio.h>
#include <stdint.h>
uint8_t exported_data[] = {
0xB5, 0x03, 0x00, 0x00, 0xE8, 0xC6, 0x66, 0x0C, 0xD7, 0xC1,
0xC7, 0x64, 0x9D, 0x11, 0x1C, 0xBE, 0x12, 0x75, 0x58, 0xCA,
0x6E, 0x00, 0x4E, 0x4C, 0x45, 0x2D, 0xA4, 0x46, 0x89, 0x8C,
0xD5, 0x65, 0x35, 0xBB, 0x9B, 0xC2, 0xCB, 0xEB, 0x36, 0x30,
0xB5, 0x90, 0x2A, 0xAA, 0x35, 0x44, 0xD1, 0xDC, 0xBA, 0xB8,
0x05, 0x61, 0x5A, 0xFD, 0xF9, 0x6B, 0x6F, 0xCB, 0x5B, 0x7E,
0x5A, 0xDA, 0xBE, 0xF4, 0xB6, 0x0F, 0xEB, 0x17, 0x05, 0x45,
0xB0, 0x47, 0xF3, 0x4A, 0x17, 0xF3, 0x71, 0x11, 0xDA, 0x5A,
0x2B, 0x86, 0xEA, 0x79, 0xEB, 0x1A, 0xA2, 0xEC, 0x17, 0xA1,
0x0B, 0x83, 0x79, 0x6D, 0xD4, 0xF3, 0xDF, 0x96, 0x5B, 0x57,
0x41, 0x7F, 0x4E, 0xE7, 0x68, 0xE9, 0x8F, 0x48, 0x41, 0x77,
0x0E, 0x1B, 0x9F, 0x1A, 0xAD, 0x3E, 0xF8, 0xA4, 0x89, 0xD3,
0x63, 0x52, 0x40, 0xB8, 0xAE, 0xC6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
typedef struct _node {
uint8_t value;
uint8_t freq;
struct _node* lchild;
struct _node* rchild;
} node;
node tree[256] = {0};
uint32_t node_cnt = 0;
node* build_tree(uint8_t* ptr, uint32_t bit, uint32_t node_idx, uint32_t* next_bit) {
uint32_t byte = bit / 8;
uint32_t offset = 7 - bit % 8;
if( (ptr[byte] >> offset) & 1)
{
uint32_t next1, next2;
tree[node_idx].lchild = build_tree(ptr, bit+1, ++node_cnt, &next1);
tree[node_idx].rchild = build_tree(ptr, next1, ++node_cnt, &next2);
*next_bit = next2;
}
else
{
uint8_t val = 0;
for(int i=8;i>=1;i--)
{
byte = (bit + i) / 8;
offset = 7 - (bit + i) % 8;
val = (val<<1) + ((ptr[byte] >> offset) & 1);
}
tree[node_idx].lchild = NULL;
tree[node_idx].rchild = NULL;
tree[node_idx].value = val;
tree[node_idx].freq = 0;
*next_bit = bit + 8 + 1;
}
return &tree[node_idx];
}
char prefix[256];
uint32_t prefix_len=0;
void print_tree(node *nd, uint32_t depth, uint32_t is_right)
{
if(depth>0)
{
prefix[prefix_len] = 0;
printf("%s|-", prefix);
prefix[prefix_len++] = is_right ? ' ':'|';
prefix[prefix_len++] = ' ';
prefix[prefix_len] = 0;
}
if (nd->lchild || nd->rchild)
{
printf("\\\n");
print_tree(nd->lchild, depth+1, 0);
print_tree(nd->rchild, depth+1, 1);
}
else
{
printf("'%c'\n",nd->value);
}
if(depth>0)
prefix_len -= 2;
}
void get_flag(uint8_t* ptr, node* tree_root, uint16_t bit_start, uint16_t bit_limit)
{
uint32_t byte;
uint32_t offset;
node *nd = tree_root;
for (uint32_t bit = bit_start; bit <= bit_limit; bit++)
{
byte = bit / 8;
offset = 7 - bit % 8;
if((ptr[byte] >> offset) & 1)
nd = nd->rchild;
else
nd = nd->lchild;
if(!nd->lchild && !nd->rchild)
{
printf("%c",nd->value);
nd = tree_root;
}
}
}
int main()
{
uint32_t bit_stream_len = *(uint32_t*)&exported_data[0];
uint8_t* data_ptr = &exported_data[4];
uint32_t start_bit;
node* root = build_tree(data_ptr, 0, 0, &start_bit);
// printf("tree built\n");
// print_tree(root, 0, 0);
get_flag(data_ptr, root, start_bit, bit_stream_len);
}
flag: crew{7H15_15_4_V3rY_V3rY_V3rY_5UP3r_1NCr3D181Y_10N6_F146_600D_J08_1_C0MPr3553D_17_W17H_MY_C0MPU73r_5C13NC3_6C53_KN0W13D63_1_6U355_f4c91dbe}