Skip to content

Commit

Permalink
more specs for linux post libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
h00die committed Dec 2, 2024
1 parent 61705db commit cde6600
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 109 deletions.
217 changes: 111 additions & 106 deletions lib/msf/core/post/linux/busy_box.rb
Original file line number Diff line number Diff line change
@@ -1,111 +1,116 @@
# -*- coding: binary -*-


module Msf
class Post
module Linux
module BusyBox

include ::Msf::Post::Common
include ::Msf::Post::File

# Checks if the file exists in the target
#
# @param file_path [String] the target file path
# @return [Boolean] true if files exists, false otherwise
# @note Msf::Post::File#file? doesnt work because test -f is not available in busybox
def busy_box_file_exist?(file_path)
contents = read_file(file_path)
if contents.nil? || contents.empty?
return false
end

true
end

# Checks if the directory is writable in the target
#
# @param dir_path [String] the target directory path
# @return [Boolean] true if target directory is writable, false otherwise
def busy_box_is_writable_dir?(dir_path)
res = false
rand_str = Rex::Text.rand_text_alpha(16)
file_path = "#{dir_path}/#{rand_str}"

cmd_exec("echo #{rand_str}XXX#{rand_str} > #{file_path}")
Rex::sleep(0.3)
rcv = read_file(file_path)

if rcv.include?("#{rand_str}XXX#{rand_str}")
res = true
end

cmd_exec("rm -f #{file_path}")
Rex::sleep(0.3)

res
end

# Checks some directories that usually are writable in devices running busybox
#
# @return [String] If the function finds a writable directory, it returns the path. Else it returns nil
#
def busy_box_writable_dir
dirs = %w(/etc/ /mnt/ /var/ /var/tmp/)

dirs.each do |d|
return d if busy_box_is_writable_dir?(d)
end

nil
end


# Writes data to a file
#
# @param file_path [String] the file path to write on the target
# @param data [String] the content to be written
# @param prepend [Boolean] if true, prepend the data to the target file. Otherwise, overwrite
# the target file
# @return [Boolean] true if target file is writable and it was written. Otherwise, false.
# @note BusyBox commands are limited and Msf::Post::File#write_file doesn't work here, because
# of it is necessary to implement an specific method.
def busy_box_write_file(file_path, data, prepend = false)
if prepend
dir = busy_box_writable_dir
return false unless dir
cmd_exec("cp -f #{file_path} #{dir}tmp")
Rex::sleep(0.3)
class Post
module Linux
module BusyBox
include ::Msf::Post::Common
include ::Msf::Post::File

#
# Checks if the file exists in the target
#
# @param file_path [String] the target file path
# @return [Boolean] true if files exists, false otherwise
# @note Msf::Post::File#file? doesnt work because test -f is not available in busybox
#
def busy_box_file_exist?(file_path)
contents = read_file(file_path)
if contents.nil? || contents.empty?
return false
end

true
end

#
# Checks if the directory is writable in the target
#
# @param dir_path [String] the target directory path
# @return [Boolean] true if target directory is writable, false otherwise
#
def busy_box_is_writable_dir?(dir_path)
res = false
rand_str = Rex::Text.rand_text_alpha(16)
file_path = "#{dir_path}/#{rand_str}"

cmd_exec("echo #{rand_str}XXX#{rand_str} > #{file_path}")
Rex.sleep(0.3)
rcv = read_file(file_path)

if rcv.include?("#{rand_str}XXX#{rand_str}")
res = true
end

cmd_exec("rm -f #{file_path}")
Rex.sleep(0.3)

res
end

#
# Checks some directories that usually are writable in devices running busybox
#
# @return [String] If the function finds a writable directory, it returns the path. Else it returns nil
#
def busy_box_writable_dir
dirs = %w[/etc/ /mnt/ /var/ /var/tmp/]

dirs.each do |d|
return d if busy_box_is_writable_dir?(d)
end

nil
end

#
# Writes data to a file
#
# @param file_path [String] the file path to write on the target
# @param data [String] the content to be written
# @param prepend [Boolean] if true, prepend the data to the target file. Otherwise, overwrite
# the target file
# @return [Boolean] true if target file is writable and it was written. Otherwise, false.
# @note BusyBox commands are limited and Msf::Post::File#write_file doesn't work here, because
# of it is necessary to implement an specific method.
#
def busy_box_write_file(file_path, data, prepend = false)
if prepend
dir = busy_box_writable_dir
return false unless dir

cmd_exec("cp -f #{file_path} #{dir}tmp")
Rex.sleep(0.3)
end

rand_str = Rex::Text.rand_text_alpha(16)
cmd_exec("echo #{rand_str} > #{file_path}")
Rex.sleep(0.3)

unless read_file(file_path).include?(rand_str)
return false
end

cmd_exec("echo \"\"> #{file_path}")
Rex.sleep(0.3)

lines = data.lines.map(&:chomp)
lines.each do |line|
cmd_exec("echo #{line.chomp} >> #{file_path}")
Rex.sleep(0.3)
end

if prepend
cmd_exec("cat #{dir}tmp >> #{file_path}")
Rex.sleep(0.3)

cmd_exec("rm -f #{dir}tmp")
Rex.sleep(0.3)
end

true
end
end
end

rand_str = Rex::Text.rand_text_alpha(16)
cmd_exec("echo #{rand_str} > #{file_path}")
Rex::sleep(0.3)

unless read_file(file_path).include?(rand_str)
return false
end

cmd_exec("echo \"\"> #{file_path}")
Rex::sleep(0.3)

lines = data.lines.map(&:chomp)
lines.each do |line|
cmd_exec("echo #{line.chomp} >> #{file_path}")
Rex::sleep(0.3)
end

if prepend
cmd_exec("cat #{dir}tmp >> #{file_path}")
Rex::sleep(0.3)

cmd_exec("rm -f #{dir}tmp")
Rex::sleep(0.3)
end

true
end
end # Busybox
end # Linux
end # Post
end # Msf
end
8 changes: 5 additions & 3 deletions lib/msf/core/post/linux/compile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Post
module Linux
module Compile
include ::Msf::Post::Common
include ::Msf::Post::Linux::System
include ::Msf::Post::File
include ::Msf::Post::Unix

Expand Down Expand Up @@ -62,14 +63,15 @@ def live_compile?
# @raise [Module::Failure::BadConfig] If compilation fails or no compiler is found.
#
def upload_and_compile(path, data, compiler_args = '')
write_file "#{path}.c", strip_comments(data)

compiler = datastore['COMPILER']
if datastore['COMPILER'] == 'Auto'
compiler = get_compiler
fail_with(Module::Failure::BadConfig, 'Unable to find a compiler on the remote target.') unless compiler.present?
fail_with(Module::Failure::BadConfig, 'Unable to find a compiler on the remote target.') if compiler.nil?
end

# only upload the file if a compiler exists
write_file "#{path}.c", strip_comments(data)

compiler_cmd = "#{compiler} -o '#{path}' '#{path}.c'"
if session.type == 'shell'
compiler_cmd = "PATH=\"$PATH:/usr/bin/\" #{compiler_cmd}"
Expand Down
121 changes: 121 additions & 0 deletions spec/lib/msf/core/post/linux/compile_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
require 'spec_helper'

RSpec.describe Msf::Post::Linux::Compile do
subject do
mod = Msf::Module.new
mod.extend(Msf::Post::Linux::Compile)
mod
end

describe '#get_compiler' do
context 'when gcc is available' do
it 'returns gcc' do
allow(subject).to receive(:has_gcc?).and_return(true)
expect(subject.get_compiler).to eq('gcc')
end
end

context 'when clang is available' do
it 'returns clang' do
allow(subject).to receive(:has_gcc?).and_return(false)
allow(subject).to receive(:has_clang?).and_return(true)
expect(subject.get_compiler).to eq('clang')
end
end

context 'when no compiler is available' do
it 'returns nil' do
allow(subject).to receive(:has_gcc?).and_return(false)
allow(subject).to receive(:has_clang?).and_return(false)
expect(subject.get_compiler).to be_nil
end
end

describe '#live_compile?' do
context 'when COMPILE is not Auto or True' do
it 'returns false' do
allow(subject).to receive(:datastore).and_return({ 'COMPILE' => 'False' })
expect(subject.live_compile?).to be false
end
end

context 'when COMPILE is Auto or True' do
it 'returns true if gcc is specified and available' do
allow(subject).to receive(:datastore).and_return({ 'COMPILE' => 'Auto', 'COMPILER' => 'gcc' })
allow(subject).to receive(:has_gcc?).and_return(true)
expect(subject.live_compile?).to be true
end

it 'returns true if clang is specified and available' do
allow(subject).to receive(:datastore).and_return({ 'COMPILE' => 'Auto', 'COMPILER' => 'clang' })
allow(subject).to receive(:has_clang?).and_return(true)
expect(subject.live_compile?).to be true
end

it 'returns true if Auto is specified and a compiler is available' do
allow(subject).to receive(:datastore).and_return({ 'COMPILE' => 'Auto', 'COMPILER' => 'Auto' })
allow(subject).to receive(:get_compiler).and_return('gcc')
expect(subject.live_compile?).to be true
end

it 'raises an error if the specified compiler is not available' do
allow(subject).to receive(:datastore).and_return({ 'COMPILE' => 'True', 'COMPILER' => 'gcc' })
allow(subject).to receive(:has_gcc?).and_return(false)
expect { subject.live_compile? }.to raise_error(Msf::Module::Failure::BadConfig, 'gcc is not installed. Set COMPILE False to upload a pre-compiled executable')
end
end

describe '#upload_and_compile' do
let(:source) { '/path/to/source.c' }
let(:destination) { '/tmp/source.c' }
let(:output) { '/tmp/output' }
let(:session) { double('session') }

before do
allow(subject).to receive(:get_compiler).and_return('gcc')
allow(subject).to receive(:session).and_return(session)
end

it 'uploads the source file and compiles it' do
expect(subject).to receive(:upload_file).with(destination, source)
expect(subject).to receive(:cmd_exec).with("gcc #{destination} -o #{output}")
expect(subject).to receive(:write_file).and_return('/tmp/foo')
allow(session).to receive(:type).and_return('meterpreter')

subject.upload_and_compile(source, destination, output)
end

it 'raises an error if no compiler is available' do
allow(subject).to receive(:get_compiler).and_return(nil)

expect { subject.upload_and_compile(source, destination, output) }.to raise_error('No compiler available on target')
end
end

describe '#strip_comments' do
it 'removes comments from the source code' do
source_code = <<-CODE
// This is a single line comment
int main() {
/* This is a
multi-line comment */
printf("Hello, world!");
return 0;
}
CODE

expected_output = <<-CODE
int main() {
#{' '}
printf("Hello, world!");
return 0;
}
CODE

expect(subject.strip_comments(source_code)).to eq(expected_output)
end
end
end
end
end
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit cde6600

Please sign in to comment.