evil-winrm 3.9 → 4.1

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +0 -0
  3. data/evil-winrm.rb +121 -50
  4. metadata +39 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00667efb62697e6dd9757369f1db060361b7fe6c38cdc2f2b740d8d97040e9db
4
- data.tar.gz: 6cd43aa52b1e1c1e6d91a155f653c3c72d259dd32a9bd2cd64f9b3173927a466
3
+ metadata.gz: 30c79c310ded67cef1d5bf2b58e0a40fab19f4677a35c3d6c60f5cd8c50d838e
4
+ data.tar.gz: 434a47d6a124f8769fe549ec89e40fe7b2c582fd48c023c72e7944697cc9efbb
5
5
  SHA512:
6
- metadata.gz: dff7266f3a6045fa0944b45851e434ed5d53508a73af2446d702979a09cb7a2bb121eba672f4bbb26ba84966e2e55d661d1b6ec869018169383ca0dafc0de1ba
7
- data.tar.gz: c540b57db84aa63896c72f109243e3e10f93d4919765f7c1a97b43d2f7a97dbb15e99550d8dc0bdf82520c0188363eaa302e045ea9026ff2d8ef53573f8a0e5f
6
+ metadata.gz: baa637901d809d708be6580a8cc12ad6296929d246aebc4d31e225c34aac31de1ac02b174141f5bda902c1c675c2000bd29c866a903f4c2cc0738183ca50be82
7
+ data.tar.gz: 5c318e9333a6d934a84dece396a1c189819545bf6ef7c61d801a65a062128561e7d4b8b2b0bfe323155ca5547d466d28051f4b8fcc28c038397b46e58c59e96b
data/LICENSE CHANGED
File without changes
data/evil-winrm.rb CHANGED
@@ -18,11 +18,12 @@ require 'time'
18
18
  require 'fileutils'
19
19
  require 'logger'
20
20
  require 'shellwords'
21
+ require 'thread'
21
22
 
22
23
  # Constants
23
24
 
24
25
  # Version
25
- VERSION = '3.9'
26
+ VERSION = '4.1'
26
27
 
27
28
  # Msg types
28
29
  TYPE_INFO = 0
@@ -34,7 +35,7 @@ TYPE_SUCCESS = 4
34
35
  # Global vars
35
36
 
36
37
  # Available commands
37
- $LIST = %w[Bypass-4MSI services upload download clear cls menu exit]
38
+ $LIST = %w[Bypass-4MSI services upload download clear cls menu exit quit]
38
39
  $COMMANDS = $LIST.dup
39
40
  $CMDS = $COMMANDS.clone
40
41
  $LISTASSEM = [''].sort
@@ -94,6 +95,9 @@ $WORDS_RANDOM_CASE = [
94
95
  $colors_enabled = true
95
96
  $check_rpath_completion = true
96
97
 
98
+ # Flush stdout after each write
99
+ $stdout.sync = true
100
+
97
101
  # Path for ps1 scripts and exec files
98
102
  $scripts_path = ''
99
103
  $executables_path = ''
@@ -155,7 +159,10 @@ module WinRM
155
159
  command = "Get-ChildItem #{remote_path} | Select-Object Name"
156
160
 
157
161
  @connection.shell(:powershell) { |e| e.run(command) }.stdout.strip.split(/\n/).drop(2).each do |file|
158
- download(File.join(remote_file_path.to_s, file.strip), File.join(local_path, file.strip), chunk_size, false)
162
+ sanitized_path = File.basename(file.strip)
163
+ next if sanitized_path.empty? || sanitized_path == '.' || sanitized_path == '..'
164
+
165
+ download(File.join(remote_file_path.to_s, file.strip), File.join(local_path, sanitized_path), chunk_size, false)
159
166
  end
160
167
  end
161
168
 
@@ -1073,6 +1080,80 @@ class EvilWinRM
1073
1080
  ccache_path
1074
1081
  end
1075
1082
 
1083
+ # True when the exception indicates the WinRM session is dead or was never established
1084
+ def connection_error?(error)
1085
+ error_msg = error.message.to_s.downcase
1086
+ error_class = error.class.to_s
1087
+
1088
+ return true if error_msg.match?(/timeout|connection|closed|broken|reset|refused|unreachable|unauthorized|digest|ssl|ntlm|spnego|kerberos|gssapi|wsman|winrm|http|401|403|500|503|expired|empty response/)
1089
+ return true if error_class.match?(/Timeout|Connection|Socket|HTTP|WinRM|GSSAPI|GssApi|OpenSSL|Errno/)
1090
+
1091
+ false
1092
+ end
1093
+
1094
+ # WinRM::Connection#shell is lazy: the TCP/WinRM handshake happens on the first command.
1095
+ # Never show the interactive prompt unless that command actually succeeds.
1096
+ def verify_remote_connection(shell)
1097
+ result = shell.run('(get-location).path')
1098
+ pwd = result&.output.to_s.strip
1099
+ raise 'Empty response from remote endpoint; session is not ready' if pwd.empty?
1100
+
1101
+ pwd
1102
+ rescue => e
1103
+ print_message('Cannot establish connection to remote endpoint. Check credentials and network.', TYPE_ERROR, true, $logger)
1104
+ print_message("#{e.class}: #{e.message}", TYPE_ERROR, true, $logger)
1105
+ custom_exit(1, false)
1106
+ end
1107
+
1108
+ def handle_lost_connection(error)
1109
+ puts
1110
+ print_message("Connection timeout or error occurred: #{error.class} - #{error.message}", TYPE_ERROR, true, $logger)
1111
+ print_message('Cleaning up and exiting...', TYPE_WARNING, true, $logger)
1112
+ custom_exit(1, false)
1113
+ end
1114
+
1115
+ # Keep the remote shell active while the local prompt is idle.
1116
+ # Without this, Windows marks the shell Disconnected after ~1 minute of ShellInactivity
1117
+ # and the next command fails even though the prompt still looks connected.
1118
+ def decorate_shell_with_keepalive(shell, interval = 30)
1119
+ mutex = Mutex.new
1120
+ state = { last_activity: Time.now, stop: false }
1121
+ original_run = shell.method(:run)
1122
+
1123
+ shell.define_singleton_method(:run) do |*args, &block|
1124
+ mutex.synchronize do
1125
+ result = original_run.call(*args, &block)
1126
+ state[:last_activity] = Time.now
1127
+ result
1128
+ end
1129
+ end
1130
+
1131
+ thread = Thread.new do
1132
+ Thread.current.abort_on_exception = false
1133
+ until state[:stop]
1134
+ sleep(interval)
1135
+ break if state[:stop]
1136
+ next if (Time.now - state[:last_activity]) < interval
1137
+ next unless shell.shell_id
1138
+
1139
+ begin
1140
+ shell.run('[void]0')
1141
+ rescue StandardError
1142
+ # The next user command will surface a dead session
1143
+ end
1144
+ end
1145
+ end
1146
+
1147
+ lambda do
1148
+ state[:stop] = true
1149
+ begin
1150
+ thread.wakeup
1151
+ rescue ThreadError
1152
+ end
1153
+ thread.join(1)
1154
+ end
1155
+ end
1156
+
1076
1157
  # Main function
1077
1158
  def main
1078
1159
  arguments
@@ -1128,7 +1209,14 @@ class EvilWinRM
1128
1209
  time = Time.now.to_i
1129
1210
  print_message('Establishing connection to remote endpoint', TYPE_INFO)
1130
1211
  $conn.shell(:powershell) do |shell|
1212
+ stop_keepalive = nil
1131
1213
  begin
1214
+ last_pwd = verify_remote_connection(shell)
1215
+ pwd = last_pwd
1216
+ skip_pwd_refresh = true
1217
+ print_message('Connection successful', TYPE_INFO)
1218
+ stop_keepalive ||= decorate_shell_with_keepalive(shell)
1219
+
1132
1220
  completion = proc do |str|
1133
1221
  case
1134
1222
  when Readline.line_buffer =~ /help.*/i
@@ -1198,32 +1286,22 @@ class EvilWinRM
1198
1286
  # Load history for this host/user
1199
1287
  load_history
1200
1288
 
1201
- until command == 'exit' do
1202
- begin
1203
- pwd = shell.run('(get-location).path').output.strip
1204
- rescue => e
1205
- # Handle connection/timeout errors when getting pwd
1206
- error_msg = e.message.to_s.downcase
1207
- if error_msg.include?('timeout') || error_msg.include?('connection') ||
1208
- error_msg.include?('closed') || error_msg.include?('broken') ||
1209
- e.class.to_s.include?('Timeout') || e.class.to_s.include?('Connection')
1210
- puts
1211
- print_message("Connection timeout or error occurred: #{e.class} - #{e.message}", TYPE_ERROR, true, $logger)
1212
- print_message("Cleaning up and exiting...", TYPE_WARNING, true, $logger)
1213
- # Clean up KRB5CCNAME before exiting
1214
- begin
1215
- if defined?($original_krb5ccname) && !$original_krb5ccname.nil?
1216
- ENV['KRB5CCNAME'] = $original_krb5ccname
1217
- elsif defined?($original_krb5ccname) && $original_krb5ccname.nil?
1218
- ENV.delete('KRB5CCNAME') if ENV.key?('KRB5CCNAME')
1219
- end
1220
- rescue => cleanup_error
1221
- # Ignore cleanup errors
1289
+ until %w[exit quit].include?(command) do
1290
+ if skip_pwd_refresh
1291
+ skip_pwd_refresh = false
1292
+ else
1293
+ begin
1294
+ new_pwd = shell.run('(get-location).path').output.to_s.strip
1295
+ unless new_pwd.empty?
1296
+ pwd = new_pwd
1297
+ last_pwd = pwd
1298
+ end
1299
+ rescue => e
1300
+ if connection_error?(e)
1301
+ handle_lost_connection(e)
1302
+ else
1303
+ pwd = last_pwd
1222
1304
  end
1223
- custom_exit(1, false)
1224
- else
1225
- # For other errors, try to continue with a default pwd
1226
- pwd = "C:\\"
1227
1305
  end
1228
1306
  end
1229
1307
 
@@ -1233,8 +1311,14 @@ class EvilWinRM
1233
1311
  command = Readline.readline("*Evil-WinRM* PS #{pwd}> ", true)
1234
1312
  end
1235
1313
 
1236
- # Handle Ctrl+L if it returns as empty or special character
1237
- if command == "\f" || (command.nil? && Readline.line_buffer.empty?)
1314
+ if command.nil?
1315
+ custom_exit(0)
1316
+ end
1317
+
1318
+ break if %w[exit quit].include?(command.strip.downcase)
1319
+
1320
+ # Handle Ctrl+L if it returns as a special character
1321
+ if command == "\f"
1238
1322
  clear_screen
1239
1323
  command = ''
1240
1324
  next
@@ -1511,27 +1595,9 @@ class EvilWinRM
1511
1595
  end
1512
1596
  $logger.info(output_logger)
1513
1597
  rescue => e
1514
- # Handle connection/timeout errors gracefully
1515
- error_msg = e.message.to_s.downcase
1516
- if error_msg.include?('timeout') || error_msg.include?('connection') ||
1517
- error_msg.include?('closed') || error_msg.include?('broken') ||
1518
- e.class.to_s.include?('Timeout') || e.class.to_s.include?('Connection')
1519
- puts
1520
- print_message("Connection timeout or error occurred: #{e.class} - #{e.message}", TYPE_ERROR, true, $logger)
1521
- print_message("Cleaning up and exiting...", TYPE_WARNING, true, $logger)
1522
- # Clean up KRB5CCNAME before exiting
1523
- begin
1524
- if defined?($original_krb5ccname) && !$original_krb5ccname.nil?
1525
- ENV['KRB5CCNAME'] = $original_krb5ccname
1526
- elsif defined?($original_krb5ccname) && $original_krb5ccname.nil?
1527
- ENV.delete('KRB5CCNAME') if ENV.key?('KRB5CCNAME')
1528
- end
1529
- rescue => cleanup_error
1530
- # Ignore cleanup errors
1531
- end
1532
- custom_exit(1, false)
1598
+ if connection_error?(e)
1599
+ handle_lost_connection(e)
1533
1600
  else
1534
- # Re-raise other errors
1535
1601
  raise
1536
1602
  end
1537
1603
  end
@@ -1548,6 +1614,11 @@ class EvilWinRM
1548
1614
  else
1549
1615
  retry
1550
1616
  end
1617
+ ensure
1618
+ if stop_keepalive
1619
+ stop_keepalive.call
1620
+ stop_keepalive = nil
1621
+ end
1551
1622
  end
1552
1623
 
1553
1624
  custom_exit(0)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evil-winrm
3
3
  version: !ruby/object:Gem::Version
4
- version: '3.9'
4
+ version: '4.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - CyberVaca
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2025-12-07 00:00:00.000000000 Z
14
+ date: 2026-09-03 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: benchmark
@@ -75,6 +75,34 @@ dependencies:
75
75
  - - ">="
76
76
  - !ruby/object:Gem::Version
77
77
  version: 1.4.3
78
+ - !ruby/object:Gem::Dependency
79
+ name: readline
80
+ requirement: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: 0.0.4
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: 0.0.4
92
+ - !ruby/object:Gem::Dependency
93
+ name: readline-ext
94
+ requirement: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: 0.2.0
99
+ type: :runtime
100
+ prerelease: false
101
+ version_requirements: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: 0.2.0
78
106
  - !ruby/object:Gem::Dependency
79
107
  name: stringio
80
108
  requirement: !ruby/object:Gem::Requirement
@@ -147,16 +175,22 @@ dependencies:
147
175
  name: bundler
148
176
  requirement: !ruby/object:Gem::Requirement
149
177
  requirements:
150
- - - "~>"
178
+ - - ">="
151
179
  - !ruby/object:Gem::Version
152
180
  version: '2.0'
181
+ - - "<"
182
+ - !ruby/object:Gem::Version
183
+ version: '5.0'
153
184
  type: :development
154
185
  prerelease: false
155
186
  version_requirements: !ruby/object:Gem::Requirement
156
187
  requirements:
157
- - - "~>"
188
+ - - ">="
158
189
  - !ruby/object:Gem::Version
159
190
  version: '2.0'
191
+ - - "<"
192
+ - !ruby/object:Gem::Version
193
+ version: '5.0'
160
194
  description: The ultimate WinRM shell for hacking/pentesting
161
195
  email:
162
196
  - oscar.alfonso.diaz@gmail.com
@@ -193,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
227
  - !ruby/object:Gem::Version
194
228
  version: '0'
195
229
  requirements: []
196
- rubygems_version: 3.3.15
230
+ rubygems_version: 3.5.22
197
231
  signing_key:
198
232
  specification_version: 4
199
233
  summary: Evil-WinRM