pwn 0.5.745 → 0.5.748

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: f3d60fd604a668b531459517b97dfee6448f9485679bd4c25bfe3ec12090cee9
4
- data.tar.gz: 64a4dd65248017933e877c03db222ce61741e60044a03a1c9c9a1297ca7a4fde
3
+ metadata.gz: 02f94f46fc7627fd28e1b815d61a9ff8988739c4394054e21fd7001e0cbedb1b
4
+ data.tar.gz: ee53f4520e4d847623db8918e3ece5c182939d17a0e8ba9984be8d98ed21237e
5
5
  SHA512:
6
- metadata.gz: 1792ff5aea6a7c406248ab0fd6647732c490e56450afdf768840f340b4d7b4847a2d2cd996ff1349b3ca1a1bbfc05be40310225345e675937cc16022aaad5ec3
7
- data.tar.gz: 7155e5d3cfca02bda195d0b0f926b85d6cd95b306b570d5956484ae237d360b36628a979e2d98a2c5ce3008343c7cecc1d14f12d0a986dbc577923a9f7905930
6
+ metadata.gz: 6c0ee2ce5915bebaea32f40413080d0c36ed34547911deee16f7dab729f238c0fc5e7352152c5764618925a882c7046bd5b1bb5f2ae35bb23d4be3ce847498f5
7
+ data.tar.gz: 032bb3d28b79afd00223158af6b2747d16fb7101b6a852dbe356ccf961d813906c44b1d0efb2972020c82f48a8518b3317f804a955a5a4f6682b153377b1161c
data/Gemfile CHANGED
@@ -50,7 +50,7 @@ gem 'libusb', '0.8.0'
50
50
  gem 'luhn', '3.0.0'
51
51
  gem 'mail', '2.9.1'
52
52
  gem 'mcp', '1.6.0'
53
- gem 'meshtastic', '0.0.184'
53
+ gem 'meshtastic', '0.0.186'
54
54
  gem 'metasm', '1.0.6'
55
55
  gem 'mongo', '2.26.0'
56
56
  gem 'msfrpc-client', '1.1.2'
@@ -77,7 +77,7 @@ gem 'rbvmomi2', '3.10.0'
77
77
  gem 'rdoc', '7.0.4'
78
78
  gem 'rest-client', '2.1.0'
79
79
  gem 'rex', '2.0.13'
80
- gem 'rmagick', '7.1.4'
80
+ gem 'rmagick', '7.1.5'
81
81
  gem 'rqrcode', '3.2.0'
82
82
  gem 'rspec', '3.13.2'
83
83
  gem 'rtesseract', '3.1.4'
@@ -22,6 +22,7 @@ PWN::AI::Agent::Registry.register(
22
22
  arch: { type: 'string' },
23
23
  offset: { type: 'integer' },
24
24
  extra: { type: 'string' },
25
+ pad: { type: 'integer' },
25
26
  cmd: { type: 'string' },
26
27
  host: { type: 'string' },
27
28
  port: { type: 'integer' },
@@ -55,7 +56,7 @@ PWN::AI::Agent::Registry.register(
55
56
  when 'io'
56
57
  PWN::Plugins::ExploitDev.io(kind: args[:kind] || 'process', cmd: args[:cmd] || args[:path], host: args[:host], port: args[:port])
57
58
  when 'ret2libc'
58
- PWN::Plugins::ExploitDev.ret2libc(path: args[:path], offset: args[:offset], extra: args[:extra])
59
+ PWN::Plugins::ExploitDev.ret2libc(path: args[:path], offset: args[:offset], extra: args[:extra], pad: args[:pad])
59
60
  when 'checksec'
60
61
  PWN::Plugins::GDB.mitigations(binary: args[:path])
61
62
  else
@@ -231,7 +231,9 @@ module PWN
231
231
  offset = (opts[:offset] || 72).to_i
232
232
  raise 'ERROR: missing pop rdi, system, or argument address' unless pop && system && arg
233
233
 
234
- align = opts[:ret] || Array(gadgets[:gadgets]).find { |row| row[:gadget].to_s == 'ret' }&.fetch(:address, nil)
234
+ align = opts[:ret]
235
+ align ||= pop + 1 if pop && Array(gadgets[:gadgets]).any? { |row| row[:address] == pop && row[:gadget].to_s.match?(/pop rdi; ret/i) }
236
+ align ||= Array(gadgets[:gadgets]).find { |row| row[:gadget].to_s == 'ret' && row[:address] != pop }&.fetch(:address, nil)
235
237
  packed = [pop, arg]
236
238
  packed << align if align && align != pop
237
239
  packed << system
@@ -100,6 +100,8 @@ module PWN
100
100
  rescue IO::WaitReadable
101
101
  t[:r].wait_readable(0.2)
102
102
  retry
103
+ rescue EOFError
104
+ return consume_buf(tube: t, bytes: t[:buf].bytesize)
103
105
  end
104
106
  end
105
107
  end
@@ -149,11 +149,14 @@ module PWN
149
149
  end
150
150
  end
151
151
  PWN_MESH_TRANSPORTS = %i[serial bluetooth tcp mqtt].freeze
152
- # meshtastic 0.0.184 loses protobuf defaults in Data#to_h. Empty
153
- # decoded data is not an unsupported application payload.
152
+ # meshtastic 0.0.184 Data#to_h omits empty payload bytes, and an
153
+ # unknown PortNum stays an integer (51 is not in the enum). That is
154
+ # not an undecodable application payload. Bytes on an unsupported
155
+ # port still warn.
154
156
  EMPTY_DATA_DECODER = Module.new do
155
157
  define_method(:decode_payload) do |opts = {}|
156
- return nil if opts[:payload].nil? && opts[:msg_type].nil?
158
+ payload = opts[:payload]
159
+ return nil if payload.nil? || payload == ''
157
160
 
158
161
  super(opts)
159
162
  end
@@ -1140,6 +1143,9 @@ module PWN
1140
1143
  text = opts[:text].to_s.dup.force_encoding(Encoding::UTF_8).scrub
1141
1144
  text.gsub!(/\e\[[0-9;?]*[A-Za-z]/, '')
1142
1145
  text.gsub!(/[\x00-\x08\x0b\x0c\x0e-\x1f]/, '')
1146
+ # Serial.subscribe prints this on startup. It is not a diagnostic,
1147
+ # and capturing it opens a NOTICE before the TUI can be used.
1148
+ text.gsub!('Subscribing to serial FromRadio stream...', '')
1143
1149
  text.strip!
1144
1150
  text.empty? ? nil : text
1145
1151
  end
@@ -81,21 +81,16 @@ module PWN
81
81
 
82
82
  private_class_method def self.run!(opts = {})
83
83
  argv = opts[:argv]
84
- if Open3.respond_to?(:capture3)
85
- out, err, st = Open3.capture3(*argv)
86
- raise IOError, "#{argv.first} failed: #{err}" unless st.success? || opts[:allow_nonzero] == true
84
+ out, st = Open3.capture2(*argv)
85
+ raise IOError, "#{argv.first} failed" unless st.success? || opts[:allow_nonzero] == true
87
86
 
88
- else
89
- out, = Open3.capture2(*argv)
90
- end
91
- out
92
- rescue Errno::ENOENT
93
- out, = Open3.capture2(*opts[:argv])
94
87
  out
95
88
  end
96
89
 
97
90
  private_class_method def self.parse_grype(opts = {})
98
91
  out = run!(argv: ['grype', '-o', 'json', opts[:target]], allow_nonzero: true)
92
+ raise IOError, 'grype returned no JSON' if out.to_s.strip.empty?
93
+
99
94
  json = JSON.parse(out)
100
95
  Array(json['matches']).map do |row|
101
96
  art = row['artifact'] || {}
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.745'
4
+ VERSION = '0.5.748'
5
5
  end
@@ -27,25 +27,35 @@ describe 'PWN::AI::Agent::Tools exploitdev' do
27
27
  void keep(void) { __asm__ volatile("pop %rdi; ret"); }
28
28
  int main(int argc, char **argv) {
29
29
  char buf[64];
30
+ void *frame = __builtin_frame_address(0);
30
31
  (void)argv;
31
32
  if (argc > 100) { system(cmd); keep(); }
33
+ char line[32];
34
+ snprintf(line, sizeof(line), "%ld", (long)((char *)frame + sizeof(void *) - buf));
35
+ puts(line);
36
+ fflush(stdout);
32
37
  read(0, buf, 256);
33
38
  return 0;
34
39
  }
35
40
  C
36
- out, status = Open3.capture2e('cc', '-O0', '-fno-stack-protector', '-no-pie', '-o', bin, src)
41
+ out, status = Open3.capture2e('cc', '-O0', '-fno-stack-protector', '-fno-pie', '-no-pie', '-o', bin, src)
37
42
  expect(status.success?).to eq(true), out
43
+ elf = File.binread(bin)
44
+ expect(elf[16, 2].unpack1('v')).to eq(2), 'cc produced a PIE binary; file addresses would not be the runtime addresses'
45
+ io = PWN::Plugins::ExploitDev.io(cmd: [bin], pty: false)
46
+ offset = PWN::Plugins::ProcessTube.recvuntil(id: io[:id], until: "\n", timeout: 3).to_i
47
+ expect(offset).to be > 64
38
48
  raw = PWN::AI::Agent::Dispatch.call(
39
- tool_call: { function: { name: 'exploitdev', arguments: JSON.generate(action: 'ret2libc', path: bin, offset: 72, extra: 'cmd') } },
49
+ tool_call: { function: { name: 'exploitdev', arguments: JSON.generate(action: 'ret2libc', path: bin, offset: offset, extra: 'cmd') } },
40
50
  scope_path: File.join(dir, 'absent.yaml')
41
51
  )
42
52
  row = JSON.parse(raw, symbolize_names: true).fetch(:result)
53
+ row = JSON.parse(File.binread(row[:artifact][:path]), symbolize_names: true) if row[:artifact]
43
54
  expect(row[:payload]).not_to be_empty
44
55
  expect(row[:plt].keys.map(&:to_s)).to include('system')
45
56
  expect(row[:pop_rdi]).to be_a(Integer)
46
57
  packed = [row[:payload_hex] || row['payload_hex']].pack('H*')
47
58
  packed = row[:payload].to_s if packed.empty?
48
- io = PWN::Plugins::ExploitDev.io(cmd: [bin], pty: false)
49
59
  PWN::Plugins::ProcessTube.send_raw(id: io[:id], bytes: packed)
50
60
  out = PWN::Plugins::ProcessTube.recvuntil(id: io[:id], until: 'RET2LIBC_OK', timeout: 3)
51
61
  expect(out.to_s).to include('RET2LIBC_OK')
@@ -32,12 +32,14 @@ describe PWN::Plugins::Jobs do
32
32
  end
33
33
 
34
34
  it 'survives an exited creator and records completion in a fresh session' do
35
- row = fresh("puts JSON.generate(PWN::Plugins::Jobs.start(command: 'sleep 2; printf durable', max_runtime: 21600, session_id: 'old'))")
35
+ gate = File.join(@root, 'release')
36
+ row = fresh("puts JSON.generate(PWN::Plugins::Jobs.start(command: 'while [ ! -f #{gate} ]; do sleep 0.05; done; printf durable', max_runtime: 21600, session_id: 'old'))")
36
37
  expect(row[:status]).to eq('RUNNING')
37
38
  expect(row[:worker_pid]).to be_a(Integer)
38
39
  expect(row[:worker_identity]).not_to be_nil
39
40
  worker_session = Process.getsid(row[:worker_pid])
40
41
  expect(fresh("puts JSON.generate(PWN::Plugins::Jobs.status(id: '#{row[:id]}'))")[:status]).to eq('RUNNING')
42
+ File.write(gate, '1')
41
43
  done = finished(row[:id])
42
44
  expect(worker_session).to eq(row[:worker_pid])
43
45
  expect(done).to include(status: 'COMPLETED', exit_code: 0, max_runtime: 21_600, session_id: 'old')
@@ -18,8 +18,26 @@ describe PWN::Plugins::REPL, 'mesh compose history' do
18
18
  described_class.send(:mesh_subscribe, obj: { from_radio_queue: queue }, psks: {}, on_message: proc { |message| received << message })
19
19
  expect(received.size).to eq(2)
20
20
  expect(output.string).not_to include("Can't decode")
21
- Meshtastic::MeshInterface.new.decode_payload(payload: 'unsupported', msg_type: :PRIVATE_APP)
22
- expect(output.string).to include("Can't decode", 'unsupported')
21
+ # meshtastic 0.0.186 returns unknown application bytes instead of warning.
22
+ decoded = Meshtastic::MeshInterface.new.decode_payload(payload: 'unsupported', msg_type: :PRIVATE_APP)
23
+ expect(decoded).to eq('unsupported')
24
+ expect(output.string).not_to include("Can't decode")
25
+ ensure
26
+ $stdout = old
27
+ end
28
+
29
+ it 'does not warn when a packet has an unknown portnum and no payload' do
30
+ stub_const('PWN::MeshTransport', :serial)
31
+ queue = Queue.new
32
+ encoded = Meshtastic::FromRadio.new(packet: { decoded: Meshtastic::Data.new(portnum: 51) }).to_proto
33
+ queue << Meshtastic::FromRadio.decode(encoded)
34
+ queue.close
35
+ output = StringIO.new
36
+ old = $stdout
37
+ $stdout = output
38
+ described_class.send(:mesh_subscribe, obj: { from_radio_queue: queue }, psks: {}, on_message: proc { |_message| })
39
+ expect(output.string).not_to include("Can't decode")
40
+ expect(output.string).not_to include('portnum: 51')
23
41
  ensure
24
42
  $stdout = old
25
43
  end
@@ -69,6 +87,25 @@ describe PWN::Plugins::REPL, 'mesh compose history' do
69
87
  expect(events).to be_empty
70
88
  end
71
89
 
90
+ it 'does not open a notice for the serial subscribe banner' do
91
+ events = Queue.new
92
+ stub_const('PWN::MeshEvents', events)
93
+ described_class.send(:mesh_capture_output, run: proc {
94
+ puts 'Subscribing to serial FromRadio stream...'
95
+ })
96
+ expect(events).to be_empty
97
+ end
98
+
99
+ it 'keeps a warning that shares a chunk with the serial subscribe banner' do
100
+ events = Queue.new
101
+ stub_const('PWN::MeshEvents', events)
102
+ described_class.send(:mesh_capture_output, run: proc {
103
+ $stdout.write("Subscribing to serial FromRadio stream...\nWARNING: decoder fixture\n")
104
+ })
105
+ expect(events.pop(true)[:notice]).to eq('WARNING: decoder fixture')
106
+ expect(events).to be_empty
107
+ end
108
+
72
109
  it 'captures dependency stdout and stderr and restores both streams on failure' do
73
110
  events = Queue.new
74
111
  stub_const('PWN::MeshEvents', events)
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.745
4
+ version: 0.5.748
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -519,14 +519,14 @@ dependencies:
519
519
  requirements:
520
520
  - - '='
521
521
  - !ruby/object:Gem::Version
522
- version: 0.0.184
522
+ version: 0.0.186
523
523
  type: :development
524
524
  prerelease: false
525
525
  version_requirements: !ruby/object:Gem::Requirement
526
526
  requirements:
527
527
  - - '='
528
528
  - !ruby/object:Gem::Version
529
- version: 0.0.184
529
+ version: 0.0.186
530
530
  - !ruby/object:Gem::Dependency
531
531
  name: metasm
532
532
  requirement: !ruby/object:Gem::Requirement
@@ -897,14 +897,14 @@ dependencies:
897
897
  requirements:
898
898
  - - '='
899
899
  - !ruby/object:Gem::Version
900
- version: 7.1.4
900
+ version: 7.1.5
901
901
  type: :development
902
902
  prerelease: false
903
903
  version_requirements: !ruby/object:Gem::Requirement
904
904
  requirements:
905
905
  - - '='
906
906
  - !ruby/object:Gem::Version
907
- version: 7.1.4
907
+ version: 7.1.5
908
908
  - !ruby/object:Gem::Dependency
909
909
  name: rqrcode
910
910
  requirement: !ruby/object:Gem::Requirement