181116 逆向-EIS2018(SimpleAssemblyReverse)

忘记报名了OTZ就看了个"web"题,re有空再看看吧~

SimpleAssemblyReverse

不太明白题目里都写着Reverse了为啥还算在Web分类下面0.0

静态分析及准备

访问IP只有一个输入窗口和按钮
之前做过几个WebAssembly的题目了,也算轻车熟路了233
用Chrome按F12调出Network窗口,找到flag.wasm,选择Open in new tab,即可保存下来binary
(Firefox也是可以下载和动态调试的,但是试了一下没有找到在哪里查看内存,所以就放弃了)

这个binary是编译过的,没有可读性,因此需要使用官方的wabt套件中的wasm2c, wasm2wat工具来反编译

当然这个c语言和wat的可读性仍然不高,只是将每个寄存器都直接作为局部变量显示出来而已

所以要逆向的话一方面以此作为参考,另一方面主要还是动态调试

动态调试的方法

打开f12的Sources窗口,看到html的源代码中调用了_check函数
而在wasm中是看不到函数名字的,只有标号
通过wasm2wat可以找到对应关系

(export “_check” (func 53))

然后在左侧找到wasm,选择合适的函数打开即可下断
(如果没有wasm,多刷新两次,瞎鸡儿点点试试233)

另一边也可以打开wasm2c的结果对照着看,里面的函数是有check的名字

当下在check的断点被触发以后,就能看到此时的内存和栈了

wasm是基于栈的,它的所有计算都是以栈顶的若干元素进行操作的,例如add表示将栈顶的两个数弹出,相加后再压进栈;store表示将栈顶的数存入下一个数指向的内存,然后弹出这两个数。

右侧Scope一栏里的信息是最主要需要关注的

  • Global
    对应Firefox中的Memory,Chrome将其视作一个巨大的数组,通过下标查看值
  • Local
    • locals
      显示参数和局部变量,每个函数使用的局部变量是彼此独立的
    • stack
      操作栈,所有运算都基于它

右上方的几个熟悉的图标就是调试命令的按钮啦,快捷键分别为F8是Run,F10是步过Next,F11是步进Step

本题题解

单步跟着调试,通过locals的值可以发现两个参数分别是input和len
第一个判断很容易发现

//104 line
  get_local 172
  i32.const 38
  i32.ne
  set_local 173
  get_local 173
  if

将长度和38放入栈中,然后用i32.ne来判断,后面if就是识别栈里的内容为1则继续,否则跳到else分支里
我们可以看到,不等时的分支会直接return 0;
也就是说长度要求为48

第二个运算在这里

//line 247
      get_local 14
      i32.const 3
      i32.add
      set_local 15
      get_local 15
      i32.const 255
      i32.and

逐字符+3
(前面还有一些初始化空间的循环操作,没啥关系我就直接跳过了)

后面我跟了很久的正向操作都没有找到有用的信息,f797里实在是太绕了
(可能其实就是没啥用-A-)

后来想起来倒着从返回值逆推
拉到函数最后去看,这里推荐主要参考wasm2c的结果

  i0 = l149;
  i0 = i32_load8_s(Z_envZ_memory, (u64)(i0));
  l5 = i0;
  i0 = l5;
  i1 = 1u;
  i0 &= i1;
  l100 = i0;
  i0 = l191;
  g10 = i0;
  i0 = l100;
  goto Bfunc;
  Bfunc:;
  FUNC_EPILOGUE;
  return i0;

这里可以看出来返回值存储在l149指向的内存中,继续跟着l149向上找

  i0 = l123;
  i0 = i32_load8_s(Z_envZ_memory, (u64)(i0));
  l4 = i0;
  i0 = l4;
  i1 = 1u;
  i0 &= i1;
  l99 = i0;
  i0 = l99;
  i1 = 1u;
  i0 &= i1;
  l3 = i0;
  i0 = l149;
  i1 = l3;
  i32_store8(Z_envZ_memory, (u64)(i0), i1);

同理,返回值在l123指向的内存中

    i0 = f798(i0, i1, i2, i3, i4);//要求i0=1
    l97 = i0;
    i0 = l97;
    i1 = 0u;
    i0 = i0 == i1;
    l98 = i0;
    i0 = l98;
    i1 = 1u;
    i0 &= i1;
    l2 = i0;
    i0 = l123;
    i1 = l2;
    i32_store8(Z_envZ_memory, (u64)(i0), i1);

往上一点儿就看到这里了
这一大堆东西实际上可以简化成

return f798();

因此我们需要去逆一下f798?
不,直接动调看它的参数和返回值就好了,当成黑盒来操作
下断,运行,触发断点

stack:
0:24624
1:0
2:-1
3:13910
4:52

去Globals里依次查看i0和i3

很明显这是一串数据,先放着不管,去看另一个

这个地方只有3个字节,但其实它是个指针(这一点是在之前正向跟随的时候注意到有i32.store会四字节存放、而调试器只会把每个字节以十进制显示)
把624开头的4个字节以大端序转换成十六进制可以得到下一个地址:0x506998,再转成十进制5269912去查看Globals

又是一串数据,前后都是0,可以看到长度为52个字节,对应上参数4

可以修改一下输入再测试一下,发现后者的地址和值都变了,而13910处的值没有改变,所以可以推测出是input->*24624, strcmp(13910, *24624)

继续往上溯源,可以找到是f52对Input进行了改变,由38个字节input变成52个字节output

这两个数字敏感一些的人就直接可以猜到了
我觉得奇怪不是逐字节加密,于是继续往里跟着看了一下,发现有/3、/4操作的时候就反应过来了–base64

于是把之前那两串数据dump下来,转成ASCII再解b64,分别得到

>>> a = [chr(i) for i in [77, 122, 81, 49, 78, 106, 99, 52, 79, 84, 111, 55, 80, 68, 77, 48, 78, 84, 89, 51, 79, 68, 107, 54, 79, 122, 119, 122, 78, 68, 85, 50, 78, 122, 103, 53, 79, 106, 115, 56, 77, 122, 81, 49, 78, 106, 99, 52, 79, 84, 111, 61]]
>>> base64.b64decode("".join(a).encode())
b'3456789:;<3456789:;<3456789:;<3456789:'
>>> a = [chr(i) for i in [97, 87, 57, 107, 97, 110, 52, 48, 78, 71, 103, 122, 79, 84, 78, 107, 78, 87, 90, 111, 78, 68, 116, 108, 79, 106, 108, 111, 78, 109, 107, 49, 79, 84, 104, 109, 78, 122, 107, 52, 79, 50, 100, 107, 80, 68, 82, 111, 90, 111, 65, 61
... ]]
>>> base64.b64decode("".join(a).encode())
b'iodj~44h393d5fh4;e:9h6i598f798;gd<4hf\x80'

有点像乱码,但是没有报错说明思路没错
前者对应的输入是

01234567890123456789012345678901234567

可以看到非常相似,再回想一下还有一个操作,是逐字符+3,正好对应上
于是对后一个串做逐字符-3,就得到了结果

>>> "".join([chr(i-3) for i in base64.b64decode("".join(a).encode())])
'flag{11e060a2ce18b76e3f265c4658da91ec}'

回头想一下,13910显然是静态数据,应该是固定存下来的
在wasm2wat的结果中翻了一下,发现在最底下有data
眼尖的话直接发现这串b64就可以直接求解了

这个思路其实也很有道理的,虽然data是比较大,但是大部分都是不可见字符
排除掉那些\00的东西其实就两大块可读的内容
根据编译和链接的原理,用户模块加入的data会被放在一起、其他库中的字符串会被放在一起。也就是说用户的data很可能不在头就在尾,分别看一下就能找到那个字符串,它前面的是b64的表–更加明显XD

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值