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 +4 -4
- data/Gemfile +1 -1
- data/etc/default_skills/pwn/ffi/capstone/SKILL.md +1 -0
- data/lib/pwn/ffi/capstone.rb +24 -3
- data/lib/pwn/plugins/assembly.rb +27 -6
- data/lib/pwn/plugins/binary_parser.rb +3 -1
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/ai/agent/tools/exploitdev_spec.rb +3 -1
- data/spec/lib/pwn/plugins/assembly_spec.rb +14 -0
- data/spec/lib/pwn/plugins/binary_parser_spec.rb +17 -0
- data/third_party/pwn_rdoc.jsonl +3 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bf86add502082e14d3299544ffc90b9e86887ada46569fa867d9ce6e0cc8560a
|
|
4
|
+
data.tar.gz: bb266bdff59a1187f99664eb876dc6b591a43d4f725689025e8af2325b735c00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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'
|
data/lib/pwn/ffi/capstone.rb
CHANGED
|
@@ -17,8 +17,20 @@ module PWN
|
|
|
17
17
|
CS_MODE_32 = 4
|
|
18
18
|
CS_MODE_64 = 8
|
|
19
19
|
|
|
20
|
-
# Capstone
|
|
21
|
-
|
|
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 =
|
|
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
|
data/lib/pwn/plugins/assembly.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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*(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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 +
|
|
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
|
@@ -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
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
25
|
+
version: 8.1.4
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: anemone
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|