BUU-Reveser-XMAN2018排位赛_easywasm(WebAssembly逆向分析)

解题思路:

加密手段:
数据结构分析:
逆向分析中的问题:
获得flag:flag{99754106633f94d350db34d548}

学到的知识:

题目类型:
WebAssembly分析和调试技巧
浏览器本地服务器搭建
MD5算法API
直接爆破

题目信息:

简介:
wp借鉴:XMAN【我真的好菜-同pizza师傅修炼笔记一】easyvm&easywasm - 灰信网(软件开发博客聚合) (freesion.com)
wasm2c needed_dynlibs_wasm2c使用不了-CSDN博客

预运行发现这个wasm是单文件没有其他可以运行他的程序!
所以先使用js把它加载进入浏览器看看有什么东西!!

创建main.js

fetch('easywasm.wasm')  
    .then(response => {  
        return WebAssembly.instantiateStreaming(response);  
    })  
    .then(results => {  
        // results.instance 是Wasm实例,你可以通过它访问导出的函数和变量  
        const exports = results.instance.exports;  
          
        // 假设你的Wasm模块导出了一个名为'myFunction'的函数  
        exports.myFunction();  
    })  

创建index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset='utf-8'>
  <style>
  </style>
</head>
<body>
  <span id="container"></span>
  <script src="./main.js"></script>
</body>
</html>


用python来映射本地端口来调试js代码!

python -m http.server 8888

成功加载出easywasm文件看看他的基本信息!

成功加载!!!

//这里是这个wasm所用的环境变量
  (memory $env.memory (;0;) (import "env" "memory") 256)
  (table $env.table (;0;) (import "env" "table") 16 funcref)
  (global $env.memoryBase (;0;) (import "env" "memoryBase") i32)
  (global $env.tableBase (;1;) (import "env" "tableBase") i32)
  (func $env.abort (;0;) (import "env" "abort") (param i32))
  (memory $env.memory (;0;) 256)
  (global $global2 (mut i32) (i32.const 0))
  (global $global3 (mut i32) (i32.const 0))
  (global $_flag_enc (;4;) (export "_flag_enc") i32 (i32.const 0))
  (global $_k (;5;) (export "_k") i32 (i32.const 1104))
  (global $_r (;6;) (export "_r") i32 (i32.const 1360))
//这里使用的是wasm所使用的函数!
  (elem $elem0 
	  (global.get $env.tableBase) 
	  funcref 
		  (ref.func $func11) 
		  (ref.func $_to_bytes) 
		  (ref.func $_to_int32) 
		  (ref.func $_memcpy) 
		  (ref.func $_memset) 
		  (ref.func $_memcmp) 
		  (ref.func $_strlen) 
		  (ref.func $_md5) 
		  (ref.func $_check) 
		  (ref.func $func11) 
		  (ref.func $func11) 
		  (ref.func $func11) 
		  (ref.func $func11) 
		  (ref.func $func11) 
		  (ref.func $func11)
		  (ref.func $func11)
  )

获取这些信息后大概明白了,尤其是其中的check函数因该就是我们要分析的目标了!!

如果直接硬看哇什么的话太难了,所以直接丢给jeb!
成功翻译成伪代码!

但是太复杂了只知道check函数只需要一个参数大概就是我们需要的flag了!
还发现了一大段静态数据大概就是我们要对照的表!

简化一下逻辑提取出来!

int _check(int INput) {
	//...
	//初始化一大堆变量
	//...
	
    int len = _strlen(INput);
    if(len == 32) {
        do {
            table1[idx1 + "14c260d9e8cf4ed38c77a"] = 
								            *(char*)(INput + idx);//貌似是取出一个字符
            _md5(idx1, 32, idx1 + "3b7e7a51b5213470a8d");//进行一次md5
            int v6 = "562fe3cc50014c260d9e8cf4ed38c77a";
            do {
				...进行一次循环好像在找什么东西
            }
            while(v2 != "0d9e8cf4ed38c77a");
            _md5(idx1, 32, idx1 + 64);//又进行了一次md5
            v6 = "562fe3cc50014c260d9e8cf4ed38c77a";
            do {
				...进行一次循环好像在找什么东西
			}
            while(v1 != "0d9e8cf4ed38c77a");
            
            //这里毫无以为是进行flag验证了
            int v7 = 
_memcmp(32, idx * &table2 + (int)memoryBase, idx1 + "0d9e8cf4ed38c77a");
            if(v7) {  //如果不等于就退出循环!
                INput = "562fe3cc50014c260d9e8cf4ed38c77a";
                __g2 = idx1;
                return INput;
            }
			...如果等于就继续循环
            ++idx;
        }
        while(v0 < 32);这里发现会循环32次,也就意味这我们的flag有32个字节的长度!
        ...
    }
...
}

下面就剩下动态调试了!让chatgpt优化一下调试脚本!

const imports = {  
  env: {  
    memory: new WebAssembly.Memory({ initial: 256 }),  
    table: new WebAssembly.Table({ initial: 16, element: 'anyfunc' }),  
    memoryBase: 0,  
    tableBase: 0,  
    abort: function(param) {  
      // 实现 abort 函数的逻辑  
    }  
  }  
};  

  
fetch('easywasm.wasm')  
  .then(response => response.arrayBuffer())  
  .then(buffer => WebAssembly.instantiate(buffer, imports))  
  .then(results => {  
  const instance = results.instance;  //导入函数
  const env1 = imports.env; //导入变量
  const memoryBuffer = new Uint8Array(env1.memory.buffer);  //创建一片空间指向env1.memory.buffer
      
  // 要传递给 Wasm 的字符串  
  const str = "abcdaaaaaaaaaaaaaaaaaaaaaaaaaaaa";  
    
  // 使用 TextEncoder 将字符串转换为 UTF-8 字节数组  
  const encoder = new TextEncoder();  
  const strBytes = encoder.encode(str);  
    
  // 将字节数组写入 WebAssembly 内存  
  const offset = 1800; // 从内存的哪个位置开始写入  ,相当于内存地址
  memoryBuffer.set(strBytes, offset);  //将数据写入内存

  // 调用 Wasm 函数,传递字节数组的地址和长度  
  const result = instance.exports._check(1800);  //这里的1800相当于我们存储的字符串的地址
    
  console.log('Result:', result);  
});


开始在check函数下断点!在两个md5函数和memcmp函数下个断点看看他们的参数!

第一个md5的参数:根据wasm的地址机制,猜测112和32是一个内存地址!

去看看果然是一个地址:
发现居然是一个字符串:“2333333333333333333333333333333a”,这个a大概就是我们的输入了!
多改变几次变量发现这个a也是跟随变化!!那么就可以得出第一条规则!

1. 112: 50   -》2
2. 113: 51  ——》3
3. 114: 51
.....
30. 141: 51
31. 142: 51
32. 143: 97 ——》 a
33. 144: 0

第一条加密规则:读取输入的一个字节拼接上"2333333333333333333333333333333"进行md5加密!

开始动调第二个md5:

再去看看地址64:
发现这里的内容貌似都是可见字符是:8ac44c16486e4d259983938fad820a7a
这里又可以合理猜测一波,将上一次md5的值传入第二次md5!

1. 64: 56 -》8
2. 65: 97 -》a
3. 66: 99 ->c
4. 67: 52
5. 68: 52
6. 69: 99
7. 70: 49
8. 71: 54
9. 72: 52
10. 73: 56
11. 74: 54
12. 75: 101
13. 76: 52
14. 77: 100
15. 78: 50
16. 79: 53
17. 80: 57
18. 81: 57
19. 82: 56
20. 83: 51
21. 84: 57
22. 85: 51
23. 86: 56
24. 87: 102
25. 88: 97
26. 89: 100
27. 90: 56
28. 91: 50
29. 92: 48
30. 93: 97
31. 94: 55
32. 95: 97
33. 96: 0

第二条加密规则:读取第一次md5加密产生的16进制字符的md5值进行再次md5!!
合理猜测一下第一次的md5加密到的二次的md5加密之间的代码是在进行赋值操作,将第一次加密的md5值放入64的位置!因为第一次md5运算后64的地址为空,当我调试中间这段代码时发现64地址逐渐有值了!

继续调试到第三个断点!
比较函数嘛,一定是比较两个地址的内存了呗!猜测是比较地址16和地址0

直接查看:
16地址:发现就是我们第二次加密的结果:64c286cfc623aa8d7df7c088ebf7d718
0地址去查看却发现是一串乱数据QAQ
但是去看地址0却发现不对劲这个数值不对!!按理来说这里应该是我们的输入经过两次md5后的值呀QAQ!
又去看了看:
发现这一大段的数据都是放置在memoryBase的地址处!但是我们给他赋值为0了!
而且我们的程序使用的暂存变量地址都是0,所以前面的地址内存数据会被破坏,所以我们的数据就被损坏了memcmp函数对比的两个数据!

  (data (global.get $env.memoryBase) "562fe3cc50014c260d9e8cf4ed38c77a\00c022ad0cc0075a9ab14b412a1082d5f3\0064c286cfc623aa8d7df7c088ebf7d718\0083664bdee4b613b7e7a51b5213470a8d\00b020bf598aaa2b3e03ed02c85436268a\004fdac5ac807506938103e775c50099ed\004fdac5ac807506938103e775c50099ed\00c231d607b6823fd0a68e813760809754\00d168c21d10371a5ab61bcfe6c759ef6e\00f60d709ccf989d849028f97a03d2f3ba\00a0184f8240e2fe46861dc8d15a819cb0\009dbec414336e741e9c73422df59de297\006fb5209d8fc8bb8507245bcfa24ae11f\006fb5209d8fc8bb8507245bcfa24ae11f\0000c77fbc60a5bfc466d3d069876ec348\0000c77fbc60a5bfc466d3d069876ec348\00df33464fb471c46abaf691c000a0e30d\004fdac5ac807506938103e775c50099ed\00f60d709ccf989d849028f97a03d2f3ba\00fcc94a20596f2619868f3a4bf52eadf7\0000c77fbc60a5bfc466d3d069876ec348\00d168c21d10371a5ab61bcfe6c759ef6e\009dbec414336e741e9c73422df59de297\00fcc94a20596f2619868f3a4bf52eadf7\009b37db091979bedf00a7095851ba6f59\0000c77fbc60a5bfc466d3d069876ec348\00f60d709ccf989d849028f97a03d2f3ba\00fcc94a20596f2619868f3a4bf52eadf7\00d168c21d10371a5ab61bcfe6c759ef6e\00f60d709ccf989d849028f97a03d2f3ba\00183342997ffed4b3189e977d077a60b4\00f404a3368d2d8f57464f739d4ed01c0e\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00x\a4j\d7V\b7\c7\e8\dbp $\ee\ce\bd\c1\af\0f|\f5*\c6\87G\13F0\a8\01\95F\fd\d8\98\80i\af\f7D\8b\b1[\ff\ff\be\d7\5c\89\22\11\90k\93q\98\fd\8eCy\a6!\08\b4Ib%\1e\f6@\b3@\c0QZ^&\aa\c7\b6\e9]\10/\d6S\14D\02\81\e6\a1\d8\c8\fb\d3\e7\e6\cd\e1!\d6\077\c3\87\0d\d5\f4\ed\14ZE\05\e9\e3\a9\f8\a3\ef\fc\d9\02og\8aL*\8dB9\fa\ff\81\f6q\87\22a\9dm\0c8\e5\fdD\ea\be\a4\a9\cf\deK`K\bb\f6p\bc\bf\be\c6~\9b(\fa'\a1\ea\850\ef\d4\05\1d\88\049\d0\d4\d9\e5\99\db\e6\f8|\a2\1feV\ac\c4D\22)\f4\97\ff*C\a7#\94\ab9\a0\93\fc\c3Y[e\92\cc\0c\8f}\f4\ef\ff\d1]\84\85O~\a8o\e0\e6,\fe\14C\01\a3\a1\11\08N\82~S\f75\f2:\bd\bb\d2\d7*\91\d3\86\eb\07\00\00\00\0c\00\00\00\11\00\00\00\16\00\00\00\07\00\00\00\0c\00\00\00\11\00\00\00\16\00\00\00\07\00\00\00\0c\00\00\00\11\00\00\00\16\00\00\00\07\00\00\00\0c\00\00\00\11\00\00\00\16\00\00\00\05\00\00\00\09\00\00\00\0e\00\00\00\14\00\00\00\05\00\00\00\09\00\00\00\0e\00\00\00\14\00\00\00\05\00\00\00\09\00\00\00\0e\00\00\00\14\00\00\00\05\00\00\00\09\00\00\00\0e\00\00\00\14\00\00\00\04\00\00\00\0b\00\00\00\10\00\00\00\17\00\00\00\04\00\00\00\0b\00\00\00\10\00\00\00\17\00\00\00\04\00\00\00\0b\00\00\00\10\00\00\00\17\00\00\00\04\00\00\00\0b\00\00\00\10\00\00\00\17\00\00\00\06\00\00\00\0a\00\00\00\0f\00\00\00\15\00\00\00\06\00\00\00\0a\00\00\00\0f\00\00\00\15\00\00\00\06\00\00\00\0a\00\00\00\0f\00\00\00\15\00\00\00\06\00\00\00\0a\00\00\00\0f\00\00\00\15\00\00\0023333333333333333333333333333333")

所以我们就可以开始修改memoryBase的地址,为这段数据重新分配一片新的空间

const imports = {  
  env: {  
    memory: new WebAssembly.Memory({ initial: 256 }),  //为整个wasm分配一片空间
    table: new WebAssembly.Table({ initial: 16, element: 'anyfunc' }),  
    memoryBase: 1900,//常量字符串的存放地址
    tableBase: 2000,  //一个变量的存放地址
    abort: function(param) {  
      // 实现 abort 函数的逻辑  
    }  
  }  
};  

fetch('easywasm.wasm')  
  .then(response => response.arrayBuffer())  
  .then(buffer => WebAssembly.instantiate(buffer, imports))  
  .then(results => {  
  const instance = results.instance;  //导入函数
  const env1 = imports.env; //导入变量
  const memoryBuffer = new Uint8Array(env1.memory.buffer);  //创建一片空间指向env1.memory.buffer
  // const Base = new Uint8Array(env1.memoryBase.buffer);  //创建一片空间指向env1.memory.buffer
  // 要传递给 Wasm 的字符串  
  const str = "abcdaaaaaaaaaaaaaaaaaaaaaaaaaaaa";  
    
  // 使用 TextEncoder 将字符串转换为 UTF-8 字节数组  
  const encoder = new TextEncoder();  
  const strBytes = encoder.encode(str);  
    
  // 将字节数组写入 WebAssembly 内存  
  const offset = 1800; // 从内存的哪个位置开始写入  ,相当于内存地址
  memoryBuffer.set(strBytes, offset);  //将数据写入内存

  // 调用 Wasm 函数,传递字节数组的地址和长度  
  const result = instance.exports._check(1800);  //这里的1800相当于我们存储的字符串的地址
    
  console.log('Result:', result);  
});

现在再去调试就发现是正确的了!对比16地址和1900地址的值,如果正确就继续下一次循环!

最终分析出结果!
加密算法就是:将我们输入的32个字节每个提取出来拼接上一个字符串进行md5加密产生一个16进制的字符串值,再将这个字符串进行md5加密继续产出16进制的字符串值,与目标值进行对比!

由于md5不可逆,所以只需要采用简单爆破就好了!
脚本就出来了!

脚本:
from hashlib import md5
a=['562fe3cc50014c260d9e8cf4ed38c77a',
'c022ad0cc0075a9ab14b412a1082d5f3',
'64c286cfc623aa8d7df7c088ebf7d718',
'83664bdee4b613b7e7a51b5213470a8d',
'b020bf598aaa2b3e03ed02c85436268a',
'4fdac5ac807506938103e775c50099ed',
'4fdac5ac807506938103e775c50099ed',
'c231d607b6823fd0a68e813760809754',
'd168c21d10371a5ab61bcfe6c759ef6e',
'f60d709ccf989d849028f97a03d2f3ba',
'a0184f8240e2fe46861dc8d15a819cb0',
'9dbec414336e741e9c73422df59de297',
'6fb5209d8fc8bb8507245bcfa24ae11f',
'6fb5209d8fc8bb8507245bcfa24ae11f',
'00c77fbc60a5bfc466d3d069876ec348',
'00c77fbc60a5bfc466d3d069876ec348',
'df33464fb471c46abaf691c000a0e30d',
'4fdac5ac807506938103e775c50099ed',
'f60d709ccf989d849028f97a03d2f3ba',
'fcc94a20596f2619868f3a4bf52eadf7',
'00c77fbc60a5bfc466d3d069876ec348',
'd168c21d10371a5ab61bcfe6c759ef6e',
'9dbec414336e741e9c73422df59de297',
'fcc94a20596f2619868f3a4bf52eadf7',
'9b37db091979bedf00a7095851ba6f59',
'00c77fbc60a5bfc466d3d069876ec348',
'f60d709ccf989d849028f97a03d2f3ba',
'fcc94a20596f2619868f3a4bf52eadf7',
'd168c21d10371a5ab61bcfe6c759ef6e',
'f60d709ccf989d849028f97a03d2f3ba',
'183342997ffed4b3189e977d077a60b4',
'f404a3368d2d8f57464f739d4ed01c0e']
flag = ""
for i in range(0, 32):
    ci = 0
    for cc in range(0x20, 0x7F):
        ch = chr(cc).encode('ascii')
        str1 = md5(b'2333333333333333333333333333333' + ch).hexdigest()
        x = md5(str1.encode('utf-8')  ).hexdigest()
        if(x == a[i]):
            ci = cc
            break
    assert(ci != 0)
    flag += chr(ci)
print(flag)


  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值