lictor 0.4.0
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 +7 -0
- data/CHANGELOG.md +240 -0
- data/LICENSE +21 -0
- data/README.md +285 -0
- data/bin/lictor +609 -0
- data/lib/config/build_layouts.yml +67 -0
- data/lib/lictor/acquisition.rb +78 -0
- data/lib/lictor/config.rb +24 -0
- data/lib/lictor/executor.rb +119 -0
- data/lib/lictor/exploit_scaffold.rb +61 -0
- data/lib/lictor/handoff.rb +62 -0
- data/lib/lictor/harvest.rb +58 -0
- data/lib/lictor/pack_draft.rb +84 -0
- data/lib/lictor/payload_builder.rb +187 -0
- data/lib/lictor/priors.rb +30 -0
- data/lib/lictor/profiler.rb +38 -0
- data/lib/lictor/recon.rb +61 -0
- data/lib/lictor/report.rb +123 -0
- data/lib/lictor/run.rb +174 -0
- data/lib/lictor/scanner.rb +31 -0
- data/lib/lictor/suggestion.rb +40 -0
- data/lib/lictor/targets.rb +38 -0
- data/lib/lictor/technique_packs.rb +25 -0
- data/lib/lictor/templates/scaffold_exploit.rb.erb +47 -0
- data/lib/lictor/trail.rb +77 -0
- data/lib/lictor/tube.rb +76 -0
- data/lib/lictor/version.rb +5 -0
- data/lib/lictor.rb +26 -0
- metadata +101 -0
data/lib/lictor/trail.rb
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
module Lictor
|
|
7
|
+
# Append-only JSONL trail: one line per event, timestamped. An engagement
|
|
8
|
+
# you cannot replay is an engagement you cannot defend. (SQLite/sequel
|
|
9
|
+
# comes when queries are actually needed; JSONL is the honest v1.)
|
|
10
|
+
class Trail
|
|
11
|
+
def initialize(path)
|
|
12
|
+
@path = path
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def record(event)
|
|
16
|
+
line = JSON.generate(event.merge(timestamp: Time.now.utc.iso8601))
|
|
17
|
+
# 0600 at creation: the record of offensive operations is private
|
|
18
|
+
# from birth (existing files keep the operator's perms)
|
|
19
|
+
File.open(@path, "a", 0o600) { |f| f.puts(line) }
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def events
|
|
24
|
+
File.readlines(@path).map { |l| JSON.parse(l) }
|
|
25
|
+
rescue Errno::ENOENT
|
|
26
|
+
[]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Plan and outcome events grouped per run, in run order.
|
|
30
|
+
def runs
|
|
31
|
+
grouped = {}
|
|
32
|
+
events.each do |event|
|
|
33
|
+
run = grouped[event["run"]] ||= {run: event["run"], outcomes: []}
|
|
34
|
+
case event["type"]
|
|
35
|
+
when "plan"
|
|
36
|
+
run.merge!(event.slice("mode", "selected", "applicable", "explain",
|
|
37
|
+
"facts_file", "profiled", "priors_adapted")
|
|
38
|
+
.transform_keys(&:to_sym))
|
|
39
|
+
when "profile"
|
|
40
|
+
run[:profiled] = event["binary"]
|
|
41
|
+
when "outcome"
|
|
42
|
+
run[:outcomes] << {technique: event["technique"], success: event["success"],
|
|
43
|
+
executed: event["executed"] || false}
|
|
44
|
+
when "scan"
|
|
45
|
+
run[:scanned] = event["target"]
|
|
46
|
+
when "fuzz"
|
|
47
|
+
run[:fuzzed] = event["binary"]
|
|
48
|
+
run[:crashes] = event["crashes"]
|
|
49
|
+
when "triage"
|
|
50
|
+
run[:triage] = {signal: event["signal"], control: event["control"]}
|
|
51
|
+
when "rule_install"
|
|
52
|
+
run[:taught] = event["technique"]
|
|
53
|
+
when "acquire"
|
|
54
|
+
run[:acquired] = event["binary"]
|
|
55
|
+
when "build"
|
|
56
|
+
run[:built] = {technique: event["technique"], offset: event["offset"],
|
|
57
|
+
ret: event["ret"], size: event["size"], out: event["out"]}
|
|
58
|
+
when "scaffold"
|
|
59
|
+
run[:scaffolded] = event["file"]
|
|
60
|
+
when "harvest"
|
|
61
|
+
run[:harvested] = event["addresses"]
|
|
62
|
+
when "verify"
|
|
63
|
+
run[:verified] = event["remote_crashed"]
|
|
64
|
+
when "suggest"
|
|
65
|
+
run[:suggested] = event["added"]
|
|
66
|
+
when "leak"
|
|
67
|
+
run[:leaked] = event["addresses"]
|
|
68
|
+
when "measure"
|
|
69
|
+
run[:measured] = {sink: event["sink"], offset: event["offset"], source: event["source"]}
|
|
70
|
+
when "pack_draft"
|
|
71
|
+
run[:drafted] = event["technique"]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
grouped.values
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/lictor/tube.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
|
|
5
|
+
module Lictor
|
|
6
|
+
class TubeError < StandardError; end
|
|
7
|
+
|
|
8
|
+
# The ONE TCP implementation behind the banner readers (harvest,
|
|
9
|
+
# verify): connect, read_until (EOF/reset/deadline/limit), write,
|
|
10
|
+
# reset tracking, close. The run channel stays the ronin subprocess
|
|
11
|
+
# (ADR 0001's honest boundary).
|
|
12
|
+
# UDP, TLS and HTTP-aware delivery become flavors of the same seam
|
|
13
|
+
# (ADR lictor 0001). The timeout/deadline semantics live here, once.
|
|
14
|
+
class Tube
|
|
15
|
+
attr_reader :reset_seen
|
|
16
|
+
|
|
17
|
+
def self.tcp(host, port, timeout: 5)
|
|
18
|
+
sock = Socket.tcp(host, port.to_i, connect_timeout: timeout)
|
|
19
|
+
new(sock, deadline: Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout)
|
|
20
|
+
rescue SystemCallError => e
|
|
21
|
+
raise TubeError, "connect #{host}:#{port} failed: #{e.message}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(sock, deadline:)
|
|
25
|
+
@sock = sock
|
|
26
|
+
@deadline = deadline
|
|
27
|
+
@reset_seen = false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Read until the connection dies (EOF or reset), the deadline or the
|
|
31
|
+
# limit; returns everything captured. A reset is recorded, not
|
|
32
|
+
# raised: the callers' crash heuristics need the distinction.
|
|
33
|
+
def read_bytes(limit: 512)
|
|
34
|
+
reply = +""
|
|
35
|
+
loop do
|
|
36
|
+
break if reply.bytesize >= limit
|
|
37
|
+
break if Process.clock_gettime(Process::CLOCK_MONOTONIC) > @deadline
|
|
38
|
+
|
|
39
|
+
chunk = @sock.read_nonblock(limit - reply.bytesize, exception: false)
|
|
40
|
+
case chunk
|
|
41
|
+
# nil: read_nonblock(exception: false) signals EOF as nil, "" as
|
|
42
|
+
# a truly empty read - both mean the socket will yield no more
|
|
43
|
+
when "", nil then break
|
|
44
|
+
when :wait_readable
|
|
45
|
+
break unless IO.select([@sock], nil, nil, 0.5)
|
|
46
|
+
else
|
|
47
|
+
reply << chunk
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
reply
|
|
51
|
+
rescue Errno::ECONNRESET, Errno::EPIPE
|
|
52
|
+
@reset_seen = true
|
|
53
|
+
reply
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def gets(delim = "\n", wait: 2)
|
|
57
|
+
@sock.wait_readable(wait) ? @sock.gets(delim) : nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def write(data)
|
|
61
|
+
@sock.write(data)
|
|
62
|
+
self
|
|
63
|
+
rescue Errno::EPIPE, Errno::ECONNRESET
|
|
64
|
+
@reset_seen = true
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def close
|
|
69
|
+
@sock.close unless @sock.closed?
|
|
70
|
+
rescue IOError, Errno::ENOTCONN
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def reset? = @reset_seen
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/lictor.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "lictor/version"
|
|
5
|
+
require_relative "lictor/config"
|
|
6
|
+
require_relative "lictor/acquisition"
|
|
7
|
+
require_relative "lictor/recon"
|
|
8
|
+
require_relative "lictor/scanner"
|
|
9
|
+
require_relative "lictor/profiler"
|
|
10
|
+
require_relative "lictor/handoff"
|
|
11
|
+
require_relative "lictor/technique_packs"
|
|
12
|
+
require_relative "lictor/suggestion"
|
|
13
|
+
require_relative "lictor/payload_builder" # lazy-loads metasm (the profiler) on first build
|
|
14
|
+
require_relative "lictor/exploit_scaffold"
|
|
15
|
+
require_relative "lictor/pack_draft"
|
|
16
|
+
require_relative "lictor/tube"
|
|
17
|
+
require_relative "lictor/harvest"
|
|
18
|
+
require_relative "lictor/executor"
|
|
19
|
+
require_relative "lictor/priors"
|
|
20
|
+
require_relative "lictor/trail"
|
|
21
|
+
require_relative "lictor/report"
|
|
22
|
+
require_relative "lictor/targets"
|
|
23
|
+
require_relative "lictor/run"
|
|
24
|
+
|
|
25
|
+
module Lictor
|
|
26
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lictor
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.4.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Gregory Epp
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: augure
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.1.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.1.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: ruby-nmap
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
description: 'Carries out the decisions augure judged: handoff by default, everything
|
|
41
|
+
trailed, the operator is the authorization. Scan, acquire, fuzz, verify, plan, build,
|
|
42
|
+
run, report. Lictor executes; it never decides.'
|
|
43
|
+
email:
|
|
44
|
+
- log0u7@users.noreply.github.com
|
|
45
|
+
executables:
|
|
46
|
+
- lictor
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- CHANGELOG.md
|
|
51
|
+
- LICENSE
|
|
52
|
+
- README.md
|
|
53
|
+
- bin/lictor
|
|
54
|
+
- lib/config/build_layouts.yml
|
|
55
|
+
- lib/lictor.rb
|
|
56
|
+
- lib/lictor/acquisition.rb
|
|
57
|
+
- lib/lictor/config.rb
|
|
58
|
+
- lib/lictor/executor.rb
|
|
59
|
+
- lib/lictor/exploit_scaffold.rb
|
|
60
|
+
- lib/lictor/handoff.rb
|
|
61
|
+
- lib/lictor/harvest.rb
|
|
62
|
+
- lib/lictor/pack_draft.rb
|
|
63
|
+
- lib/lictor/payload_builder.rb
|
|
64
|
+
- lib/lictor/priors.rb
|
|
65
|
+
- lib/lictor/profiler.rb
|
|
66
|
+
- lib/lictor/recon.rb
|
|
67
|
+
- lib/lictor/report.rb
|
|
68
|
+
- lib/lictor/run.rb
|
|
69
|
+
- lib/lictor/scanner.rb
|
|
70
|
+
- lib/lictor/suggestion.rb
|
|
71
|
+
- lib/lictor/targets.rb
|
|
72
|
+
- lib/lictor/technique_packs.rb
|
|
73
|
+
- lib/lictor/templates/scaffold_exploit.rb.erb
|
|
74
|
+
- lib/lictor/trail.rb
|
|
75
|
+
- lib/lictor/tube.rb
|
|
76
|
+
- lib/lictor/version.rb
|
|
77
|
+
homepage: https://github.com/log0u7/lictor
|
|
78
|
+
licenses:
|
|
79
|
+
- MIT
|
|
80
|
+
metadata:
|
|
81
|
+
homepage_uri: https://github.com/log0u7/lictor
|
|
82
|
+
source_code_uri: https://github.com/log0u7/lictor
|
|
83
|
+
rdoc_options: []
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: '3.4'
|
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
requirements: []
|
|
97
|
+
rubygems_version: 3.6.9
|
|
98
|
+
specification_version: 4
|
|
99
|
+
summary: The orchestrator that carries out augure's judgments - handoff by default,
|
|
100
|
+
everything trailed.
|
|
101
|
+
test_files: []
|