# cs2100 - HackTM CTF Quals 2023

tl;dr

• LOAD and S_TYPE opcodes lead to OOB when addr > DRAM_BASE+DRAM_SIZE
• Get libc and stack pointers and offset to obtain RIP offset and base
• Write ropchain on stack using libc gadgets
• Perform ORW on flag file

Challenge Points: 462
No. of solves: 29
Solved by: k1R4

## Challenge Description

Handout has the challenge binary, libc, server.py and Dockerfile

## Initial Analysis

The binary is not stripped and has most mitigations turned on, typical for a binary compiled without explicit GCC flags.

Here is the checksec output:

This challenge seems to be a VM which implements the RISC-V architecture. There is a github repo provided from which the challenge seems to be based on. The next obvious step is to look at the src for bugs.

## Source Code

The source code can be found here

Before this challenge, I had never tried and RISC based challenges. I went ahead with solving this challenge, without understanding the architecture. I solved it by reversing the instruction opcodes. In hindsight it would’ve been much easier if I went through the register and instruction structure first. Wikipedia has a decent explanation of the architecture design, which you can find here.

Moving on to the src, the src/cpu.c file contains majority of the code that drives the VM. However there doesn’t appear to be any useful bugs on the surface. Since it is a VM challenge, the bug is probably OOB. In that case, the first instructions to look at are ones which involve memory derefences. The LOAD and S_TYPE opcodes seem to have the most potential in that case. Here is the implementation of the LD instruction:

Seems like memory is accessed through addresses which are passed to cpu_load() which calls bus_load() which again calls dram_load().

## Bug

dram_load() calls dram_load_x() where x is the number of bits. In the case of LD, dram_load_64() is called. It is implemented as follows:

The following code is from include/dram.h:

In the end dram->mem array is accessed, which is part of the CPU struct located on the stack. Since addr can be controlled, giving an addr larger than DRAM_BASE+DRAM_SIZE will lead to OOB on the stack.

## Exploit Stratergy

The LOAD and S_TYPE opcodes can be used to achieve OOB read and write respectively. Stack and libc pointers that are down the stack, can be copied and performed arithmetic on to obtain address of saved RIP and libc base. The LUI instruction can be used to move immutables to upper 20 bytes of registers and ADDIW can be used to add immutables to the lower 12 bits of registers. ADD can be used to offset from libc base to get gadgets. This seems pretty straightforward but I ran into some trouble. The ADD or LUI instructions were causing the value to be off by 0x1000 sometimes, so I had to manually increase the offset in those cases.

Finally a ropchain is written at saved RIP of main, using SW and SD instructions. execve isn’t feasible here since server.py is what we interact with and not the binary directly. So open,read,write is used instead.

## Conclusion

I learnt a lot about the RISC-V architecture from this challenge and had a lot of fun solving this.

You can find the full exploit here

Flag: HackTM{Now_get_an_A_for_the_class!}