骑麦兜看落日

[WriteUp]2019WCTF

字数统计: 1.3k阅读时长: 5 min
2019/07/07 Share

Login - 360


题目信息

Description:

Login, and you will get your flag
Hint: 1. Chrome 64 bit is recommended
\2. Username is your platform login name, and it’s case sensitive

Extra Info:

ip : 172.16.13.42
port : 80


程序分析

web师傅做到一半发现是个wasm的题目,边做边膜,360的出题师傅真的是太强了


解题过程

第一部分:js

首先从js代码入手,给了一个拨号键,按照一定的规则从一个表中选择字符,表如下

1
2
3
4
5
6
7
8
9
10
map = [
"x", "6", "Z", "G", "s", "2", "4", "B", "h",
"J", "d", "0", "n", "v", "p", "q", "C", "F",
"z", "r", "o", "P", "g", "a", "k", "N", "u",
"e", "w", "Y", "X", "Q", "I", "T", "R", "5",
"m", "H", "3", "8", "V", "1", "t", "M", "y",
"K", "7", "U", "O", "f", "A", "b", "9", "W",
"l", "S", "D", "E", "%", "j", "L", "c", "i",
".",
]

但是分析到后边的wasm代码后会发现,我们只可以选取0-f,这里先不说

submit后会调用check_submit

1
2
3
4
5
6
7
8
9
10
11
12
13
function check_submit() {
var input1 = document.forms["input_form"]["input1"].value;
var klass = MonoClass("", "Question");
if (klass) {
var method = MonoMethod(klass, "my_check", true);
if (method) {
console.log(input2Value)
MonoInvoke(0, method, [input1, input2Value]);
input2Value = ''
}
}
return false;
}

这里有几个函数MonoClassMonoMethodMonoInvoke封装了对应wasm提供的接口,总的代码逻辑是去调用my_check

有一个值得关注的地方是在MonoInvoke中,会申请heap来保存参数,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var argv = 0;
if (argc > 0) {
argv = instance.exports.malloc(4 * argc);
for (var i in params) {
var param = params[i];
var arg = undefined;
if (Number.isInteger(param)) {
arg = instance.exports.malloc(4);
heap_set_int(arg, param);
} else if (typeof param === 'string') {
var param_str = heap_malloc_string(param);
arg = instance.exports.mono_string_new(_MonoDomain(), param_str)
instance.exports.free(param_str);
} else {
throw "unsupported param type";
}
heap_set_int(argv + (i * 4), arg);
}
}

这里要注意到申请到的heap地址是wasm中的heap地址,所以对于我们定位到wasm中的函数有奇效

第二部分:c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
.class private auto ansi beforefieldinit Question extends [mscorlib]System.Object
{
.field private static unsigned int8[] Matrix
.method private static hidebysig unsigned int8[] f1_cal(unsigned int8[] value1) noinlining
{
.maxstack 8
ret
}
.method private static hidebysig unsigned int8[] f2_cal(unsigned int8[] val1) noinlining
{
.maxstack 8
ret
}
.method private static hidebysig unsigned int8[] strToToHexByte(string hexString) noinlining
{
.maxstack 8
ret
}
.method private static hidebysig bool compare_func(string name, string key) noinlining
{
.maxstack 8
ret
}
.method private static hidebysig string output_flag(string key) noinlining
{
.maxstack 8
ret
}
.method private static hidebysig void my_check(string name, string key) noinlining
{
.maxstack 8
ret
}.method private static hidebysig void Main(string[] args) noinlining
{
.entrypoint
.maxstack 8
ret
}
.method private static hidebysig specialname rtspecialname void .cctor() noinlining
{
.maxstack 8
ret
}
}

用ida打开之后发现就是一个类,包含了好几个方法,比如f1_calf2_calstrToToHexBytecompare_funcoutput_flagmy_check

这里顺带一提,如果早点看到静态属性Matrix的话就没有后边的那么多事情了…

没怎么接触过C#,在IDA里看感觉很奇怪,因为函数没有实现,拿到队友机器上用dnSpy打开也没有东西,老实说这里卡了很久,直到在github找到一个项目mono-wasm,他的工作机制如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+----------------+-------------+  +---------------------+
| Mono runtime | C library | | C# assemblies | <-------+
+----------------+-------------+ +----------+----------+ |
clang | | mono |
-target=wasm32 | | -aot=llvmonly |
v v |
+-------------------------------------------------------+ | load
| LLVM bitcode | | metadata
+----------------------------+--------------------------+ | (runtime)
| mono-wasm |
| (bitcode -> wasm) |
v |
+-------------------------------------------------------+ |
| index.wasm |---------+
+----------------------------------------+--------------+
^ | libc
load, compile | | syscalls
+ run main() | v
+----------------+--------------------------------------+ +-----------+
| index.js | <-----> | Browser |
+-------------------------------------------------------+ +-----------+

从这里开始了对出题师傅的膜拜

从文档描述来看,wasm会在运行时载入c#的代码,所以说c#中函数的实现应该是在wasm中的,因此需要在wasm中找到我们需要的几个函数,那么放到第三部分讲好了

第三部分:wasm

刚开始尝试从wasm入手,试用wasm2wat,wasm2c都报错失败,之后从网上找到资料添加参数—ignore,成功编译成wat,但依然无法编译c

打开wat发现有200多万行,开始不知道为什么,后来了解到第二部分c#后猜测应该是由于把mscorlib也编译了进去,总之不管怎么说,200多万行代码搜索一下都要卡半天,分析是不可能分析的

这个时候就要提到第一部分讲的参数的保存了,由于参数在保存的时候会得到wasm内存中的地址,那么我们可以根据调用函数时的内存地址来判断是否是我们想要分析的函数,不过一步一步跟着调试很浪费时间,无意间发现了浏览器会有密码错误的弹窗,于是通过查看调用堆栈缩小了目标函数的范围

1
2
3
4
5
6
7
8
9
10
11
12
7026
9408
9399
9397
9237
18314
17907
6921
6678
2044
2039
8598

通过第二部分我们可以得知函数my_check需要两个字符串参数,最终锁定在函数9237,这里遇到的一个坑是c#中的string类型是一个对象,存在一个数据结构,可能是下表所示,所以其对应指针并不实际对应字符串

0
4
8 length
12 char[0x100]
CATALOG
  1. 1. Login - 360
    1. 1.1. 题目信息
      1. 1.1.1. Description:
      2. 1.1.2. Extra Info:
    2. 1.2. 程序分析
    3. 1.3. 解题过程
      1. 1.3.1. 第一部分:js
      2. 1.3.2. 第二部分:c
      3. 1.3.3. 第三部分:wasm