AVALON 强网杯 2019 强网杯 webassembly_write_up

强网杯 webassembly_write_up

环境配置

系统 : Linux kali 4.15.0-kali2-amd64 \ win10 64bit
程序 : task_webassembly3.tar_WAOLbMu
要求 : 输入口令
使用工具 :wabt / ida pro

开始分析

拿信息

二进制文件下载后使用rar解压出三个文件,使用wabt项目构建能被ida识别的o文件:

./wasm2c webassembly.wasm -o wasm.c
gcc -c wasm.c -o wasm.o

静态分析

ida载入程序,主流程如下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // ST1C_4
  unsigned int v4; // ST18_4
  unsigned int v5; // eax
  __int64 v6; // rdx

  if ( ++wasm_rt_call_stack_depth > 0x1F4u )
    wasm_rt_trap(7LL, argv, envp);
  v3 = g7;
  g7 += 0x30;
  i32_store(Z_envZ_memory, v3 + 40, v3);
  v4 = g7;
  g7 += 0x10;
  i32_store(Z_envZ_memory, v4, v3 + 40);
  v5 = i32_load(Z_envZ_memory, 2144LL);
  f54(v5, v4, v4);
  g7 = v4;
  f15(v3, v4, v6);
  g7 = v3;
  --wasm_rt_call_stack_depth;
  return 0;
}

代码比较抽象哦,重点看看第二个函数。

关键函数

进入f15函数:

__int64 __fastcall f15(unsigned int a1, __int64 a2, __int64 a3)
{
  int v3; // ST40_4
  int v4; // ST40_4
  int v5; // ST40_4
  int v6; // ST40_4
  int v7; // ST40_4
  int v8; // ST40_4
  int v9; // ST40_4
  int v10; // ST40_4
  int v11; // ST40_4
  int v12; // ST40_4
  int v13; // ST40_4
  int v14; // ST40_4
  int v15; // ST40_4
  int v16; // ST40_4
  int v17; // ST40_4
  int v18; // ST40_4
  int v19; // ST40_4
  int v20; // ST40_4
  int v21; // ST40_4
  int v22; // ST40_4
  int v23; // ST40_4
  int v24; // ST40_4
  int v25; // ST40_4
  int v26; // ST40_4
  int v27; // ST40_4
  int v28; // ST40_4
  int v29; // ST40_4
  int v30; // ST40_4
  int v31; // ST40_4
  int v32; // ST40_4
  int v33; // ST40_4
  int v34; // ST40_4
  int v35; // ST40_4
  int v36; // ST40_4
  int v37; // ST40_4
  int v38; // ST40_4
  __int64 v39; // rdx
  __int64 v40; // rdx
  unsigned int v42; // [rsp+44h] [rbp-2Ch]
  unsigned int v43; // [rsp+48h] [rbp-28h]
  unsigned int v44; // [rsp+54h] [rbp-1Ch]
  unsigned int v45; // [rsp+5Ch] [rbp-14h]
  int v46; // [rsp+60h] [rbp-10h]
  int v47; // [rsp+60h] [rbp-10h]
  int v48; // [rsp+60h] [rbp-10h]
  int v49; // [rsp+64h] [rbp-Ch]
  unsigned int v50; // [rsp+64h] [rbp-Ch]
  unsigned int v51; // [rsp+64h] [rbp-Ch]
  unsigned int v52; // [rsp+64h] [rbp-Ch]
  unsigned int v53; // [rsp+68h] [rbp-8h]
  unsigned int v54; // [rsp+68h] [rbp-8h]
  unsigned int v55; // [rsp+68h] [rbp-8h]
  unsigned int v56; // [rsp+68h] [rbp-8h]
  unsigned int v57; // [rsp+6Ch] [rbp-4h]
  unsigned int v58; // [rsp+6Ch] [rbp-4h]
  unsigned int v59; // [rsp+6Ch] [rbp-4h]
  unsigned int v60; // [rsp+6Ch] [rbp-4h]

  v53 = 0;
  v49 = 0;
  if ( ++wasm_rt_call_stack_depth > 0x1F4u )
    wasm_rt_trap(7LL, a2, a3);
  v44 = g7;
  g7 += 0x20;
  v43 = v44 + 24;
  v42 = v44 + 16;
  i64_store(Z_envZ_memory, v44, 0LL);
  i64_store(Z_envZ_memory, v44 + 8, 0LL);
  v57 = i32_load(Z_envZ_memory, a1 + 4);
  v45 = i32_load(Z_envZ_memory, a1);
  do
  {
    v45 += (((v57 >> 5) ^ 16 * v57) + v57) ^ (i32_load(Z_envZ_memory, v44 + 4 * (v53 & 3)) + v53);
    v53 -= -0x9E3779B9;
    v57 += (i32_load(Z_envZ_memory, v44 + 4 * ((v53 >> 11) & 3)) + v53) ^ (((v45 >> 5) ^ 16 * v45) + v45);
    ++v49;
  }
  while ( v49 != 32 );
  i32_store(Z_envZ_memory, a1, v45);
  i32_store(Z_envZ_memory, a1 + 4, v57);
  v54 = 0;
  v58 = i32_load(Z_envZ_memory, a1 + 12);
  v50 = i32_load(Z_envZ_memory, a1 + 8);
  v46 = 0;
  do
  {
    v50 += (i32_load(Z_envZ_memory, v44 + 4 * (v54 & 3)) + v54) ^ (((v58 >> 5) ^ 16 * v58) + v58);
    v54 -= -0x9E3779B9;
    v58 += (i32_load(Z_envZ_memory, v44 + 4 * ((v54 >> 11) & 3)) + v54) ^ (((v50 >> 5) ^ 16 * v50) + v50);
    ++v46;
  }
  while ( v46 != 32 );
  i32_store(Z_envZ_memory, a1 + 8, v50);
  i32_store(Z_envZ_memory, a1 + 12, v58);
  v55 = 0;
  v59 = i32_load(Z_envZ_memory, a1 + 20);
  v51 = i32_load(Z_envZ_memory, a1 + 16);
  v47 = 0;
  do
  {
    v51 += (i32_load(Z_envZ_memory, v44 + 4 * (v55 & 3)) + v55) ^ (((v59 >> 5) ^ 16 * v59) + v59);
    v55 -= -0x9E3779B9;
    v59 += (i32_load(Z_envZ_memory, v44 + 4 * ((v55 >> 11) & 3)) + v55) ^ (((v51 >> 5) ^ 16 * v51) + v51);
    ++v47;
  }
  while ( v47 != 32 );
  i32_store(Z_envZ_memory, a1 + 16, v51);
  i32_store(Z_envZ_memory, a1 + 20, v59);
  v56 = 0;
  v60 = i32_load(Z_envZ_memory, a1 + 28);
  v52 = i32_load(Z_envZ_memory, a1 + 24);
  v48 = 0;
  do
  {
    v52 += (i32_load(Z_envZ_memory, v44 + 4 * (v56 & 3)) + v56) ^ (((v60 >> 5) ^ 16 * v60) + v60);
    v56 -= -0x9E3779B9;
    v60 += (v56 + i32_load(Z_envZ_memory, v44 + 4 * ((v56 >> 11) & 3))) ^ (((v52 >> 5) ^ 16 * v52) + v52);
    ++v48;
  }
  while ( v48 != 32 );
  i32_store(Z_envZ_memory, a1 + 24, v52);
  i32_store(Z_envZ_memory, a1 + 28, v60);
  v3 = (v45 ^ 0xE8) + ((i32_load8_s(Z_envZ_memory, a1 + 1) ^ 0xFFFFFF9A) & 0xFF);
  v4 = ((i32_load8_s(Z_envZ_memory, a1 + 2) ^ 0xFFFFFFB7) & 0xFF) + v3;
  v5 = ((i32_load8_s(Z_envZ_memory, a1 + 3) ^ 0xFFFFFFE9) & 0xFF) + v4;
  v6 = ((i32_load8_s(Z_envZ_memory, a1 + 4) ^ 0xFFFFFF8F) & 0xFF) + v5;
  v7 = ((i32_load8_s(Z_envZ_memory, a1 + 5) ^ 0x1C) & 0xFF) + v6;
  v8 = ((i32_load8_s(Z_envZ_memory, a1 + 6) ^ 0xFFFFFF96) & 0xFF) + v7;
  v9 = ((i32_load8_s(Z_envZ_memory, a1 + 7) ^ 0xFFFFFFC2) & 0xFF) + v8;
  v10 = ((i32_load8_s(Z_envZ_memory, a1 + 8) ^ 0xFFFFFFF0) & 0xFF) + v9;
  v11 = ((i32_load8_s(Z_envZ_memory, a1 + 9) ^ 0xFFFFFFBE) & 0xFF) + v10;
  v12 = ((i32_load8_s(Z_envZ_memory, a1 + 10) ^ 0xFFFFFF8C) & 0xFF) + v11;
  v13 = ((i32_load8_s(Z_envZ_memory, a1 + 11) ^ 6) & 0xFF) + v12;
  v14 = ((i32_load8_s(Z_envZ_memory, a1 + 12) ^ 0x23) & 0xFF) + v13;
  v15 = ((i32_load8_s(Z_envZ_memory, a1 + 13) ^ 0x68) & 0xFF) + v14;
  v16 = ((i32_load8_s(Z_envZ_memory, a1 + 14) ^ 0x1C) & 0xFF) + v15;
  v17 = ((i32_load8_s(Z_envZ_memory, a1 + 15) ^ 0xFFFFFFA8) & 0xFF) + v16;
  v18 = ((i32_load8_s(Z_envZ_memory, a1 + 16) ^ 0x43) & 0xFF) + v17;
  v19 = ((i32_load8_s(Z_envZ_memory, a1 + 17) ^ 0x2F) & 0xFF) + v18;
  v20 = ((i32_load8_s(Z_envZ_memory, a1 + 18) ^ 0xFFFFFFC1) & 0xFF) + v19;
  v21 = ((i32_load8_s(Z_envZ_memory, a1 + 19) ^ 0xFFFFFFD2) & 0xFF) + v20;
  v22 = ((i32_load8_s(Z_envZ_memory, a1 + 20) ^ 0x29) & 0xFF) + v21;
  v23 = ((i32_load8_s(Z_envZ_memory, a1 + 21) ^ 0xFFFFFFA2) & 0xFF) + v22;
  v24 = ((i32_load8_s(Z_envZ_memory, a1 + 22) ^ 0xFFFFFFBB) & 0xFF) + v23;
  v25 = ((i32_load8_s(Z_envZ_memory, a1 + 23) ^ 4) & 0xFF) + v24;
  v26 = ((i32_load8_s(Z_envZ_memory, a1 + 24) ^ 0x3E) & 0xFF) + v25;
  v27 = ((i32_load8_s(Z_envZ_memory, a1 + 25) ^ 7) & 0xFF) + v26;
  v28 = ((i32_load8_s(Z_envZ_memory, a1 + 26) ^ 0x39) & 0xFF) + v27;
  v29 = ((i32_load8_s(Z_envZ_memory, a1 + 27) ^ 0xFFFFFF9A) & 0xFF) + v28;
  v30 = ((i32_load8_s(Z_envZ_memory, a1 + 28) ^ 0x5F) & 0xFF) + v29;
  v31 = ((i32_load8_s(Z_envZ_memory, a1 + 29) ^ 9) & 0xFF) + v30;
  v32 = ((i32_load8_s(Z_envZ_memory, a1 + 30) ^ 0xFFFFFF87) & 0xFF) + v31;
  v33 = ((i32_load8_s(Z_envZ_memory, a1 + 31) ^ 0xFFFFFFBD) & 0xFF) + v32;
  v34 = ((i32_load8_s(Z_envZ_memory, a1 + 32) ^ 'c') & 0xFF) + v33;
  v35 = ((i32_load8_s(Z_envZ_memory, a1 + 33) ^ 'd') & 0xFF) + v34;
  v36 = ((i32_load8_s(Z_envZ_memory, a1 + 34) ^ '5') & 0xFF) + v35;
  v37 = ((i32_load8_s(Z_envZ_memory, a1 + 35) ^ '1') & 0xFF) + v36;
  v38 = ((i32_load8_s(Z_envZ_memory, a1 + 36) ^ 'e') & 0xFF) + v37;
  if ( v38 == -((i32_load8_s(Z_envZ_memory, a1 + 37) ^ '}') & 0xFF) )
  {
    f66(2524LL, v42);
    f67(2524LL, v42, v39);
  }
  else
  {
    f66(2531LL, v43);
    f67(2531LL, v43, v40);
  }
  g7 = v44;
  return (wasm_rt_call_stack_depth-- - 1);
}

就是四个xtea算法啦,最后几个flag字符白给,这里需要注意的是:
1. 迭代轮数32次
2. key都是0
3. 密文需要自己慢慢提取

提取密文

ida中拷贝密文内容至tmp文件:

v3 = (v45 ^ 0xE8) + ((i32_load8_s(Z_envZ_memory, a1 + 1) ^ 0xFFFFFF9A) & 0xFF);
  v4 = ((i32_load8_s(Z_envZ_memory, a1 + 2) ^ 0xFFFFFFB7) & 0xFF) + v3;
  v5 = ((i32_load8_s(Z_envZ_memory, a1 + 3) ^ 0xFFFFFFE9) & 0xFF) + v4;
  v6 = ((i32_load8_s(Z_envZ_memory, a1 + 4) ^ 0xFFFFFF8F) & 0xFF) + v5;
  ..............

写脚本提取:

import re

f = open("./tmp","rb")
data = f.read()
f.close()

pattern = re.compile(r'0x\w*')
result1 = pattern.findall(data)
my_list = []

for STR in result1:
    if STR != '0xFF':
        my_list.append(STR[-2:])

print len(my_list)

print '[',
for STR in my_list:
    print '0x' + STR + ',',
print ']',
#[ 0xE8,  0x9A,  0xB7,  0xE9,  0x8F,  0x1C,  0x96,  0xC2,  0xF0,  0xBE,  0x8C,  0x23,  0x68,  0x1C,  0xA8,  0x43,  0x2F,  0xC1,  0xD2,  0x29,  0xA2,  0xBB,  0x3E,  0x39,  0x9A,  0x5F,  0x87,  0xBD,  ]
#[ 0xE8,  0x9A,  0xB7,  0xE9,  0x8F,  0x1C,  0x96,  0xC2,  0xF0,  0xBE,  0x8C, 0x6, 0x23,  0x68,  0x1C,  0xA8,  0x43,  0x2F,  0xC1,  0xD2,  0x29,  0xA2,  0xBB, 0x4, 0x3E, 0x7, 0x39,  0x9A,  0x5F, 0x9, 0x87,  0xBD ]
a = [ 0xE8,  0x9A,  0xB7,  0xE9,  0x8F,  0x1C,  0x96,  0xC2,  0xF0,  0xBE,  0x8C, 0x6, 0x23,  0x68,  0x1C,  0xA8,  0x43,  0x2F,  0xC1,  0xD2,  0x29,  0xA2,  0xBB, 0x4, 0x3E, 0x7, 0x39,  0x9A,  0x5F, 0x9, 0x87,  0xBD ]

solved

直接用xtea算法解开密文:

#include <stdio.h>
#include <stdint.h>

/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */

void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
    for (i=0; i < num_rounds; i++) {
        v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
        sum += delta;
        v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
    }
    v[0]=v0; v[1]=v1;
}

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
    for (i=0; i < num_rounds; i++) {
        v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
        sum -= delta;
        v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
    }
    v[0]=v0; v[1]=v1;
}

int main()
{
    uint32_t v[2]={1,2};
    uint32_t const k[4]={0,0,0,0};
    unsigned int r=32;//num_rounds建议取值为32
    // v为要加密的数据是两个32位无符号整数
    // k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
/*
    printf("加密前原始数据:%u %u\n",v[0],v[1]);
    encipher(r, v, k);
    printf("加密后的数据:%u %u\n",v[0],v[1]);
    decipher(r, v, k);
    printf("解密后的数据:%u %u\n",v[0],v[1]);
*/
    uint32_t my_data[8] = {0xe9b79ae8, 0xc2961c8f, 0x68cbef0, 0xa81c6823, 0xd2c12f43, 0x4bba229, 0x9a39073e, 0xbd87095f};
    decipher(r, my_data, k);
    printf("解密后的数据:0x%x 0x%x\n",my_data[0],my_data[1]);

    decipher(r, my_data+2, k);
    printf("解密后的数据:0x%x 0x%x\n",my_data[2],my_data[3]);

    decipher(r, my_data+4, k);
    printf("解密后的数据:0x%x 0x%x\n",my_data[4],my_data[5]);

    decipher(r, my_data+6, k);
    printf("解密后的数据:0x%x 0x%x\n",my_data[6],my_data[7]);

    return 0;
}

运行结果:

➜  playground ./test
解密后的数据:0x67616c66 0x3466377b
解密后的数据:0x64623164 0x61363037
解密后的数据:0x65303038 0x31343364
解密后的数据:0x63646232 0x39396163

考虑到数据在内存中是以小尾方式存放的,我们使用python脚本转换一下:

from libnum import n2s

flag_data = [0x65303038, 0x31343364, 0x63646232, 0x39396163]
flag = ""

for data in flag_data:
    flag += n2s(data)[::-1]

print flag

运行结果:

➜  playground python test.py    
flag{7f4d1bd706a
➜  playground python test.py
800ed3412bdcca99
➜  playground flag{7f4d1bd706a800ed3412bdcca99

夺旗成功

使用得到的两段内容拼接成flag{7f4d1bd706a800ed3412bdcca99cd51e}作为输入:

参考链接

  1. XMAN【我真的好菜-同pizza师傅修炼笔记一】easyvm&easywasm https://blog.csdn.net/qq_33438733/article/details/81613223
  2. 一种Wasm逆向静态分析方法 https://www.xd10086.com/posts/232962536903522576/
  3. TEA、XTEA、XXTEA加密解密算法 https://blog.csdn.net/gsls200808/article/details/48243019
  4. 从2018强网杯hide题中学习手动脱壳与算法识别的思路 http://dreamcracker.today/2019/05/16/%e8%87%aa2018%e5%bc%ba%e7%bd%91%e6%9d%afhide%e4%b8%80%e9%a2%98%e5%ad%a6%e4%b9%a0%e6%89%8b%e5%8a%a8%e8%84%b1%e5%a3%b3%e4%b8%8e%e7%ae%97%e6%b3%95%e8%af%86%e5%88%ab/