前言
今天突然来感觉想写点什么,但是最近确实没啥技术长进,没东西可写。看了看文章发现好久没写猿人学的东西了,就去猿人学比赛网站逛了一圈,发现最新出来的20题还没有去搞,这怎么可以!但是自己也没啥wasm逆向的经验,在网上看了一下,也都是一些rpc的方案,正好这次就补充学习一下了
一、认识wasm
什么是wasm:参考链接
在开始做题前呢先熟悉一下wasm,进入20题,f12查看参数发现需要解决的只有一个sign,老规矩点击调用堆栈信息进入发包位置:
可以看出sign是由页码加上时间戳经过window.sign
方法之后返回的加密结果:
继续跟进window.sign
,打断点进入该函数,发现长这样:
方法调用的名字确实长,靠经验我们直接看看这个_index_bg_wasm__WEBPACK_IMPORTED_MODULE_0__["sign"]
毕竟有个熟悉的sign字眼嘛,进入之后来到wasm的代码内:
这里就要先补一下wasm的基础了,大概如下:
| 代表导出函数 sign |
伪id 71 | export修饰 | $var0,$var1 参数名 i32代表类型 int32
(func $sign (;71;) (export "sign") (param $var0 i32) (param $var1 i32) (param $var2 i32)
local 为当前函数内部用到的变量
(local $var3 i32) (local $var4 i32) (local $var5 i32) (local $var6 i32) (local $var7 i32) (local $var8 i32) (local $var9 i32) (local $var10 i32) (local $var11 i32) (local $var12 i32) (local $var13 i32) (local $var14 i32) (local $var15 i32) (local $var16 i32) (local $var17 i32) (local $var18 i32) (local $var19 i32) (local $var20 i32) (local $var21 i32) (local $var22 i32) (local $var23 i32) (local $var24 i32) (local $var25 i32) (local $var26 i32) (local $var27 i32) (local $var28 i32) (local $var29 i32) (local $var30 i32) (local $var31 i32) (local $var32 i32) (local $var33 i32) (local $var34 i32) (local $var35 i32) (local $var36 i64)
global.get $global0 global.get 从全局变量中读东西
local.set $var3 local.set 写到局部变量中 读写一般都是成对出现的,一般为赋值操作 (约等于$var3 = $global0)
i32.const 80 也是读操作,从类型定义了一个80的数
local.set $var4 写入到 $var4 (约等于:$var4 = 80)
local.get $var3 读$var3
local.get $var4 读$var4
i32.sub 写操作,i32的减法操作
local.set $var5 写入到 $var5 (约等于:$var5 = $var3 - $var4)堆栈操作 先读的在前
local.get $var5
global.set $global0
local.get $var5
local.get $var1
i32.store offset=64 内存操作,store:缓存,load:读取,offset:偏移量
local.get $var5
local.get $var2
i32.store offset=68
i32.const 16
local.set $var6
local.get $var5
local.get $var6
i32.add 加操作,也属于写操作
local.set $var7
local.get $var7
local.get $var1
local.get $var2
call $func246 执行函数操作,可看之前的get操作一直到set停止,这里就相当于 将$var7,$var1,$var2,传入函数执行
local.get $var5
i32.load offset=16
local.set $var8
再看看调试器的不同:
可能大家浏览器不一样,版本不一样所以会有一点小差别
调试器:
scope:
expression:
stack: 堆栈,每次读操作会进行一次压栈,每次写操作会进行一次出栈,调用方法时会进行平栈操作(清除栈内容),可查看当前堆栈内内容
local:局部变量(如$var5,$var6,$var7),显示的指针/内存地址,并不是实际值
module:
function tables:可以理解为wasm与js互动的关联方法
globals:全局变量
instance:
exports: wasm提供给js直接能调用的方法
memory: 虚拟内存
了解个大概就差不多了
二、逆向分析
通过之前定位的sign函数,可知最后的getStringFromWasm0(r0, r1);
得到最后的结果,getStringFromWasm0
为获取内存中指定位置,长度的数据:
r1参数恒为32,我们只需要求r0即可,r0通过getInt32Memory0
参数取索引定义而来,查看getInt32Memory0
方法:
看见memory,猜测和虚拟内存有关系,这里int32:294912
,对应方法中的除以4操作,我们将其*4得到1179648
,再次查看调试器中的scope下的module下的memory,发现一致,该方法返回虚拟内存的数组通过索引获得加密参数的指针地址:
再看看retptr
怎么来的,通过断点调试来到wasm方法中,翻译一下大概是这个样子:
global = var0 + global
ptr0
呢,则是传入的明文在虚拟内存中的指针地址,可以通过getStringFromWasm0(1114120, 15)
来查看,也是不变的,再次打断点在_index_bg_wasm__WEBPACK_IMPORTED_MODULE_0__["sign"](retptr, ptr0, len0);
发现每次经过该方法之后,改内存地址下的数据会发生改变,我们再次跟入该函数,但是这个函数很长不说,还调用了很多其他的方法,我们直接找到关键字sign方法这里,当然你想跟/全部翻译的也可以
进入到sign函数中,查看函数整体逻辑,发现了个有意思的东西:md5,这就很巧了,加密参数长度也是32,并且数据格式也是对的上的,我们直接打断点在此处,看看传参是啥
通过getStringFromWasm0(var56,var55)
查询真实值,发现一段加了盐的明文
尝试使用标准md5加密该参数,对比结果
不得了,一摸一样,自此加密解决,为标准的md5加密,至于盐值是否动态变化,可以多次尝试查看,发现是不会变的,
这里就不告诉你们盐值是多少了,还是希望能自己去走一遍流程来完成学习,不希望你们看了明文之后直接调用算法直接过加密做题,因为如果是这样的话,其实整篇文章看了也白看,真想只做题的话,可以直接出门左转直接rpc好了
三、其他
jeb 可以打开wasm文件查看,反编译方法为c代码,可以看的更加清晰一点
参考链接:tql
按照步骤走就行,提示:Class com/pnfsoftware/jeb/rcpclient/Launcher not found.
说明你有中文路径
总结
看了如果你有熟悉的感觉,没错,我就是看着志远大佬的公开课学习的,这里也算是做一个知识储备吧