crewCTF 2025 -- WASM Vault

image

从 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

去混淆前
eea4e349-1635-4273-a2d9-9173868ab23c

去混淆后
5596fdec-1b42-4943-babe-bfe82d832a64

接下来花了亿点时间分析。它根据输入的 flag 构建了一个 Huffman 树,flag 每个字符用根到叶子节点的路径编码表示。
最后把序列化后的 Huffman 树和 flag 的编码和一个固定的字符串比较。

b31bacbe-b209-4439-8c17-e63f8f9febf1

#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}

posted @ 2025-09-28 12:38  矛盾空间  阅读(26)  评论(0)    收藏  举报