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.
- checksums.yaml +4 -4
- data/LICENSE +0 -0
- data/bin/evil-winrm-ai +0 -0
- data/evil-winrm-ai.rb +121 -50
- metadata +39 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e1983c8cf5a0096c9baea9fd20c3047177640ec4f740311b51a46d847c5b696f
|
|
4
|
+
data.tar.gz: f37cc6856e0fbcc1eecbea0f26d236eb5619aa06c2a7c6fd01f14780e9a29ab0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 = '
|
|
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
|
-
|
|
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
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
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
|
-
|
|
1501
|
-
|
|
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
|
-
|
|
1810
|
-
|
|
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: '
|
|
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:
|
|
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.
|
|
330
|
+
rubygems_version: 3.5.22
|
|
297
331
|
signing_key:
|
|
298
332
|
specification_version: 4
|
|
299
333
|
summary: Evil-WinRM
|