ronin-post_ex 0.1.0.beta1
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/.document +6 -0
- data/.github/workflows/ruby.yml +31 -0
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.yardopts +1 -0
- data/API_SPEC.md +235 -0
- data/COPYING.txt +165 -0
- data/ChangeLog.md +23 -0
- data/Gemfile +36 -0
- data/README.md +245 -0
- data/Rakefile +34 -0
- data/examples/bind_shell.rb +19 -0
- data/gemspec.yml +25 -0
- data/lib/ronin/post_ex/cli/shell_shell.rb +66 -0
- data/lib/ronin/post_ex/cli/system_shell.rb +811 -0
- data/lib/ronin/post_ex/remote_dir.rb +190 -0
- data/lib/ronin/post_ex/remote_file/stat.rb +174 -0
- data/lib/ronin/post_ex/remote_file.rb +417 -0
- data/lib/ronin/post_ex/remote_process.rb +170 -0
- data/lib/ronin/post_ex/resource.rb +144 -0
- data/lib/ronin/post_ex/sessions/bind_shell.rb +60 -0
- data/lib/ronin/post_ex/sessions/remote_shell_session.rb +48 -0
- data/lib/ronin/post_ex/sessions/reverse_shell.rb +67 -0
- data/lib/ronin/post_ex/sessions/rpc_session.rb +779 -0
- data/lib/ronin/post_ex/sessions/session.rb +73 -0
- data/lib/ronin/post_ex/sessions/shell_session.rb +618 -0
- data/lib/ronin/post_ex/system/fs.rb +650 -0
- data/lib/ronin/post_ex/system/process.rb +422 -0
- data/lib/ronin/post_ex/system/shell.rb +1037 -0
- data/lib/ronin/post_ex/system.rb +191 -0
- data/lib/ronin/post_ex/version.rb +26 -0
- data/lib/ronin/post_ex.rb +22 -0
- data/ronin-post_ex.gemspec +61 -0
- data/spec/sessions/bind_shell_spec.rb +31 -0
- data/spec/sessions/remote_shell_session_spec.rb +28 -0
- data/spec/sessions/reverse_shell_spec.rb +49 -0
- data/spec/sessions/rpc_session_spec.rb +500 -0
- data/spec/sessions/session_spec.rb +61 -0
- data/spec/sessions/shell_session_spec.rb +482 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/system_spec.rb +66 -0
- metadata +155 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# ronin-post_ex - a Ruby API for Post-Exploitation.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2007-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
|
6
|
+
#
|
7
|
+
# ronin-post_ex is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# ronin-post_ex is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with ronin-post_ex. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'ronin/post_ex/resource'
|
22
|
+
require 'ronin/post_ex/system/fs'
|
23
|
+
require 'ronin/post_ex/system/process'
|
24
|
+
require 'ronin/post_ex/system/shell'
|
25
|
+
require 'ronin/post_ex/cli/system_shell'
|
26
|
+
|
27
|
+
module Ronin
|
28
|
+
module PostEx
|
29
|
+
#
|
30
|
+
# Represents a successfully compromised system. The {System} class will
|
31
|
+
# wraps around a session object which defines syscall-like post-exploitation
|
32
|
+
# API for reading/writing files, run commands, etc.
|
33
|
+
#
|
34
|
+
# ## Supported API Functions
|
35
|
+
#
|
36
|
+
# * `sys_time -> Integer`
|
37
|
+
# * `sys_hostname -> String`
|
38
|
+
#
|
39
|
+
# ## Example
|
40
|
+
#
|
41
|
+
# Define the session class which defines the Post-Exploitation API methods:
|
42
|
+
#
|
43
|
+
# require 'base64'
|
44
|
+
#
|
45
|
+
# class SimpleRATSession < Ronin::PostEx::Sessions::Session
|
46
|
+
#
|
47
|
+
# def initialize(socket)
|
48
|
+
# @socket = socket
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# def call(name,*args)
|
52
|
+
# @socket.puts("#{name} #{args.join(' ')}")
|
53
|
+
#
|
54
|
+
# Base64.strict_decode64(@socket.gets(chomp: true)(
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# def shell_exec(command)
|
58
|
+
# call('EXEC',command)
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# def fs_readfile(path)
|
62
|
+
# call('READ',path)
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# def process_pid
|
66
|
+
# call('PID').to_i
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# def process_getuid
|
70
|
+
# call('UID').to_i
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# def process_environ
|
74
|
+
# Hash[
|
75
|
+
# call('ENV').each_line(chomp: true).map { |line|
|
76
|
+
# line.split('=',2)
|
77
|
+
# }
|
78
|
+
# ]
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# Initialize a new {System} object that wraps around the client:
|
84
|
+
#
|
85
|
+
# session = SimpleRATSession.new(socket)
|
86
|
+
# system = Ronin::PostEx::System.new(session)
|
87
|
+
#
|
88
|
+
# Interact with the system's remote files as if they were local files:
|
89
|
+
#
|
90
|
+
# file = system.fs.open('/etc/passwd')
|
91
|
+
# file.each_line do |line|
|
92
|
+
# user, x, uid, gid, name, home_dir, shell = line.split(':')
|
93
|
+
#
|
94
|
+
# puts "User Detected: #{user} (id=#{uid})"
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# Get information about the current process:
|
98
|
+
#
|
99
|
+
# system.process.pid
|
100
|
+
# # => 1234
|
101
|
+
# system.process.getuid
|
102
|
+
# # => 1001
|
103
|
+
# system.process.environ
|
104
|
+
# # => {"HOME"=>"...", "PATH"=>"...", ...}
|
105
|
+
#
|
106
|
+
# Execute commands on the remote system:
|
107
|
+
#
|
108
|
+
# system.shell.ls('/')
|
109
|
+
# # => "bin\nboot\ndev\netc\nhome\nlib\nlib64\nlost+found\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsnap\nsrv\nsys\ntmp\nusr\nvar\n"
|
110
|
+
# system.shell.exec("find -type f -name '*.xls' /srv") do |path|
|
111
|
+
# puts "Found XLS file: #{path}"
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
class System < Resource
|
115
|
+
|
116
|
+
# The File-System resource.
|
117
|
+
#
|
118
|
+
# @return [System::FS]
|
119
|
+
attr_reader :fs
|
120
|
+
|
121
|
+
# The Process resource.
|
122
|
+
#
|
123
|
+
# @return [System::Process]
|
124
|
+
attr_reader :process
|
125
|
+
|
126
|
+
# The Shell resource.
|
127
|
+
#
|
128
|
+
# @return [System::Shell]
|
129
|
+
attr_reader :shell
|
130
|
+
|
131
|
+
#
|
132
|
+
# Initializes the system.
|
133
|
+
#
|
134
|
+
# @param [Object] session
|
135
|
+
# The object which defines the Post-Exploitation API methods.
|
136
|
+
#
|
137
|
+
def initialize(session)
|
138
|
+
super(session)
|
139
|
+
|
140
|
+
@fs = FS.new(session)
|
141
|
+
@process = Process.new(session)
|
142
|
+
@shell = Shell.new(session)
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Gets the current time.
|
147
|
+
#
|
148
|
+
# @return [Time]
|
149
|
+
# The current time.
|
150
|
+
#
|
151
|
+
# @note
|
152
|
+
# Requires the `sys_time` method be defined by the {#session} object.
|
153
|
+
#
|
154
|
+
def time
|
155
|
+
Time.at(@session.sys_time.to_i)
|
156
|
+
end
|
157
|
+
resource_method :time, [:sys_time]
|
158
|
+
|
159
|
+
#
|
160
|
+
# Gets the system's hostname.
|
161
|
+
#
|
162
|
+
# @return [String]
|
163
|
+
# The system's local hostname.
|
164
|
+
#
|
165
|
+
# @note
|
166
|
+
# Requires the `sys_hostname` method be defined by the {#session}
|
167
|
+
# object.
|
168
|
+
#
|
169
|
+
def hostname
|
170
|
+
@session.sys_hostname
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Starts an interactive post-exploitation system shell.
|
175
|
+
#
|
176
|
+
def interact
|
177
|
+
CLI::SystemShell.start(self)
|
178
|
+
end
|
179
|
+
|
180
|
+
#
|
181
|
+
# Exits the process.
|
182
|
+
#
|
183
|
+
# @see Process#exit
|
184
|
+
#
|
185
|
+
def exit
|
186
|
+
@process.exit
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# ronin-post_ex - a Ruby API for Post-Exploitation.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2007-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
|
6
|
+
#
|
7
|
+
# ronin-post_ex is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# ronin-post_ex is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with ronin-post_ex. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
module Ronin
|
22
|
+
module PostEx
|
23
|
+
# ronin-post_ex version
|
24
|
+
VERSION = '0.1.0.beta1'
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# ronin-post_ex - a Ruby API for Post-Exploitation.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2007-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
|
6
|
+
#
|
7
|
+
# ronin-post_ex is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# ronin-post_ex is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with ronin-post_ex. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'ronin/post_ex/system'
|
22
|
+
require 'ronin/post_ex/version'
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gemspec = YAML.load_file('gemspec.yml')
|
7
|
+
|
8
|
+
gem.name = gemspec.fetch('name')
|
9
|
+
gem.version = gemspec.fetch('version') do
|
10
|
+
lib_dir = File.join(File.dirname(__FILE__),'lib')
|
11
|
+
$LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
|
12
|
+
|
13
|
+
require 'ronin/post_ex/version'
|
14
|
+
Ronin::PostEx::VERSION
|
15
|
+
end
|
16
|
+
|
17
|
+
gem.summary = gemspec['summary']
|
18
|
+
gem.description = gemspec['description']
|
19
|
+
gem.licenses = Array(gemspec['license'])
|
20
|
+
gem.authors = Array(gemspec['authors'])
|
21
|
+
gem.email = gemspec['email']
|
22
|
+
gem.homepage = gemspec['homepage']
|
23
|
+
gem.metadata = gemspec['metadata'] if gemspec['metadata']
|
24
|
+
|
25
|
+
glob = lambda { |patterns| gem.files & Dir[*patterns] }
|
26
|
+
|
27
|
+
gem.files = `git ls-files`.split($/)
|
28
|
+
gem.files = glob[gemspec['files']] if gemspec['files']
|
29
|
+
gem.files += Array(gemspec['generated_files'])
|
30
|
+
|
31
|
+
gem.executables = gemspec.fetch('executables') do
|
32
|
+
glob['bin/*'].map { |path| File.basename(path) }
|
33
|
+
end
|
34
|
+
|
35
|
+
gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
|
36
|
+
gem.test_files = glob[gemspec['test_files'] || 'spec/{**/}*_spec.rb']
|
37
|
+
gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
|
38
|
+
|
39
|
+
gem.require_paths = Array(gemspec.fetch('require_paths') {
|
40
|
+
%w[ext lib].select { |dir| File.directory?(dir) }
|
41
|
+
})
|
42
|
+
|
43
|
+
gem.requirements = gemspec['requirements']
|
44
|
+
gem.required_ruby_version = gemspec['required_ruby_version']
|
45
|
+
gem.required_rubygems_version = gemspec['required_rubygems_version']
|
46
|
+
gem.post_install_message = gemspec['post_install_message']
|
47
|
+
|
48
|
+
split = lambda { |string| string.split(/,\s*/) }
|
49
|
+
|
50
|
+
if gemspec['dependencies']
|
51
|
+
gemspec['dependencies'].each do |name,versions|
|
52
|
+
gem.add_dependency(name,split[versions])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if gemspec['development_dependencies']
|
57
|
+
gemspec['development_dependencies'].each do |name,versions|
|
58
|
+
gem.add_development_dependency(name,split[versions])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ronin/post_ex/sessions/bind_shell'
|
3
|
+
|
4
|
+
describe Ronin::PostEx::Sessions::BindShell do
|
5
|
+
it "must inherit from Ronin::PostEx::Sessions::RemoteShellSession" do
|
6
|
+
expect(described_class).to be < Ronin::PostEx::Sessions::RemoteShellSession
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:host) { 'example.com' }
|
10
|
+
let(:port) { 1337 }
|
11
|
+
let(:addrinfo) { Addrinfo.tcp(host,port) }
|
12
|
+
|
13
|
+
describe ".connect" do
|
14
|
+
let(:host) { 'example.com' }
|
15
|
+
let(:port) { 1337 }
|
16
|
+
|
17
|
+
let(:socket) { double('TCPSocket') }
|
18
|
+
|
19
|
+
before { allow(socket).to receive(:remote_address).and_return(addrinfo) }
|
20
|
+
subject { described_class }
|
21
|
+
|
22
|
+
it "must connect to the remote host and port and return a #{described_class} object" do
|
23
|
+
expect(TCPSocket).to receive(:new).with(host,port).and_return(socket)
|
24
|
+
|
25
|
+
bind_shell = subject.connect(host,port)
|
26
|
+
|
27
|
+
expect(bind_shell).to be_kind_of(described_class)
|
28
|
+
expect(bind_shell.io).to be(socket)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ronin/post_ex/sessions/bind_shell'
|
3
|
+
|
4
|
+
describe Ronin::PostEx::Sessions::BindShell do
|
5
|
+
it "must inherit from Ronin::PostEx::Sessions::ShellSession" do
|
6
|
+
expect(described_class).to be < Ronin::PostEx::Sessions::ShellSession
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:host) { 'example.com' }
|
10
|
+
let(:port) { 1337 }
|
11
|
+
let(:addrinfo) { Addrinfo.tcp(host,port) }
|
12
|
+
let(:socket) { double('TCPSocket') }
|
13
|
+
|
14
|
+
before { allow(socket).to receive(:remote_address).and_return(addrinfo) }
|
15
|
+
subject { described_class.new(socket) }
|
16
|
+
|
17
|
+
describe "#initialize" do
|
18
|
+
it "must set #io" do
|
19
|
+
expect(subject.io).to be(socket)
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:ip) { addrinfo.ip_address }
|
23
|
+
|
24
|
+
it "musst set #name to \"ip:port\"" do
|
25
|
+
expect(subject.name).to eq("#{ip}:#{port}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ronin/post_ex/sessions/reverse_shell'
|
3
|
+
|
4
|
+
describe Ronin::PostEx::Sessions::ReverseShell do
|
5
|
+
it "must inherit from Ronin::PostEx::Sessions::RemoteShellSession" do
|
6
|
+
expect(described_class).to be < Ronin::PostEx::Sessions::RemoteShellSession
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:host) { 'localhost' }
|
10
|
+
let(:port) { 1337 }
|
11
|
+
let(:addrinfo) { Addrinfo.tcp(host,port) }
|
12
|
+
|
13
|
+
describe ".listen" do
|
14
|
+
let(:server_socket) { double('TCPServer') }
|
15
|
+
let(:client_socket) { double('TCPSocket') }
|
16
|
+
|
17
|
+
before { allow(client_socket).to receive(:remote_address).and_return(addrinfo) }
|
18
|
+
subject { described_class }
|
19
|
+
|
20
|
+
it "must listen on a local port, accept a connection, return a #{described_class} object, and close the server socket" do
|
21
|
+
expect(TCPServer).to receive(:new).with(port,nil).and_return(server_socket)
|
22
|
+
expect(server_socket).to receive(:listen).with(1)
|
23
|
+
expect(server_socket).to receive(:accept).and_return(client_socket)
|
24
|
+
expect(server_socket).to receive(:close)
|
25
|
+
|
26
|
+
reverse_shell = subject.listen(port)
|
27
|
+
|
28
|
+
expect(reverse_shell).to be_kind_of(described_class)
|
29
|
+
expect(reverse_shell.io).to be(client_socket)
|
30
|
+
end
|
31
|
+
|
32
|
+
context "and a host argument is given" do
|
33
|
+
let(:host) { '127.0.0.1' }
|
34
|
+
|
35
|
+
it "must listen on a local host and port, accept a connection, return a #{described_class} object, and close the server socket" do
|
36
|
+
expect(TCPServer).to receive(:new).with(port,host).and_return(server_socket)
|
37
|
+
expect(server_socket).to receive(:listen).with(1)
|
38
|
+
expect(server_socket).to receive(:accept).and_return(client_socket)
|
39
|
+
expect(server_socket).to receive(:close)
|
40
|
+
allow(client_socket).to receive(:local_address).and_return(addrinfo)
|
41
|
+
|
42
|
+
reverse_shell = subject.listen(host,port)
|
43
|
+
|
44
|
+
expect(reverse_shell).to be_kind_of(described_class)
|
45
|
+
expect(reverse_shell.io).to be(client_socket)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|