当前位置:首页|资讯

【pwn16 2024SCTF VmCode】

作者:K3yB0ard发布时间:2024-10-03

刚刚结束的SCTF联赛SCTF的一道虚拟机pwn。这题主要难点就在于对虚拟机指令集的分析,以及程序如何解析opcode。

首先看一下程序保护

checksec

没有canary并且got表可写。

看一下程序逻辑:

main

main开始时候开了沙箱,然后后面估计就是进虚拟机执行opcode了,IDA无法解析,只能嗯看汇编了。首先看一眼沙箱:

sandbox

可以看到允许的系统调用只有open read write和exit,那么这题肯定是要标准ORW来打了。

现在尝试分析一下虚拟机对opcode的解析。

run pwn

直接运行程序有输出的,并且接受我们的输入,找了一下发现程序里并没有完整“shellcode”字符串,也没有write和read函数的调用,那么猜想程序的这些操作也都是通过opcode来实现的。

可以看到程序中有一个code变量、一个stack变量,那么肯定是一个用于当作输入的opcode缓冲区和虚拟机的虚拟栈来运行了。

syscall text

找一下可以看到程序代码段有这么一段代码,用于执行syscall,并且程序也就只有这一段有syscall,那么程序的输入输出肯定是通过这个来实现的,gdb看一下:

write syscall
read syscall

第一次断下来,程序执行的是write,输出了shellcode字符串,第二次断下来,程序执行read,向code+65的位置读入0x50的数据。分析到这可以知道了,程序的函数调用确实都是由固定这一段syscall代码来实现的,程序包含的code变量用作opcode缓冲区来给虚拟机执行。并且我们注意到code初始化的值:

code init val

估计程序这部分初始化的代码就是用来执行输出shelloce字符串+调用read读入数据的功能了。

现在分析本题核心,虚拟机对opcode的解析逻辑:

我们注意到main函数中有这样一段:

位于开启沙箱之后,准备开始进入虚拟机。

vmcode analyse

程序首先初始化rdi和rsi寄存器为0,之后程序从code中取一个字节,存在al中,rsi自增,这里rsi寄存器当作opcode指针来用,我们可以类比为虚拟机的rip指针,用于从头遍历opcode,取操作数和响应的参数执行虚拟机代码逻辑。取出code的一个字节后,将其减掉0x21,结果存放在rax,rax大于等于0的话,会去到0x1257处进一步执行。

0x1257处代码首先将取出上一步rax中的结果放在rcx,之后根据这个rcx结果作为偏移从offset全局变量中取出两个字节的数放在rax,之后程序将当前栈顶数据复制了一份压入栈,并执行ret返回到rcx+rax处执行代码。

分析到这里我们应该已经基本理解虚拟机执行的逻辑了,根据我们输入的值,减掉0x21之后当作偏移,去offset全局变量中取两个字节的数据当作rip偏移,去执行rcx+这个偏移处的代码。这一个字节就是对应的虚拟机opcode了,这里opcode都是大于等于0x21才行,下一步分析一下这个rcx是什么,gdb调试一下:

rcx val

多次调试发现,这里压入栈的rcx就是main+209,对应pie偏移为0x123A,可参考上面main汇编代码查看,接下来去看一下offset全局偏移:

offset

以两个字节为单位,我们得到offset偏移数组为:

一共19个偏移,将其加上0x123A之后计算得到对应的真实代码地址:

vmcode func

发现就是对应从0x1274开始,以retn分割的一个个代码块。一共19个代码块,对应19种opcode。指令号分别为0+0x21~19+0x21即0x21~0x33.接下来分析每种指令操作的含义:

code数组为虚拟机代码段数据,stack数组为虚拟机的虚拟栈,rsi寄存器看作虚拟机rip,rdi寄存器当作虚拟机rsp。

分析得到所有opcode如下:

分析到这我们已经完全了解虚拟机执行逻辑了,接下来直接构造ORW的payload即可。

完整exp如下:

【注】在read读取flag数据时由于没有可控地址,笔者将其读在虚拟栈上,为给flag输入腾空间,用了多次copy_and_push操作来抬高栈且保证flag首地址始终位于栈顶,后续可以用作read的参数传入。

脚本执行结果:

exp result

成功读出flag。

By  Del0n1x

Keyboard


Copyright © 2024 aigcdaily.cn  北京智识时代科技有限公司  版权所有  京ICP备2023006237号-1