pwn 0.5.748 → 0.5.749

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: 02f94f46fc7627fd28e1b815d61a9ff8988739c4394054e21fd7001e0cbedb1b
4
- data.tar.gz: ee53f4520e4d847623db8918e3ece5c182939d17a0e8ba9984be8d98ed21237e
3
+ metadata.gz: bf86add502082e14d3299544ffc90b9e86887ada46569fa867d9ce6e0cc8560a
4
+ data.tar.gz: bb266bdff59a1187f99664eb876dc6b591a43d4f725689025e8af2325b735c00
5
5
  SHA512:
6
- metadata.gz: 6c0ee2ce5915bebaea32f40413080d0c36ed34547911deee16f7dab729f238c0fc5e7352152c5764618925a882c7046bd5b1bb5f2ae35bb23d4be3ce847498f5
7
- data.tar.gz: 032bb3d28b79afd00223158af6b2747d16fb7101b6a852dbe356ccf961d813906c44b1d0efb2972020c82f48a8518b3317f804a955a5a4f6682b153377b1161c
6
+ metadata.gz: b5191f1ecd9004cd4152ead0630c3c91df671ecb6f1f7b1c801a8a9983b6d919734b4006dca79740b9dc5be7181880ac5cddd219d343478a4c5cb66c86bad741
7
+ data.tar.gz: 8ef2550a781242412f81402b29fa5257fcf404fe5a97facd8126544aabe651cd4e26d008cf92bb040b8d675985943a06cac1800a88eb85b2b3c35bb3f79c4692
data/Gemfile CHANGED
@@ -11,7 +11,7 @@ gemspec
11
11
  # In some circumstances custom flags are passed to gems in order
12
12
  # to build appropriately. Defer to ./reinstall_pwn_gemset.sh
13
13
  # to review these custom flags (e.g. pg, serialport, etc).
14
- gem 'activesupport', '<8.1.3.1'
14
+ gem 'activesupport', '<8.1.4'
15
15
  gem 'anemone', '0.7.2'
16
16
  gem 'authy', '3.0.1'
17
17
  gem 'aws-sdk', '3.3.0'
@@ -42,6 +42,7 @@ PWN::FFI::Capstone.available(opts)
42
42
  - `cs_disasm`
43
43
  - `cs_free`
44
44
  - `cs_open`
45
+ - `cs_version`
45
46
  - `load_error`
46
47
 
47
48
  ## Source
@@ -17,8 +17,20 @@ module PWN
17
17
  CS_MODE_32 = 4
18
18
  CS_MODE_64 = 8
19
19
 
20
- # Capstone cs_insn layout used by cs_disasm.
21
- class Insn < PubFFI::Struct
20
+ # Capstone 4 stores 16 instruction bytes; Capstone 5 stores 24.
21
+ # A mismatched layout returns non-empty garbage instead of raising.
22
+ class InsnV4 < PubFFI::Struct
23
+ layout :id, :uint,
24
+ :address, :uint64,
25
+ :size, :ushort,
26
+ :bytes, [:uchar, 16],
27
+ :mnemonic, [:char, 32],
28
+ :op_str, [:char, 160],
29
+ :detail, :pointer
30
+ end
31
+
32
+ # Capstone 5 widened cs_insn.bytes from 16 to 24.
33
+ class InsnV5 < PubFFI::Struct
22
34
  layout :id, :uint,
23
35
  :address, :uint64,
24
36
  :size, :ushort,
@@ -44,6 +56,7 @@ module PWN
44
56
  attach_function :cs_disasm, %i[size_t pointer size_t uint64 size_t pointer], :size_t
45
57
  attach_function :cs_free, %i[pointer size_t], :void
46
58
  attach_function :cs_close, [:pointer], :int
59
+ attach_function :cs_version, %i[pointer pointer], :uint
47
60
  end
48
61
 
49
62
  public_class_method def self.available?(opts = {})
@@ -67,8 +80,9 @@ module PWN
67
80
  count = cs_disasm(handle.read_ulong, buf, bytes.bytesize, (opts[:address] || 0).to_i, 0, insn_ptr)
68
81
  insns = []
69
82
  base = insn_ptr.read_pointer
83
+ klass = insn_class
70
84
  count.times do |i|
71
- insn = Insn.new(base + (i * Insn.size))
85
+ insn = klass.new(base + (i * klass.size))
72
86
  insns << { address: insn[:address], mnemonic: insn[:mnemonic].to_s, op_str: insn[:op_str].to_s, size: insn[:size] }
73
87
  end
74
88
  cs_free(base, count) unless base.null?
@@ -76,6 +90,13 @@ module PWN
76
90
  { engine: 'capstone', insns: insns, count: count }
77
91
  end
78
92
 
93
+ private_class_method def self.insn_class
94
+ major = PubFFI::MemoryPointer.new(:int)
95
+ minor = PubFFI::MemoryPointer.new(:int)
96
+ cs_version(major, minor)
97
+ major.read_int >= 5 ? InsnV5 : InsnV4
98
+ end
99
+
79
100
  private_class_method def self.arch_mode(opts = {})
80
101
  arch = opts[:arch].to_s.downcase
81
102
  case arch
@@ -204,7 +204,8 @@ module PWN
204
204
 
205
205
  if opts[:engine].to_s != 'metasm' && PWN::FFI.available?(mod: :Keystone)
206
206
  begin
207
- return PWN::FFI::Keystone.assemble(opts)
207
+ row = PWN::FFI::Keystone.assemble(opts)
208
+ return row if assembled_matches?(bytes: row[:bytes], asm: asm, arch: opts[:arch])
208
209
  rescue StandardError
209
210
  nil
210
211
  end
@@ -222,7 +223,7 @@ module PWN
222
223
  if opts[:engine].to_s != 'metasm' && PWN::FFI.available?(mod: :Capstone)
223
224
  begin
224
225
  row = PWN::FFI::Capstone.disassemble(opts)
225
- return row if Array(row[:insns]).any?
226
+ return row if plausible_insns?(insns: row[:insns])
226
227
  rescue StandardError
227
228
  nil
228
229
  end
@@ -231,14 +232,34 @@ module PWN
231
232
  arch_obj = arch_object(opts)
232
233
  text = Metasm::Shellcode.disassemble(arch_obj, raw.to_s.b).to_s
233
234
  insns = text.lines.filter_map do |line|
234
- match = line.match(/^\s*(?:0x)?([0-9a-f]+)\s+(\S+)\s*(.*)$/i)
235
- next unless match
236
-
237
- { address: match[1].to_i(16), mnemonic: match[2], op_str: match[3].to_s.strip }
235
+ if (match = line.match(/^\s*([A-Za-z][A-Za-z0-9.]*)\s*(.*?)\s*;\s*@([0-9a-f]+)/))
236
+ { address: match[3].to_i(16), mnemonic: match[1], op_str: match[2].to_s.strip }
237
+ elsif (match = line.match(/^\s*(?:0x)?([0-9a-f]+)\s+(\S+)\s*(.*)$/i))
238
+ { address: match[1].to_i(16), mnemonic: match[2], op_str: match[3].to_s.strip }
239
+ end
238
240
  end
239
241
  { engine: 'metasm', insns: insns, count: insns.length, text: text }
240
242
  end
241
243
 
244
+ # A mis-laid-out libcapstone still returns rows. Reject those and use Metasm.
245
+ private_class_method def self.plausible_insns?(opts = {})
246
+ insns = Array(opts[:insns])
247
+ return false if insns.empty?
248
+
249
+ insns.all? do |insn|
250
+ insn[:mnemonic].to_s.match?(/\A[A-Za-z][A-Za-z0-9.]{0,15}\z/) && insn[:size].to_i.between?(1, 15)
251
+ end
252
+ end
253
+
254
+ # Keystone ABI mismatches can return bytes that are not the requested instructions.
255
+ private_class_method def self.assembled_matches?(opts = {})
256
+ token = opts[:asm].to_s.lines.filter_map { |line| line.strip.split(/\s+/, 2).first }.find { |word| !word.end_with?(':') }
257
+ return false if token.to_s.empty?
258
+
259
+ dis = disassemble(bytes: opts[:bytes], arch: opts[:arch], engine: 'metasm')
260
+ Array(dis[:insns]).any? { |insn| insn[:mnemonic].to_s.casecmp?(token) }
261
+ end
262
+
242
263
  public_class_method def self.list_supported_archs
243
264
  [
244
265
  { name: 'i386', endian: 'little' },
@@ -578,6 +578,8 @@ module PWN
578
578
  pltrelsz = dyn[2].to_i
579
579
  relaent = bits == 64 ? 24 : 12
580
580
  plt_base = sections['.plt.sec']&.[](:addr) || sections['.plt']&.[](:addr)
581
+ # .plt.sec slots start at the section base. Classic .plt reserves the first 16 bytes for the lazy resolver.
582
+ plt_bias = sections['.plt.sec'] ? 0 : 1
581
583
  if jmprel && pltrelsz.positive?
582
584
  rel_off = v2off.call(jmprel)
583
585
  idx = 0
@@ -593,7 +595,7 @@ module PWN
593
595
  end
594
596
  unless name.to_s.empty?
595
597
  got[name] = r_offset
596
- plt[name] = plt_base + (16 * (idx + 1)) if plt_base
598
+ plt[name] = plt_base + (16 * (idx + plt_bias)) if plt_base
597
599
  end
598
600
  idx += 1
599
601
  end
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.748'
4
+ VERSION = '0.5.749'
5
5
  end
@@ -38,7 +38,9 @@ describe 'PWN::AI::Agent::Tools exploitdev' do
38
38
  return 0;
39
39
  }
40
40
  C
41
- out, status = Open3.capture2e('cc', '-O0', '-fno-stack-protector', '-fno-pie', '-no-pie', '-o', bin, src)
41
+ out, status = Open3.capture2e('cc', '-O0', '-fno-stack-protector', '-fno-pie', '-no-pie', '-Wl,-z,ibt', '-o', bin, src)
42
+ linked = status.success? && File.binread(bin).include?('.plt.sec')
43
+ out, status = Open3.capture2e('cc', '-O0', '-fno-stack-protector', '-fno-pie', '-no-pie', '-o', bin, src) unless linked
42
44
  expect(status.success?).to eq(true), out
43
45
  elf = File.binread(bin)
44
46
  expect(elf[16, 2].unpack1('v')).to eq(2), 'cc produced a PIE binary; file addresses would not be the runtime addresses'
@@ -24,4 +24,18 @@ describe PWN::Plugins::Assembly do
24
24
  expect(blob).to match(/nop/i)
25
25
  expect(%w[capstone metasm]).to include(dis[:engine].to_s)
26
26
  end
27
+
28
+ it 'falls back to metasm when capstone returns a mis-laid-out mnemonic' do
29
+ allow(PWN::FFI).to receive(:available?).and_call_original
30
+ allow(PWN::FFI).to receive(:available?).with(mod: :Capstone).and_return(true)
31
+ allow(PWN::FFI::Capstone).to receive(:disassemble).and_return(
32
+ engine: 'capstone',
33
+ insns: [{ mnemonic: "L\xCB\xEFy", op_str: "L\xCB\xEFy", size: 4 }]
34
+ )
35
+ asm = described_class.assemble(asm: "nop\nret", arch: 'x86_64', engine: 'metasm')
36
+ dis = described_class.disassemble(bytes: asm[:bytes], arch: 'x86_64')
37
+ expect(dis[:engine]).to eq('metasm')
38
+ blob = dis[:insns].map { |row| row[:mnemonic] }.join(' ')
39
+ expect(blob).to match(/nop/i)
40
+ end
27
41
  end
@@ -89,4 +89,21 @@ describe PWN::Plugins::BinaryParser do
89
89
  expect(row[:symbols]['main'] || row[:symbols][:main]).to be_a(Integer)
90
90
  end
91
91
  end
92
+
93
+ it 'elf_resolve points system at the .plt.sec stub, not the next slot' do
94
+ Dir.mktmpdir('pwn-elf-ibt-') do |dir|
95
+ src = File.join(dir, 't.c')
96
+ path = File.join(dir, 't')
97
+ File.write(src, "#include <stdlib.h>\nint main(void) { return system(\"x\"); }\n")
98
+ out, status = Open3.capture2e('cc', '-O0', '-fno-pie', '-no-pie', '-Wl,-z,ibt', '-o', path, src)
99
+ expect(status.success?).to eq(true), out
100
+ expect(File.binread(path)).to include('.plt.sec')
101
+ row = described_class.elf_resolve(path: path)
102
+ dump, dump_status = Open3.capture2('objdump', '-d', path)
103
+ expect(dump_status.success?).to eq(true), dump
104
+ real = dump[/^([0-9a-f]+) <system@plt>:/, 1]
105
+ expect(real).not_to be_nil
106
+ expect(row[:plt]['system']).to eq(real.to_i(16))
107
+ end
108
+ end
92
109
  end
@@ -1952,6 +1952,7 @@
1952
1952
  {"messages":[{"role":"user","content":"PWN::FFI::Capstone.available? Usage"},{"role":"assistant","content":"`PWN::FFI::Capstone.available?`: "}]}
1953
1953
  {"messages":[{"role":"user","content":"PWN::FFI::Capstone.disassemble Usage"},{"role":"assistant","content":"`PWN::FFI::Capstone.disassemble`: "}]}
1954
1954
  {"messages":[{"role":"user","content":"PWN::FFI::Capstone.help Usage"},{"role":"assistant","content":"`PWN::FFI::Capstone.help`: "}]}
1955
+ {"messages":[{"role":"user","content":"PWN::FFI::Capstone.insn_class Usage"},{"role":"assistant","content":"`PWN::FFI::Capstone.insn_class`: "}]}
1955
1956
  {"messages":[{"role":"user","content":"PWN::FFI::DSPNative.authors Usage"},{"role":"assistant","content":"`PWN::FFI::DSPNative.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
1956
1957
  {"messages":[{"role":"user","content":"PWN::FFI::DSPNative.available? Usage"},{"role":"assistant","content":"`PWN::FFI::DSPNative.available?`: Supported Method Parameters\n\nPWN::FFI::DSPNative.available?\n"}]}
1957
1958
  {"messages":[{"role":"user","content":"PWN::FFI::DSPNative.cfft_mag Usage"},{"role":"assistant","content":"`PWN::FFI::DSPNative.cfft_mag`: Supported Method Parameters\n\nmagnitudes = PWN::FFI::DSPNative.cfft_mag(iq:, n:) Power-of-two FFT; input is zero-padded/truncated, output unshifted.\n"}]}
@@ -2188,11 +2189,13 @@
2188
2189
  {"messages":[{"role":"user","content":"PWN::Plugins::Assembly.arch_object Usage"},{"role":"assistant","content":"`PWN::Plugins::Assembly.arch_object`: "}]}
2189
2190
  {"messages":[{"role":"user","content":"PWN::Plugins::Assembly.asm_to_opcodes Usage"},{"role":"assistant","content":"`PWN::Plugins::Assembly.asm_to_opcodes`: Supported Method Parameters\n\nPWN::Plugins::Assembly.asm_to_opcodes(\n\nasm: 'required - assembly instruction(s) (e.g. 'nop\\nnop\\nnop\\njmp rsp\\n)',\narch: 'optional - architecture returned from objdump --info (defaults to PWN::Plugins::DetectOS.arch)',\nendian: 'optional - endianess :big|:little (defaults to current system endianess)'\n\n)\n"}]}
2190
2191
  {"messages":[{"role":"user","content":"PWN::Plugins::Assembly.assemble Usage"},{"role":"assistant","content":"`PWN::Plugins::Assembly.assemble`: "}]}
2192
+ {"messages":[{"role":"user","content":"PWN::Plugins::Assembly.assembled_matches? Usage"},{"role":"assistant","content":"`PWN::Plugins::Assembly.assembled_matches?`: "}]}
2191
2193
  {"messages":[{"role":"user","content":"PWN::Plugins::Assembly.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::Assembly.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
2192
2194
  {"messages":[{"role":"user","content":"PWN::Plugins::Assembly.disassemble Usage"},{"role":"assistant","content":"`PWN::Plugins::Assembly.disassemble`: "}]}
2193
2195
  {"messages":[{"role":"user","content":"PWN::Plugins::Assembly.help Usage"},{"role":"assistant","content":"`PWN::Plugins::Assembly.help`: "}]}
2194
2196
  {"messages":[{"role":"user","content":"PWN::Plugins::Assembly.list_supported_archs Usage"},{"role":"assistant","content":"`PWN::Plugins::Assembly.list_supported_archs`: "}]}
2195
2197
  {"messages":[{"role":"user","content":"PWN::Plugins::Assembly.opcodes_to_asm Usage"},{"role":"assistant","content":"`PWN::Plugins::Assembly.opcodes_to_asm`: Supported Method Parameters\n\nPWN::Plugins::Assembly.opcodes_to_asm(\n\nopcodes: 'required - hex escaped opcode(s) (e.g. \"\\x90\\x90\\x90\")',\nopcodes_always_string_obj: 'optional - always interpret opcodes passed in as a string object (defaults to false)',\narch: 'optional - architecture returned from objdump --info (defaults to PWN::Plugins::DetectOS.arch)',\nendian: 'optional - endianess :big|:little (defaults to current system endianess)'\n\n)\n"}]}
2198
+ {"messages":[{"role":"user","content":"PWN::Plugins::Assembly.plausible_insns? Usage"},{"role":"assistant","content":"`PWN::Plugins::Assembly.plausible_insns?`: "}]}
2196
2199
  {"messages":[{"role":"user","content":"PWN::Plugins::AuthenticationHelper.authors Usage"},{"role":"assistant","content":"`PWN::Plugins::AuthenticationHelper.authors`: Author(s)\n\n0day Inc. <support@0dayinc.com>\n"}]}
2197
2200
  {"messages":[{"role":"user","content":"PWN::Plugins::AuthenticationHelper.help Usage"},{"role":"assistant","content":"`PWN::Plugins::AuthenticationHelper.help`: "}]}
2198
2201
  {"messages":[{"role":"user","content":"PWN::Plugins::AuthenticationHelper.mask_password Usage"},{"role":"assistant","content":"`PWN::Plugins::AuthenticationHelper.mask_password`: Supported Method Parameters\n\nPWN::Plugins::AuthenticationHelper.mask_password(\n\nprompt: 'optional - string to display at prompt (Default: Password)'\n\n)\n"}]}
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.748
4
+ version: 0.5.749
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "<"
17
17
  - !ruby/object:Gem::Version
18
- version: 8.1.3.1
18
+ version: 8.1.4
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "<"
24
24
  - !ruby/object:Gem::Version
25
- version: 8.1.3.1
25
+ version: 8.1.4
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: anemone
28
28
  requirement: !ruby/object:Gem::Requirement