pwn 0.5.475 → 0.5.476

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9925d872d4ad9fd60b7b6bdcfae94e7dd9cfbbdf3b6940cbadabf7272262bcdf
4
- data.tar.gz: 617935fb1c6d04fc405653444a5902e712a7f991d32125c8f61d3c409cb67495
3
+ metadata.gz: 27b283c37fc8b736e65e2c64b23a21333dad62b689ce43e5dd3e958f665d29ae
4
+ data.tar.gz: 4a80083f4126e1628992e3c7029cfc358ee9e4c257605045bfab5ba82623efad
5
5
  SHA512:
6
- metadata.gz: 16ff0e642b1bb240f649061e58940ab33274c9ddf3ede313d1cb6a8a0cf6a867d7a670e5417a1d87439e14dc2d8683e1e6936936cae6877b6d4d6d5fac546405
7
- data.tar.gz: e065a9f313c72510212c198836419e61cddd6d2baad2b42874046c8c5421b4cde6d2287b5a8303ad006c0bd539e04841aaae3557019bcfb4baf40f5068042094
6
+ metadata.gz: 1d68f38ceda2d002c7c6ba6a8f4efc94d9b6b7b96e33988c5a7d37c06bd131d61140e2ed3b7c5af5fe7d7a2da5528c98d579a3d0ce4f3436439bd948e9bed5aa
7
+ data.tar.gz: d3c54e9f9fdb049ee1055df0287935f88f4f2f3f4bd61020983a9b379d68b7f8ed06d86a3ddf28e611c906f23314fb14febe2ca7f68d16fa2f5de6bb49e53fdb
data/Gemfile CHANGED
@@ -25,6 +25,7 @@ gem 'bundler-audit', '0.9.2'
25
25
  gem 'bunny', '2.24.0'
26
26
  gem 'colorize', '1.1.0'
27
27
  gem 'credit_card_validations', '7.0.0'
28
+ gem 'curses', '1.5.3'
28
29
  gem 'diffy', '3.4.4'
29
30
  gem 'eventmachine', '1.2.7'
30
31
  gem 'executable-hooks', '1.7.1'
data/README.md CHANGED
@@ -37,7 +37,7 @@ $ cd /opt/pwn
37
37
  $ ./install.sh
38
38
  $ ./install.sh ruby-gem
39
39
  $ pwn
40
- pwn[v0.5.475]:001 >>> PWN.help
40
+ pwn[v0.5.476]:001 >>> PWN.help
41
41
  ```
42
42
 
43
43
  [![Installing the pwn Security Automation Framework](https://raw.githubusercontent.com/0dayInc/pwn/master/documentation/pwn_install.png)](https://youtu.be/G7iLUY4FzsI)
@@ -52,7 +52,7 @@ $ rvm use ruby-3.4.4@pwn
52
52
  $ gem uninstall --all --executables pwn
53
53
  $ gem install --verbose pwn
54
54
  $ pwn
55
- pwn[v0.5.475]:001 >>> PWN.help
55
+ pwn[v0.5.476]:001 >>> PWN.help
56
56
  ```
57
57
 
58
58
  If you're using a multi-user install of RVM do:
@@ -62,7 +62,7 @@ $ rvm use ruby-3.4.4@pwn
62
62
  $ rvmsudo gem uninstall --all --executables pwn
63
63
  $ rvmsudo gem install --verbose pwn
64
64
  $ pwn
65
- pwn[v0.5.475]:001 >>> PWN.help
65
+ pwn[v0.5.476]:001 >>> PWN.help
66
66
  ```
67
67
 
68
68
  PWN periodically upgrades to the latest version of Ruby which is reflected in `/opt/pwn/.ruby-version`. The easiest way to upgrade to the latest version of Ruby from a previous PWN installation is to run the following script:
@@ -530,16 +530,38 @@ module PWN
530
530
  end
531
531
  end
532
532
 
533
- # Create the cloned issue
534
- cloned_issue = create_issue(
535
- project_key: project_key,
536
- summary: summary,
537
- issue_type: issue_type,
538
- epic_name: epic_name,
539
- description: description,
540
- additional_fields: additional_fields,
541
- attachments: attachments
542
- )
533
+ # Attempt create, dynamically omit unlicensed fields on failure
534
+ attempt = 1
535
+ max_attempts = 5
536
+ cloned_issue = nil
537
+ begin
538
+ cloned_issue = create_issue(
539
+ project_key: project_key,
540
+ summary: summary,
541
+ issue_type: issue_type,
542
+ epic_name: epic_name,
543
+ description: description,
544
+ additional_fields: additional_fields,
545
+ attachments: attachments
546
+ )
547
+ rescue RestClient::ExceptionWithResponse => e
548
+ raise e if attempt >= max_attempts || e.response.body.to_s.empty?
549
+
550
+ json = JSON.parse(e.response.body, symbolize_names: true)
551
+ errors_hash = json[:errors] if json.is_a?(Hash)
552
+ if errors_hash.is_a?(Hash)
553
+ unlicensed_field_keys = errors_hash.select { |_fk, msg| msg.to_s =~ /unlicensed/i }.keys
554
+ if unlicensed_field_keys.any?
555
+ unlicensed_field_keys.each do |fk|
556
+ additional_fields[:fields].delete(fk.to_sym)
557
+ additional_fields[:fields].delete(fk.to_s)
558
+ end
559
+ @@logger.warn("Omitting unlicensed fields: #{unlicensed_field_keys.join(', ')} (attempt #{attempt}/#{max_attempts}). Retrying clone.") if defined?(@@logger)
560
+ attempt += 1
561
+ retry
562
+ end
563
+ end
564
+ end
543
565
 
544
566
  # Return the fully fetched cloned issue
545
567
  get_issue(issue: cloned_issue[:key])
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'curses'
3
4
  require 'fileutils'
4
5
  require 'meshtastic'
5
6
  require 'pry'
@@ -437,6 +438,14 @@ module PWN
437
438
  pi.config.pwn_mesh = true
438
439
  meshtastic_env = PWN::Env[:plugins][:meshtastic]
439
440
 
441
+ PWN.send(:remove_const, :MeshTxEchoThread) if PWN.const_defined?(:MeshTxEchoThread)
442
+ PWN.send(:remove_const, :MqttObj) if PWN.const_defined?(:MqttObj)
443
+ PWN.send(:remove_const, :MeshRxWin) if PWN.const_defined?(:MeshRxWin)
444
+ PWN.send(:remove_const, :MeshTxWin) if PWN.const_defined?(:MeshTxWin)
445
+ PWN.send(:remove_const, :MeshPi) if PWN.const_defined?(:MeshPi)
446
+ PWN.send(:remove_const, :MeshMutex) if PWN.const_defined?(:MeshMutex)
447
+ PWN.send(:remove_const, :MqttSubThread) if PWN.const_defined?(:MqttSubThread)
448
+
440
449
  mqtt_env = meshtastic_env[:mqtt]
441
450
  host = mqtt_env[:host]
442
451
  port = mqtt_env[:port]
@@ -458,6 +467,101 @@ module PWN
458
467
  psk = channel_env[:psk]
459
468
  region = channel_env[:region]
460
469
  topic = channel_env[:topic]
470
+ channel_num = channel_env[:channel_num]
471
+
472
+ # Init ncurses UI (idempotent) with separate RX (top) and TX (bottom) panes
473
+ Curses.init_screen
474
+ Curses.curs_set(0)
475
+ # Curses.crmode
476
+ # Curses.ESCDELAY = 0
477
+ Curses.start_color
478
+ Curses.use_default_colors
479
+
480
+ rx_height = Curses.lines - 4
481
+ rx_win = Curses::Window.new(rx_height, Curses.cols, 0, 0)
482
+ rx_win.scrollok(true)
483
+ rx_win.nodelay = true
484
+ rx_win.box('|', "\u2500")
485
+ rx_win.addstr("#{host}:#{port} >>> #{region}/#{topic}:#{channel_num} >>> Messages >>>\n")
486
+ rx_win.refresh
487
+
488
+ tx_win = Curses::Window.new(4, Curses.cols, rx_height, 0)
489
+ tx_win.scrollok(false)
490
+ tx_win.nodelay = true
491
+ tx_win.box('|', "\u2500")
492
+ tx_win.addstr("TX Pane (echo while typing, last sent shown after Enter)\n")
493
+ tx_win.refresh
494
+
495
+ PWN.const_set(:MeshRxWin, rx_win)
496
+ PWN.const_set(:MeshTxWin, tx_win)
497
+ PWN.const_set(:MeshMutex, Mutex.new)
498
+ PWN.const_set(:MeshPi, pi)
499
+
500
+ # Live typing echo thread (idempotent)
501
+ tx_prompt = "#{region}/#{topic}:#{channel_num} >>> "
502
+ echo_thread = Thread.new do
503
+ loop do
504
+ break unless pi.config.pwn_mesh
505
+
506
+ tx_win = PWN.const_get(:MeshTxWin)
507
+ mutex = PWN.const_get(:MeshMutex)
508
+ msg_input = pi.input.line_buffer.to_s
509
+ ts = Time.now.strftime('%H:%M:%S')
510
+ mutex.synchronize do
511
+ # Only show draft if after_read hook hasn't just written final TX (heuristic)
512
+ tx_win.clear
513
+ tx_win.box('|', "\u2500")
514
+ tx_win.setpos(1, 1)
515
+ tx_win.addstr("[#{ts}] [TX] #{tx_prompt} #{msg_input}")
516
+ tx_win.refresh
517
+ end
518
+ sleep 0.01
519
+ end
520
+ end
521
+ echo_thread.abort_on_exception = false
522
+ PWN.const_set(:MeshTxEchoThread, echo_thread)
523
+
524
+ # Start single subscriber thread (idempotent)
525
+ # {packet: {from: 2868848892, to: 4294967295, channel: 93, encrypted: :decrypted, id: 3144461425, hop_limit: 3, topic: "msh/US/UT/2/e/BroadSec/!aaff28fc", node_id_from: "!aaff28fc", node_id_to: "!ffffffff", decoded: {portnum: :TEXT_MESSAGE_APP, payload: "testy"}}, channel_id: "BroadSec", gateway_id: "!aaff28fc"}
526
+ psks = { active_channel => psk }
527
+ PWN::Plugins::ThreadPool.fill(
528
+ enumerable_array: [:mesh_sub],
529
+ max_threads: 1,
530
+ detach: true
531
+ ) do |_|
532
+ Meshtastic::MQTT.subscribe(
533
+ mqtt_obj: mqtt_obj,
534
+ region: region,
535
+ topic: topic,
536
+ channel: channel_num,
537
+ psks: psks
538
+ ) do |msg|
539
+ next unless msg.key?(:packet) && msg[:packet].key?(:decoded) && msg[:packet][:decoded].is_a?(Hash)
540
+
541
+ packet = msg[:packet]
542
+ decoded = packet[:decoded]
543
+ next unless decoded.key?(:portnum) && decoded[:portnum] == :TEXT_MESSAGE_APP
544
+
545
+ rx_win = PWN.const_get(:MeshRxWin)
546
+ mutex = PWN.const_get(:MeshMutex)
547
+
548
+ from = packet[:node_id_from]
549
+ absolute_topic = "#{region}/#{topic.gsub('#', from)}:#{channel_num}"
550
+ to = packet[:node_id_to]
551
+ rx_text = decoded[:payload]
552
+ ts = Time.now.strftime('%H:%M:%S')
553
+ mutex.synchronize do
554
+ if to == '!ffffffff'
555
+ rx_win.addstr("[#{ts}] [RX] #{absolute_topic}: #{rx_text}\n")
556
+ else
557
+ rx_win.addstr("[#{ts}] [RX][DM INTERCEPTED] #{absolute_topic} >>> #{to}: #{rx_text}\n")
558
+ end
559
+ rx_win.addstr("#{msg.inspect}\n\n\n")
560
+ rx_win.refresh
561
+ end
562
+ end
563
+ end
564
+ PWN.const_set(:MqttSubThread, true)
461
565
  rescue StandardError => e
462
566
  raise e
463
567
  end
@@ -523,8 +627,32 @@ module PWN
523
627
  pi.config.pwn_ai_debug = false if pi.config.pwn_ai_debug
524
628
  pi.config.pwn_ai_speak = false if pi.config.pwn_ai_speak
525
629
  pi.config.completer = Pry::InputCompleter
526
- PWN.send(:remove_const, :MqttObj) if PWN.const_defined?(:MqttObj)
527
- pi.config.pwn_mesh = false if pi.config.pwn_mesh
630
+ return unless pi.config.pwn_mesh
631
+
632
+ pi.config.pwn_mesh = false
633
+ # Stop echo thread
634
+ if PWN.const_defined?(:MeshTxEchoThread)
635
+ PWN.const_get(:MeshTxEchoThread).kill
636
+ PWN.send(:remove_const, :MeshTxEchoThread)
637
+ end
638
+
639
+ if PWN.const_defined?(:MqttObj)
640
+ Meshtastic::MQTT.disconnect(mqtt_obj: PWN.const_get(:MqttObj))
641
+ PWN.send(:remove_const, :MqttObj)
642
+ end
643
+
644
+ if PWN.const_defined?(:MeshRxWin)
645
+ PWN.const_get(:MeshRxWin).close
646
+ PWN.send(:remove_const, :MeshRxWin)
647
+ end
648
+ if PWN.const_defined?(:MeshTxWin)
649
+ PWN.const_get(:MeshTxWin).close
650
+ PWN.send(:remove_const, :MeshTxWin)
651
+ end
652
+ PWN.send(:remove_const, :MeshPi) if PWN.const_defined?(:MeshPi)
653
+ PWN.send(:remove_const, :MeshMutex) if PWN.const_defined?(:MeshMutex)
654
+ PWN.send(:remove_const, :MqttSubThread) if PWN.const_defined?(:MqttSubThread)
655
+ Curses.close_screen
528
656
  end
529
657
  end
530
658
  rescue StandardError => e
@@ -656,21 +784,18 @@ module PWN
656
784
  psks = {}
657
785
  psks[active_channel] = psk
658
786
 
659
- text = pi.input.line_buffer
787
+ tx_text = pi.input.line_buffer.to_s
660
788
  to = '!ffffffff'
661
- # If text include @! with 8 byte length OR
662
- if text.include?('@!')
663
- to_raw = text.split('@').last.chomp[0..8]
789
+ # If text include @! with 8 byte length,
790
+ # send DM to that address
791
+ if tx_text.include?('@!')
792
+ to_raw = tx_text.split('@').last.chomp[0..8]
664
793
  # If to_raw[1..-1] is hex than set to = to_raw
665
794
  to = to_raw if to_raw[1..-1].match?(/^[a-fA-F0-9]{8}$/)
666
- text.gsub!("@#{to_raw}", '')
795
+ # Remove any spaces from beginning of to_raw
796
+ tx_text.gsub!("@#{to_raw}", '').strip!
667
797
  end
668
- puts "\nFrom: #{from}"
669
- puts "To: #{to}"
670
- puts "Region: #{region}"
671
- puts "Topic: #{topic}"
672
- puts "Channel #: #{channel_num}"
673
- puts "Text: #{text}\n\n"
798
+
674
799
  Meshtastic::MQTT.send_text(
675
800
  mqtt_obj: mqtt_obj,
676
801
  from: from,
@@ -678,9 +803,24 @@ module PWN
678
803
  region: region,
679
804
  topic: topic,
680
805
  channel: channel_num,
681
- text: text,
806
+ text: tx_text,
682
807
  psks: psks
683
808
  )
809
+
810
+ # Update TX pane (bottom) with last sent message
811
+ if PWN.const_defined?(:MeshTxWin)
812
+ tx_win = PWN.const_get(:MeshTxWin)
813
+ mutex = PWN.const_get(:MeshMutex)
814
+ absolute_topic = "#{region}/#{topic.gsub('#', from)}:#{channel_num}"
815
+ ts = Time.now.strftime('%H:%M:%S')
816
+ mutex.synchronize do
817
+ tx_win.clear
818
+ tx_win.box('|', "\u2500")
819
+ tx_win.setpos(1, 1)
820
+ tx_win.addstr("[#{ts}] [TX] #{absolute_topic} >>> #{to}: #{tx_text}")
821
+ tx_win.refresh
822
+ end
823
+ end
684
824
  end
685
825
  end
686
826
  rescue StandardError => e
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.475'
4
+ VERSION = '0.5.476'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pwn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.475
4
+ version: 0.5.476
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -205,6 +205,20 @@ dependencies:
205
205
  - - '='
206
206
  - !ruby/object:Gem::Version
207
207
  version: 7.0.0
208
+ - !ruby/object:Gem::Dependency
209
+ name: curses
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - '='
213
+ - !ruby/object:Gem::Version
214
+ version: 1.5.3
215
+ type: :runtime
216
+ prerelease: false
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - '='
220
+ - !ruby/object:Gem::Version
221
+ version: 1.5.3
208
222
  - !ruby/object:Gem::Dependency
209
223
  name: diffy
210
224
  requirement: !ruby/object:Gem::Requirement