evil-winrm-ai 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 (5) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +0 -0
  3. data/bin/evil-winrm-ai +0 -0
  4. data/evil-winrm-ai.rb +121 -50
  5. metadata +39 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0154b8e0b66745fda3c9c3307b4a1b0ce87b2f06f09cf6ef1781572a97217f1
4
- data.tar.gz: 7d1e174ae8c5701fb1c71ab10c7a977ae21c643bf747eaa3e897831d0d128868
3
+ metadata.gz: e1983c8cf5a0096c9baea9fd20c3047177640ec4f740311b51a46d847c5b696f
4
+ data.tar.gz: f37cc6856e0fbcc1eecbea0f26d236eb5619aa06c2a7c6fd01f14780e9a29ab0
5
5
  SHA512:
6
- metadata.gz: 259ce4272eed5f43b4f750fb3648098742f3f531f77f4c9da5a8d00bb84710a9212acfb37c16cf7ee937b3afac0ef35fa4c21059439a44655b0208af8efc4cc3
7
- data.tar.gz: efa099a376d3352765684da5ebf46d59590398a1f3d6771cad1be0e65d9fa6fbcd55707efbe14b9672266bfaf259619317318d90c3ae4e51daebad12cc9fd7bf
6
+ metadata.gz: 2c5094ccd7de1c78acf3110510b5ed5503e482ca4479763acfb8909df7c3614d2e556d95addbdb3af563197b63499287696937cb90c6a935f997b2f919094a8f
7
+ data.tar.gz: 84da83fa4c8a87f03d5360e86c6ec2d77523311edbf611c87ef8c733227fd095156d8220e842a0d4dfd084533666c61cc9e7b6ec8f7c3fc32ca523e67744cdfb
data/LICENSE CHANGED
File without changes
data/bin/evil-winrm-ai CHANGED
File without changes
data/evil-winrm-ai.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 = ''
@@ -184,7 +188,10 @@ module WinRM
184
188
  command = "Get-ChildItem #{remote_path} | Select-Object Name"
185
189
 
186
190
  @connection.shell(:powershell) { |e| e.run(command) }.stdout.strip.split(/\n/).drop(2).each do |file|
187
- download(File.join(remote_file_path.to_s, file.strip), File.join(local_path, file.strip), chunk_size, false)
191
+ sanitized_path = File.basename(file.strip)
192
+ next if sanitized_path.empty? || sanitized_path == '.' || sanitized_path == '..'
193
+
194
+ download(File.join(remote_file_path.to_s, file.strip), File.join(local_path, sanitized_path), chunk_size, false)
188
195
  end
189
196
  end
190
197
 
@@ -1324,6 +1331,80 @@ class EvilWinRM
1324
1331
  ccache_path
1325
1332
  end
1326
1333
 
1334
+ # True when the exception indicates the WinRM session is dead or was never established
1335
+ def connection_error?(error)
1336
+ error_msg = error.message.to_s.downcase
1337
+ error_class = error.class.to_s
1338
+
1339
+ 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/)
1340
+ return true if error_class.match?(/Timeout|Connection|Socket|HTTP|WinRM|GSSAPI|GssApi|OpenSSL|Errno/)
1341
+
1342
+ false
1343
+ end
1344
+
1345
+ # WinRM::Connection#shell is lazy: the TCP/WinRM handshake happens on the first command.
1346
+ # Never show the interactive prompt unless that command actually succeeds.
1347
+ def verify_remote_connection(shell)
1348
+ result = shell.run('(get-location).path')
1349
+ pwd = result&.output.to_s.strip
1350
+ raise 'Empty response from remote endpoint; session is not ready' if pwd.empty?
1351
+
1352
+ pwd
1353
+ rescue => e
1354
+ print_message('Cannot establish connection to remote endpoint. Check credentials and network.', TYPE_ERROR, true, $logger)
1355
+ print_message("#{e.class}: #{e.message}", TYPE_ERROR, true, $logger)
1356
+ custom_exit(1, false)
1357
+ end
1358
+
1359
+ def handle_lost_connection(error)
1360
+ puts
1361
+ print_message("Connection timeout or error occurred: #{error.class} - #{error.message}", TYPE_ERROR, true, $logger)
1362
+ print_message('Cleaning up and exiting...', TYPE_WARNING, true, $logger)
1363
+ custom_exit(1, false)
1364
+ end
1365
+
1366
+ # Keep the remote shell active while the local prompt is idle.
1367
+ # Without this, Windows marks the shell Disconnected after ~1 minute of ShellInactivity
1368
+ # and the next command fails even though the prompt still looks connected.
1369
+ def decorate_shell_with_keepalive(shell, interval = 30)
1370
+ mutex = Mutex.new
1371
+ state = { last_activity: Time.now, stop: false }
1372
+ original_run = shell.method(:run)
1373
+
1374
+ shell.define_singleton_method(:run) do |*args, &block|
1375
+ mutex.synchronize do
1376
+ result = original_run.call(*args, &block)
1377
+ state[:last_activity] = Time.now
1378
+ result
1379
+ end
1380
+ end
1381
+
1382
+ thread = Thread.new do
1383
+ Thread.current.abort_on_exception = false
1384
+ until state[:stop]
1385
+ sleep(interval)
1386
+ break if state[:stop]
1387
+ next if (Time.now - state[:last_activity]) < interval
1388
+ next unless shell.shell_id
1389
+
1390
+ begin
1391
+ shell.run('[void]0')
1392
+ rescue StandardError
1393
+ # The next user command will surface a dead session
1394
+ end
1395
+ end
1396
+ end
1397
+
1398
+ lambda do
1399
+ state[:stop] = true
1400
+ begin
1401
+ thread.wakeup
1402
+ rescue ThreadError
1403
+ end
1404
+ thread.join(1)
1405
+ end
1406
+ end
1407
+
1327
1408
  # Main function
1328
1409
  def main
1329
1410
  arguments
@@ -1392,7 +1473,14 @@ class EvilWinRM
1392
1473
  time = Time.now.to_i
1393
1474
  print_message('Establishing connection to remote endpoint', TYPE_INFO)
1394
1475
  $conn.shell(:powershell) do |shell|
1476
+ stop_keepalive = nil
1395
1477
  begin
1478
+ last_pwd = verify_remote_connection(shell)
1479
+ pwd = last_pwd
1480
+ skip_pwd_refresh = true
1481
+ print_message('Connection successful', TYPE_INFO)
1482
+ stop_keepalive ||= decorate_shell_with_keepalive(shell)
1483
+
1396
1484
  completion = proc do |str|
1397
1485
  case
1398
1486
  when Readline.line_buffer =~ /help.*/i
@@ -1462,32 +1550,22 @@ class EvilWinRM
1462
1550
  # Load history for this host/user
1463
1551
  load_history
1464
1552
 
1465
- until command == 'exit' do
1466
- begin
1467
- pwd = shell.run('(get-location).path').output.strip
1468
- rescue => e
1469
- # Handle connection/timeout errors when getting pwd
1470
- error_msg = e.message.to_s.downcase
1471
- if error_msg.include?('timeout') || error_msg.include?('connection') ||
1472
- error_msg.include?('closed') || error_msg.include?('broken') ||
1473
- e.class.to_s.include?('Timeout') || e.class.to_s.include?('Connection')
1474
- puts
1475
- print_message("Connection timeout or error occurred: #{e.class} - #{e.message}", TYPE_ERROR, true, $logger)
1476
- print_message("Cleaning up and exiting...", TYPE_WARNING, true, $logger)
1477
- # Clean up KRB5CCNAME before exiting
1478
- begin
1479
- if defined?($original_krb5ccname) && !$original_krb5ccname.nil?
1480
- ENV['KRB5CCNAME'] = $original_krb5ccname
1481
- elsif defined?($original_krb5ccname) && $original_krb5ccname.nil?
1482
- ENV.delete('KRB5CCNAME') if ENV.key?('KRB5CCNAME')
1483
- end
1484
- rescue => cleanup_error
1485
- # Ignore cleanup errors
1553
+ until %w[exit quit].include?(command) do
1554
+ if skip_pwd_refresh
1555
+ skip_pwd_refresh = false
1556
+ else
1557
+ begin
1558
+ new_pwd = shell.run('(get-location).path').output.to_s.strip
1559
+ unless new_pwd.empty?
1560
+ pwd = new_pwd
1561
+ last_pwd = pwd
1562
+ end
1563
+ rescue => e
1564
+ if connection_error?(e)
1565
+ handle_lost_connection(e)
1566
+ else
1567
+ pwd = last_pwd
1486
1568
  end
1487
- custom_exit(1, false)
1488
- else
1489
- # For other errors, try to continue with a default pwd
1490
- pwd = "C:\\"
1491
1569
  end
1492
1570
  end
1493
1571
 
@@ -1497,8 +1575,14 @@ class EvilWinRM
1497
1575
  command = Readline.readline("*Evil-WinRM* PS #{pwd}> ", true)
1498
1576
  end
1499
1577
 
1500
- # Handle Ctrl+L if it returns as empty or special character
1501
- if command == "\f" || (command.nil? && Readline.line_buffer.empty?)
1578
+ if command.nil?
1579
+ custom_exit(0)
1580
+ end
1581
+
1582
+ break if %w[exit quit].include?(command.strip.downcase)
1583
+
1584
+ # Handle Ctrl+L if it returns as a special character
1585
+ if command == "\f"
1502
1586
  clear_screen
1503
1587
  command = ''
1504
1588
  next
@@ -1806,27 +1890,9 @@ class EvilWinRM
1806
1890
  end
1807
1891
  $logger.info(output_logger)
1808
1892
  rescue => e
1809
- # Handle connection/timeout errors gracefully
1810
- error_msg = e.message.to_s.downcase
1811
- if error_msg.include?('timeout') || error_msg.include?('connection') ||
1812
- error_msg.include?('closed') || error_msg.include?('broken') ||
1813
- e.class.to_s.include?('Timeout') || e.class.to_s.include?('Connection')
1814
- puts
1815
- print_message("Connection timeout or error occurred: #{e.class} - #{e.message}", TYPE_ERROR, true, $logger)
1816
- print_message("Cleaning up and exiting...", TYPE_WARNING, true, $logger)
1817
- # Clean up KRB5CCNAME before exiting
1818
- begin
1819
- if defined?($original_krb5ccname) && !$original_krb5ccname.nil?
1820
- ENV['KRB5CCNAME'] = $original_krb5ccname
1821
- elsif defined?($original_krb5ccname) && $original_krb5ccname.nil?
1822
- ENV.delete('KRB5CCNAME') if ENV.key?('KRB5CCNAME')
1823
- end
1824
- rescue => cleanup_error
1825
- # Ignore cleanup errors
1826
- end
1827
- custom_exit(1, false)
1893
+ if connection_error?(e)
1894
+ handle_lost_connection(e)
1828
1895
  else
1829
- # Re-raise other errors
1830
1896
  raise
1831
1897
  end
1832
1898
  end
@@ -1843,6 +1909,11 @@ class EvilWinRM
1843
1909
  else
1844
1910
  retry
1845
1911
  end
1912
+ ensure
1913
+ if stop_keepalive
1914
+ stop_keepalive.call
1915
+ stop_keepalive = nil
1916
+ end
1846
1917
  end
1847
1918
 
1848
1919
  custom_exit(0)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evil-winrm-ai
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-15 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: anthropic
@@ -155,6 +155,34 @@ dependencies:
155
155
  - - ">="
156
156
  - !ruby/object:Gem::Version
157
157
  version: 1.2.0
158
+ - !ruby/object:Gem::Dependency
159
+ name: readline
160
+ requirement: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - "~>"
163
+ - !ruby/object:Gem::Version
164
+ version: 0.0.4
165
+ type: :runtime
166
+ prerelease: false
167
+ version_requirements: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - "~>"
170
+ - !ruby/object:Gem::Version
171
+ version: 0.0.4
172
+ - !ruby/object:Gem::Dependency
173
+ name: readline-ext
174
+ requirement: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - "~>"
177
+ - !ruby/object:Gem::Version
178
+ version: 0.2.0
179
+ type: :runtime
180
+ prerelease: false
181
+ version_requirements: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - "~>"
184
+ - !ruby/object:Gem::Version
185
+ version: 0.2.0
158
186
  - !ruby/object:Gem::Dependency
159
187
  name: ruby-openai
160
188
  requirement: !ruby/object:Gem::Requirement
@@ -247,16 +275,22 @@ dependencies:
247
275
  name: bundler
248
276
  requirement: !ruby/object:Gem::Requirement
249
277
  requirements:
250
- - - "~>"
278
+ - - ">="
251
279
  - !ruby/object:Gem::Version
252
280
  version: '2.0'
281
+ - - "<"
282
+ - !ruby/object:Gem::Version
283
+ version: '5.0'
253
284
  type: :development
254
285
  prerelease: false
255
286
  version_requirements: !ruby/object:Gem::Requirement
256
287
  requirements:
257
- - - "~>"
288
+ - - ">="
258
289
  - !ruby/object:Gem::Version
259
290
  version: '2.0'
291
+ - - "<"
292
+ - !ruby/object:Gem::Version
293
+ version: '5.0'
260
294
  description: The ultimate WinRM shell for hacking/pentesting. AI edition.
261
295
  email:
262
296
  - oscar.alfonso.diaz@gmail.com
@@ -293,7 +327,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
293
327
  - !ruby/object:Gem::Version
294
328
  version: '0'
295
329
  requirements: []
296
- rubygems_version: 3.3.15
330
+ rubygems_version: 3.5.22
297
331
  signing_key:
298
332
  specification_version: 4
299
333
  summary: Evil-WinRM