ronin-post_ex 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.document +6 -0
  3. data/.github/workflows/ruby.yml +31 -0
  4. data/.gitignore +13 -0
  5. data/.rspec +1 -0
  6. data/.ruby-version +1 -0
  7. data/.yardopts +1 -0
  8. data/API_SPEC.md +235 -0
  9. data/COPYING.txt +165 -0
  10. data/ChangeLog.md +23 -0
  11. data/Gemfile +36 -0
  12. data/README.md +245 -0
  13. data/Rakefile +34 -0
  14. data/examples/bind_shell.rb +19 -0
  15. data/gemspec.yml +25 -0
  16. data/lib/ronin/post_ex/cli/shell_shell.rb +66 -0
  17. data/lib/ronin/post_ex/cli/system_shell.rb +811 -0
  18. data/lib/ronin/post_ex/remote_dir.rb +190 -0
  19. data/lib/ronin/post_ex/remote_file/stat.rb +174 -0
  20. data/lib/ronin/post_ex/remote_file.rb +417 -0
  21. data/lib/ronin/post_ex/remote_process.rb +170 -0
  22. data/lib/ronin/post_ex/resource.rb +144 -0
  23. data/lib/ronin/post_ex/sessions/bind_shell.rb +60 -0
  24. data/lib/ronin/post_ex/sessions/remote_shell_session.rb +48 -0
  25. data/lib/ronin/post_ex/sessions/reverse_shell.rb +67 -0
  26. data/lib/ronin/post_ex/sessions/rpc_session.rb +779 -0
  27. data/lib/ronin/post_ex/sessions/session.rb +73 -0
  28. data/lib/ronin/post_ex/sessions/shell_session.rb +618 -0
  29. data/lib/ronin/post_ex/system/fs.rb +650 -0
  30. data/lib/ronin/post_ex/system/process.rb +422 -0
  31. data/lib/ronin/post_ex/system/shell.rb +1037 -0
  32. data/lib/ronin/post_ex/system.rb +191 -0
  33. data/lib/ronin/post_ex/version.rb +26 -0
  34. data/lib/ronin/post_ex.rb +22 -0
  35. data/ronin-post_ex.gemspec +61 -0
  36. data/spec/sessions/bind_shell_spec.rb +31 -0
  37. data/spec/sessions/remote_shell_session_spec.rb +28 -0
  38. data/spec/sessions/reverse_shell_spec.rb +49 -0
  39. data/spec/sessions/rpc_session_spec.rb +500 -0
  40. data/spec/sessions/session_spec.rb +61 -0
  41. data/spec/sessions/shell_session_spec.rb +482 -0
  42. data/spec/spec_helper.rb +9 -0
  43. data/spec/system_spec.rb +66 -0
  44. 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