# frozen_string_literal: true
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Encoder::Xor
Rank = NormalRanking
def initialize
super(
'Name' => 'XOR Encoder with Cipher Feedback',
'Description' => %q{
Dword XOR encoder with cipher feedback for RISC-V 64-bit
(Little Endian). Each dword is XORed with the previous encoded
dword rather than a static key, creating a chained dependency
that makes the output more resistant to frequency analysis. The
first dword is XORed with the key to bootstrap the chain.
},
'Author' => ['bcoles'],
'Arch' => ARCH_RISCV64LE,
'License' => MSF_LICENSE,
'Decoder' => {
'KeySize' => 4,
'BlockSize' => 4,
'KeyPack' => 'V'
}
)
end
#
# Returns the decoder stub that is adjusted for the size of
# the buffer being encoded.
#
def decoder_stub(state)
if state.badchars.to_s.include?("\x00".b)
raise EncodingError, 'The RISC-V decoder stub inherently contains null bytes (auipc, ecall)'
end
raise EncodingError, 'The payload is empty' if state.buf.empty?
block_count = state.buf.length / 4
raise EncodingError, "The payload being encoded is too long (#{state.buf.length} bytes)" if block_count > 2047
raise EncodingError, "The payload is not aligned to 4 bytes (#{state.buf.length} bytes)" if (state.buf.length % 4) != 0
# Decoder stub layout (76 bytes = 18 instructions + 4-byte key):
#
# 0x00: auipc t0, 0 # t0 = address of this instruction
# 0x04: addi t4, t0, 76 # t4 = start of encoded payload
# 0x08: lw t1, 72(t0) # t1 = feedback value (initially the key)
# 0x0c: addi t2, x0, count # t2 = number of dwords to decode
# 0x10: addi t0, t4, 0 # t0 = working pointer
# 0x14: lw t5, 0(t0) # load encoded dword into t5
# 0x18: xor t3, t5, t1 # decoded = encoded XOR feedback
# 0x1c: sw t3, 0(t0) # store decoded dword
# 0x20: addi t1, t5, 0 # feedback = previous encoded dword
# 0x24: addi t0, t0, 4 # advance pointer
# 0x28: addi t2, t2, -1 # decrement counter
# 0x2c: bne t2, x0, -0x18 # loop to 0x14
# 0x30: addi a0, t4, 0 # cache flush start address
# 0x34: addi a1, t0, 0 # cache flush end address
# 0x38: addi a2, x0, 0 # flags (0 = all harts)
# 0x3c: addi a7, x0, 259 # __NR_riscv_flush_icache
# 0x40: ecall # flush icache
# 0x44: jalr x0, t4, 0 # jump to decoded payload
# 0x48: <4-byte XOR key>
#
decoder = [
0x00000297, # auipc t0, 0
encode_addi(29, 5, 76), # addi t4, t0, 76
encode_lw(6, 5, 72), # lw t1, 72(t0)
encode_addi(7, 0, block_count), # addi t2, x0, count
encode_addi(5, 29, 0), # addi t0, t4, 0
encode_lw(30, 5, 0), # lw t5, 0(t0)
encode_xor(28, 30, 6), # xor t3, t5, t1
encode_sw(28, 5, 0), # sw t3, 0(t0)
encode_addi(6, 30, 0), # addi t1, t5, 0
encode_addi(5, 5, 4), # addi t0, t0, 4
encode_addi(7, 7, -1), # addi t2, t2, -1
encode_bne(7, 0, -24), # bne t2, x0, loop
encode_addi(10, 29, 0), # addi a0, t4, 0
encode_addi(11, 5, 0), # addi a1, t0, 0
encode_addi(12, 0, 0), # addi a2, x0, 0
encode_addi(17, 0, 259), # addi a7, x0, 259
0x00000073, # ecall
encode_jalr(0, 29, 0), # jalr x0, t4, 0
].pack('V*')
state.decoder_key_offset = decoder.length
decoder + "\x00\x00\x00\x00".b
end
#
# Initialize the feedback value prior to encoding.
#
def encode_begin(state)
@feedback = state.key.to_i
end
#
# Encode a block using XOR with cipher feedback. Each encoded
# dword becomes the XOR operand for the next block.
#
def encode_block(_state, block)
encoded_val = block.unpack1('V') ^ @feedback
@feedback = encoded_val
[encoded_val].pack('V')
end
#
# Verify that a candidate key remains valid once cipher feedback
# encoding has been applied across the full buffer.
#
def find_key_verify(buf, key_bytes, badchars)
return false unless super
return true if badchars.to_s.empty?
feedback = key_bytes_to_integer(key_bytes)
buf.bytes.each_slice(4) do |bytes|
block = bytes.pack('C*').ljust(4, "\x00".b)
encoded_val = block.unpack1('V') ^ feedback
return false unless has_badchars?([encoded_val].pack('V'), badchars).nil?
feedback = encoded_val
end
true
end
private
# I-type: ADDI rd, rs1, imm12
def encode_addi(rd, rs1, imm12)
((imm12 & 0xfff) << 20) | (rs1 << 15) | (rd << 7) | 0b0010011
end
# I-type: LW rd, imm12(rs1)
def encode_lw(rd, rs1, imm12)
((imm12 & 0xfff) << 20) | (rs1 << 15) | (0b010 << 12) | (rd << 7) | 0b0000011
end
# S-type: SW rs2, imm12(rs1)
def encode_sw(rs2, rs1, imm12)
imm = imm12 & 0xfff
(((imm >> 5) & 0x7f) << 25) | (rs2 << 20) | (rs1 << 15) | (0b010 << 12) | ((imm & 0x1f) << 7) | 0b0100011
end
# R-type: XOR rd, rs1, rs2
def encode_xor(rd, rs1, rs2)
(rs2 << 20) | (rs1 << 15) | (0b100 << 12) | (rd << 7) | 0b0110011
end
# B-type: BNE rs1, rs2, offset
def encode_bne(rs1, rs2, offset)
imm = offset & 0x1fff
bit12 = (imm >> 12) & 1
bit11 = (imm >> 11) & 1
hi6 = (imm >> 5) & 0x3f
lo4 = (imm >> 1) & 0xf
(bit12 << 31) | (hi6 << 25) | (rs2 << 20) | (rs1 << 15) |
(0b001 << 12) | (lo4 << 8) | (bit11 << 7) | 0b1100011
end
# I-type: JALR rd, rs1, imm12
def encode_jalr(rd, rs1, imm12)
((imm12 & 0xfff) << 20) | (rs1 << 15) | (rd << 7) | 0b1100111
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation