c0f 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,61 @@
1
+ def dump_load_path
2
+ puts $LOAD_PATH.join("\n")
3
+ found = nil
4
+ $LOAD_PATH.each do |path|
5
+ if File.exists?(File.join(path,"rspec"))
6
+ puts "Found rspec in #{path}"
7
+ if File.exists?(File.join(path,"rspec","core"))
8
+ puts "Found core"
9
+ if File.exists?(File.join(path,"rspec","core","rake_task"))
10
+ puts "Found rake_task"
11
+ found = path
12
+ else
13
+ puts "!! no rake_task"
14
+ end
15
+ else
16
+ puts "!!! no core"
17
+ end
18
+ end
19
+ end
20
+ if found.nil?
21
+ puts "Didn't find rspec/core/rake_task anywhere"
22
+ else
23
+ puts "Found in #{path}"
24
+ end
25
+ end
26
+ require 'bundler'
27
+ require 'rake/clean'
28
+
29
+ require 'rake/testtask'
30
+
31
+ require 'cucumber'
32
+ require 'cucumber/rake/task'
33
+ gem 'rdoc' # we need the installed RDoc gem, not the system one
34
+ require 'rdoc/task'
35
+
36
+ include Rake::DSL
37
+
38
+ Bundler::GemHelper.install_tasks
39
+
40
+
41
+ Rake::TestTask.new do |t|
42
+ t.pattern = 'test/tc_*.rb'
43
+ end
44
+
45
+
46
+ CUKE_RESULTS = 'results.html'
47
+ CLEAN << CUKE_RESULTS
48
+ Cucumber::Rake::Task.new(:features) do |t|
49
+ t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty --no-source -x"
50
+ t.fork = false
51
+ end
52
+
53
+ Rake::RDocTask.new do |rd|
54
+
55
+ rd.main = "README.rdoc"
56
+
57
+ rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
58
+ end
59
+
60
+ task :default => [:test,:features]
61
+
data/bin/c0f ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env ruby
2
+ # c0f - CAN of Fingers
3
+ #
4
+ # Analyzes CAN bus packets to create a fingerprint or identify a BUS line
5
+ #
6
+ # (c) 2015 Craig Smith <craig@theialabs.com> GPLv3
7
+
8
+ require 'optparse'
9
+ require 'methadone'
10
+ require 'formatador'
11
+ require 'json'
12
+ require 'c0f.rb'
13
+
14
+ class App
15
+ include Methadone::Main
16
+ include Methadone::CLILogging
17
+
18
+ # Main c0f routine
19
+ main do |candevice|
20
+ options[:progress] = false if options[:quiet] == true
21
+ @stat = C0f::CANPacketStat.new
22
+ candb = nil
23
+ candump = nil
24
+ if candevice then
25
+ if options[:logfile] then
26
+ puts "You can specify logfile OR a candevice but not both"
27
+ exit 1
28
+ end
29
+ end
30
+ if not candevice and not options[:logfile] and not options["add-fp"] then
31
+ puts "Current use does not perform a valid action. See --help"
32
+ exit -1
33
+ end
34
+ if options[:fpdb] then
35
+ candb = C0f::CANFingerprintDB.new(options[:fpdb])
36
+ puts "Loaded #{candb.count} fingerprints from DB" if not options[:quiet]
37
+ end
38
+ if not options["add-fp"].nil? then
39
+ if candb.nil? then
40
+ puts "Can not add a fingerprint unless a DB is specified"
41
+ exit 5
42
+ else
43
+ if not File.exists? options["add-fp"] then
44
+ puts "Fingerprint JSON file not found"
45
+ exit 6
46
+ else
47
+ fp = JSON.parse(File.read(options["add-fp"]))
48
+ if fp["Make"] == "Unknown" then
49
+ puts "You must specify at *least* the make before adding the fingerprint to the DB"
50
+ exit 20
51
+ end
52
+ r = candb.match_json(fp)
53
+ row = r.next
54
+ if row then
55
+ puts "Base Fingerprint already exists. TODO: Added common ID matching/inserting"
56
+ # TODO: add a fuzzy match and an exact match function. If not exact and we don't have the
57
+ # same make, model, year and trim then insert the slightly different one
58
+ exit 10
59
+ else
60
+ id = candb.insert_json(fp)
61
+ puts "Successfully inserted fingprint (#{id})" if id
62
+ end
63
+ end
64
+ end
65
+ end
66
+ if not candevice.nil? then
67
+ progress = Formatador::ProgressBar.new(options["sample-size"]) { |b|
68
+ b.opts[:color] = "green"
69
+ b.opts[:started_at] = Time.now
70
+ b.opts[:label] = "Reading Packets..."
71
+ } if options[:progress]
72
+ IO.popen "candump -t a -n #{options["sample-size"]} #{candevice}" do |pipe|
73
+ pipe.each do |line|
74
+ progress.increment if options[:progress]
75
+ pkt = C0f::CANPacket.new
76
+ @stat.add pkt if pkt.parse line
77
+ end
78
+ end
79
+ end
80
+ if options[:logfile] then
81
+ log = options[:logfile]
82
+ if File.read log then
83
+ lines = File.readlines(log)
84
+ progress = Formatador::ProgressBar.new(lines.count) { |b|
85
+ b.opts[:color] = "green"
86
+ b.opts[:started_at] = Time.now
87
+ b.opts[:label] = "Loading Packets..."
88
+ } if options[:progress]
89
+ lines.each do |line|
90
+ progress.increment if options[:progress]
91
+ pkt = C0f::CANPacket.new
92
+ @stat.add pkt if pkt.parse line
93
+ end
94
+ else
95
+ puts "Could not read logfile #{log}"
96
+ exit 3
97
+ end
98
+ end
99
+ puts @stat if @stat.pkt_count > 0 and options["print-stats"]
100
+ if (options["print-fp"] or options["save-fp"]) and (options[:logfile] or candevice) then
101
+ if @stat.pkt_count < options["sample-size"] then
102
+ puts "Fingerprinting requires at least 2000 packets"
103
+ exit 4
104
+ end
105
+ fp = C0f::CANFingerprint.new(@stat)
106
+ if candb then
107
+ r = candb.match_json(fp.to_json)
108
+ row = r.next
109
+ if row then
110
+ fp.make = row["make"]
111
+ fp.model = row["model"]
112
+ fp.year = row["year"]
113
+ fp.trim = row["trim"]
114
+ end
115
+ end
116
+ puts fp.to_json if options["print-fp"]
117
+ if options["save-fp"] then
118
+ File.open(options["save-fp"], "w") { |f| f.write fp.to_json }
119
+ end
120
+ end
121
+ end
122
+
123
+ description "CAN Bus Passive Make/Model analyzer"
124
+
125
+ options["print-fp"] = true
126
+ options[:progress] = true
127
+ options["sample-size"] = 2000
128
+ on("--logfile FILENAME", "CANDump logfile")
129
+ on("--print-stats", "Prints logfile stats (Default: No)")
130
+ on("--[no-]print-fp", "Print fingerprint info (Default: Yes)")
131
+ on("--[no-]progress", "Print a progress bar")
132
+ on("--add-fp FINGERPRINT", "Add a JSON fingerprint from a file to the DB")
133
+ on("--fpdb DB", "Location of saved fingerprint DB")
134
+ on("--quiet", "No output other than what is specified")
135
+ on("--sample-size PACKETCOUNT", "The number of packets to process")
136
+ on("--save-fp FILE", "Saves the fingerprint to a file")
137
+ arg :candevice, "Launch candump on a given CAN device", :optional
138
+
139
+ version C0f::VERSION
140
+
141
+ use_log_level_option :toggle_debug_on_signal => 'USR1'
142
+
143
+ go!
144
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'c0f/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "c0f"
8
+ spec.version = C0f::VERSION
9
+ spec.authors = ["Craig Smith"]
10
+ spec.email = ["craig@theialabs.com"]
11
+ spec.summary = %q{c0f - CAN Bus Fingerprinting}
12
+ spec.description = %q{c0f - CAN of Fingers. CAN Bus fingerprinting to passively determine make/model of vehicle}
13
+ spec.homepage = "http://opengarages.orb"
14
+ spec.license = "GPLv3"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "minitest", "~> 4.7"
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency('rdoc')
25
+ spec.add_development_dependency('aruba')
26
+ spec.add_dependency('methadone', '~> 1.8')
27
+ spec.add_dependency('formatador')
28
+ spec.add_dependency('sqlite3')
29
+ spec.add_dependency('json')
30
+ end
@@ -0,0 +1,45 @@
1
+ Feature: Analyze CAN Bus fingerprints
2
+ In order to determine fingerprints for CAN Bus traffic
3
+ I want to have a tool that can analyze, save and report CAN bus fingerprints
4
+ So I can eventually make fingerprints for shellcode
5
+
6
+ Scenario: Basic UI
7
+ When I get help for "c0f"
8
+ Then the exit status should be 0
9
+ And the banner should be present
10
+ And the banner should document that this app takes options
11
+ And the following options should be documented:
12
+ |--version|
13
+ |--logfile|
14
+ |--[no-]progress|
15
+ |--[no-]print-fp|
16
+ |--print-stats|
17
+ |--add-fp|
18
+ |--fpdb|
19
+ |--quiet|
20
+ |--sample-size|
21
+ |--save-fp|
22
+ And the banner should document that this app's arguments are:
23
+ | candevice |
24
+
25
+ Scenario: Parse a valid CANDump Log with enough sample packets
26
+ Given a valid candump logfile at "/tmp/sample-can.log"
27
+ When I successfully run `c0f --logfile /tmp/sample-can.log --quiet`
28
+ Then the stdout should contain valid JSON
29
+ And the Make is "Unknown" and the Model is "Unknown" and the Year is "Unknown" and the Trim is "Unknown"
30
+ And the common IDs should be "166 158 161 191 18E 133 136 13A 13F 164 17C 183 143 095"
31
+ And the main ID should be "143"
32
+ And the main interval should be "0.009998683195847732"
33
+
34
+ Scenario: Take a valid signature and initialze a fingerprint database with it
35
+ Given a completed fingerprint at "/tmp/sample-fp.json" and a fingerprint db at "/tmp/can.db"
36
+ When I successfully run `c0f --add-fp /tmp/sample-fp.json --fpdb /tmp/can.db`
37
+ Then the output should contain "Created Tables"
38
+ And the output should contain "Successfully inserted fingprint"
39
+
40
+ Scenario: Match a canbus fingerprint to that in the fingerprint DB
41
+ Given a valid fingerprint db at "/tmp/sample-fp.db" valid logfile "/tmp/sample-can.log"
42
+ When I successfully run `c0f --fpdb /tmp/sample-fp.db --logfile /tmp/sample-can.log --quiet`
43
+ Then the stdout should contain valid JSON
44
+ And the Make is "Honda" and the Model is "Civic" and the Year is "2009" and the Trim is "Hybrid"
45
+
@@ -0,0 +1,51 @@
1
+ # Put your step definitions here
2
+ require "fileutils"
3
+ require 'json'
4
+
5
+ Given(/^a valid candump logfile at "(.*?)"$/) do |logfile|
6
+ FileUtils.copy "test/sample-can.log", logfile
7
+ end
8
+
9
+ Then(/^the stdout should contain valid JSON$/) do
10
+ expect { JSON.parse(all_output) }.not_to raise_error
11
+ end
12
+
13
+ Then(/^the common IDs should be "(.*?)"$/) do |idstr|
14
+ ids = idstr.split
15
+ fp = JSON.parse(all_output)
16
+ missing = false
17
+ ids.each do |id|
18
+ found = false
19
+ fp["Common"].each do |cid|
20
+ found = true if cid["ID"] == id
21
+ end
22
+ missing = true if not found
23
+ end
24
+ expect(missing).to be_falsey
25
+ end
26
+
27
+ Then(/^the main ID should be "(.*?)"$/) do |id|
28
+ fp = JSON.parse(all_output)
29
+ expect(fp["MainID"] == id).to be_truthy
30
+ end
31
+
32
+ Then(/^the main interval should be "(.*?)"$/) do |i|
33
+ fp = JSON.parse(all_output)
34
+ expect(fp["MainInterval"] == i).to be_truthy
35
+ end
36
+
37
+ Given(/^a completed fingerprint at "(.*?)" and a fingerprint db at "(.*?)"$/) do |fp_dest, candb_dest|
38
+ File.unlink candb_dest if File.exists? candb_dest
39
+ FileUtils.cp "test/sample-fp.json", fp_dest
40
+ end
41
+
42
+ Given(/^a valid fingerprint db at "(.*?)" valid logfile "(.*?)"$/) do |db_dest, log_dest|
43
+ FileUtils.cp "test/sample.db", db_dest
44
+ FileUtils.cp "test/sample-can.log", log_dest
45
+ end
46
+
47
+ Then(/^the Make is "(.*?)" and the Model is "(.*?)" and the Year is "(.*?)" and the Trim is "(.*?)"$/) do |make, model, year, trim|
48
+ fp = JSON.parse(all_output)
49
+ expect((fp["Make"] == make and fp["Model"] == model and fp["Year"] == year and fp["Trim"] == trim)).to be_truthy
50
+ end
51
+
@@ -0,0 +1,16 @@
1
+ require 'aruba/cucumber'
2
+ require 'methadone/cucumber'
3
+
4
+ ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
5
+ LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
6
+
7
+ Before do
8
+ # Using "announce" causes massive warnings on 1.9.2
9
+ @puts = true
10
+ @original_rubylib = ENV['RUBYLIB']
11
+ ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
12
+ end
13
+
14
+ After do
15
+ ENV['RUBYLIB'] = @original_rubylib
16
+ end
@@ -0,0 +1,9 @@
1
+ require "c0f/version"
2
+ require "c0f/can_packet"
3
+ require "c0f/can_packet_stat"
4
+ require "c0f/can_fingerprint"
5
+ require "c0f/can_fingerprint_db"
6
+
7
+ module C0f
8
+
9
+ end
@@ -0,0 +1,97 @@
1
+ module C0f
2
+ class CANFingerprint
3
+ attr_reader :common_ids, :most_common, :padding
4
+ attr_accessor :make, :model, :year, :trim
5
+ # Requires CANPacketStat
6
+ def initialize(stats, db=nil)
7
+ @stats = stats
8
+ @db = db
9
+ @most_common = nil
10
+ @common_ids = Array.new
11
+ @padding = nil
12
+ self.analyze
13
+ @make = "Unknown"
14
+ @model = "Unknown"
15
+ @year = "Unknown"
16
+ @trim = "Unknown"
17
+ end
18
+
19
+ # intended for direct access to the most common IDs interval
20
+ def main_interval
21
+ @most_common.avg_delta
22
+ end
23
+
24
+ def main_id
25
+ @most_common.id
26
+ end
27
+
28
+ # helper function
29
+ def dynamic
30
+ @stats.dynamic
31
+ end
32
+
33
+ def to_s
34
+ s = "Make: #{@make} Model: #{@model} Year: #{@year} Trim: #{@trim}\n"
35
+ s += "Dyanmic: #{@stats.dynamic}\n"
36
+ s += "Padding: 0x#{@padding}\n" if @padding.to_s(16).upcase
37
+ s += "Common IDs: #{@common_ids}\n"
38
+ s += "Most Common: #{@most_common.id} Expected interval: #{@most_common.avg_delta}ms\n"
39
+ s
40
+ end
41
+
42
+ def to_json
43
+ s = '{"Make": "' + @make + '", "Model": "' + @model + '", "Year": "' + @year + '", "Trim": "' + @trim + '"'
44
+ s += ', "Dynamic": "' + (@stats.dynamic ? "true" : "false") + '", '
45
+ s += '"Padding": "' + @padding.to_s(16).upcase + '", ' if @padding
46
+ s += '"Common": [ '
47
+ json_ids = Array.new
48
+ @common_ids.each do |id|
49
+ json_ids << '{ "ID": "' + id + '" }'
50
+ end
51
+ s += json_ids.join(',')
52
+ s += ' ], "MainID": "' + @most_common.id + '", "MainInterval": "' + @most_common.avg_delta.to_s + '"'
53
+ s += "}"
54
+ s
55
+ end
56
+
57
+ # Analyzes the CANPacketStats
58
+ # @stat must be set
59
+ def analyze
60
+ high_count = 0
61
+ padd_stat = Hash.new
62
+ # First pass
63
+ # * calculate the IDs we saw the most
64
+ @stats.pkts.each_value do | pkt|
65
+ high_count = pkt.count if pkt.count > high_count
66
+ if not @stats.dynamic then
67
+ if padd_stat.has_key? pkt.data[-1] then
68
+ padd_stat[pkt.data[-1]] += 1
69
+ else
70
+ padd_stat[pkt.data[-1]] = 1
71
+ end
72
+ end
73
+ end
74
+ # Second pass
75
+ fastest_interval = 5.0
76
+ @stats.pkts.each_value do |pkt|
77
+ # Common IDs are ones that the highest report - 1% of the total reported
78
+ if pkt.count >= high_count - (@stats.pkt_count * 0.01) then
79
+ @common_ids << pkt.id
80
+ if pkt.avg_delta < fastest_interval then
81
+ fastest_interval = pkt.avg_delta
82
+ @most_common = pkt
83
+ end
84
+ end
85
+ end
86
+ if @stats.dynamic then
87
+ highest_pad = 0
88
+ padd_stat.each do |val, count|
89
+ if count > highest_pad
90
+ hightest_pad = count
91
+ @padding = val
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end # CANFingerprint
97
+ end