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 +7 -0
- data/README.md +15 -0
- data/bin/edr_gen +5 -0
- data/config/initializers/edr_gen_initializer.rb +17 -0
- data/lib/edr_gen/base.rb +82 -0
- data/lib/edr_gen/create_file.rb +37 -0
- data/lib/edr_gen/delete_file.rb +34 -0
- data/lib/edr_gen/foreign_executable.rb +22 -0
- data/lib/edr_gen/modify_file.rb +42 -0
- data/lib/edr_gen/tcp_transmit.rb +52 -0
- data/lib/edr_gen/version.rb +5 -0
- data/lib/edr_gen.rb +58 -0
- data/lib/mixins/yaml_logger.rb +48 -0
- metadata +111 -0
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
data/bin/edr_gen
ADDED
@@ -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'
|
data/lib/edr_gen/base.rb
ADDED
@@ -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
|
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: []
|