{"id":22096213,"url":"https://github.com/blessedrebus/riscv-attacks","last_synced_at":"2025-04-09T23:10:26.882Z","repository":{"id":247456396,"uuid":"773416580","full_name":"BlessedRebuS/RISCV-Attacks","owner":"BlessedRebuS","description":"Security analysis for the RISC-V ISA","archived":false,"fork":false,"pushed_at":"2024-08-01T19:49:50.000Z","size":42396,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-24T00:56:53.910Z","etag":null,"topics":["bof","cybersecurity","embedded-systems","risc-v","riscv","rop"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BlessedRebuS.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-17T15:54:15.000Z","updated_at":"2025-03-19T14:48:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"09ea9d12-3c1b-48df-8043-afcb9a778ec1","html_url":"https://github.com/BlessedRebuS/RISCV-Attacks","commit_stats":null,"previous_names":["blessedrebus/riscv-attacks"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlessedRebuS%2FRISCV-Attacks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlessedRebuS%2FRISCV-Attacks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlessedRebuS%2FRISCV-Attacks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BlessedRebuS%2FRISCV-Attacks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BlessedRebuS","download_url":"https://codeload.github.com/BlessedRebuS/RISCV-Attacks/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248125613,"owners_count":21051770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bof","cybersecurity","embedded-systems","risc-v","riscv","rop"],"created_at":"2024-12-01T04:09:52.533Z","updated_at":"2025-04-09T23:10:26.849Z","avatar_url":"https://github.com/BlessedRebuS.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Analysis of RISC-V Security\n\n\u003cp align=\"center\"\u003e\n\u003cimg src='img/riscv.jpg' width='400'\u003e\n\u003c/p\u003e\n\n# Side channel attacks\nIn computer security, a side-channel attack is any attack based on extra information that can be gathered because of the fundamental way a computer protocol or algorithm is implemented, rather than flaws in the design of the protocol or algorithm itself. In this study case, the attacks will work only if the microarchitecture offers **[out of order execution](https://www.cs.uaf.edu/2011/spring/cs641/proj1/vsanditi/)** and **[speculative execution](https://en.wikipedia.org/wiki/Speculative_execution)**.\n\n## Cache Flush and Reload (mitigated)\n\n\u003cimg src='img/flush-reload.png' width='500'\u003e\n\nUsually after a cache miss the data is fetched from the DRAM, but the second time is fetched from the CPU cache that is much faster.\n\n\u003cimg src='img/shared-memory.png' width='500'\u003e\n\nAfter the attacker flushes the cache, the victim now loads (due to a read operation) a data on the empty cache. \nNow the attacker can read data and due to its access time knows if the data is loaded in cache or not.\n\n## Cache Flush and Fault (new approach)\n\nAgain the attacker flushes the cache, then it jump to the address containing victim cache line and handles the error. From the timing of the fault the attacker can predict if the data was on cache or not.\n\n\u003cimg src='img/fault-reload.png' width='500'\u003e\n\n## [Performance Counters](https://docs.openhwgroup.org/projects/cv32e40x-user-manual/en/latest/perf_counters.html)\n\nThey give a benchmarking capability. They can count report events like cache missed, instruction executed, CPU frequency etc...\nBy default performance counters are available also to the attackers on the system. The mitigation for this type of attacks is making the interface root only.\n\n\u003cimg src='img/branch-taken.png' width='500'\u003e\n\n*Some performance counters still remain privileges by standard.* The events are privileged apart from a few. The \"branch taken\" event is now privileged, but remains the number of **CPU cycles elapsed** and the **number of instruction retired**.\n\n## KASLR: Kernel Address Space Layout Randomization\n\nRandomization of address space at every boot of the system. An attacker to use a memory exploit has to know the addresses of the program before running it.\n\n### BPU: Branch-Prediction Unit\n\nThe core idea of this methodolody is that branches impact execution speed. Knowing that there is a jump still doesn't tell what jump is taken in the code.\nAt this part it comes the **Optimize by Prediction**. The CPU has a history of last branches to optimize the future branches. For example if a code is \"always\" the processor tends to optimize the jump pre-loading the false branch condition.\n\n## CPU OPTIMIZATION: Speculative Execution\n\nInstead of only predicting the branch, the CPU could actually execute the prediction. That makes modern CPUs very fast. [Spectre](https://spectreattack.com/spectre.pdf) used this optimization to exploit the CPU. *Some RISC-V doesn't have speculative execution*, but they have **speculative prefetching**.\nThis means that the attacker can let the CPU prefetch whatever he wants.\n\n\u003cimg src='img/prefetch.png' width='500'\u003e\n\nThis also means that **the Speculative Prefetching is exploitable** and Spectre can be exploited on some RISC-V processors. On some specific CPUs it is present the limited speculation that mitigates this attacks.\n\nThe speculative execution is often removed from RISC-V processors (for example is missing in the [SiFive](https://www.sifive.com/)) but is present in recent microarchitectures as the [C910/C920](https://github.com/sophgo/sophgo-doc/blob/main/SG2042/T-Head/XuanTie-C910-C920-UserManual.pdf). In the CPU itself, this type of speculation is given by the precondition of the microarchitecture to do [indirect branches](https://en.wikipedia.org/wiki/Indirect_branch), that is the possibility to jump to a register instead of a address jump.\n\n## Case Study: Monte Cimone's RISC-V SiFive u74-mc\n\nOn the Monte Cimone's RISC-V cluster it is present the [SiFive U74-MC](https://starfivetech.com/uploads/u74mc_core_complex_manual_21G1.pdf). As the manual says and [as reported from the SiFive statement](https://www.sifive.com/blog/sifive-statement-on-meltdown-and-spectre), the IP core is not allowed to perform speculative execution:\n\n\"_Meltdown attacks (CVE-2017-5754) rely upon speculative access to memory that the processor does not have permission to access; our processors do not perform this form of speculation. The Spectre attacks (CVE-2017-5753 and CVE-2017-5715) rely upon speculative memory accesses causing cache state transitions; our processors do not speculatively refill or evict data cache lines._\"\n\nLet's try it in the SiFive present on the cluster \n\n\u003cimg src='img/sifive-arch.png' width='500'\u003e\n\nI will run [this](https://github.com/cispa/Security-RISC/tree/main/spectre) spectre POC for RISC-V from Lukas Gerlach, Michael Schwarz and Daniel Weber.\n\n\u003cimg src='img/strace-sifive.png' width='500'\u003e\n\nAs expected this type of attack is mitigated in this processor due to the **limited speculation**. In general, more optimized cores are more vulnerable. This attack actually works on C910 that allows speculative execution.\n\n## Case Study: Discover Hidden files with retired instructions\n\nIn many RISC-V Core implementation there is a counter for the number of instructions retired during the program execution. As the [SiFive U74 manual](https://www.scs.stanford.edu/~zyedidia/docs/sifive/sifive-u74.pdf) says, the `RDINSTRET rd` \"Reads the64-bits of the instret CSR, which counts the number of instructions retired by this hart from some arbitrary start point in the past.\" \nThe useful user-space accessible register for the SiFive RISC-V implementation are\n\n```\nRDCYCLE rd Reads the64-bits of the cycle CSR which holds a count of the number of clock cycles executed by the processor core on which the hart is running from an arbitrary start time in the past.\nRDTIME rd Generates an illegal instruction exception. The mtime register is memory mapped to the CLINT register space and can be read using a regular load instruction.\nRDINSTRET rd Reads the64-bits of the instret CSR, which counts the number of instructions retired by this hart from some arbitrary start point in the past.\n```\n\nIn the [PoC](https://github.com/cispa/Security-RISC/blob/main/rlibsc.h) implementation of Cispa's researchers, the `rdinstret` register is printed as follows\n\n```c\nstatic inline size_t rdinstret() {\n  size_t val;\n  asm volatile(\"rdinstret %0\" : \"=r\"(val));\n  return val;\n}\n```\n\nNow we can get the number of instructions retired during the execution of a program. \nAn interesting side-channel attack comes out from this. As the researchers present in the [Access Retired PoC](https://github.com/cispa/Security-RISC/tree/main/access-retired), the value of rdinstret can be used to see what the processor has \"prefetched\". The PoC tries to open various files, which always fails (return value is NULL). However, the number of retired instructions is higher if the file exists. The core concept of this experiment is presented below.\n\n```c\nsize_t before = rdinstret();\nf = fopen(p, \"r\");\nsize_t after = rdinstret();\nsize_t delta = after - before;\n```\n\nHere, if the file is present on the filesystem, the number of instructions retired will be higher because **the value of f will not be NULL**. Running the experiment on a folder with no listing permission will give the following result\n\n\u003cimg src='img/access-retired.png' width='700'\u003e\n\n### NOP retired instructions\n\nAnother example to understand better the number of the retired instruction is [this NOP example](https://github.com/BlessedRebuS/RISCV-Attacks/blob/main/bin/access-retired/example-nop.c). The program has 10x10 `NOP` instructions, executed only if the branch is taken. \n\n```c\nsize_t before = rdinstret();\nif (COND){\t\n\tasm volatile(\"nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;\");\n\tasm volatile(\"nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;\");\n\tasm volatile(\"nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;\");\n\tasm volatile(\"nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;\");\n\tasm volatile(\"nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;\");\n\tasm volatile(\"nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;\");\n\tasm volatile(\"nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;\");\n\tasm volatile(\"nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;\");\n\tasm volatile(\"nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;\");\n\tasm volatile(\"nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;\");\n  }\nsize_t after = rdinstret();\n```\n\nWith COND = 0 (false) this is the output\n\n\u003cimg src='img/nop-false.png' width='700'\u003e\n\nWith COND = 1 (true) this is the output\n\n\u003cimg src='img/nop-true.png' width='700'\u003e\n\nHere It is clear how the Out of Order execution, mixed with some speculative mechanism is prefetching the code that has to be run. In this case the NOP is 1 byte long and count as an instruction itself, hence the difference between the two executions (one with the NOP and one without it) differs by 100 instructions, as underlined in the screenshots.\n\n### What to do with this?\n\nThis can lead to bruteforce attacks in the filesystem to access unlistable directories or more complex memory based attacks. \n\nA simple implementation of that, can be [this extension](https://github.com/BlessedRebuS/RISCV-Attacks/blob/main/bin/access-retired/dir-list.c) to bruteforce users' home directories and find who in the system has the file `test`. \n\n# Control Flow Integrity: Buffer Overflow \u0026 Return Oriented Programming \nIn this section It will be analyzed memory attacks such as ROP (Return Oriented Programming) with or without a Buffer Overflow entrypoint.\n\n## ISA Analysis\n### Function Calls: Callee and Caller\nIn programming, the function that calls another function is the `caller` and the called function is the `callee` or `leaf function`. The main itself is a caller function, because It initialzies and call all the following function calls, but is also a callee because It is called at the start of the program by the **start** function. As the image below shows, in the [RISC-V calling convention](https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf) there are 12 integer registers (S0-S11) and 12 floating point registers (FS0-FS11) that are preserved across function calls and must be saved by the callee if they are used.\nGenerally a callee performs a task or computations and returns the result to the caller, _eg: function that performs a sum_.\nA function, such as the `main()` function, can be both caller and callee.\nIn conclusion, callers can use the \"S\" registers to get the result saved by the callee and callees can use the \"T\" (temporary) registers to temporary save the data. This is generally true until the program runs out of registers and in this case both the S and T registers can be used, but at the end they have to be backed up.\n\n\u003cimg src='img/caller-callee.png' width='500'\u003e\n\n## 64bit RISC-V Assembly\nAll the specifications of the RISC-V Assembly are described in the [official manual](https://riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdf) at Chapter 20.\n\n### Simple Syscall\nThe following asm code use the system call `exit` loaded with the value `1`. \n\n```asm\n.global _start\n\n.section .text\n_start:\n\tli a0, 1\n\tli a7, 93\n\tecall\n```\n\nWe can find the syscall number for the riscv64 and many other architectures [here](https://gpages.juszkiewicz.com.pl/syscalls-table/syscalls.html). In this case the exit syscall has the number 93, so It has to be loaded in the register `a7`, **that holds the system call code** and it is called with the parameter 1, loaded in the register `a0`. The `ecall` instruction tells the system to run the system call mapped in the register a7 with the arguments mapped in the argument registers, in this case only a0.\n\nAfter that, because this is not run through an emulator or in a VM, It will be compiled with the basic GNU assembler.\n\n```bash\nriscv64-linux-gnu-as simple_syscall.s -o simple_syscall.o\nriscv64-linux-gnu-ld simple_syscall.o -o simple_syscall\n```\n\nThis will generate an ELF `simple_syscall` program, runnable with\n\n```bash\n./simple_syscall\n```\n\nWe can check the exit status with \n\n```bash\necho $?\n```\n\nAnd It will tell the last exit status of a program, in this case it will be \"1\"\n\n### Simple execve: executing /bin/bash\n\n```asm\n.global _start\n\n.section .text\n_start:\n\tla a0, shell\n\tli a1, 0\n\tli a2, 0\n\tli a7, 221\n\tecall \n\n\tli a0, 1\n\tli a7, 93\n\tecall\n\nshell:\n .ascii \"/bin/bash\"\n```\n\nHere the asm code for calling a simple shell. This will pop a normal shell for the same user that runs the program. The **/bin/bash** string can be replaced to run other type of syscalls. If this program had a setuid bit, the owner was **root** and the bash call preserved the setuid, this program can be a backdoor for the system we are in.\n\n\u003cimg src='img/simple_execve.png' width='600'\u003e\n\n### Simple execve: executing /bin/bash - GCC variant\n\nThe following code is generated by GCC that uses libraries. Here we can see that the system call is not an ecall, but is first a jump to the execve function in the C library. In terms of exploitation, this means that there is a \"prologue\" and an \"epilogue\" of a function to be respected when calling the execve and the registers we are using in the current call are not preserved across function calls, hence an exploitation is more difficult for this scenario.\n\n```asm\n   0x0000000000000668 \u003c+0\u003e:\taddi\tsp,sp,-16\n   0x000000000000066a \u003c+2\u003e:\tsd\tra,8(sp)\n   0x000000000000066c \u003c+4\u003e:\tsd\ts0,0(sp)\n   0x000000000000066e \u003c+6\u003e:\taddi\ts0,sp,16\n   0x0000000000000670 \u003c+8\u003e:\tli\ta2,0\n   0x0000000000000672 \u003c+10\u003e:\tli\ta1,0\n   0x0000000000000674 \u003c+12\u003e:\tauipc\ta0,0x0\n   0x0000000000000678 \u003c+16\u003e:\taddi\ta0,a0,36 # 0x698\n   0x000000000000067c \u003c+20\u003e:\tjal\tra,0x5a0 \u003cexecve@plt\u003e\n   0x0000000000000680 \u003c+24\u003e:\tli\ta5,0\n   0x0000000000000682 \u003c+26\u003e:\tmv\ta0,a5\n   0x0000000000000684 \u003c+28\u003e:\tld\tra,8(sp)\n   0x0000000000000686 \u003c+30\u003e:\tld\ts0,0(sp)\n   0x0000000000000688 \u003c+32\u003e:\taddi\tsp,sp,16\n   0x000000000000068a \u003c+34\u003e:\tret\n```\n\n## ROP Contraints\nAs It is well explained [here](https://arxiv.org/pdf/2007.14995.pdf), the open source nature of the ISA, brought some security updates to mitigate some ROP attacks. ROPs are built using gadget found across the asm code, that in RISC-V translates in using the epilogue of a function and knowing that the used registers has to be restored. One more complication is the usage of the link register. The purpose of this register is to optimize calls to leaf subroutines since the return address need not be pushed or popped on the stack as It happens in X86_64. As It is specified in the paper, to have a full functional and chainable ROP gadget the following contraints have to be satisfied:\n\n* ROP exploitation is mostly limited to **non-leaf function epilogues**\n* Loads a value from a(**sp**) into **ra** where a is some positive immediate value divisible by 8\n* Adds an immediate value b to **sp** where b \u003e a and b is divisible by 16 (due to stack-alignment requirements)\n* Ends in a **ret** (equivalent to **jr ra**)\n* Find some extra instruction to perform operations in the ROP\n\nAfter that, as suggested in [this](https://pure.royalholloway.ac.uk/ws/portalfiles/portal/37157938/ROP_RISCV.pdf) paper, It could be used a nested function call to emit these save and restore sequences for the registers. As they say, the presence of a **restore sequence** is crucial for mounting a ROP attack, as we need to tamper with the return address register **ra**, which is callee-saved.\n\n### Non-Leaf functions\nConsider the following C function\n\n```c\nint test_empty() {\n    int test = 1;\n    printf(\"test\");\n    return 1;\n}\n```\n\nThat can be disassembled with\n\n```bash\ndisasm test_empty()\n```\n\nHere there is the disassembled code, using GDB\n\n```gdb\nDump of assembler code for function test_empty:\n   0x0000000000000802 \u003c+0\u003e:\taddi\tsp,sp,-32\n   0x0000000000000804 \u003c+2\u003e:\tsd\ts0,24(sp)\n   0x0000000000000806 \u003c+4\u003e:\taddi\ts0,sp,32\n   0x0000000000000808 \u003c+6\u003e:\tli\ta5,1\n   0x000000000000080a \u003c+8\u003e:\tsw\ta5,-20(s0)\n   0x000000000000080e \u003c+12\u003e:\tli\ta5,1\n   0x0000000000000810 \u003c+14\u003e:\tmv\ta0,a5\n   0x0000000000000812 \u003c+16\u003e:\tld\ts0,24(sp)\n   0x0000000000000814 \u003c+18\u003e:\taddi\tsp,sp,32\n   0x0000000000000816 \u003c+20\u003e:\tret\n```\n\nThis asm code can't jump to another function and is a \"dead end\" for the ROP chain. To bypass this wall we must use non-leaf functions. Let's try to add a simple nested function call inside the test_empty().\n\n```c\nint test_empty2() {\n    int test = 1;\n    return 1;\n\n}\nint test_empty() {\n    test_empty2();\n    return 1;\n}\n```\n\nThe disassembled of the test_empty now is the following\n\n```gdb\nDump of assembler code for function test_empty:\n   0x0000000000000818 \u003c+0\u003e:\taddi\tsp,sp,-16\n   0x000000000000081a \u003c+2\u003e:\tsd\tra,8(sp)\n   0x000000000000081c \u003c+4\u003e:\tsd\ts0,0(sp)\n   0x000000000000081e \u003c+6\u003e:\taddi\ts0,sp,16\n   0x0000000000000820 \u003c+8\u003e:\tjal\tra,0x802 \u003ctest_empty2\u003e\n   0x0000000000000824 \u003c+12\u003e:\tli\ta5,1\n   0x0000000000000826 \u003c+14\u003e:\tmv\ta0,a5\n   0x0000000000000828 \u003c+16\u003e:\tld\tra,8(sp)\n   0x000000000000082a \u003c+18\u003e:\tld\ts0,0(sp)\n   0x000000000000082c \u003c+20\u003e:\taddi\tsp,sp,16\n   0x000000000000082e \u003c+22\u003e:\tret\n```\n\nHere we se that the ra register is modified with\n\n`0x000000000000081a \u003c+2\u003e:\tsd\tra,8(sp)`\n\nand the value is loaded with\n\n`0x0000000000000828 \u003c+16\u003e:\tld\tra,8(sp)`\n\nFrom this example It is clear that to build gadget a non-leaf function should be used or a leaf function should be modified to embed a **dummy** function call.\n\n`Another consideration is that without the ra register, hence in a leaf function, neither the initial buffer overflow can be done because no ra register is used and no return address can be overwritten.`\n\n## Global register manipulation\nUsing inline ASM we can overwrite the callee saved registers (s2-s11) with arbitrary value. Those registers are by convention not reset and then GCC at compile time doesn't handle the prologue and epilogue of those registers. For this we can pass the value of those register in all the C program. A simple function that sets the **s2** register is the following.\n\n```c\nvoid not_called(){\n    asm volatile (\"li s2, 1\");\n    return;\n}\n```\n\nWe can then print this in another part of the program and across two functions (main and not_called). In this case the value of s2 will be 1 because of the load immediate.\n\n\u003cimg src='img/s2-reg.png' width='400'\u003e\n\n## Manipulating exit() syscall status code with S registers\nUsing the previous assumtpions, we can now control the system call arguments using values passed across the functions using callee saved registers.\nIn this PoC I will use the **S2** register as the argument passed to the _exit_ system call. The PoC consists in a jump from the cuntion not_called to the exit function but just after the S2 register is set. In this way the S2 register is the register preserved in the not_called function that the attacker can control. \nThe jump lands after in the instruction `asm volatile (\"mv a0, s2\")` and loads with the value 1 the register S2 that reflects in the value 1 on the exit system call.\n\n```c\n#include \u003cstdio.h\u003e\n\nvoid not_called(){\n\tasm volatile (\"li s2, 1\");\n\tasm volatile (\"jal ra, exit_function +22\");\n\treturn;\n}\n\nvoid exit_function(){\n\tprintf(\"exit function\\n\");\n\tasm volatile (\"li s2, 0\");\n\tasm volatile (\"mv a0, s2\");\n\tasm volatile (\"li a7, 93\");\n\tasm volatile (\"ecall\");\n\treturn;\n}\n\nint main(){\n\tregister long s2 asm (\"s2\");\n\tasm volatile (\"jal ra, not_called\");\n\tprintf(\"Val of res: %ld\\n\", s2);\n\treturn 0;\n}\n```\n\nNow with `echo $?` I can print the exit status code. If the exit_function() would be executed entirely It should returns the value `0`, but with the S2 manipulation returns `1`.\n\n\u003cimg src='img/manipulate_exit.png' width='700'\u003e\n\nUsing **ROPGadget** we can actually see the two gadgets we are using to manipulate the _exit_ output. \n\n\u003cimg src='img/ROPGadgets_s2.png' width='1000'\u003e\n\n## Manipulating exit function status code with A registers\nRISC-V registers, except for **x0** are general purpose. This means that every register can be used for anything. In compiled programs usually is the compiler that takes care of prologues and epilogues of the functions and of the state of the registers. The following PoC uses the register **a1** as the argument for the _exit()_ function. Once again the **not_called** function is jumping to the offset (+22) of the call, replacing the value 1 with 0.\n\n```c\n#include \u003cstdio.h\u003e\n#include \u003cstdlib.h\u003e\n\nvoid not_called(){\n        asm (\"li a5, 1\");\n        asm (\"jal ra, exit_function +20\");\n        return;\n}\n\nvoid exit_function(){\n        printf(\"exit function\");\n        exit(0);\n        return;\n}\n\nint main(){\n        register long s2 asm (\"s2\");\n        asm volatile (\"jal ra, not_called\");\n        printf(\"Val of res: %ld\\n\", s2);\n        return 0;\n}\n```\n\nAs before, this result in a exit return value of 1.\n\n\u003cimg src='img/manipulate_exit_a0.png' width='700'\u003e\n\n## Manipulating exit syscall status code with ROP\nUsing Return Oriented Programming is it possible hence to manipulate registers. In the previous codes the call was straightforward and done with the normal code execution. This time a particular piece of code will be called (code reuse) and will set a register with a value. This value will be passed to the syscall as argument and the program flow will be interrupted to use. Depending on what piece of code, then what return address is taken, the exit function will have a value or another value.\n\n\u003cimg src='img/rop_exit.png' width='700'\u003e\n\nIn yellow It is highlighted the difference between a direct jump to `not_called` or a jump that goes first in the `test_empty2` function.\n\n## Manipulating exit syscall status using arbitrary stack values\nWe can exploit this different type of program, knowing that the a6 register will be overflow after the **56th** character put in the buffer. Once had the overflow to the not_called function, we return directly to the point the a6 value is overwritten and here we find our arbitrary filled buffer (and then arbitrary crafted stack) that will be copied in the a0 register and will be used as argument of the **exit** function. The ecall runs the system call.\n\n```c\n#include \u003cstdlib.h\u003e\n#include \u003cstdio.h\u003e\n#include \u003cstring.h\u003e\n\nvoid not_called() {\n    printf(\"exit function\\n\");\n    asm volatile (\"li a6, 0\");\n    asm volatile (\"mv a0, a6\");\n    asm volatile (\"li a7, 93\");\n    asm volatile (\"ecall\");\n    return;\n}\n\nint test_empty() {\n    printf(\"Empty function\\n\");\n    return 1;\n}\n\nvoid vulnerable_function(char* string) {\n    char buffer[64];\n    test_empty();\n    strcpy(buffer, string);\n}\n\nint main(int argc, char** argv) {\n    vulnerable_function(argv[1]);\n    return 0;\n}\n```\n\nHere, after the 56th character the a6 register is overwritten with the arbitrary **HEX** value put in the exploit. If the **not_called** function was executed normally It would result in a exit code 0.\nThe following code will give an exit value of 1.\n\n```bash\n./rop.out \"$(python3 -c 'print(\"A\"*56 + \"\\x01\" + \"B\"*15 + \"\\x5a\\x05\\x01\\x00\")')\"\n$?\n\u003e 1\n```\n\nGive that, replacing this character will give us an arbitrary value of the exit function and this means that **we can control the exit system call return value**.\n\n\u003cimg src='img/exit_manipulation_stack.png' width='700'\u003e\n\n---\n\n## Testing the MILK-V Duo S board\nMilk-V Duo S is an upgraded model of Duo, featuring an upgraded **SG2000** main controller with a larger 512MB memory and expanded IO capabilities. It integrates wireless capabilities with WI-FI 6/BT 5, and comes equipped with a USB 2.0 HOST interface and a 100Mbps Ethernet port for user convenience. Supporting dual cameras (2x MIPI CSI 2-lane) and MIPI video output (MIPI DSI 2-lane), it allows for versatile applications. The device also supports switching between RISC-V and ARM boot through a switch. With enhanced functionality, Duo S is better suited for a variety of scenarios with more complex project development requirements.\n\n### eMMC version firmware burning\n[As the documentation says](https://milkv.io/docs/duo/getting-started/duos), the Duo S doesn't have a firmware and It has to be burned. We can do via Windows following these steps\n\n. Install driver\n\n. Download the USB driver installation tool: [CviUsbDownloadInstallDriver.zip](https://github.com/milkv-duo/duo-buildroot-sdk/releases/download/Duo-V1.1.0/CviUsbDownloadInstallDriver.zip). After downloading, unzip and install.\n\n. Download burning tool\n\n. Download the command line burning tool under Windows [CviBurn_v2.0_cli_windows.zip](https://github.com/milkv-duo/duo-buildroot-sdk/releases/download/Duo-V1.1.0/CviBurn_v2.0_cli_windows.zip), unzip it after downloading.\n\n. Download firmware\n\nDownload the latest version of DuoS eMMC firmware, currently [milkv-duos-emmc-v1.1.0-2024-0410.zip](https://github.com/milkv-duo/duo-buildroot-sdk/releases/download/Duo-V1.1.0/milkv-duos-emmc-v1.1.0-2024-0410.zip).\n\nOnce downloaded the cli, inside the CviBurn cli folder we can run\n\n```powershell\n.\\usb_dl.exe -s linux -c cv181x -i .\\extracted\n```\n\nWhere the extracted folder is the extracted firmware.\n\n### Serial connetion using UART\n\nUsing some UART to USB tool we can connect via serial communication to the board. I used [this one](https://www.amazon.it/dp/B07TXVRQ7V?psc=1\u0026ref=ppx_yo2ov_dt_b_product_details) from Amazon.\n\n\u003cimg src='img/uart_usb.jpg' width='500'\u003e\n\nI wired up as the documentation says, with the following pinout\n\n\nGND (pin 6)\t\u003c---\u003e\tBlack wire\n\nTX (pin 8)\t\u003c---\u003e\tWhite wire\n\nRX (pin 10)\t\u003c---\u003e\tGreen wire\n\n\n\u003cimg src='img/duo_uart.jpg' width='350'\u003e \u003cimg src='img/duo_up.jpg' width='600'\u003e\n\nNow using PuTTY or some serial tool as `minicom` we can read the Duo S serial interface. Here we can see the [OpenSBI](https://github.com/riscv-software-src/opensbi) and the boot process of the device.\n\n\u003cimg src='img/bootloader.png' width='500'\u003e\n\n### Flashing Custom Linux (Ubuntu 22.04)\nThe flashed ISO is just a custom Buildroot of Linux, so there is no package manager or other tools, but only stuff like micropython, but we can compile or flash our custom operating system.\n\nTo compile ourselves the system we have to _cross-compile_ It using **qemu**, then replace the rootfs of the system with the one created with in the emulated system and then flash it.\nAnother faster option is using prebuilt ISO images, like [this one](https://drive.google.com/file/d/1y1NQamzUDzot_kVT2yKkbusoJmtvH5tD/view?usp=sharing) of Ubuntu 22.04. This image is specifically for Milk-V Duo 256M, but it has pretty much the same architecture as the Milk-V Duo S, so It can be compatible. Once downloaded the ISO, we can use BalenaEtcher to flash on the SD card the ISO, then the SD card should be inserted in the Board before boot.\n\n\u003cimg src='img/duo_bottom.jpg' width='600'\u003e\n\nWe have to wait for a few seconds and then SSH into the board. We can use the RNDIS USB connection if we have the drivers, but I suggest to SSH into the Board plugging an Ethernet cable and doing it local IP. Now the board is up and running with an Ubuntu 22.04 inside.\n\n\u003cimg src='img/ubuntu-milkv.png' width='600'\u003e\n\nOnce logged in I am configuring it to talk as the default gateway with my firewall and not the USB connection using\n\n```bash\nip route del default via 192.168.42.2 dev usb0\nip route add default via 192.168.2.99 dev eth0\n```\n\nAnd i replace the `/etc/apt/sources.list` with the latest repo available for RISC-V Ubuntu 22.04.\n\n```txt\ndeb http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse\ndeb-src http://ports.ubuntu.com/ubuntu-ports jammy main restricted universe multiverse\n\ndeb http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse\ndeb-src http://ports.ubuntu.com/ubuntu-ports jammy-updates main restricted universe multiverse\n\ndeb http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse\ndeb-src http://ports.ubuntu.com/ubuntu-ports jammy-backports main restricted universe multiverse\n\ndeb http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse\ndeb-src http://ports.ubuntu.com/ubuntu-ports jammy-security main restricted universe multiverse\n\ndeb http://archive.canonical.com/ubuntu jammy partner\ndeb-src http://archive.canonical.com/ubuntu jammy partner\n```\n\nThen the board can reach the Internet and the package manager can download updates and packages.\n\n\u003cimg src='img/duo_update.png' width='600'\u003e\n\nTo install custom distros on other boards like the **Milk-V Duo** or **Milk-V Duo 256M** It can be done the same thing but you have to solder the pins to get to the serial console and get the Ethernet connection.\n\n### Led Blink using GPIO\nUsing General Purpose I/O pins we can generate signals to power on simple things like leds. For the pin N 466 looking at the pinout of the Milk-V we can turn It on providing 3.3V with the following.\n\n```bash\nexport 466 \u003e /sys/class/gpio/export\necho out \u003e /sys/class/gpio/gpio466/direction\necho 1 \u003e /sys/class/gpio/gpio466/value\n```\n\n\u003cimg src='img/blink.jpg' width='600'\u003e\n\nAnd to turn It off\n\n```bash\necho 1 \u003e /sys/class/gpio/gpio466/value\n```\n\nA simple C program that export and writes the value on the GPIO interface is [the following](https://raw.githubusercontent.com/BlessedRebuS/RISCV-Attacks/main/bin/led_blink/led_blink.c?token=GHSAT0AAAAAACNVEGJNJ5ZPFGDBGQWLTFJIZSI334Q). \n\n### Buffer Overflow on the MILK-V\nOn the RISC-V board, after turning off ASLR [the BOF](https://raw.githubusercontent.com/BlessedRebuS/RISCV-ROP-Testbed/main/buffer_overflow/risc_bof.c) is the same as a normal RISC-V architecture. Strangely _it uses the same addressess_ also if the program is compiled on different microarchitecture, like the SiFive chips.\n\n\u003cimg src='/img/milkv-bof.png' width='600'\u003e\n\n### Manipulating LED color on the MILK-V board with Buffer Overflow / Return Oriented Programming\nUsing a little variation of the BOF, we can control the led output of a the board, modifying also the timing behiaviour of the program. Exploiting a buffer overlow in a specific place of the code will overwrite the **RA** and cause a loop on the blink function. As the result we get a permanent blinking **red LED** instead of a single blink of a **green LED**. [Here the is a video for the PoC](https://github.com/BlessedRebuS/RISCV-Attacks/raw/main/img/blink-bof-demo.mov). \nI can think this attack in a scenario of a working mechanical and electric element that has to output his state thourgh LEDs and is now manipulated by the attacker.\n\n\u003ca href=\"https://github.com/BlessedRebuS/RISCV-Attacks/raw/main/img/blink-bof-demo.mov\"\u003e\u003cimg src='img/blink-bof-screen.jpg' width='1200'\u003e\u003c/a\u003e\n\n---\n### Resources\n[Black Hat conference ](https://www.youtube.com/watch?v=HCZ6DCu2ciE) and [RISC-V Attacks POC](https://github.com/cispa/Security-RISC/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblessedrebus%2Friscv-attacks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblessedrebus%2Friscv-attacks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblessedrebus%2Friscv-attacks/lists"}