pwn 0.5.632 → 0.5.635
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/.github/workflows/install-matrix.yml +24 -7
- data/.rubocop.yml +1 -0
- data/Gemfile +2 -2
- data/Rakefile +1 -1
- data/bin/pwn_fuzz_net_app_proto +1 -7
- data/documentation/Agent-Tool-Registry.md +2 -2
- data/documentation/Installation.md +1 -1
- data/git_commit.sh +1 -1
- data/lib/pwn/ai/agent/learning.rb +13 -16
- data/lib/pwn/ai/agent/prompt_builder.rb +7 -3
- data/lib/pwn/ai/agent/tools/skills.rb +122 -63
- data/lib/pwn/config.rb +245 -30
- data/lib/pwn/ffi/stdio.rb +3 -1
- data/lib/pwn/plugins/fuzz.rb +9 -7
- data/lib/pwn/plugins/github.rb +205 -47
- data/lib/pwn/plugins/repl.rb +12 -1
- data/lib/pwn/setup.rb +35 -5
- data/lib/pwn/version.rb +1 -1
- data/packer/provisioners/pwn.sh +2 -1
- data/pwn.gemspec +36 -3
- data/spec/integration/bin_drivers_spec.rb +73 -0
- data/spec/integration/config_spec.rb +68 -0
- data/spec/integration/dispatch_spec.rb +106 -0
- data/spec/integration/documentation_sync_spec.rb +74 -0
- data/spec/integration/extrospection_classify_spec.rb +90 -0
- data/spec/integration/help_contract_spec.rb +104 -0
- data/spec/integration/persistence_roundtrip_spec.rb +112 -0
- data/spec/integration/prompt_builder_spec.rb +68 -0
- data/spec/integration/reinforced_feedback_loop_spec.rb +121 -0
- data/spec/integration/reports_spec.rb +76 -0
- data/spec/integration/result_condition_spec.rb +77 -0
- data/spec/integration/sast_functional_spec.rb +80 -0
- data/spec/integration/tool_registry_spec.rb +91 -0
- data/spec/lib/pwn/ai/agent/tools/skills_spec.rb +88 -1
- data/spec/lib/pwn/ffi/adalm_pluto_spec.rb +5 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/support/sandbox.rb +77 -0
- data/third_party/pwn_rdoc.jsonl +16 -5
- metadata +32 -19
data/lib/pwn/config.rb
CHANGED
|
@@ -397,6 +397,30 @@ module PWN
|
|
|
397
397
|
raise e
|
|
398
398
|
end
|
|
399
399
|
|
|
400
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
401
|
+
# SKILLS (agentskills.io/specification conformant, with legacy shim)
|
|
402
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
403
|
+
#
|
|
404
|
+
# On-disk layout (spec):
|
|
405
|
+
# ~/.pwn/skills/<name>/SKILL.md ← required entrypoint, YAML frontmatter
|
|
406
|
+
# ~/.pwn/skills/<name>/scripts/ ← optional executables (was flat *.rb)
|
|
407
|
+
# ~/.pwn/skills/<name>/references/ ← optional supporting docs
|
|
408
|
+
# ~/.pwn/skills/<name>/assets/ ← optional binary assets
|
|
409
|
+
#
|
|
410
|
+
# Legacy shim (read-only, still loaded so nothing breaks on upgrade):
|
|
411
|
+
# ~/.pwn/skills/<name>.{md,txt,rb,skill,yml,yaml}
|
|
412
|
+
#
|
|
413
|
+
# Frontmatter (SKILL.md, `---` YAML block at top of file):
|
|
414
|
+
# name: REQUIRED [a-z0-9-]{1,64}, must equal parent dir name
|
|
415
|
+
# description: REQUIRED 1..1024 chars
|
|
416
|
+
# license: optional
|
|
417
|
+
# metadata: optional Hash (pwn stores references here too)
|
|
418
|
+
# allowed-tools: optional Array of toolset names
|
|
419
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
420
|
+
|
|
421
|
+
SKILL_ENTRY = 'SKILL.md'
|
|
422
|
+
SKILL_NAME_RE = /\A[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?\z/
|
|
423
|
+
|
|
400
424
|
# Supported Method Parameters::
|
|
401
425
|
# pwn_skills_path = PWN::Config.pwn_skills_path(
|
|
402
426
|
# pwn_env_path: 'optional - Path to pwn.yaml file. Defaults to ~/.pwn/pwn.yaml'
|
|
@@ -406,33 +430,65 @@ module PWN
|
|
|
406
430
|
File.join(File.dirname(pwn_env_path), 'skills')
|
|
407
431
|
end
|
|
408
432
|
|
|
433
|
+
# Supported Method Parameters::
|
|
434
|
+
# name = PWN::Config.sanitize_skill_name(name: 'My Cool Skill!')
|
|
435
|
+
#
|
|
436
|
+
# Coerce to an agentskills.io-valid identifier:
|
|
437
|
+
# downcase → non [a-z0-9] → '-' → squeeze '-' → strip edge '-' → cap 64.
|
|
438
|
+
# Raises ArgumentError when the result is empty.
|
|
439
|
+
public_class_method def self.sanitize_skill_name(opts = {})
|
|
440
|
+
n = opts[:name].to_s.downcase
|
|
441
|
+
.gsub(/[^a-z0-9-]+/, '-')
|
|
442
|
+
.gsub(/-{2,}/, '-')
|
|
443
|
+
.gsub(/\A-+|-+\z/, '')[0, 64]
|
|
444
|
+
.to_s
|
|
445
|
+
.gsub(/-+\z/, '') # re-strip in case truncation left a trailing '-'
|
|
446
|
+
raise ArgumentError, "skill name #{opts[:name].inspect} sanitises to empty" if n.empty?
|
|
447
|
+
raise ArgumentError, "skill name #{n.inspect} !~ #{SKILL_NAME_RE.inspect}" unless n.match?(SKILL_NAME_RE)
|
|
448
|
+
|
|
449
|
+
n
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# Supported Method Parameters::
|
|
453
|
+
# fm = PWN::Config.parse_skill_frontmatter(content: '...')
|
|
454
|
+
#
|
|
455
|
+
# → { frontmatter: Hash(String keys), body: String }
|
|
456
|
+
# Missing / malformed frontmatter returns { frontmatter: {}, body: content }.
|
|
457
|
+
public_class_method def self.parse_skill_frontmatter(opts = {})
|
|
458
|
+
content = opts[:content].to_s
|
|
459
|
+
return { frontmatter: {}, body: content } unless content.start_with?("---\n")
|
|
460
|
+
|
|
461
|
+
fm_end = content.index(/^---\s*$/, 4)
|
|
462
|
+
return { frontmatter: {}, body: content } unless fm_end
|
|
463
|
+
|
|
464
|
+
require 'yaml'
|
|
465
|
+
raw = content[4...fm_end]
|
|
466
|
+
fm = YAML.safe_load(raw, permitted_classes: [Symbol, Date, Time], aliases: true) || {}
|
|
467
|
+
fm = {} unless fm.is_a?(Hash)
|
|
468
|
+
body = content[fm_end..].to_s.sub(/\A---\s*\n?/, '')
|
|
469
|
+
{ frontmatter: fm, body: body }
|
|
470
|
+
rescue StandardError
|
|
471
|
+
{ frontmatter: {}, body: content }
|
|
472
|
+
end
|
|
473
|
+
|
|
409
474
|
# Supported Method Parameters::
|
|
410
475
|
# refs = PWN::Config.parse_skill_references(content: '...')
|
|
411
476
|
#
|
|
412
477
|
# Extracts an Array of reference strings (URLs, CWE/CVE/ATT&CK ids, etc.)
|
|
413
|
-
# from a skill body. Supports
|
|
414
|
-
# 1)
|
|
415
|
-
# 2)
|
|
478
|
+
# from a skill body. Supports three sources, merged & uniq'd:
|
|
479
|
+
# 1) frontmatter `references:` (legacy pwn)
|
|
480
|
+
# 2) frontmatter `metadata: { references: [...] }` (spec-conformant slot)
|
|
481
|
+
# 3) markdown `## References` bullet section
|
|
416
482
|
public_class_method def self.parse_skill_references(opts = {})
|
|
417
483
|
content = opts[:content].to_s
|
|
418
|
-
|
|
484
|
+
parsed = parse_skill_frontmatter(content: content)
|
|
485
|
+
fm = parsed[:frontmatter]
|
|
486
|
+
refs = []
|
|
419
487
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if fm_end
|
|
424
|
-
begin
|
|
425
|
-
require 'yaml'
|
|
426
|
-
fm = YAML.safe_load(content[4..fm_end], permitted_classes: [], aliases: false) || {}
|
|
427
|
-
r = fm['references'] || fm[:references]
|
|
428
|
-
refs.concat(Array(r).map(&:to_s)) if r
|
|
429
|
-
rescue StandardError
|
|
430
|
-
# ignore malformed front-matter
|
|
431
|
-
end
|
|
432
|
-
end
|
|
433
|
-
end
|
|
488
|
+
refs.concat(Array(fm['references'] || fm[:references]).map(&:to_s))
|
|
489
|
+
md = fm['metadata'] || fm[:metadata]
|
|
490
|
+
refs.concat(Array(md['references'] || md[:references]).map(&:to_s)) if md.is_a?(Hash)
|
|
434
491
|
|
|
435
|
-
# Markdown "## References" section (bullets or bare lines until next heading / EOF)
|
|
436
492
|
if content =~ /^\s*\#{1,3}\s*References\s*$/i
|
|
437
493
|
in_section = false
|
|
438
494
|
content.each_line do |line|
|
|
@@ -441,7 +497,7 @@ module PWN
|
|
|
441
497
|
next
|
|
442
498
|
end
|
|
443
499
|
next unless in_section
|
|
444
|
-
break if line =~ /^\s*\#{1,3}\s+\S/
|
|
500
|
+
break if line =~ /^\s*\#{1,3}\s+\S/
|
|
445
501
|
|
|
446
502
|
l = line.strip.sub(/^[-*]\s*/, '')
|
|
447
503
|
refs << l unless l.empty?
|
|
@@ -453,36 +509,180 @@ module PWN
|
|
|
453
509
|
[]
|
|
454
510
|
end
|
|
455
511
|
|
|
512
|
+
# Supported Method Parameters::
|
|
513
|
+
# out = PWN::Config.write_skill(
|
|
514
|
+
# name: 'required - free-form; sanitised to [a-z0-9-]',
|
|
515
|
+
# content: 'required - markdown body (WITHOUT frontmatter)',
|
|
516
|
+
# description: 'optional - 1..1024 chars; derived from body when omitted',
|
|
517
|
+
# references: 'optional - Array of URLs / CWE / CVE / ATT&CK / NIST ids',
|
|
518
|
+
# license: 'optional - SPDX id or free text',
|
|
519
|
+
# metadata: 'optional - Hash of arbitrary metadata',
|
|
520
|
+
# allowed_tools: 'optional - Array of toolset names',
|
|
521
|
+
# pwn_skills_path: 'optional - override skills root'
|
|
522
|
+
# )
|
|
523
|
+
#
|
|
524
|
+
# The single agentskills.io-conformant writer used by skill_create,
|
|
525
|
+
# learning_distill_skill and migrate_legacy_skills. Always writes
|
|
526
|
+
# <root>/<name>/SKILL.md with required name+description frontmatter.
|
|
527
|
+
public_class_method def self.write_skill(opts = {})
|
|
528
|
+
root = opts[:pwn_skills_path] || pwn_skills_path
|
|
529
|
+
name = sanitize_skill_name(name: opts[:name])
|
|
530
|
+
body = opts[:content].to_s
|
|
531
|
+
raise ArgumentError, 'content is required' if body.strip.empty?
|
|
532
|
+
|
|
533
|
+
# If caller handed us a body that already has frontmatter, strip &
|
|
534
|
+
# merge it so we never emit doubled `---` blocks.
|
|
535
|
+
parsed = parse_skill_frontmatter(content: body)
|
|
536
|
+
body = parsed[:body].to_s.sub(/\A\n+/, '')
|
|
537
|
+
merged = parsed[:frontmatter]
|
|
538
|
+
|
|
539
|
+
desc = (opts[:description] || merged['description'] || merged[:description]).to_s.strip
|
|
540
|
+
if desc.empty?
|
|
541
|
+
first = body.lines.reject { |l| l.strip.empty? || l.strip.start_with?('#') }.first.to_s.strip
|
|
542
|
+
first = body.lines.first.to_s.strip.sub(/^#+\s*/, '') if first.empty?
|
|
543
|
+
desc = first[0, 1024]
|
|
544
|
+
end
|
|
545
|
+
desc = desc[0, 1024]
|
|
546
|
+
raise ArgumentError, 'description could not be derived (empty body?)' if desc.empty?
|
|
547
|
+
|
|
548
|
+
refs = (Array(opts[:references]) + Array(merged['references']) + Array(merged[:references]))
|
|
549
|
+
.map(&:to_s).map(&:strip).reject(&:empty?).uniq
|
|
550
|
+
meta = merged['metadata'] || merged[:metadata] || {}
|
|
551
|
+
meta = {} unless meta.is_a?(Hash)
|
|
552
|
+
meta = meta.merge(opts[:metadata]) if opts[:metadata].is_a?(Hash)
|
|
553
|
+
meta['references'] = refs unless refs.empty?
|
|
554
|
+
|
|
555
|
+
fm = { 'name' => name, 'description' => desc }
|
|
556
|
+
fm['license'] = opts[:license].to_s if opts[:license]
|
|
557
|
+
fm['allowed-tools'] = Array(opts[:allowed_tools]).map(&:to_s) if opts[:allowed_tools]
|
|
558
|
+
fm['metadata'] = meta unless meta.empty?
|
|
559
|
+
|
|
560
|
+
require 'yaml'
|
|
561
|
+
frontmatter = YAML.dump(fm).sub(/\A---\n/, '') # YAML.dump already emits leading ---
|
|
562
|
+
out = "---\n#{frontmatter}---\n\n#{body.rstrip}\n"
|
|
563
|
+
out << "\n## References\n#{refs.map { |r| "- #{r}" }.join("\n")}\n" if refs.any? && body !~ /^\#{1,3}\s*References\s*$/i
|
|
564
|
+
|
|
565
|
+
dir = File.join(root, name)
|
|
566
|
+
path = File.join(dir, SKILL_ENTRY)
|
|
567
|
+
FileUtils.mkdir_p(dir)
|
|
568
|
+
File.write(path, out)
|
|
569
|
+
|
|
570
|
+
{ name: name, dir: dir, path: path, bytes: out.bytesize, description: desc, references: refs, format: :agentskills }
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# Supported Method Parameters::
|
|
574
|
+
# report = PWN::Config.migrate_legacy_skills(
|
|
575
|
+
# pwn_skills_path: 'optional - override skills root',
|
|
576
|
+
# delete_legacy: 'optional - remove flat file after migration (default true)'
|
|
577
|
+
# )
|
|
578
|
+
#
|
|
579
|
+
# One-shot converter: every flat ~/.pwn/skills/*.md (etc.) becomes a
|
|
580
|
+
# spec-conformant <name>/SKILL.md with backfilled frontmatter. Idempotent.
|
|
581
|
+
public_class_method def self.migrate_legacy_skills(opts = {})
|
|
582
|
+
root = opts[:pwn_skills_path] || pwn_skills_path
|
|
583
|
+
del = opts.fetch(:delete_legacy, true)
|
|
584
|
+
migrated = []
|
|
585
|
+
Dir.glob(File.join(root, '*.{rb,md,txt,skill,yml,yaml}')).each do |legacy|
|
|
586
|
+
content = File.read(legacy)
|
|
587
|
+
base = File.basename(legacy, '.*')
|
|
588
|
+
out = write_skill(name: base, content: content, pwn_skills_path: root)
|
|
589
|
+
if File.extname(legacy) == '.rb'
|
|
590
|
+
FileUtils.mkdir_p(File.join(out[:dir], 'scripts'))
|
|
591
|
+
FileUtils.cp(legacy, File.join(out[:dir], 'scripts', File.basename(legacy)))
|
|
592
|
+
end
|
|
593
|
+
FileUtils.rm_f(legacy) if del
|
|
594
|
+
migrated << { from: legacy, to: out[:path] }
|
|
595
|
+
rescue StandardError => e
|
|
596
|
+
migrated << { from: legacy, error: e.message }
|
|
597
|
+
end
|
|
598
|
+
load_skills(pwn_skills_path: root)
|
|
599
|
+
{ migrated: migrated.length, details: migrated }
|
|
600
|
+
end
|
|
601
|
+
|
|
456
602
|
# Supported Method Parameters::
|
|
457
603
|
# skills = PWN::Config.load_skills(
|
|
458
604
|
# pwn_skills_path: 'optional - Path to skills folder. Defaults to ~/.pwn/skills'
|
|
459
605
|
# )
|
|
460
606
|
#
|
|
461
|
-
# Loads
|
|
462
|
-
#
|
|
463
|
-
#
|
|
464
|
-
#
|
|
607
|
+
# Loads skills into the PWN::Skills constant. Two on-disk shapes are
|
|
608
|
+
# accepted so upgrades are seamless:
|
|
609
|
+
#
|
|
610
|
+
# agentskills.io → <root>/<name>/SKILL.md (preferred; written by write_skill)
|
|
611
|
+
# legacy flat → <root>/<name>.{md,txt,rb,skill,yml,yaml}
|
|
612
|
+
#
|
|
613
|
+
# Each entry: { type:, format:, path:, dir:, content:, description:,
|
|
614
|
+
# references:, frontmatter:, loaded:?, error:? }
|
|
465
615
|
public_class_method def self.load_skills(opts = {})
|
|
466
|
-
pwn_skills_path = opts[:pwn_skills_path] || PWN::Env[:pwn_skills_path] || pwn_skills_path
|
|
616
|
+
pwn_skills_path = opts[:pwn_skills_path] || (PWN.const_defined?(:Env) && PWN::Env.is_a?(Hash) && PWN::Env[:pwn_skills_path]) || self.pwn_skills_path
|
|
467
617
|
FileUtils.mkdir_p(pwn_skills_path) if pwn_skills_path && !Dir.exist?(pwn_skills_path.to_s)
|
|
468
618
|
|
|
469
619
|
skills = {}
|
|
470
620
|
return skills unless pwn_skills_path && Dir.exist?(pwn_skills_path.to_s)
|
|
471
621
|
|
|
622
|
+
# ── agentskills.io directory layout ───────────────────────────────
|
|
623
|
+
Dir.glob(File.join(pwn_skills_path, '*', SKILL_ENTRY)).each do |entry|
|
|
624
|
+
dir = File.dirname(entry)
|
|
625
|
+
key = File.basename(dir).to_sym
|
|
626
|
+
content = File.read(entry)
|
|
627
|
+
parsed = parse_skill_frontmatter(content: content)
|
|
628
|
+
fm = parsed[:frontmatter]
|
|
629
|
+
desc = (fm['description'] || fm[:description]).to_s.strip
|
|
630
|
+
desc = parsed[:body].to_s.lines.first.to_s.strip.sub(/^#+\s*/, '')[0, 200] if desc.empty?
|
|
631
|
+
scripts = Dir.glob(File.join(dir, 'scripts', '*.rb'))
|
|
632
|
+
|
|
633
|
+
meta = {
|
|
634
|
+
type: scripts.any? ? :ruby : :instruction,
|
|
635
|
+
format: :agentskills,
|
|
636
|
+
path: entry,
|
|
637
|
+
dir: dir,
|
|
638
|
+
content: content,
|
|
639
|
+
description: desc,
|
|
640
|
+
frontmatter: fm,
|
|
641
|
+
references: parse_skill_references(content: content),
|
|
642
|
+
allowed_tools: Array(fm['allowed-tools'] || fm[:'allowed-tools'] || fm['allowed_tools'])
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
scripts.each do |rb|
|
|
646
|
+
require rb
|
|
647
|
+
rescue StandardError => e
|
|
648
|
+
meta[:loaded] = false
|
|
649
|
+
meta[:error] = e.message
|
|
650
|
+
end
|
|
651
|
+
meta[:loaded] = true unless meta.key?(:loaded) || scripts.empty?
|
|
652
|
+
|
|
653
|
+
skills[key] = meta
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
# ── legacy flat files (backward-compat shim) ──────────────────────
|
|
472
657
|
Dir.glob(File.join(pwn_skills_path, '*.{rb,md,txt,skill,yml,yaml}')).each do |skill_file|
|
|
473
|
-
|
|
658
|
+
key = File.basename(skill_file, '.*').to_sym
|
|
659
|
+
next if skills.key?(key) # directory format wins on collision
|
|
660
|
+
|
|
474
661
|
content = File.read(skill_file)
|
|
475
|
-
ext
|
|
662
|
+
ext = File.extname(skill_file).downcase
|
|
663
|
+
parsed = parse_skill_frontmatter(content: content)
|
|
664
|
+
desc = parsed[:body].to_s.lines.reject { |l| l.strip.empty? || l.strip.start_with?('#', '---') }.first.to_s.strip
|
|
665
|
+
desc = parsed[:body].to_s.lines.first.to_s.strip.sub(/^#+\s*/, '')[0, 200] if desc.empty?
|
|
666
|
+
|
|
667
|
+
base = {
|
|
668
|
+
format: :legacy,
|
|
669
|
+
path: skill_file,
|
|
670
|
+
dir: pwn_skills_path,
|
|
671
|
+
content: content,
|
|
672
|
+
description: desc,
|
|
673
|
+
frontmatter: parsed[:frontmatter],
|
|
674
|
+
references: parse_skill_references(content: content)
|
|
675
|
+
}
|
|
476
676
|
|
|
477
677
|
if ext == '.rb'
|
|
478
678
|
begin
|
|
479
679
|
require skill_file
|
|
480
|
-
skills[
|
|
680
|
+
skills[key] = base.merge(type: :ruby, loaded: true)
|
|
481
681
|
rescue StandardError => e
|
|
482
|
-
skills[
|
|
682
|
+
skills[key] = base.merge(type: :ruby, loaded: false, error: e.message)
|
|
483
683
|
end
|
|
484
684
|
else
|
|
485
|
-
skills[
|
|
685
|
+
skills[key] = base.merge(type: :instruction)
|
|
486
686
|
end
|
|
487
687
|
end
|
|
488
688
|
|
|
@@ -537,6 +737,21 @@ module PWN
|
|
|
537
737
|
config: 'optional - Hash to redact sensitive artifacts from. Defaults to PWN::Env'
|
|
538
738
|
)
|
|
539
739
|
|
|
740
|
+
#{self}.pwn_skills_path
|
|
741
|
+
|
|
742
|
+
#{self}.sanitize_skill_name(name: '...')
|
|
743
|
+
|
|
744
|
+
#{self}.write_skill(
|
|
745
|
+
name: 'required', content: 'required',
|
|
746
|
+
description: 'optional', references: 'optional Array',
|
|
747
|
+
license: 'optional', metadata: 'optional Hash',
|
|
748
|
+
allowed_tools: 'optional Array', pwn_skills_path: 'optional'
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
#{self}.load_skills(pwn_skills_path: 'optional')
|
|
752
|
+
|
|
753
|
+
#{self}.migrate_legacy_skills(pwn_skills_path: 'optional', delete_legacy: true)
|
|
754
|
+
|
|
540
755
|
#{self}.refresh_env(
|
|
541
756
|
pwn_env_path: 'optional - Path to pwn.yaml file. Defaults to ~/.pwn/pwn.yaml',
|
|
542
757
|
pwn_dec_path: 'optional - Path to pwn.yaml.decryptor file. Defaults to ~/.pwn/pwn.yaml.decryptor'
|
data/lib/pwn/ffi/stdio.rb
CHANGED
|
@@ -34,7 +34,9 @@ module PWN
|
|
|
34
34
|
# Display Usage for this Module
|
|
35
35
|
|
|
36
36
|
public_class_method def self.help
|
|
37
|
-
puts
|
|
37
|
+
# NB: bare `puts` here would resolve to the FFI-attached libc puts
|
|
38
|
+
# (writes to C stdout, bypasses $stdout capture, flushes at exit).
|
|
39
|
+
$stdout.puts "USAGE:
|
|
38
40
|
#{self}.puts string
|
|
39
41
|
#{self}.printf(\"format string\", str, int, etc)
|
|
40
42
|
|
data/lib/pwn/plugins/fuzz.rb
CHANGED
|
@@ -6,7 +6,9 @@ require 'htmlentities'
|
|
|
6
6
|
|
|
7
7
|
module PWN
|
|
8
8
|
module Plugins
|
|
9
|
-
# This plugin was created to support fuzzing various networking protocols
|
|
9
|
+
# This plugin was created to support fuzzing various networking protocols.
|
|
10
|
+
# A request template with fuzz delimiters is combined with a payload and
|
|
11
|
+
# optional layered encodings, then replayed over TCP/UDP (optionally TLS).
|
|
10
12
|
module Fuzz
|
|
11
13
|
# Supported Method Parameters::
|
|
12
14
|
# socket_fuzz_results_arr = PWN::Plugins::Fuzz.socket(
|
|
@@ -17,11 +19,11 @@ module PWN
|
|
|
17
19
|
# fuzz_delimeter: 'optional - fuzz delimeter used in request to specify where payloads should reside (defaults to \u2665)',
|
|
18
20
|
# request: 'required - String object of socket request w/ \u001A as fuzz delimeter (e.g. "GET /\u001A\u001A HTTP/1.1\r\nHost: \u001A127..0.0.1\u001A\r\n\r\n")',
|
|
19
21
|
# payload: 'required - payload string',
|
|
22
|
+
# response_timeout: 'optional - float (defaults to 0.9)',
|
|
23
|
+
# request_rate_limit: 'optional - float (defaults to 0.3)',
|
|
20
24
|
# encoding: 'optional - :base64 || :hex || :html_entity || :url (Defaults to nil)',
|
|
21
25
|
# encoding_depth: 'optional - number of times to encode payload (defaults to 1)',
|
|
22
|
-
# char_encoding: 'optional - character encoding returned by PWN::Plugins::Char.list_encoders (defaults to UTF-8)'
|
|
23
|
-
# response_timeout: 'optional - float (defaults to 0.9)',
|
|
24
|
-
# request_rate_limit: 'optional - float (defaults to 0.3)'
|
|
26
|
+
# char_encoding: 'optional - character encoding returned by PWN::Plugins::Char.list_encoders (defaults to UTF-8)'
|
|
25
27
|
# )
|
|
26
28
|
|
|
27
29
|
public_class_method def self.socket(opts = {})
|
|
@@ -189,11 +191,11 @@ module PWN
|
|
|
189
191
|
fuzz_delimeter: \"optional - fuzz delimeter used in request to specify where payloads should reside (defaults to \u2665)\",
|
|
190
192
|
request: \"required - String object of socket request w/ \u2665 as fuzz delimeter (e.g. '\"GET /\u2665\u2665 HTTP/1.1\\r\\nHost: \u2665127.0.0.1\u2665\\r\\n\\r\\n\"')\",
|
|
191
193
|
payload: 'required - payload string',
|
|
194
|
+
response_timeout: 'optional - float (defaults to 0.9)',
|
|
195
|
+
request_rate_limit: 'optional - float (defaults to 0.3)',
|
|
192
196
|
encoding: 'optional - :base64 || :hex || :html_entity || :url (Defaults to nil)',
|
|
193
197
|
encoding_depth: 'optional - number of times to encode payload (defaults to 1)',
|
|
194
|
-
char_encoding: 'optional - character encoding returned by PWN::Plugins::Char.list_encoders (defaults to UTF-8)'
|
|
195
|
-
response_timeout: 'optional - float (defaults to 0.9)',
|
|
196
|
-
request_rate_limit: 'optional - float (defaults to 0.3)'
|
|
198
|
+
char_encoding: 'optional - character encoding returned by PWN::Plugins::Char.list_encoders (defaults to UTF-8)'
|
|
197
199
|
)
|
|
198
200
|
|
|
199
201
|
#{self}.authors
|