pwn 0.5.480 → 0.5.482

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: be16c14d474f80d808e7389475a17ec6d7dc52cb4da44395ef516810a7054ae2
4
- data.tar.gz: 4e33ef6a047b000581e6fa1aa94ac177f00324ef011f60b43ad0fa2a01435c11
3
+ metadata.gz: f08d37f0ef017a76ac0b7bfea02093a55aebd68954ac13761b6969d4937e44f6
4
+ data.tar.gz: e187138d17aac3845fb86a3166bd7b31013110cd93113227bde83ede9cf4041d
5
5
  SHA512:
6
- metadata.gz: 1c561adc55354180fc0d53ca294e83693ee8ddcd625fe75f99b357e5cfb697bc95fdb9326c01e939c22f2cf2f80ff1bc00fe8a4b111e3eb889bffa02992dd248
7
- data.tar.gz: 841188de9b7921592cf1baa653e74eab49a7039468506930d095cdbe6076bfcd55071c99554d010ce1642e88d9730bf623fa912d67ee05a30ae4902c4790d7ef
6
+ metadata.gz: cd0f3d1caa5a0cc98d79daeb267a7577a1ef2fa1ec40eff5a707743206c9c2cbf4fa6c5fcba124be4bddef90331e91466bbddb94de776df605a72f5f3510e52a
7
+ data.tar.gz: 1dac1bc3dac0ab48b2572d43c1dee10e6c9f89c4389c43acfae4ffff4f8d546e7c29c8f77e95a9cf6d2b23df39fd3303ef85065d7f892037a636051d432378c1
data/.rubocop.yml CHANGED
@@ -6,7 +6,7 @@ Layout/LineLength:
6
6
  Lint/UselessRescue:
7
7
  Enabled: false
8
8
  Metrics/AbcSize:
9
- Max: 537.6
9
+ Max: 552.3
10
10
  Metrics/BlockLength:
11
11
  Max: 292
12
12
  Metrics/BlockNesting:
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.480]:001 >>> PWN.help
40
+ pwn[v0.5.482]: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.480]:001 >>> PWN.help
55
+ pwn[v0.5.482]: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.480]:001 >>> PWN.help
65
+ pwn[v0.5.482]: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:
@@ -64,19 +64,10 @@ module PWN
64
64
  end
65
65
  end
66
66
 
67
- if pi.config.pwn_mesh
68
- active_channel = PWN::Env[:plugins][:meshtastic][:channel][:active].to_s.to_sym
69
- region = PWN::Env[:plugins][:meshtastic][:channel][active_channel][:region]
70
- topic = PWN::Env[:plugins][:meshtastic][:channel][active_channel][:topic]
71
- channel_num = PWN::Env[:plugins][:meshtastic][:channel][active_channel][:channel_num]
72
-
73
- pi.config.prompt_name = "pwn.mesh:#{region}/#{topic}:#{channel_num}"
74
- name = "\001\e[1m\002\001\e[32m\002#{pi.config.prompt_name}\001\e[0m\002"
75
- dchars = "\001\e[32m\002>>>\001\e[33m\002"
76
- dchars = "\001\e[33m\002***\001\e[33m\002" if mode == :splat
77
- end
67
+ ps1_proc = "#{name}[#{version}]:#{line_count} #{dchars} ".to_s.scrub
68
+ ps1_proc = '' if pi.config.pwn_mesh
78
69
 
79
- "#{name}[#{version}]:#{line_count} #{dchars} ".to_s.scrub
70
+ ps1_proc
80
71
  end
81
72
  rescue StandardError => e
82
73
  raise e
@@ -472,24 +463,77 @@ module PWN
472
463
  # Init ncurses UI (idempotent) with separate RX (top) and TX (bottom) panes
473
464
  Curses.init_screen
474
465
  Curses.curs_set(0)
475
- # Curses.crmode
476
- # Curses.ESCDELAY = 0
466
+ Curses.noecho
467
+ Curses.cbreak
468
+ Curses.crmode
469
+ Curses.ESCDELAY = 0
477
470
  Curses.start_color
478
471
  Curses.use_default_colors
479
472
 
473
+ mesh_highlight_colors = [
474
+ { fg: Curses::COLOR_RED, bg: Curses::COLOR_WHITE },
475
+ { fg: Curses::COLOR_GREEN, bg: Curses::COLOR_BLACK },
476
+ { fg: Curses::COLOR_YELLOW, bg: Curses::COLOR_BLACK },
477
+ { fg: Curses::COLOR_BLUE, bg: Curses::COLOR_WHITE },
478
+ { fg: Curses::COLOR_CYAN, bg: Curses::COLOR_BLACK },
479
+ { fg: Curses::COLOR_MAGENTA, bg: Curses::COLOR_WHITE },
480
+ { fg: Curses::COLOR_WHITE, bg: Curses::COLOR_BLUE }
481
+ ]
482
+ mesh_highlight_colors.each_with_index do |hash, idx|
483
+ color_id = idx + 1
484
+ color_fg = hash[:fg]
485
+ color_bg = hash[:bg]
486
+ Curses.init_pair(color_id, color_fg, color_bg)
487
+ end
488
+ PWN.const_set(:MeshColors, (1..mesh_highlight_colors.length).to_a)
489
+ PWN.const_set(:MeshLastPair, PWN::MeshColors.sample)
490
+
491
+ mesh_ui_colors = []
492
+ mesh_highlight_colors.each_with_index do |hl_hash, idx|
493
+ ui_hash = {
494
+ color_id: idx + 10,
495
+ fg: hl_hash[:fg],
496
+ bg: -1
497
+ }
498
+ Curses.init_pair(ui_hash[:color_id], ui_hash[:fg], ui_hash[:bg])
499
+ mesh_ui_colors.push(ui_hash)
500
+ end
501
+
502
+ red = mesh_ui_colors[0][:color_id]
503
+ green = mesh_ui_colors[1][:color_id]
504
+ yellow = mesh_ui_colors[2][:color_id]
505
+ blue = mesh_ui_colors[3][:color_id]
506
+ cyan = mesh_ui_colors[4][:color_id]
507
+ magenta = mesh_ui_colors[5][:color_id]
508
+ white = mesh_ui_colors[6][:color_id]
509
+
480
510
  rx_height = Curses.lines - 4
481
- rx_win = Curses::Window.new(rx_height, Curses.cols, 2, 0)
511
+ rx_win = Curses::Window.new(rx_height, Curses.cols, 0, 0)
482
512
  rx_win.scrollok(true)
483
513
  rx_win.nodelay = true
484
- rx_win.box('|', "\u2500")
485
- rx_win.addstr("<<< #{host}:#{port} | #{region}/#{topic} | ch:#{channel_num} >>>\n")
514
+ rx_win.attron(Curses.color_pair(cyan) | Curses::A_BOLD)
515
+
516
+ # Make rx_header bold and green
517
+ rx_win.attron(Curses.color_pair(green) | Curses::A_BOLD)
518
+ rx_header = "<<< #{host}:#{port} | #{region}/#{topic} | ch:#{channel_num} >>>"
519
+ rx_header_len = rx_header.length
520
+ rx_header_pos = (Curses.cols / 2) - (rx_header_len / 2)
521
+ rx_win.setpos(1, rx_header_pos)
522
+ rx_win.addstr(rx_header)
523
+ rx_win.attroff(Curses.color_pair(green) | Curses::A_BOLD)
524
+ # Jump two lines below header before messages begin
525
+ rx_win.setpos(2, 0)
526
+ rx_win.attron(Curses.color_pair(cyan) | Curses::A_BOLD)
527
+ header_line = "\u2014" * Curses.cols
528
+ rx_header_bottom_line_pos = (Curses.cols / 2) - (header_line.length / 2)
529
+ rx_win.addstr(header_line)
530
+ rx_win.attroff(Curses.color_pair(cyan) | Curses::A_BOLD)
531
+
486
532
  rx_win.refresh
487
533
 
488
534
  tx_win = Curses::Window.new(4, Curses.cols, rx_height, 0)
489
535
  tx_win.scrollok(false)
490
536
  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
537
  tx_win.refresh
494
538
 
495
539
  PWN.const_set(:MeshRxWin, rx_win)
@@ -498,24 +542,59 @@ module PWN
498
542
  PWN.const_set(:MeshPi, pi)
499
543
 
500
544
  # Live typing echo thread (idempotent)
501
- tx_prompt = "#{region}/#{topic}:#{channel_num} >>> "
545
+ tx_prompt = "#{region}/#{topic} >>>"
502
546
  echo_thread = Thread.new do
547
+ last_drawn = nil
503
548
  loop do
504
549
  break unless pi.config.pwn_mesh
505
550
 
506
551
  tx_win = PWN.const_get(:MeshTxWin)
507
552
  mutex = PWN.const_get(:MeshMutex)
508
553
  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
554
+ ts = Time.now.strftime('%H:%M:%S%z')
555
+ cursor_pos = Readline.point
556
+ prefix = "[#{ts}] [TX] #{tx_prompt} "
557
+ base_line = "#{prefix}#{msg_input}"
558
+ cursor_abs_index = prefix.length + cursor_pos
559
+ current_line = base_line
560
+ if current_line != last_drawn
561
+ mutex.synchronize do
562
+ tx_win.clear
563
+ tx_win.attron(Curses.color_pair(red) | Curses::A_BOLD)
564
+ tx_header_line_pos = (Curses.cols / 2) - (header_line.length / 2)
565
+ tx_win.addstr(header_line)
566
+ tx_win.attroff(Curses.color_pair(red) | Curses::A_BOLD)
567
+
568
+ tx_win.attron(Curses.color_pair(yellow) | Curses::A_BOLD)
569
+ inner_width = Curses.cols
570
+ segments = current_line.chars.each_slice(inner_width).map(&:join)
571
+ available_rows = tx_win.maxy - 1
572
+ segments.first(available_rows).each_with_index do |seg, idx|
573
+ tx_win.setpos(1 + idx, 0)
574
+ start_index = idx * inner_width
575
+ end_index = start_index + inner_width
576
+ if cursor_abs_index.between?(start_index, end_index)
577
+ cursor_col = cursor_abs_index - start_index
578
+ (0..inner_width).each do |col|
579
+ ch = seg[col] || ' '
580
+ if col == cursor_col
581
+ tx_win.attron(Curses.color_pair(red) | Curses::A_REVERSE | Curses::A_BOLD)
582
+ tx_win.addch(ch)
583
+ tx_win.attroff(Curses.color_pair(red) | Curses::A_REVERSE | Curses::A_BOLD)
584
+ else
585
+ tx_win.addch(ch)
586
+ end
587
+ end
588
+ else
589
+ tx_win.addstr(seg.ljust(inner_width))
590
+ end
591
+ end
592
+ tx_win.attroff(Curses.color_pair(yellow) | Curses::A_BOLD)
593
+ tx_win.refresh
594
+ end
595
+ last_drawn = current_line
517
596
  end
518
- sleep 0.01
597
+ sleep 0.001
519
598
  end
520
599
  end
521
600
  echo_thread.abort_on_exception = false
@@ -528,6 +607,7 @@ module PWN
528
607
  max_threads: 1,
529
608
  detach: true
530
609
  ) do |_|
610
+ last_from = nil
531
611
  Meshtastic::MQTT.subscribe(
532
612
  mqtt_obj: mqtt_obj,
533
613
  region: region,
@@ -545,22 +625,44 @@ module PWN
545
625
  mutex = PWN.const_get(:MeshMutex)
546
626
 
547
627
  from = packet[:node_id_from]
548
- absolute_topic = "#{region}/#{topic.gsub('#', from)}:#{channel_num}"
628
+ absolute_topic = "#{region}/#{topic.gsub('#', from)}"
549
629
  to = packet[:node_id_to]
550
630
  rx_text = decoded[:payload]
551
- ts = Time.now.strftime('%H:%M:%S')
631
+ ts = Time.now.strftime('%H:%M:%S%z')
632
+
633
+ # Select a random color pair different from the last used one
634
+ colors_arr = PWN.const_get(:MeshColors)
635
+ last_pair = PWN.const_get(:MeshLastPair)
636
+ PWN.send(:remove_const, :MeshLastPair)
637
+ if last_from != from
638
+ pair_choices = colors_arr.reject { |c| c == last_pair }
639
+ pair = pair_choices.sample
640
+ else
641
+ pair = last_pair
642
+ end
643
+ PWN.const_set(:MeshLastPair, pair)
644
+ rx_win.attron(Curses.color_pair(pair) | Curses::A_REVERSE)
645
+
646
+ current_line = "\n[#{ts}] [RX] #{absolute_topic} >>> #{rx_text}"
647
+ current_line = "\n[#{ts}] [RX][DM INTERCEPTED] #{absolute_topic} to: #{to} >>> #{rx_text}" unless to == '!ffffffff'
648
+
552
649
  mutex.synchronize do
553
- if to == '!ffffffff'
554
- rx_win.addstr("[#{ts}] [RX] #{absolute_topic}: #{rx_text}\n")
555
- else
556
- rx_win.addstr("[#{ts}] [RX][DM INTERCEPTED] #{absolute_topic} >>> #{to}: #{rx_text}\n")
650
+ inner_width = Curses.cols - 2
651
+ content = current_line.sub(/\A\n/, '')
652
+ segments = content.scan(/.{1,#{inner_width}}/)
653
+ segments.each do |seg|
654
+ rx_win.addstr("\n")
655
+ rx_win.setpos(rx_win.cury, 1)
656
+ line = seg.ljust(inner_width)
657
+ rx_win.addstr(line)
557
658
  end
558
- # rx_win.addstr("#{msg.inspect}\n\n\n")
559
659
  rx_win.refresh
560
660
  end
661
+ rx_win.attroff(Curses.color_pair(pair) | Curses::A_REVERSE)
662
+
663
+ last_from = from
561
664
  end
562
665
  end
563
- PWN.const_set(:MqttSubThread, true)
564
666
  rescue StandardError => e
565
667
  raise e
566
668
  end
@@ -648,6 +750,8 @@ module PWN
648
750
  PWN.const_get(:MeshTxWin).close
649
751
  PWN.send(:remove_const, :MeshTxWin)
650
752
  end
753
+ PWN.send(:remove_const, :MeshColors) if PWN.const_defined?(:MeshColors)
754
+ PWN.send(:remove_const, :MeshLastPair) if PWN.const_defined?(:MeshLastPair)
651
755
  PWN.send(:remove_const, :MeshPi) if PWN.const_defined?(:MeshPi)
652
756
  PWN.send(:remove_const, :MeshMutex) if PWN.const_defined?(:MeshMutex)
653
757
  PWN.send(:remove_const, :MqttSubThread) if PWN.const_defined?(:MqttSubThread)
@@ -805,21 +909,6 @@ module PWN
805
909
  text: tx_text,
806
910
  psks: psks
807
911
  )
808
-
809
- # Update TX pane (bottom) with last sent message
810
- if PWN.const_defined?(:MeshTxWin)
811
- tx_win = PWN.const_get(:MeshTxWin)
812
- mutex = PWN.const_get(:MeshMutex)
813
- absolute_topic = "#{region}/#{topic.gsub('#', from)}:#{channel_num}"
814
- ts = Time.now.strftime('%H:%M:%S')
815
- mutex.synchronize do
816
- tx_win.clear
817
- tx_win.box('|', "\u2500")
818
- tx_win.setpos(1, 1)
819
- tx_win.addstr("[#{ts}] [TX] #{absolute_topic} >>> #{to}: #{tx_text}")
820
- tx_win.refresh
821
- end
822
- end
823
912
  end
824
913
  end
825
914
  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.480'
4
+ VERSION = '0.5.482'
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.480
4
+ version: 0.5.482
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.