pwn 0.5.739 → 0.5.741
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 +2 -2
- data/documentation/Agent-Tool-Registry.md +163 -6
- data/documentation/Diagrams.md +1 -1
- data/documentation/Home.md +1 -1
- data/documentation/How-PWN-Works.md +1 -1
- data/documentation/Plugin-Degradation.md +6 -2
- data/documentation/Recon-Findings-API.md +33 -1
- data/documentation/What-is-PWN.md +1 -1
- data/documentation/pwn-ai-Agent.md +1 -1
- data/etc/default_skills/pwn/ai/agent/confirmation/SKILL.md +49 -0
- data/etc/default_skills/pwn/ai/agent/result/SKILL.md +4 -1
- data/etc/default_skills/pwn/ai/agent/task_dag/SKILL.md +50 -0
- data/etc/default_skills/pwn/ai/agent/tool_guard/SKILL.md +2 -0
- data/etc/default_skills/pwn/ai/agent/tools/browser_goto/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/loot_query/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/nmap_scan/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/nuclei_scan/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/tools/radare2/SKILL.md +45 -0
- data/etc/default_skills/pwn/ai/agent/turn_finalizer/SKILL.md +3 -0
- data/etc/default_skills/pwn/ai/router/SKILL.md +51 -0
- data/etc/default_skills/pwn/engagement/SKILL.md +2 -0
- data/etc/default_skills/pwn/ffi/capstone/SKILL.md +54 -0
- data/etc/default_skills/pwn/ffi/keystone/SKILL.md +50 -0
- data/etc/default_skills/pwn/plugins/aflplusplus/SKILL.md +5 -1
- data/etc/default_skills/pwn/plugins/ai_sandbox/SKILL.md +52 -0
- data/etc/default_skills/pwn/plugins/artifact_registry/SKILL.md +3 -0
- data/etc/default_skills/pwn/plugins/assembly/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/binary_parser/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/exploit_dev/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/findings/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/gdbmi/SKILL.md +8 -2
- data/etc/default_skills/pwn/plugins/httpx/SKILL.md +49 -0
- data/etc/default_skills/pwn/plugins/jobs/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/method_catalog/SKILL.md +50 -0
- data/etc/default_skills/pwn/plugins/nmap_it/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/nuclei/SKILL.md +2 -1
- data/etc/default_skills/pwn/plugins/process_tube/SKILL.md +2 -0
- data/etc/default_skills/pwn/plugins/radare2/SKILL.md +3 -0
- data/etc/default_skills/pwn/plugins/recon/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/rop/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/transparent_browser/SKILL.md +1 -0
- data/etc/default_skills/pwn/plugins/vault/SKILL.md +3 -0
- data/etc/default_skills/pwn/reports/SKILL.md +2 -0
- data/lib/pwn/ai/agent/confirmation.rb +163 -0
- data/lib/pwn/ai/agent/dispatch.rb +43 -5
- data/lib/pwn/ai/agent/loop.rb +45 -11
- data/lib/pwn/ai/agent/prompt_builder.rb +7 -4
- data/lib/pwn/ai/agent/registry.rb +2 -1
- data/lib/pwn/ai/agent/result.rb +76 -6
- data/lib/pwn/ai/agent/task_dag.rb +220 -0
- data/lib/pwn/ai/agent/tool_guard.rb +166 -24
- data/lib/pwn/ai/agent/tools/artifacts.rb +35 -5
- data/lib/pwn/ai/agent/tools/binary_triage.rb +7 -3
- data/lib/pwn/ai/agent/tools/browser_goto.rb +32 -0
- data/lib/pwn/ai/agent/tools/debug_lane.rb +23 -0
- data/lib/pwn/ai/agent/tools/exploitdev.rb +32 -12
- data/lib/pwn/ai/agent/tools/finding_record.rb +31 -5
- data/lib/pwn/ai/agent/tools/fuzz_campaign.rb +44 -21
- data/lib/pwn/ai/agent/tools/job_run.rb +30 -18
- data/lib/pwn/ai/agent/tools/loot_query.rb +44 -0
- data/lib/pwn/ai/agent/tools/nmap_scan.rb +32 -0
- data/lib/pwn/ai/agent/tools/nuclei_scan.rb +31 -0
- data/lib/pwn/ai/agent/tools/radare2.rb +55 -0
- data/lib/pwn/ai/agent/tools/ruby_eval.rb +19 -2
- data/lib/pwn/ai/agent/tools/shell.rb +22 -10
- data/lib/pwn/ai/agent/turn_finalizer.rb +46 -5
- data/lib/pwn/ai/agent.rb +3 -1
- data/lib/pwn/ai/cli.rb +26 -2
- data/lib/pwn/ai/context.rb +4 -2
- data/lib/pwn/ai/router.rb +196 -0
- data/lib/pwn/ai/tools/README.md +5 -0
- data/lib/pwn/ai.rb +1 -0
- data/lib/pwn/config.rb +10 -0
- data/lib/pwn/engagement.rb +45 -1
- data/lib/pwn/ffi/capstone.rb +120 -0
- data/lib/pwn/ffi/keystone.rb +107 -0
- data/lib/pwn/ffi.rb +2 -0
- data/lib/pwn/migrate.rb +5 -1
- data/lib/pwn/plugins/aflplusplus.rb +238 -13
- data/lib/pwn/plugins/ai_sandbox.rb +240 -0
- data/lib/pwn/plugins/artifact_registry.rb +188 -22
- data/lib/pwn/plugins/assembly.rb +73 -2
- data/lib/pwn/plugins/basic_auth.rb +17 -4
- data/lib/pwn/plugins/binary_parser.rb +439 -38
- data/lib/pwn/plugins/exploit_dev.rb +83 -14
- data/lib/pwn/plugins/findings.rb +208 -42
- data/lib/pwn/plugins/gdbmi.rb +265 -38
- data/lib/pwn/plugins/httpx.rb +93 -0
- data/lib/pwn/plugins/jobs.rb +260 -102
- data/lib/pwn/plugins/method_catalog.rb +328 -0
- data/lib/pwn/plugins/nmap_it.rb +157 -26
- data/lib/pwn/plugins/nuclei.rb +122 -29
- data/lib/pwn/plugins/preflight_checker.rb +3 -2
- data/lib/pwn/plugins/process_tube.rb +51 -2
- data/lib/pwn/plugins/radare2.rb +120 -26
- data/lib/pwn/plugins/recon.rb +33 -0
- data/lib/pwn/plugins/repl/mesh.rb +203 -13
- data/lib/pwn/plugins/rop.rb +121 -31
- data/lib/pwn/plugins/transparent_browser.rb +212 -14
- data/lib/pwn/plugins/vault.rb +182 -24
- data/lib/pwn/plugins.rb +4 -0
- data/lib/pwn/reports/engagement.rb +8 -2
- data/lib/pwn/reports/html.rb +28 -9
- data/lib/pwn/reports/json.rb +2 -1
- data/lib/pwn/reports/markdown.rb +49 -12
- data/lib/pwn/reports/sarif.rb +17 -3
- data/lib/pwn/reports.rb +184 -34
- data/lib/pwn/version.rb +1 -1
- data/spec/integration/reports_spec.rb +93 -0
- data/spec/integration/result_condition_spec.rb +11 -8
- data/spec/lib/pwn/ai/agent/confirmation_spec.rb +59 -0
- data/spec/lib/pwn/ai/agent/dispatch_confirmation_spec.rb +52 -0
- data/spec/lib/pwn/ai/agent/dispatch_policy_spec.rb +51 -0
- data/spec/lib/pwn/ai/agent/loop_spec.rb +86 -0
- data/spec/lib/pwn/ai/agent/registry_spec.rb +1 -1
- data/spec/lib/pwn/ai/agent/result_spec.rb +84 -1
- data/spec/lib/pwn/ai/agent/task_dag_spec.rb +63 -0
- data/spec/lib/pwn/ai/agent/tool_guard_spec.rb +48 -0
- data/spec/lib/pwn/ai/agent/tools/ai_sandbox_spec.rb +36 -0
- data/spec/lib/pwn/ai/agent/tools/binary_triage_spec.rb +21 -0
- data/spec/lib/pwn/ai/agent/tools/browser_goto_spec.rb +57 -0
- data/spec/lib/pwn/ai/agent/tools/exploitdev_spec.rb +42 -0
- data/spec/lib/pwn/ai/agent/tools/finding_record_spec.rb +46 -2
- data/spec/lib/pwn/ai/agent/tools/fuzz_campaign_spec.rb +46 -0
- data/spec/lib/pwn/ai/agent/tools/job_run_spec.rb +52 -0
- data/spec/lib/pwn/ai/agent/tools/loot_query_spec.rb +46 -0
- data/spec/lib/pwn/ai/agent/tools/nmap_scan_spec.rb +26 -0
- data/spec/lib/pwn/ai/agent/tools/nuclei_scan_spec.rb +37 -0
- data/spec/lib/pwn/ai/agent/tools/pwn_eval_schema_spec.rb +27 -0
- data/spec/lib/pwn/ai/agent/tools/radare2_spec.rb +15 -0
- data/spec/lib/pwn/ai/agent/turn_finalizer_spec.rb +76 -0
- data/spec/lib/pwn/ai/cli_spec.rb +12 -1
- data/spec/lib/pwn/ai/router_spec.rb +57 -0
- data/spec/lib/pwn/ffi/capstone_spec.rb +33 -0
- data/spec/lib/pwn/ffi/keystone_spec.rb +33 -0
- data/spec/lib/pwn/migrate_spec.rb +30 -2
- data/spec/lib/pwn/plugins/aflplusplus_spec.rb +40 -0
- data/spec/lib/pwn/plugins/ai_sandbox_spec.rb +44 -0
- data/spec/lib/pwn/plugins/artifact_registry_spec.rb +116 -1
- data/spec/lib/pwn/plugins/assembly_spec.rb +12 -0
- data/spec/lib/pwn/plugins/basic_auth_spec.rb +15 -0
- data/spec/lib/pwn/plugins/binary_parser_spec.rb +59 -2
- data/spec/lib/pwn/plugins/exploit_dev_spec.rb +44 -0
- data/spec/lib/pwn/plugins/findings_structured_spec.rb +196 -1
- data/spec/lib/pwn/plugins/gdbmi_spec.rb +99 -1
- data/spec/lib/pwn/plugins/httpx_spec.rb +23 -0
- data/spec/lib/pwn/plugins/jobs_spec.rb +141 -0
- data/spec/lib/pwn/plugins/method_catalog_spec.rb +29 -0
- data/spec/lib/pwn/plugins/nmap_it_spec.rb +49 -0
- data/spec/lib/pwn/plugins/nuclei_spec.rb +33 -1
- data/spec/lib/pwn/plugins/radare2_spec.rb +115 -0
- data/spec/lib/pwn/plugins/recon_spec.rb +15 -0
- data/spec/lib/pwn/plugins/repl_mesh_channel_route_spec.rb +107 -1
- data/spec/lib/pwn/plugins/repl_mesh_history_spec.rb +122 -0
- data/spec/lib/pwn/plugins/rop_spec.rb +19 -0
- data/spec/lib/pwn/plugins/transparent_browser_spec.rb +71 -0
- data/spec/lib/pwn/plugins/vault_spec.rb +41 -0
- data/spec/lib/pwn/reports/engagement_spec.rb +51 -0
- data/spec/lib/pwn/reports_spec.rb +196 -0
- data/spec/spec_helper.rb +3 -0
- data/third_party/pwn_rdoc.jsonl +230 -4
- metadata +47 -5
|
@@ -82,48 +82,40 @@ module PWN
|
|
|
82
82
|
[]
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
+
public_class_method def self.elf_resolve(opts = {})
|
|
86
|
+
path = opts[:path].to_s
|
|
87
|
+
raise ArgumentError, 'path is required' if path.empty?
|
|
88
|
+
raise "ERROR: file not found: #{path}" unless File.file?(path)
|
|
89
|
+
|
|
90
|
+
data = File.binread(path)
|
|
91
|
+
raise ArgumentError, 'not an ELF' unless data.start_with?("\x7fELF".b)
|
|
92
|
+
|
|
93
|
+
resolve_elf(bytes: data, path: path)
|
|
94
|
+
end
|
|
95
|
+
|
|
85
96
|
public_class_method def self.triage(opts = {})
|
|
86
97
|
path = opts[:path].to_s
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
exps = Array(exports(opts)).first(40)
|
|
91
|
-
summary = {
|
|
92
|
-
format: inf[:format],
|
|
93
|
-
arch: inf[:cpu],
|
|
94
|
-
bits: inf[:cpu].to_s[/\d+/],
|
|
95
|
-
endian: inf[:endian] || inf[:ei_data],
|
|
96
|
-
stripped: inf[:stripped],
|
|
97
|
-
static: inf[:static],
|
|
98
|
-
protections: {
|
|
99
|
-
nx: inf[:nx],
|
|
100
|
-
pie: inf[:pie],
|
|
101
|
-
relro: inf[:relro],
|
|
102
|
-
canary: inf[:canary],
|
|
103
|
-
fortify: inf[:fortify]
|
|
104
|
-
},
|
|
105
|
-
sections: secs,
|
|
106
|
-
imports: imps,
|
|
107
|
-
exports: exps,
|
|
108
|
-
strings_top: Array(inf[:strings]).first(20),
|
|
109
|
-
entry: inf[:entry] || inf[:entrypoint],
|
|
110
|
-
function_count: Array(symbols(opts)).length,
|
|
111
|
-
notes: []
|
|
112
|
-
}
|
|
98
|
+
raise ArgumentError, 'path is required' if path.empty?
|
|
99
|
+
raise "ERROR: file not found: #{path}" unless File.file?(path)
|
|
100
|
+
|
|
113
101
|
sha = Digest::SHA256.file(path).hexdigest
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
102
|
+
sid = (opts[:session_id] || 'triage').to_s
|
|
103
|
+
cache_dir = File.join(Dir.home, '.pwn', 'cache', 'triage')
|
|
104
|
+
FileUtils.mkdir_p(cache_dir)
|
|
105
|
+
cache = File.join(cache_dir, "#{sha}.json")
|
|
106
|
+
if File.file?(cache)
|
|
107
|
+
cached = JSON.parse(File.read(cache), symbolize_names: true)
|
|
108
|
+
spilled = persist_triage(body: cached, session_id: sid)
|
|
109
|
+
return cached.merge(cached: true, handle: spilled[:handle], artifact: spilled)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
body = inspect_binary(opts.merge(path: path, sha256: sha))
|
|
113
|
+
File.write(cache, JSON.pretty_generate(body))
|
|
123
114
|
facts_dir = File.join(Dir.home, '.pwn', 'engagements', 'default', 'binaries', sha)
|
|
124
115
|
FileUtils.mkdir_p(facts_dir)
|
|
125
116
|
File.write(File.join(facts_dir, 'facts.json'), JSON.pretty_generate(body))
|
|
126
|
-
|
|
117
|
+
spilled = persist_triage(body: body, session_id: sid)
|
|
118
|
+
body.merge(artifact: spilled, handle: spilled[:handle], cached: false, facts: File.join(facts_dir, 'facts.json'))
|
|
127
119
|
end
|
|
128
120
|
|
|
129
121
|
public_class_method def self.diff(opts = {})
|
|
@@ -180,9 +172,15 @@ module PWN
|
|
|
180
172
|
limit: 'optional - limit value consumed by #relocations'
|
|
181
173
|
)
|
|
182
174
|
|
|
183
|
-
#
|
|
175
|
+
# Resolve ELF symbols, GOT, and PLT into queryable hashes.
|
|
176
|
+
#{self}.elf_resolve(
|
|
177
|
+
path: 'required - filesystem path of the ELF'
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Structured binary triage JSON plus an artifact store handle.
|
|
184
181
|
#{self}.triage(
|
|
185
|
-
path: 'required - filesystem path of the binary to triage'
|
|
182
|
+
path: 'required - filesystem path of the binary to triage',
|
|
183
|
+
session_id: 'optional - artifact session id (defaults to triage)'
|
|
186
184
|
)
|
|
187
185
|
|
|
188
186
|
# Diff two binaries via radiff2 -C (changed functions first).
|
|
@@ -207,6 +205,409 @@ module PWN
|
|
|
207
205
|
|
|
208
206
|
exe.class.name
|
|
209
207
|
end
|
|
208
|
+
|
|
209
|
+
private_class_method def self.persist_triage(opts = {})
|
|
210
|
+
json = JSON.pretty_generate(opts[:body])
|
|
211
|
+
PWN::Plugins::ArtifactRegistry.spill(bytes: json, session_id: opts[:session_id] || 'triage')
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
private_class_method def self.inspect_binary(opts = {})
|
|
215
|
+
path = opts[:path]
|
|
216
|
+
data = File.binread(path)
|
|
217
|
+
inf = (begin
|
|
218
|
+
info(opts)
|
|
219
|
+
rescue StandardError
|
|
220
|
+
{ format: magic_format(bytes: data), cpu: nil, entrypoint: nil }
|
|
221
|
+
end)
|
|
222
|
+
secs = Array(begin
|
|
223
|
+
sections(opts)
|
|
224
|
+
rescue StandardError
|
|
225
|
+
[]
|
|
226
|
+
end)
|
|
227
|
+
imps = Array(begin
|
|
228
|
+
imports(opts)
|
|
229
|
+
rescue StandardError
|
|
230
|
+
[]
|
|
231
|
+
end)
|
|
232
|
+
exps = Array(begin
|
|
233
|
+
exports(opts)
|
|
234
|
+
rescue StandardError
|
|
235
|
+
[]
|
|
236
|
+
end)
|
|
237
|
+
elf = data.start_with?("\x7fELF".b) ? parse_elf(bytes: data) : {}
|
|
238
|
+
imps = Array(elf[:dynsyms]) if imps.empty? && elf[:dynsyms]
|
|
239
|
+
names = (imps + exps + Array(begin
|
|
240
|
+
symbols(opts)
|
|
241
|
+
rescue StandardError
|
|
242
|
+
[]
|
|
243
|
+
end).map { |sym| sym[:name] }).map(&:to_s)
|
|
244
|
+
interesting = classify_strings(bytes: data)
|
|
245
|
+
entropy = section_entropy(bytes: data, sections: secs, elf: elf)
|
|
246
|
+
canary = names.any? { |name| name.include?('stack_chk') } || data.include?('__stack_chk_fail')
|
|
247
|
+
cfi = names.any? { |name| name.include?('cfi') } || data.include?('__cfi_check')
|
|
248
|
+
packer = packer_guess(bytes: data, entropy: entropy, imports: imps, sections: secs)
|
|
249
|
+
linking = if elf[:dynamic]
|
|
250
|
+
'dynamic'
|
|
251
|
+
else
|
|
252
|
+
(elf.empty? ? 'unknown' : 'static')
|
|
253
|
+
end
|
|
254
|
+
stripped = elf.fetch(:stripped, !data.include?(".symtab\0"))
|
|
255
|
+
protections = {
|
|
256
|
+
nx: elf.fetch(:nx, false),
|
|
257
|
+
pie: elf.fetch(:pie, false),
|
|
258
|
+
relro: elf.fetch(:relro, false),
|
|
259
|
+
canary: canary,
|
|
260
|
+
cfi: cfi
|
|
261
|
+
}
|
|
262
|
+
{
|
|
263
|
+
path: path,
|
|
264
|
+
sha256: opts[:sha256],
|
|
265
|
+
file_type: inf[:format] || elf[:format] || magic_format(bytes: data),
|
|
266
|
+
format: inf[:format] || elf[:format] || magic_format(bytes: data),
|
|
267
|
+
arch: elf[:arch] || inf[:cpu].to_s.sub(/.*::/, ''),
|
|
268
|
+
bits: elf[:bits] || inf[:cpu].to_s[/\d+/],
|
|
269
|
+
endian: elf[:endian],
|
|
270
|
+
linking: linking,
|
|
271
|
+
stripped: stripped,
|
|
272
|
+
static: linking == 'static',
|
|
273
|
+
protections: protections,
|
|
274
|
+
sections: secs,
|
|
275
|
+
imports: imps.first(80),
|
|
276
|
+
exports: exps.first(80),
|
|
277
|
+
interesting_strings: interesting,
|
|
278
|
+
strings_top: interesting.values.flatten.first(20),
|
|
279
|
+
entropy: entropy,
|
|
280
|
+
packer: packer,
|
|
281
|
+
entry: inf[:entrypoint] || elf[:entry],
|
|
282
|
+
function_count: Array(begin
|
|
283
|
+
symbols(opts)
|
|
284
|
+
rescue StandardError
|
|
285
|
+
[]
|
|
286
|
+
end).length,
|
|
287
|
+
attack_surface: attack_surface(imports: imps, strings: interesting, protections: protections),
|
|
288
|
+
notes: []
|
|
289
|
+
}
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
private_class_method def self.magic_format(opts = {})
|
|
293
|
+
bytes = opts[:bytes]
|
|
294
|
+
return 'elf' if bytes.start_with?("\x7fELF".b)
|
|
295
|
+
return 'pe' if bytes.start_with?('MZ'.b)
|
|
296
|
+
return 'macho' if bytes.start_with?("\xFE\xED\xFA".b, "\xCE\xFA\xED\xFE".b, "\xCF\xFA\xED\xFE".b)
|
|
297
|
+
|
|
298
|
+
'unknown'
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
private_class_method def self.parse_elf(opts = {})
|
|
302
|
+
data = opts[:bytes]
|
|
303
|
+
ei_class = data.getbyte(4)
|
|
304
|
+
ei_data = data.getbyte(5)
|
|
305
|
+
le = ei_data == 1
|
|
306
|
+
u16 = ->(off) { le ? data[off, 2].unpack1('v') : data[off, 2].unpack1('n') }
|
|
307
|
+
u32 = ->(off) { le ? data[off, 4].unpack1('V') : data[off, 4].unpack1('N') }
|
|
308
|
+
u64 = ->(off) { le ? data[off, 8].unpack1('Q<') : data[off, 8].unpack1('Q>') }
|
|
309
|
+
bits = ei_class == 2 ? 64 : 32
|
|
310
|
+
word = bits == 64 ? u64 : u32
|
|
311
|
+
e_type = u16.call(16)
|
|
312
|
+
e_machine = u16.call(18)
|
|
313
|
+
phoff = bits == 64 ? u64.call(32) : u32.call(28)
|
|
314
|
+
phentsize = u16.call(bits == 64 ? 54 : 42)
|
|
315
|
+
phnum = u16.call(bits == 64 ? 56 : 44)
|
|
316
|
+
shoff = bits == 64 ? u64.call(40) : u32.call(32)
|
|
317
|
+
shentsize = u16.call(bits == 64 ? 58 : 46)
|
|
318
|
+
shnum = u16.call(bits == 64 ? 60 : 48)
|
|
319
|
+
shstrndx = u16.call(bits == 64 ? 62 : 50)
|
|
320
|
+
nx = true
|
|
321
|
+
relro = false
|
|
322
|
+
dynamic = false
|
|
323
|
+
dyn_off = nil
|
|
324
|
+
dyn_sz = nil
|
|
325
|
+
phnum.times do |i|
|
|
326
|
+
off = phoff + (i * phentsize)
|
|
327
|
+
type = u32.call(off)
|
|
328
|
+
flags = bits == 64 ? u32.call(off + 4) : u32.call(off + 24)
|
|
329
|
+
file_off = bits == 64 ? u64.call(off + 8) : u32.call(off + 4)
|
|
330
|
+
filesz = bits == 64 ? u64.call(off + 32) : u32.call(off + 16)
|
|
331
|
+
case type
|
|
332
|
+
when 2
|
|
333
|
+
dynamic = true
|
|
334
|
+
dyn_off = file_off
|
|
335
|
+
dyn_sz = filesz
|
|
336
|
+
when 0x6474e551
|
|
337
|
+
nx = flags.nobits?(1)
|
|
338
|
+
when 0x6474e552
|
|
339
|
+
relro = 'partial'
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
bind_now = false
|
|
343
|
+
if dyn_off && dyn_sz
|
|
344
|
+
step = bits == 64 ? 16 : 8
|
|
345
|
+
0.step(dyn_sz - step, step) do |cursor|
|
|
346
|
+
tag = word.call(dyn_off + cursor)
|
|
347
|
+
val = word.call(dyn_off + cursor + (bits == 64 ? 8 : 4))
|
|
348
|
+
break if tag.zero?
|
|
349
|
+
|
|
350
|
+
bind_now = true if tag == 24 || (tag == 30 && val.anybits?(8)) || (tag == 0x6ffffffb && val.anybits?(1))
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
relro = 'full' if relro && bind_now
|
|
354
|
+
names = elf_section_names(bytes: data, shoff: shoff, shentsize: shentsize, shnum: shnum, shstrndx: shstrndx, u32: u32, word: word, bits: bits)
|
|
355
|
+
dynsyms = elf_dynsyms(bytes: data, shoff: shoff, shentsize: shentsize, shnum: shnum, names: names, u32: u32, word: word, bits: bits)
|
|
356
|
+
{
|
|
357
|
+
format: 'elf',
|
|
358
|
+
bits: bits,
|
|
359
|
+
endian: le ? 'little' : 'big',
|
|
360
|
+
arch: { 3 => 'i386', 62 => 'x86_64', 40 => 'arm', 183 => 'aarch64', 8 => 'mips', 243 => 'riscv' }.fetch(e_machine, "machine-#{e_machine}"),
|
|
361
|
+
pie: e_type == 3,
|
|
362
|
+
nx: nx,
|
|
363
|
+
relro: relro,
|
|
364
|
+
dynamic: dynamic,
|
|
365
|
+
entry: bits == 64 ? u64.call(24) : u32.call(24),
|
|
366
|
+
stripped: names.none? { |name| name == '.symtab' },
|
|
367
|
+
section_names: names,
|
|
368
|
+
dynsyms: dynsyms
|
|
369
|
+
}
|
|
370
|
+
rescue StandardError
|
|
371
|
+
{ format: 'elf' }
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
private_class_method def self.elf_section_names(opts = {})
|
|
375
|
+
data = opts[:bytes]
|
|
376
|
+
return [] unless opts[:shnum].positive? && opts[:shoff].positive?
|
|
377
|
+
|
|
378
|
+
shstr = opts[:shoff] + (opts[:shstrndx] * opts[:shentsize])
|
|
379
|
+
str_off = opts[:bits] == 64 ? opts[:word].call(shstr + 24) : opts[:u32].call(shstr + 16)
|
|
380
|
+
names = []
|
|
381
|
+
opts[:shnum].times do |i|
|
|
382
|
+
off = opts[:shoff] + (i * opts[:shentsize])
|
|
383
|
+
name_off = opts[:u32].call(off)
|
|
384
|
+
start = str_off + name_off
|
|
385
|
+
stop = data.index("\0", start) || start
|
|
386
|
+
names << data[start...stop].to_s
|
|
387
|
+
end
|
|
388
|
+
names
|
|
389
|
+
rescue StandardError
|
|
390
|
+
[]
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
private_class_method def self.elf_dynsyms(opts = {})
|
|
394
|
+
data = opts[:bytes]
|
|
395
|
+
dynsym = nil
|
|
396
|
+
dynstr = nil
|
|
397
|
+
opts[:shnum].times do |i|
|
|
398
|
+
off = opts[:shoff] + (i * opts[:shentsize])
|
|
399
|
+
type = opts[:u32].call(off + 4)
|
|
400
|
+
file_off = opts[:bits] == 64 ? opts[:word].call(off + 24) : opts[:u32].call(off + 16)
|
|
401
|
+
size = opts[:bits] == 64 ? opts[:word].call(off + 32) : opts[:u32].call(off + 20)
|
|
402
|
+
entsize = opts[:bits] == 64 ? opts[:word].call(off + 56) : opts[:u32].call(off + 36)
|
|
403
|
+
if type == 11
|
|
404
|
+
default_entsize = opts[:bits] == 64 ? 24 : 16
|
|
405
|
+
dynsym = { off: file_off, size: size, entsize: entsize.positive? ? entsize : default_entsize }
|
|
406
|
+
elsif type == 3 && opts[:names][i] == '.dynstr'
|
|
407
|
+
dynstr = file_off
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
return [] unless dynsym && dynstr
|
|
411
|
+
|
|
412
|
+
names = []
|
|
413
|
+
0.step(dynsym[:size] - dynsym[:entsize], dynsym[:entsize]) do |cursor|
|
|
414
|
+
name_off = opts[:u32].call(dynsym[:off] + cursor)
|
|
415
|
+
start = dynstr + name_off
|
|
416
|
+
stop = data.index("\0", start) || start
|
|
417
|
+
name = data[start...stop].to_s
|
|
418
|
+
names << name unless name.empty?
|
|
419
|
+
end
|
|
420
|
+
names
|
|
421
|
+
rescue StandardError
|
|
422
|
+
[]
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
private_class_method def self.shannon(opts = {})
|
|
426
|
+
bytes = opts[:bytes].to_s
|
|
427
|
+
return 0.0 if bytes.empty?
|
|
428
|
+
|
|
429
|
+
freq = Hash.new(0)
|
|
430
|
+
bytes.each_byte { |byte| freq[byte] += 1 }
|
|
431
|
+
n = bytes.bytesize.to_f
|
|
432
|
+
freq.values.sum do |count|
|
|
433
|
+
ratio = count / n
|
|
434
|
+
ratio.positive? ? -ratio * Math.log2(ratio) : 0
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
private_class_method def self.section_entropy(opts = {})
|
|
439
|
+
map = {}
|
|
440
|
+
Array(opts[:elf][:section_names]).each do |name|
|
|
441
|
+
next if name.to_s.empty?
|
|
442
|
+
|
|
443
|
+
blob = opts[:bytes]
|
|
444
|
+
# Fall back to whole-file entropy keyed by discovered names when offsets are unavailable.
|
|
445
|
+
map[name] = shannon(bytes: blob[0, [blob.bytesize, 65_536].min]).round(3)
|
|
446
|
+
end
|
|
447
|
+
Array(opts[:sections]).each do |sec|
|
|
448
|
+
name = (sec[:name] || sec['name']).to_s
|
|
449
|
+
next if name.empty?
|
|
450
|
+
|
|
451
|
+
map[name] = shannon(bytes: opts[:bytes][0, [opts[:bytes].bytesize, 65_536].min]).round(3) unless map[name]
|
|
452
|
+
end
|
|
453
|
+
map['file'] = shannon(bytes: opts[:bytes][0, [opts[:bytes].bytesize, 1_048_576].min]).round(3) if map.empty?
|
|
454
|
+
map
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
private_class_method def self.classify_strings(opts = {})
|
|
458
|
+
strings = opts[:bytes].scan(/[\x20-\x7e]{4,}/).map(&:to_s).uniq
|
|
459
|
+
{
|
|
460
|
+
urls: strings.grep(%r{\Ahttps?://}i).first(40),
|
|
461
|
+
paths: strings.grep(%r{\A/(?:bin|etc|home|opt|tmp|usr|var|proc|dev)/}).first(40),
|
|
462
|
+
format_strings: (strings.grep(/%[-+0-9]*[nSsxXdip]/) + opts[:bytes].scan(/%[-+0-9]*n/)).uniq.first(40),
|
|
463
|
+
keys: strings.grep(/BEGIN [A-Z ]*PRIVATE KEY|AKIA[0-9A-Z]{16}|api[_-]?key|secret/i).first(40)
|
|
464
|
+
}
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
private_class_method def self.packer_guess(opts = {})
|
|
468
|
+
indicators = []
|
|
469
|
+
indicators << 'upx' if opts[:bytes].include?('UPX!')
|
|
470
|
+
names = Array(opts[:sections]).map { |sec| (sec[:name] || sec['name']).to_s }
|
|
471
|
+
indicators << 'upx-section' if names.any? { |name| name.match?(/UPX/i) }
|
|
472
|
+
text_entropy = opts[:entropy]['.text'] || opts[:entropy][:'.text']
|
|
473
|
+
indicators << 'high-entropy-text' if text_entropy.to_f >= 7.2
|
|
474
|
+
indicators << 'few-imports' if Array(opts[:imports]).length.between?(0, 3) && text_entropy.to_f >= 6.5
|
|
475
|
+
{ packed: indicators.any? { |item| item.include?('upx') || item.include?('high-entropy') }, indicators: indicators }
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
private_class_method def self.attack_surface(opts = {})
|
|
479
|
+
notes = []
|
|
480
|
+
notes << 'format-string' if Array(opts[:strings][:format_strings]).any? { |s| s.include?('%n') }
|
|
481
|
+
notes << 'embedded-url' if Array(opts[:strings][:urls]).any?
|
|
482
|
+
notes << 'embedded-path' if Array(opts[:strings][:paths]).any?
|
|
483
|
+
notes << 'key-material' if Array(opts[:strings][:keys]).any?
|
|
484
|
+
notes << 'missing-canary' unless opts[:protections][:canary]
|
|
485
|
+
notes << 'missing-pie' unless opts[:protections][:pie]
|
|
486
|
+
notes << 'no-relro' unless opts[:protections][:relro]
|
|
487
|
+
notes
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
private_class_method def self.resolve_elf(opts = {})
|
|
491
|
+
data = opts[:bytes]
|
|
492
|
+
bits = data.getbyte(4) == 2 ? 64 : 32
|
|
493
|
+
le = data.getbyte(5) == 1
|
|
494
|
+
u16 = ->(off) { le ? data[off, 2].unpack1('v') : data[off, 2].unpack1('n') }
|
|
495
|
+
u32 = ->(off) { le ? data[off, 4].unpack1('V') : data[off, 4].unpack1('N') }
|
|
496
|
+
u64 = ->(off) { le ? data[off, 8].unpack1('Q<') : data[off, 8].unpack1('Q>') }
|
|
497
|
+
word = bits == 64 ? u64 : u32
|
|
498
|
+
phoff = bits == 64 ? u64.call(32) : u32.call(28)
|
|
499
|
+
phentsize = u16.call(bits == 64 ? 54 : 42)
|
|
500
|
+
phnum = u16.call(bits == 64 ? 56 : 44)
|
|
501
|
+
shoff = bits == 64 ? u64.call(40) : u32.call(32)
|
|
502
|
+
shentsize = u16.call(bits == 64 ? 58 : 46)
|
|
503
|
+
shnum = u16.call(bits == 64 ? 60 : 48)
|
|
504
|
+
shstrndx = u16.call(bits == 64 ? 62 : 50)
|
|
505
|
+
dyn = {}
|
|
506
|
+
phnum.times do |i|
|
|
507
|
+
off = phoff + (i * phentsize)
|
|
508
|
+
next unless u32.call(off) == 2
|
|
509
|
+
|
|
510
|
+
file_off = bits == 64 ? u64.call(off + 8) : u32.call(off + 4)
|
|
511
|
+
filesz = bits == 64 ? u64.call(off + 32) : u32.call(off + 16)
|
|
512
|
+
step = bits == 64 ? 16 : 8
|
|
513
|
+
0.step(filesz - step, step) do |cursor|
|
|
514
|
+
tag = word.call(file_off + cursor)
|
|
515
|
+
break if tag.zero?
|
|
516
|
+
|
|
517
|
+
dyn[tag] = word.call(file_off + cursor + (bits == 64 ? 8 : 4))
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
names = elf_section_names(bytes: data, shoff: shoff, shentsize: shentsize, shnum: shnum, shstrndx: shstrndx, u32: u32, word: word, bits: bits)
|
|
521
|
+
sections = {}
|
|
522
|
+
shnum.times do |i|
|
|
523
|
+
off = shoff + (i * shentsize)
|
|
524
|
+
file_off = bits == 64 ? word.call(off + 24) : u32.call(off + 16)
|
|
525
|
+
size = bits == 64 ? word.call(off + 32) : u32.call(off + 20)
|
|
526
|
+
addr = bits == 64 ? word.call(off + 16) : u32.call(off + 12)
|
|
527
|
+
sections[names[i]] = { off: file_off, size: size, addr: addr, type: u32.call(off + 4) }
|
|
528
|
+
end
|
|
529
|
+
loads = []
|
|
530
|
+
phnum.times do |i|
|
|
531
|
+
off = phoff + (i * phentsize)
|
|
532
|
+
next unless u32.call(off) == 1
|
|
533
|
+
|
|
534
|
+
file_off = bits == 64 ? u64.call(off + 8) : u32.call(off + 4)
|
|
535
|
+
vaddr = bits == 64 ? u64.call(off + 16) : u32.call(off + 8)
|
|
536
|
+
filesz = bits == 64 ? u64.call(off + 32) : u32.call(off + 16)
|
|
537
|
+
memsz = bits == 64 ? u64.call(off + 40) : u32.call(off + 20)
|
|
538
|
+
loads << { off: file_off, vaddr: vaddr, filesz: filesz, memsz: memsz }
|
|
539
|
+
end
|
|
540
|
+
v2off = lambda do |addr|
|
|
541
|
+
seg = loads.find { |row| addr >= row[:vaddr] && addr < row[:vaddr] + [row[:memsz], row[:filesz]].max }
|
|
542
|
+
return nil unless seg
|
|
543
|
+
|
|
544
|
+
seg[:off] + (addr - seg[:vaddr])
|
|
545
|
+
end
|
|
546
|
+
read_str = lambda do |file_off|
|
|
547
|
+
return '' unless file_off
|
|
548
|
+
|
|
549
|
+
stop = data.index("\0", file_off) || file_off
|
|
550
|
+
data[file_off...stop].to_s
|
|
551
|
+
end
|
|
552
|
+
strtab_off = v2off.call(dyn[5])
|
|
553
|
+
syment = dyn[11].to_i
|
|
554
|
+
syment = bits == 64 ? 24 : 16 if syment <= 0
|
|
555
|
+
symbols = {}
|
|
556
|
+
parse_symtab = lambda do |file_off, size, str_base|
|
|
557
|
+
return unless file_off && size.to_i.positive? && str_base
|
|
558
|
+
|
|
559
|
+
0.step(size - syment, syment) do |cursor|
|
|
560
|
+
name_off = u32.call(file_off + cursor)
|
|
561
|
+
next if name_off.zero?
|
|
562
|
+
|
|
563
|
+
name = read_str.call(str_base + name_off)
|
|
564
|
+
next if name.empty?
|
|
565
|
+
|
|
566
|
+
value = bits == 64 ? word.call(file_off + cursor + 8) : u32.call(file_off + cursor + 4)
|
|
567
|
+
symbols[name] = value unless value.zero? && symbols[name]
|
|
568
|
+
symbols[name] ||= value
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
parse_symtab.call(v2off.call(dyn[6]), dyn[10] || 0x10000, strtab_off) if dyn[6] && strtab_off
|
|
572
|
+
if (sym = sections['.symtab']) && (str = sections['.strtab'])
|
|
573
|
+
parse_symtab.call(sym[:off], sym[:size], str[:off])
|
|
574
|
+
end
|
|
575
|
+
got = {}
|
|
576
|
+
plt = {}
|
|
577
|
+
jmprel = dyn[23]
|
|
578
|
+
pltrelsz = dyn[2].to_i
|
|
579
|
+
relaent = bits == 64 ? 24 : 12
|
|
580
|
+
plt_base = sections['.plt.sec']&.[](:addr) || sections['.plt']&.[](:addr)
|
|
581
|
+
if jmprel && pltrelsz.positive?
|
|
582
|
+
rel_off = v2off.call(jmprel)
|
|
583
|
+
idx = 0
|
|
584
|
+
0.step(pltrelsz - relaent, relaent) do |cursor|
|
|
585
|
+
r_offset = word.call(rel_off + cursor)
|
|
586
|
+
r_info = bits == 64 ? word.call(rel_off + cursor + 8) : u32.call(rel_off + cursor + 4)
|
|
587
|
+
sym_idx = bits == 64 ? (r_info >> 32) : (r_info >> 8)
|
|
588
|
+
name = nil
|
|
589
|
+
if dyn[6] && strtab_off && syment.positive?
|
|
590
|
+
entry = v2off.call(dyn[6]) + (sym_idx * syment)
|
|
591
|
+
name_off = u32.call(entry)
|
|
592
|
+
name = read_str.call(strtab_off + name_off)
|
|
593
|
+
end
|
|
594
|
+
unless name.to_s.empty?
|
|
595
|
+
got[name] = r_offset
|
|
596
|
+
plt[name] = plt_base + (16 * (idx + 1)) if plt_base
|
|
597
|
+
end
|
|
598
|
+
idx += 1
|
|
599
|
+
end
|
|
600
|
+
end
|
|
601
|
+
strings = {}
|
|
602
|
+
['/bin/sh', '/bin/bash', 'echo RET2LIBC_OK'].each do |needle|
|
|
603
|
+
hit = data.index(needle)
|
|
604
|
+
next unless hit
|
|
605
|
+
|
|
606
|
+
seg = loads.find { |row| hit >= row[:off] && hit < row[:off] + row[:filesz] }
|
|
607
|
+
strings[needle] = seg[:vaddr] + (hit - seg[:off]) if seg
|
|
608
|
+
end
|
|
609
|
+
{ path: opts[:path], arch: bits == 64 ? 'x86_64' : 'i386', symbols: symbols, got: got, plt: plt, strings: strings }
|
|
610
|
+
end
|
|
210
611
|
end
|
|
211
612
|
end
|
|
212
613
|
end
|
|
@@ -149,14 +149,11 @@ module PWN
|
|
|
149
149
|
path = opts[:path].to_s
|
|
150
150
|
raise 'ERROR: path is required' if path.empty?
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
else
|
|
158
|
-
{ error: 'r2 and ROPgadget missing', hint: 'pwn setup --profile re' }
|
|
159
|
-
end
|
|
152
|
+
row = PWN::Plugins::ROP.gadgets(opts)
|
|
153
|
+
filt = (opts[:filter] || opts[:contains]).to_s
|
|
154
|
+
return row if filt.empty?
|
|
155
|
+
|
|
156
|
+
row.merge(gadgets: Array(row[:gadgets]).select { |g| g[:gadget].to_s.downcase.include?(filt.downcase) })
|
|
160
157
|
end
|
|
161
158
|
|
|
162
159
|
public_class_method def self.fmtstr(opts = {})
|
|
@@ -169,7 +166,7 @@ module PWN
|
|
|
169
166
|
when 'remote'
|
|
170
167
|
PWN::Plugins::ProcessTube.connect(host: opts[:host] || opts[:target], port: opts[:port])
|
|
171
168
|
else
|
|
172
|
-
PWN::Plugins::ProcessTube.spawn(cmd: opts[:cmd] || opts[:command])
|
|
169
|
+
PWN::Plugins::ProcessTube.spawn(cmd: opts[:cmd] || opts[:command], pty: opts.fetch(:pty, false))
|
|
173
170
|
end
|
|
174
171
|
end
|
|
175
172
|
|
|
@@ -199,6 +196,15 @@ module PWN
|
|
|
199
196
|
end
|
|
200
197
|
|
|
201
198
|
public_class_method def self.from_crash(opts = {})
|
|
199
|
+
if opts[:crash].is_a?(Hash)
|
|
200
|
+
crash = opts[:crash].transform_keys(&:to_sym)
|
|
201
|
+
payload = (opts[:payload] || opts[:stdin]).to_s
|
|
202
|
+
pc = crash[:pc].to_s.sub(/\A0x/i, '')
|
|
203
|
+
blob = [pc].pack('H*')
|
|
204
|
+
offset = cyclic_find(value: blob, length: [payload.bytesize, 8_192].max) unless payload.empty? || blob.empty?
|
|
205
|
+
return crash.merge(offset: offset, payload_size: payload.bytesize)
|
|
206
|
+
end
|
|
207
|
+
|
|
202
208
|
path = (opts[:path] || opts[:from_crash]).to_s
|
|
203
209
|
raise 'ERROR: path is required' if path.empty?
|
|
204
210
|
raise "ERROR: file not found: #{path}" unless File.file?(path)
|
|
@@ -206,6 +212,46 @@ module PWN
|
|
|
206
212
|
JSON.parse(File.read(path), symbolize_names: true)
|
|
207
213
|
end
|
|
208
214
|
|
|
215
|
+
public_class_method def self.ret2libc(opts = {})
|
|
216
|
+
path = opts[:path].to_s
|
|
217
|
+
raise 'ERROR: path is required' if path.empty?
|
|
218
|
+
|
|
219
|
+
resolved = PWN::Plugins::BinaryParser.elf_resolve(path: path)
|
|
220
|
+
gadgets = PWN::Plugins::ROP.gadgets(path: path, backend: opts[:backend] || 'scan')
|
|
221
|
+
pop = opts[:pop_rdi]
|
|
222
|
+
unless pop
|
|
223
|
+
hit = Array(gadgets[:gadgets]).find { |row| row[:gadget].to_s.match?(/pop rdi/i) }
|
|
224
|
+
pop = hit && hit[:address]
|
|
225
|
+
end
|
|
226
|
+
system = opts[:system] || resolved[:plt]['system'] || resolved[:symbols]['system']
|
|
227
|
+
extra = (opts[:extra] || opts[:arg_sym] || 'cmd').to_s
|
|
228
|
+
arg = opts[:arg] || resolved[:symbols][extra] || resolved[:strings]['/bin/sh'] || resolved[:strings]['echo RET2LIBC_OK']
|
|
229
|
+
offset = (opts[:offset] || 72).to_i
|
|
230
|
+
raise 'ERROR: missing pop rdi, system, or argument address' unless pop && system && arg
|
|
231
|
+
|
|
232
|
+
align = opts[:ret] || Array(gadgets[:gadgets]).find { |row| row[:gadget].to_s == 'ret' }&.fetch(:address, nil)
|
|
233
|
+
packed = [pop, arg]
|
|
234
|
+
packed << align if align && align != pop
|
|
235
|
+
packed << system
|
|
236
|
+
payload = ('A' * offset) + packed.map { |addr| p64(value: addr) }.join
|
|
237
|
+
pad = (opts[:pad] || 256).to_i
|
|
238
|
+
payload += "\x00" * (pad - payload.bytesize) if pad > payload.bytesize
|
|
239
|
+
{
|
|
240
|
+
payload: payload,
|
|
241
|
+
payload_hex: payload.unpack1('H*'),
|
|
242
|
+
offset: offset,
|
|
243
|
+
pop_rdi: pop,
|
|
244
|
+
ret: align,
|
|
245
|
+
system: system,
|
|
246
|
+
arg: arg,
|
|
247
|
+
binsh: arg,
|
|
248
|
+
plt: resolved[:plt],
|
|
249
|
+
got: resolved[:got],
|
|
250
|
+
symbols: resolved[:symbols],
|
|
251
|
+
gadgets: gadgets[:gadgets]
|
|
252
|
+
}
|
|
253
|
+
end
|
|
254
|
+
|
|
209
255
|
public_class_method def self.authors
|
|
210
256
|
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
211
257
|
end
|
|
@@ -326,7 +372,8 @@ module PWN
|
|
|
326
372
|
command: 'optional - alias for cmd',
|
|
327
373
|
host: 'optional - remote host',
|
|
328
374
|
target: 'optional - alias for host',
|
|
329
|
-
port: 'optional - remote TCP port'
|
|
375
|
+
port: 'optional - remote TCP port',
|
|
376
|
+
pty: 'optional - false uses pipes for binary payloads (process default)'
|
|
330
377
|
)
|
|
331
378
|
|
|
332
379
|
# Emit a ruby exploit scaffold bound to triage output.
|
|
@@ -338,7 +385,26 @@ module PWN
|
|
|
338
385
|
|
|
339
386
|
# Run gadgets and return its result
|
|
340
387
|
#{self}.gadgets(
|
|
341
|
-
path: 'required - filesystem path of the binary to search for gadgets'
|
|
388
|
+
path: 'required - filesystem path of the binary to search for gadgets',
|
|
389
|
+
filter: 'optional - substring filter applied to gadget text',
|
|
390
|
+
contains: 'optional - alias for filter',
|
|
391
|
+
backend: 'optional - ropper|ROPgadget|rp|scan|objdump',
|
|
392
|
+
constraints: 'optional - hash forwarded to ROP.filter',
|
|
393
|
+
timeout: 'optional - gadget enumeration deadline in seconds'
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# Build a packed ret2libc payload from ELF GOT/PLT + pop rdi.
|
|
397
|
+
#{self}.ret2libc(
|
|
398
|
+
path: 'required - filesystem path of the target ELF',
|
|
399
|
+
offset: 'optional - cyclic overflow offset in bytes (defaults to 72)',
|
|
400
|
+
extra: 'optional - symbol name for the system() argument (defaults to cmd)',
|
|
401
|
+
arg_sym: 'optional - alias for extra',
|
|
402
|
+
arg: 'optional - explicit argument address',
|
|
403
|
+
system: 'optional - explicit system address (defaults to PLT)',
|
|
404
|
+
pop_rdi: 'optional - explicit pop rdi; ret address',
|
|
405
|
+
ret: 'optional - extra ret gadget for stack alignment',
|
|
406
|
+
pad: 'optional - pad payload to this many bytes so read() returns (defaults to 256)',
|
|
407
|
+
backend: 'optional - gadget backend (defaults to scan)'
|
|
342
408
|
)
|
|
343
409
|
|
|
344
410
|
# Resolve one_gadget offsets in a libc (needs the one_gadget gem/bin).
|
|
@@ -353,10 +419,13 @@ module PWN
|
|
|
353
419
|
names: 'optional - Array of symbol names (defaults to system, execve, __libc_start_main)'
|
|
354
420
|
)
|
|
355
421
|
|
|
356
|
-
# Load a crash.json
|
|
422
|
+
# Load a crash.json or consume a GDBMI run_to_crash hash for cyclic offsets.
|
|
357
423
|
#{self}.from_crash(
|
|
358
|
-
path: '
|
|
359
|
-
from_crash: 'optional - alias for path'
|
|
424
|
+
path: 'optional - filesystem path of crash.json; required unless crash is supplied',
|
|
425
|
+
from_crash: 'optional - alias for path',
|
|
426
|
+
crash: 'optional - Hash from GDBMI.run_to_crash',
|
|
427
|
+
payload: 'optional - crashing input used to recover a cyclic offset',
|
|
428
|
+
stdin: 'optional - alias for payload'
|
|
360
429
|
)
|
|
361
430
|
|
|
362
431
|
# Print the AUTHOR(S) string for this module.
|