edr_gen 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: edd9a39f3423323683aec0f19f20d66fdffbcb706d43a24f8be9552cf0565823
4
+ data.tar.gz: cd191a76e3a64351998a0f5fe9cb40e6a1f7fc5cc416a39744afff0a7e336880
5
+ SHA512:
6
+ metadata.gz: 5e247c902b674882ccc062868c0a21484685e2a1a667ee2da79f94bfa8752235627df9dc37f3cedcc561354fed1befacee9e9609bfcd60ffe771e87ff7205b7c
7
+ data.tar.gz: e551c01726306490e64c3e8228cc4eb1e9a575dd27099afbe5ab84c6d399a18c3dbb3f52f2202894508bcce37827ff32dc135adab07b990ca9c11c4308b98719
data/README.md ADDED
@@ -0,0 +1,15 @@
1
+ ## Configuration
2
+ ### Dependencies
3
+ - Linux or MacOS
4
+ - Ruby 3.3.0 or higher
5
+
6
+ ### Installation
7
+ ```bash
8
+ gem install edr_gen
9
+ ```
10
+
11
+ ## Usage
12
+ To get a list of available commands, run:
13
+ ```bash
14
+ edr_gen help
15
+ ```
data/bin/edr_gen ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path('../config/initializers/edr_gen_initializer', __dir__)
4
+
5
+ EdrGen.run(ARGV)
@@ -0,0 +1,17 @@
1
+ # This is the initializer for the edr_gen gem. It requires all initializers and lib files.
2
+
3
+ # Require all initializers
4
+ current_initializer = File.expand_path(__FILE__)
5
+ Dir[File.join(File.dirname(current_initializer), '**', '*.rb')].each do |file|
6
+ require file unless File.expand_path(file) == current_initializer
7
+ end
8
+
9
+ # Require all lib files
10
+ lib_dir = File.expand_path('../../../lib', __FILE__)
11
+ Dir[File.join(lib_dir, '**', '*.rb')].each { |file| require file; }
12
+
13
+ # Global dependencies
14
+ require 'sys/proctable'
15
+ require 'rainbow'
16
+ require 'yaml'
17
+ require 'httparty'
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'etc'
4
+ require 'sys/proctable'
5
+ require_relative './../mixins/yaml_logger'
6
+
7
+ # Base class for EDR Generator
8
+ class EdrGenBase
9
+ include YamlLogger
10
+ include Sys
11
+
12
+ class EdrGenMissingDependencyError < StandardError; end
13
+
14
+ def initialize(args)
15
+ raise EdrGenMissingDependencyError, "ACTIVITY constant must be defined in the child class" unless defined? self.class::ACTIVITY
16
+
17
+ @logger = YamlLogger
18
+ @pid = nil
19
+ @process_info = {}
20
+ @path = args[0]
21
+ @args = args[1..-1]
22
+ end
23
+
24
+ private
25
+
26
+ attr_accessor :logger, :pid, :process_info, :path, :args
27
+
28
+ def execute_process
29
+ begin
30
+ @pid = Process.spawn(path, *args)
31
+
32
+ return puts Rainbow(" Process not found").color(:red) unless pid
33
+
34
+ @process_info = ProcTable.ps(pid:)
35
+
36
+ Process.detach(pid)
37
+ rescue Errno::ENOENT => e
38
+ puts Rainbow(" Failed to execute command: #{e.message}").color(:red)
39
+ end
40
+
41
+ write_log_entry
42
+ end
43
+
44
+ def common_log_data
45
+ {
46
+ timestamp: process_start_time, # Process start time
47
+ username: ENV['USER'] || ENV['USERNAME'] || 'Unknown', # User who started the process
48
+ process_name: process_info&.comm, # Process name
49
+ command_line: process_info&.cmdline, # Command line
50
+ process_id: process_info&.pid, # Process ID
51
+ }
52
+ end
53
+
54
+ def process_start_time
55
+ if process_info.respond_to?(:start_tvsec)
56
+ # macOS
57
+ seconds = process_info.start_tvsec
58
+ microseconds = process_info.start_tvusec || 0
59
+ Time.at(seconds, microseconds)
60
+ elsif process_info.respond_to?(:starttime)
61
+ clock_ticks_per_second = Etc.sysconf(Etc::SC_CLK_TCK)
62
+ start_time_in_seconds = process_info.starttime.to_f / clock_ticks_per_second
63
+
64
+ uptime_seconds = File.read('/proc/uptime').split[0].to_f
65
+
66
+ boot_time = Time.now - uptime_seconds
67
+ process_start_time = boot_time + start_time_in_seconds
68
+
69
+ Time.at(process_start_time)
70
+ else
71
+ 'Unknown'
72
+ end
73
+ end
74
+
75
+ def write_log_entry
76
+ if process_info
77
+ logger.write(activity: self.class::ACTIVITY, data: log_data)
78
+ else
79
+ puts Rainbow(" Process not found, no logs have been generated").color(:red)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateFile < EdrGenBase
4
+ ACTIVITY = "file_changes"
5
+
6
+ def initialize(args)
7
+ @args = args
8
+
9
+ validate_args
10
+ set_command
11
+
12
+ super(@args.slice(0..1))
13
+ end
14
+
15
+ def call
16
+ puts " Attempting to create file..."
17
+ execute_process
18
+ puts " File created: #{@args[0]}"
19
+ end
20
+
21
+ private
22
+
23
+ def validate_args
24
+ abort Rainbow(" No filename provided. Please provide a filename.").color(:red) if @args[0].nil?
25
+ end
26
+
27
+ def set_command
28
+ @args.unshift("touch")
29
+ end
30
+
31
+ def log_data
32
+ common_log_data.merge({
33
+ filepath: File.expand_path(@args[0]),
34
+ activity: "created",
35
+ })
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DeleteFile < EdrGenBase
4
+ ACTIVITY = "file_changes"
5
+
6
+ def initialize(args)
7
+ @filepath = args[0]
8
+ command = ["rm #{@filepath}"]
9
+
10
+ validate_args
11
+
12
+ super(command)
13
+ end
14
+
15
+ def call
16
+ puts " Attempting to delete file..."
17
+ execute_process
18
+ puts " File removed: #{@filepath}"
19
+ end
20
+
21
+ private
22
+
23
+ def validate_args
24
+ abort Rainbow(" No filename provided. Please provide a filename.").color(:red) if @filepath.nil?
25
+ abort Rainbow(" File does not exist. Please provide a valid filename.").color(:red) unless File.exist?(@filepath)
26
+ end
27
+
28
+ def log_data
29
+ common_log_data.merge({
30
+ filepath: File.expand_path(@filepath),
31
+ activity: "deleted",
32
+ })
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This class is used to run executables that are not part of this program.
4
+ class ForeignExecutable < EdrGenBase
5
+ ACTIVITY = "process_start"
6
+
7
+ def initialize(args)
8
+ super(args)
9
+ end
10
+
11
+ def call
12
+ puts " Executing \"#{path}\" with options: #{args} ..."
13
+ execute_process
14
+ puts " Process started with PID: #{pid}"
15
+ end
16
+
17
+ private
18
+
19
+ def log_data
20
+ common_log_data
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ModifyFile < EdrGenBase
4
+ ACTIVITY = "file_changes"
5
+
6
+ def initialize(args)
7
+ @filepath = args[0]
8
+ @content = args[1..-1].join(" ")
9
+ command = ["echo #{new_content} >> #{filepath}"]
10
+
11
+ validate_args
12
+
13
+ super(command)
14
+ end
15
+
16
+ def call
17
+ puts " Attempting to modify file..."
18
+ execute_process
19
+ puts " File modified: #{filepath}"
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :filepath
25
+ attr_accessor :content
26
+
27
+ def new_content
28
+ content.empty? ? "default content" : content
29
+ end
30
+
31
+ def validate_args
32
+ abort Rainbow(" No filename provided. Please provide a filename.").color(:red) if @filepath.nil?
33
+ abort Rainbow(" File does not exist. Please provide a valid filename.").color(:red) unless File.exist?(@filepath)
34
+ end
35
+
36
+ def log_data
37
+ common_log_data.merge({
38
+ filepath: File.expand_path(filepath),
39
+ activity: "modified",
40
+ })
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'socket'
5
+
6
+ # Used to transmit data to an open remote server via a TCP socket connection.
7
+ class TcpTransmit < EdrGenBase
8
+ ACTIVITY = 'transmit'
9
+ TIMEOUT = 5
10
+
11
+ def initialize(args)
12
+ super(args)
13
+
14
+ @server_host = args[0] || 'tcpbin.com'
15
+ @server_port = args[1] || 4242
16
+ @data_to_send = args[2..-1]&.join(' ') || 'placeholder data'
17
+ @process_info = ProcTable.ps(pid: Process.pid)
18
+ end
19
+
20
+ def call
21
+ puts " Initiating TCP connection..."
22
+ connect_and_transmit
23
+ write_log_entry
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :server_host, :server_port, :data_to_send, :process_info, :bytes_sent, :source, :destination
29
+
30
+ def connect_and_transmit
31
+ Socket.tcp(server_host, server_port, connect_timeout: TIMEOUT) do |socket|
32
+ @source = "#{socket.local_address.ip_address}:#{socket.local_address.ip_port}"
33
+ @destination = "#{socket.remote_address.ip_address}:#{socket.remote_address.ip_port}"
34
+
35
+ @bytes_sent = socket.write(data_to_send)
36
+ socket.puts
37
+ response = socket.gets
38
+
39
+ puts Rainbow(" Response: #{response}").green
40
+ end
41
+ rescue StandardError => e
42
+ abort(Rainbow(" An error occurred: #{e.class}: #{e.message}").red)
43
+ end
44
+
45
+ def log_data
46
+ common_log_data.merge(request_data)
47
+ end
48
+
49
+ def request_data
50
+ { destination:, source:, data_size: "#{bytes_sent} bytes", protocol: "TCP" }
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EDRGen
4
+ VERSION = '0.0.4'
5
+ end
data/lib/edr_gen.rb ADDED
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EdrGen
4
+ HELP = 'help'
5
+ EXEC = 'exec'
6
+ CREATE = 'create'
7
+ DELETE = 'delete'
8
+ MODIFY = 'modify'
9
+ TRANSMIT = 'transmit'
10
+
11
+ def self.run(args)
12
+ new(args).run
13
+ end
14
+
15
+ def initialize(args)
16
+ @command = args[0]
17
+ @command_arguments = args[1..-1]
18
+ end
19
+
20
+ def run
21
+ case command
22
+ when HELP
23
+ help_message
24
+ when EXEC
25
+ ForeignExecutable.new(command_arguments).call
26
+ when CREATE
27
+ CreateFile.new(command_arguments).call
28
+ when MODIFY
29
+ ModifyFile.new(command_arguments).call
30
+ when DELETE
31
+ DeleteFile.new(command_arguments).call
32
+ when TRANSMIT
33
+ TcpTransmit.new(command_arguments).call
34
+ else
35
+ puts "Invalid command. For a list of valid commands, run #{Rainbow("edr_gen help").color(:yellow)}"
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :command, :command_arguments
42
+
43
+ def help_message
44
+ puts Rainbow(
45
+ <<-HEREDOC
46
+ Usage: edr_gen [command] [options]
47
+
48
+ #{HELP} Displays this help message.
49
+ #{EXEC} <path> [options] Runs a foreign executable file with optional arguments.
50
+ #{CREATE} <path> Creates a new file.
51
+ #{MODIFY} <path> <content> Appends content to a file.
52
+ #{DELETE} <path> Deletes a file.
53
+ #{TRANSMIT} <url> <port> <data> Initiates a simple TCP socket connection and transmits data.
54
+ Defaults to tcpbin.com:4242 with placeholder data.
55
+ HEREDOC
56
+ ).color(:yellow)
57
+ end
58
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'fileutils'
5
+
6
+ module YamlLogger
7
+ LOG_DIRECTORY = 'logs'
8
+
9
+ def self.write(activity:, data:)
10
+ ensure_log_directory_exists
11
+
12
+ begin
13
+ File.open(file_path, "a+") do |file|
14
+ file.puts formatted_entry(activity:, data:).to_yaml
15
+ puts " Log entry added to #{File.expand_path(file)}"
16
+ end
17
+
18
+ rescue StandardError => e
19
+ puts Rainbow(" Failed to write to log file: #{e.message} #{e.backtrace}").color(:red)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def self.ensure_log_directory_exists
26
+ FileUtils.mkdir_p(LOG_DIRECTORY) unless Dir.exist?(LOG_DIRECTORY)
27
+ end
28
+
29
+ def self.file_path
30
+ File.join(LOG_DIRECTORY, "#{today}.yml")
31
+ end
32
+
33
+ def self.formatted_entry(activity:, data:)
34
+ {
35
+ self.timestamp => {
36
+ activity => data.transform_keys(&:to_s)
37
+ }
38
+ }
39
+ end
40
+
41
+ def self.timestamp
42
+ Time.now.strftime("%Y-%m-%d:%H:%M:%S")
43
+ end
44
+
45
+ def self.today
46
+ Time.now.strftime("%Y_%m_%d")
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: edr_gen
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Jason Loutensock
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-09-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sys-proctable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rainbow
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yaml
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: httparty
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.19'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.19'
69
+ description:
70
+ email:
71
+ executables:
72
+ - edr_gen
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - README.md
77
+ - bin/edr_gen
78
+ - config/initializers/edr_gen_initializer.rb
79
+ - lib/edr_gen.rb
80
+ - lib/edr_gen/base.rb
81
+ - lib/edr_gen/create_file.rb
82
+ - lib/edr_gen/delete_file.rb
83
+ - lib/edr_gen/foreign_executable.rb
84
+ - lib/edr_gen/modify_file.rb
85
+ - lib/edr_gen/tcp_transmit.rb
86
+ - lib/edr_gen/version.rb
87
+ - lib/mixins/yaml_logger.rb
88
+ homepage: https://github.com/jasonguyperson/edr_gen
89
+ licenses:
90
+ -
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 3.3.0
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubygems_version: 3.5.18
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: A testing and mixins tool for EDRs
111
+ test_files: []