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,73 @@
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
+
23
+ module Ronin
24
+ module PostEx
25
+ module Sessions
26
+ #
27
+ # Base class for all post-exploitation session classes.
28
+ #
29
+ class Session
30
+
31
+ #
32
+ # The session name.
33
+ #
34
+ # @return [String]
35
+ #
36
+ # @raise [NotImplementedError]
37
+ # The session class did not set `@name`.
38
+ #
39
+ def name
40
+ @name || raise(NotImplementedError,"#{self.class}#name was not set")
41
+ end
42
+
43
+ #
44
+ # The remote system connected to the session.
45
+ #
46
+ # @return [System]
47
+ #
48
+ def system
49
+ @system ||= System.new(self)
50
+ end
51
+
52
+ #
53
+ # Closes the session.
54
+ #
55
+ # @abstract
56
+ #
57
+ def close
58
+ end
59
+
60
+ #
61
+ # Converts the session to a String.
62
+ #
63
+ # @return [String]
64
+ # The session's {#name}.
65
+ #
66
+ def to_s
67
+ name
68
+ end
69
+
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,618 @@
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/sessions/session'
22
+
23
+ require 'shellwords'
24
+ require 'base64'
25
+
26
+ module Ronin
27
+ module PostEx
28
+ module Sessions
29
+ #
30
+ # Base class for all interactive shell based post-exploitation sessions.
31
+ #
32
+ # ## Features
33
+ #
34
+ # * Supports Bash, Zsh, and all POSIX shells.
35
+ # * Emulates most of the post-exploitation API via shell commands.
36
+ #
37
+ class ShellSession < Session
38
+
39
+ # The IO object used to communicate with the shell.
40
+ #
41
+ # @return [Socket, IO]
42
+ #
43
+ # @api private
44
+ attr_reader :io
45
+
46
+ #
47
+ # Initializes the shell session.
48
+ #
49
+ # @param [Socet, IO] io
50
+ # The IO object used to communicate with the shell.
51
+ #
52
+ def initialize(io)
53
+ @io = io
54
+ end
55
+
56
+ #
57
+ # @group Shell Methods
58
+ #
59
+
60
+ #
61
+ # Writes a line to the shell.
62
+ #
63
+ # @param [String] line
64
+ # The line to write.
65
+ #
66
+ # @api private
67
+ #
68
+ def shell_puts(line)
69
+ @io.write("#{line}\n")
70
+ end
71
+
72
+ #
73
+ # Reads a line from the shell.
74
+ #
75
+ # @return [String, nil]
76
+ #
77
+ # @api private
78
+ #
79
+ def shell_gets
80
+ @io.gets
81
+ end
82
+
83
+ # Deliminator line to indicate the beginning and end of output
84
+ DELIMINATOR = '---'
85
+
86
+ #
87
+ # Executes a shell command and returns it's output.
88
+ #
89
+ # @param [String] command
90
+ # The shell command to execute.
91
+ #
92
+ # @return [String]
93
+ # The output of the shell command.
94
+ #
95
+ def shell_exec(command)
96
+ shell_puts("echo #{DELIMINATOR}; #{command} 2>/dev/null | base64; echo #{DELIMINATOR}")
97
+
98
+ # consume any leading output before the command output
99
+ while (line = shell_gets)
100
+ if line.chomp == DELIMINATOR
101
+ break
102
+ end
103
+ end
104
+
105
+ output = String.new
106
+
107
+ while (line = shell_gets)
108
+ if line.chomp == DELIMINATOR
109
+ break
110
+ end
111
+
112
+ output << line
113
+ end
114
+
115
+ return Base64.decode64(output)
116
+ end
117
+
118
+ #
119
+ # Joins a command with arguments into a single command String.
120
+ #
121
+ # @param [String] command
122
+ # The command name to execute.
123
+ #
124
+ # @param [Array<String>] arguments
125
+ # Additional arguments for the command.
126
+ #
127
+ # @return [String]
128
+ # The command string.
129
+ #
130
+ # @api private
131
+ #
132
+ def command_join(command,*arguments)
133
+ Shellwords.join([command,*arguments])
134
+ end
135
+
136
+ #
137
+ # Invokes a specific command with arguments.
138
+ #
139
+ # @param [String] command
140
+ # The command name to execute.
141
+ #
142
+ # @param [Array<String>] arguments
143
+ # Additional arguments for the command.
144
+ #
145
+ # @return [String, nil]
146
+ # The command's output or `nil` if there was no output.
147
+ #
148
+ # @api private
149
+ #
150
+ def command_exec(command,*arguments)
151
+ output = shell_exec(command_join(command,*arguments))
152
+
153
+ if output.empty? then nil
154
+ else output
155
+ end
156
+ end
157
+
158
+ #
159
+ # @group System Methods
160
+ #
161
+
162
+ #
163
+ # Gets the current time and returns the UNIX timestamp.
164
+ #
165
+ # @return [Integer]
166
+ # The current time as a UNIX timestamp.
167
+ #
168
+ # @note executes the `date +%s` command.
169
+ #
170
+ def sys_time
171
+ shell_exec('date +%s').to_i
172
+ end
173
+
174
+ #
175
+ # Gets the system's hostname.
176
+ #
177
+ # @return [String]
178
+ #
179
+ # @note executes the `echo $HOSTNAME` command.
180
+ #
181
+ def sys_hostname
182
+ shell_exec("echo $HOSTNAME").chomp
183
+ end
184
+
185
+ #
186
+ # @group File-System methods
187
+ #
188
+
189
+ #
190
+ # Gets the current working directory and returns the directory path.
191
+ #
192
+ # @return [String]
193
+ # The remote current working directory.
194
+ #
195
+ # @note executes the `pwd` command.
196
+ #
197
+ def fs_getcwd
198
+ shell_exec('pwd').chomp
199
+ end
200
+
201
+ #
202
+ # Changes the current working directory.
203
+ #
204
+ # @param [String] path
205
+ # The new remote current working directory.
206
+ #
207
+ # @note executes the `cd <path>` command.
208
+ #
209
+ def fs_chdir(path)
210
+ shell_puts("cd #{Shellwords.escape(path)} 2>/dev/null")
211
+ end
212
+
213
+ #
214
+ # Reads the entire file at the given path and returns the full file's
215
+ # contents.
216
+ #
217
+ # @param [String] path
218
+ # The remote path to read.
219
+ #
220
+ # @return [String, nil]
221
+ # The contents of the remote file or `nil` if the file could not be
222
+ # read.
223
+ #
224
+ # @note executes the `cat <path>` command.
225
+ #
226
+ def fs_readfile(path)
227
+ command_exec('cat',path)
228
+ end
229
+
230
+ #
231
+ # Reads the destination path of a remote symbolic link.
232
+ #
233
+ # @param [String] path
234
+ # The remote path to read.
235
+ #
236
+ # @return [String, nil]
237
+ # The destination of the remote symbolic link or `nil` if the symbolic
238
+ # link could not be read.
239
+ #
240
+ # @note executes the `readlink -f <path>` command.
241
+ #
242
+ def fs_readlink(path)
243
+ command_exec('readlink','-f',path).chomp
244
+ end
245
+
246
+ #
247
+ # Reads the contents of a remote directory and returns an Array of
248
+ # directory entry names.
249
+ #
250
+ # @param [String] path
251
+ # The path of the remote directory to read.
252
+ #
253
+ # @return [Array<String>]
254
+ # The entities within the remote directory.
255
+ #
256
+ # @note executes the `ls <path>` command.
257
+ #
258
+ def fs_readdir(path)
259
+ command_exec('ls',path).lines(chomp: true)
260
+ end
261
+
262
+ #
263
+ # Evaluates a directory glob pattern and returns all matching paths.
264
+ #
265
+ # @param [String] pattern
266
+ # The glob pattern to search for remotely.
267
+ #
268
+ # @return [Array<String>]
269
+ # The matching paths.
270
+ #
271
+ # @note executes the `ls <pattern>` command.
272
+ #
273
+ def fs_glob(pattern,&block)
274
+ shell_exec("ls #{pattern}").lines(chomp: true)
275
+ end
276
+
277
+ #
278
+ # Creates a remote temporary file with the given file basename.
279
+ #
280
+ # @param [String] basename
281
+ # The basename for the new temporary file.
282
+ #
283
+ # @return [String]
284
+ # The path of the newly created temporary file.
285
+ #
286
+ # @note executes the `mktemp <basename>` command.
287
+ #
288
+ def fs_mktemp(basename)
289
+ command_exec('mktemp',basename).chomp
290
+ end
291
+
292
+ #
293
+ # Creates a new remote directory at the given path.
294
+ #
295
+ # @param [String] new_path
296
+ # The new remote directory to create.
297
+ #
298
+ # @note executes the `mkdir <path>` command.
299
+ #
300
+ def fs_mkdir(new_path)
301
+ command_exec('mkdir',new_path)
302
+ end
303
+
304
+ #
305
+ # Copies a source file to the destination path.
306
+ #
307
+ # @param [String] path
308
+ # The source file.
309
+ #
310
+ # @param [String] new_path
311
+ # The destination path.
312
+ #
313
+ # @note executes the `cp -r <path> <new_path>` command.
314
+ #
315
+ def fs_copy(path,new_path)
316
+ command_exec('cp','-r',path,new_path)
317
+ end
318
+
319
+ #
320
+ # Removes a file at the given path.
321
+ #
322
+ # @param [String] path
323
+ # The remote path to remove.
324
+ #
325
+ # @note executes the `rm <path>` command.
326
+ #
327
+ def fs_unlink(path)
328
+ command_exec('rm',path)
329
+ end
330
+
331
+ #
332
+ # Removes an empty directory at the given path.
333
+ #
334
+ # @param [String] path
335
+ # The remote directory path to remove.
336
+ #
337
+ # @note executes the `rmdir <path>` command.
338
+ #
339
+ def fs_rmdir(path)
340
+ command_exec('rmdir',path)
341
+ end
342
+
343
+ #
344
+ # Moves or renames a remote source file to a new destination path.
345
+ #
346
+ # @param [String] path
347
+ # The source file path.
348
+ #
349
+ # @param [String] new_path
350
+ # The destination file path.
351
+ #
352
+ # @note executes the `mv <path> <new_path>` command.
353
+ #
354
+ def fs_move(path,new_path)
355
+ command_exec('mv',path,new_path)
356
+ end
357
+
358
+ #
359
+ # Creates a remote symbolic link at the destination path pointing to the
360
+ # source path.
361
+ #
362
+ # @param [String] src
363
+ # The source file path for the new symbolic link.
364
+ #
365
+ # @param [String] dest
366
+ # The remote path of the new symbolic link.
367
+ #
368
+ # @note executes the `ln -s <src> <dest>` command.
369
+ #
370
+ def fs_link(src,dest)
371
+ command_exec('ln','-s',src,dest)
372
+ end
373
+
374
+ #
375
+ # Changes the group ownership of a remote file or directory.
376
+ #
377
+ # @param [String] group
378
+ # The new group name for the remote file or directory.
379
+ #
380
+ # @param [String] path
381
+ # The path of the remote file or directory.
382
+ #
383
+ # @note executes the `chgrp <group> <path>` command.
384
+ #
385
+ def fs_chgrp(group,path)
386
+ command_exec('chgrp',group,path)
387
+ end
388
+
389
+ #
390
+ # Changes the user ownership of remote a file or directory.
391
+ #
392
+ # @param [String] user
393
+ # The new user for the remote file or directory.
394
+ #
395
+ # @param [String] path
396
+ # The path of the remote file or directory.
397
+ #
398
+ # @note executes the `chown <user> <path>` command.
399
+ #
400
+ def fs_chown(user,path)
401
+ command_exec('chown',user,path)
402
+ end
403
+
404
+ #
405
+ # Changes the permissions on a remote file or directory.
406
+ #
407
+ # @param [Integer] mode
408
+ # The permissions mode for the remote file or directory.
409
+ #
410
+ # @param [String] path
411
+ # The path of the remote file or directory.
412
+ #
413
+ # @note executes the `chmod <umask> <path>` command.
414
+ #
415
+ def fs_chmod(mode,path)
416
+ umask = "%.4o" % mode
417
+
418
+ command_exec('chmod',umask,path)
419
+ end
420
+
421
+ #
422
+ # Queries file information for the given remote path and returns a Hash
423
+ # of file metadata.
424
+ #
425
+ # @param [String] path
426
+ # The path to the remote file or directory.
427
+ #
428
+ # @return [Hash{Symbol => Object}, nil]
429
+ # The metadata for the remote file.
430
+ #
431
+ # @note executes the `stat -t <path>` command.
432
+ #
433
+ def fs_stat(path)
434
+ fields = command_exec('stat','-t',path).strip.split(' ')
435
+
436
+ return {
437
+ path: path,
438
+ size: fields[1].to_i,
439
+ blocks: fields[2].to_i,
440
+ uid: fields[4].to_i,
441
+ gid: fields[5].to_i,
442
+ inode: fields[7].to_i,
443
+ links: fields[8].to_i,
444
+ atime: Time.at(fields[11].to_i),
445
+ mtime: Time.at(fields[12].to_i),
446
+ ctime: Time.at(fields[13].to_i),
447
+ blocksize: fields[14].to_i
448
+ }
449
+ end
450
+
451
+ #
452
+ # @group Process methods
453
+ #
454
+
455
+ #
456
+ # Gets the current process's Process ID (PID).
457
+ #
458
+ # @return [Integer]
459
+ # The current process's PID.
460
+ #
461
+ # @note executes the `echo $$` command.
462
+ #
463
+ def process_getpid
464
+ shell_exec('echo $$').to_i
465
+ end
466
+
467
+ #
468
+ # Gets the current process's parent Process ID (PPID).
469
+ #
470
+ # @return [Integer]
471
+ # The current process's PPID.
472
+ #
473
+ # @note executes the `echo $PPID` command.
474
+ #
475
+ def process_getppid
476
+ shell_exec('echo $PPID').to_i
477
+ end
478
+
479
+ #
480
+ # Gets the current process's user ID (UID).
481
+ #
482
+ # @return [Integer]
483
+ # The current process's UID.
484
+ #
485
+ # @note executes the `id -u` command.
486
+ #
487
+ def process_getuid
488
+ command_exec('id','-u').to_i
489
+ end
490
+
491
+ #
492
+ # Gets the current process's group ID (GID).
493
+ #
494
+ # @return [Integer]
495
+ # The group ID (GID) for the current process.
496
+ #
497
+ # @note executes the `id -g` command.
498
+ #
499
+ def process_getgid
500
+ command_exec('id','-g').to_i
501
+ end
502
+
503
+ #
504
+ # Queries all environment variables of the current process. Returns a
505
+ # Hash of the env variable names and values.
506
+ #
507
+ # @return [Hash{String => String}]
508
+ # The Hash of environment variables.
509
+ #
510
+ # @note executes the `env` command.
511
+ #
512
+ def process_environ
513
+ Hash[command_exec('env').each_line(chomp: true).map { |line|
514
+ line.split('=',2)
515
+ }]
516
+ end
517
+
518
+ #
519
+ # Gets an individual environment variable. If the environment variable
520
+ # has not been set, `nil` will be returned.
521
+ #
522
+ # @param [String] name
523
+ # The environment variable name to get.
524
+ #
525
+ # @return [String]
526
+ # The environment variable value.
527
+ #
528
+ # @note executes the `echo $<name>` command.
529
+ #
530
+ def process_getenv(name)
531
+ shell_exec("echo $#{name}").chomp
532
+ end
533
+
534
+ #
535
+ # Sets an environment variable to the given value.
536
+ #
537
+ # @param [String] name
538
+ # The environment variable name to set.
539
+ #
540
+ # @param [String] value
541
+ # The new value for the environment variable.
542
+ #
543
+ # @note executes the `export <name>=<value>` command.
544
+ #
545
+ def process_setenv(name,value)
546
+ shell_puts("export #{name}=#{value}")
547
+ end
548
+
549
+ #
550
+ # Un-sets an environment variable.
551
+ #
552
+ # @param [String] name
553
+ # The environment variable to unset.
554
+ #
555
+ # @note executes the `unset <name>` command.
556
+ #
557
+ def process_unsetenv(name)
558
+ shell_puts("unset #{name}")
559
+ end
560
+
561
+ #
562
+ # Kills another process using the given Process ID (POD) and the signal
563
+ # number.
564
+ #
565
+ # @param [Integer] pid
566
+ # The process ID (PID) to kill.
567
+ #
568
+ # @param [Integer] signal
569
+ # The signal to send the process ID (PID).
570
+ #
571
+ # @note executes the `kill -s <signal> <pid>` command.
572
+ #
573
+ def process_kill(pid,signal)
574
+ command_exec('kill','-s',signal,pid)
575
+ end
576
+
577
+ #
578
+ # Spawns a new process using the given program and additional arguments.
579
+ #
580
+ # @param [String] command
581
+ # The command name to spawn.
582
+ #
583
+ # @param [Array<String>] arguments
584
+ # Additional arguments for the program.
585
+ #
586
+ # @return [Integer]
587
+ # The process ID (PID) of the spawned process.
588
+ #
589
+ # @note
590
+ # executes the command with additional arguments as a background
591
+ # process.
592
+ #
593
+ def process_spawn(command,*arguments)
594
+ command = command_join(command,*arguments)
595
+
596
+ shell_exec("#{command} 2>&1 >/dev/null &; echo $!").to_i
597
+ end
598
+
599
+ #
600
+ # Exits the current process.
601
+ #
602
+ # @note executes the `exit` command.
603
+ #
604
+ def process_exit
605
+ shell_puts('exit')
606
+ end
607
+
608
+ #
609
+ # Closes the remote shell.
610
+ #
611
+ def close
612
+ @io.close
613
+ end
614
+
615
+ end
616
+ end
617
+ end
618
+ end