diff --git a/lib/rex/parser/group_policy_preferences.rb b/lib/rex/parser/group_policy_preferences.rb index a8fb69ce1d..3e97450b28 100644 --- a/lib/rex/parser/group_policy_preferences.rb +++ b/lib/rex/parser/group_policy_preferences.rb @@ -129,14 +129,40 @@ class GPP # Decrypts passwords using Microsoft's published key: # http://msdn.microsoft.com/en-us/library/cc422924.aspx def self.decrypt(encrypted_data) - unless encrypted_data - return "" - end + password = "" + return password unless encrypted_data password = "" - padding = "=" * (4 - (encrypted_data.length % 4)) - epassword = "#{encrypted_data}#{padding}" - decoded = Rex::Text.decode_base64(epassword) + retries = 0 + original_data = encrypted_data.dup + + begin + mod = encrypted_data.length % 4 + + # PowerSploit code strips the last character, unsure why... + case mod + when 1 + encrypted_data = encrypted_data[0..-2] + when 2, 3 + padding = '=' * (4 - mod) + encrypted_data = "#{encrypted_data}#{padding}" + end + + # Strict base64 decoding used here + decoded = encrypted_data.unpack('m0').first + rescue ::ArgumentError => e + # Appears to be some junk UTF-8 Padding appended at times in + # Win2k8 (not in Win2k8R2) + # Lets try stripping junk and see if we can decrypt + if retries < 8 + retries += 1 + original_data = original_data[0..-2] + encrypted_data = original_data + retry + else + return password + end + end key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b" aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC") diff --git a/spec/lib/rex/parser/group_policy_preferences_spec.rb b/spec/lib/rex/parser/group_policy_preferences_spec.rb index fd8401ab1c..3d0f8c03d4 100644 --- a/spec/lib/rex/parser/group_policy_preferences_spec.rb +++ b/spec/lib/rex/parser/group_policy_preferences_spec.rb @@ -1,3 +1,4 @@ +# encoding: binary require 'rex/parser/group_policy_preferences' xml_group = ' @@ -76,75 +77,89 @@ xml_ms = ' ' +# Win2k8 appears to append some junk padding in some cases +cpassword_win2k8 = [] +# Win2k8R2 - EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wEMON8tIIslS6707RU1F7Bh +cpassword_win2k8 << ['EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wEMON8tIIslS6707RU1F7BhTµkp', 'N3v3rGunnaG!veYo'] +cpassword_win2k8 << ['EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wGSwOI7Be//GJdxd5YYXUQHTµkp', 'N3v3rGunnaG!veYou'] +# Win2k8R2 - EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wFSuDccBEp/4l5EuKnwF0WS +cpassword_win2k8 << ['EqWFlA4kn2T6PHvGi09M7seHuqCYK/slkJWIl7mK+wFSuDccBEp/4l5EuKnwF0WS»YÂVAA', 'N3v3rGunnaG!veYouUp'] cpassword_normal = "j1Uyj3Vx8TY9LtLZil2uAuZkFQA/4latT76ZwgdHdhw" cpassword_bad = "blah" describe Rex::Parser::GPP do - GPP = Rex::Parser::GPP - - ## - # Decrypt - ## - it "Decrypt returns Local*P4ssword! for normal cpassword" do - result = GPP.decrypt(cpassword_normal) - result.should eq("Local*P4ssword!") - end + GPP = Rex::Parser::GPP + + ## + # Decrypt + ## + it "Decrypt returns Local*P4ssword! for normal cpassword" do + result = GPP.decrypt(cpassword_normal) + result.should eq("Local*P4ssword!") + end - it "Decrypt returns blank for bad cpassword" do - result = GPP.decrypt(cpassword_bad) - result.should eq("") - end - - it "Decrypt returns blank for nil cpassword" do - result = GPP.decrypt(nil) - result.should eq("") - end + it "Decrypt returns blank for bad cpassword" do + result = GPP.decrypt(cpassword_bad) + result.should eq("") + end + + it "Decrypt returns blank for nil cpassword" do + result = GPP.decrypt(nil) + result.should eq("") + end - ## - # Parse - ## + it 'Decrypts a cpassword containing junk padding' do + cpassword_win2k8.each do |encrypted, expected| + result = GPP.decrypt(encrypted) + result.should eq(expected) + end + end - it "Parse returns empty [] for nil" do - GPP.parse(nil).should be_empty - end + ## + # Parse + ## - it "Parse returns results for xml_ms and password is empty" do - results = GPP.parse(xml_ms) - results.should_not be_empty - results[0][:PASS].should be_empty - end + it "Parse returns empty [] for nil" do + GPP.parse(nil).should be_empty + end - it "Parse returns results for xml_datasrc, and attributes, and password is test1" do - results = GPP.parse(xml_datasrc) - results.should_not be_empty - results[0].include?(:ATTRIBUTES).should be_true - results[0][:ATTRIBUTES].should_not be_empty - results[0][:PASS].should eq("test") - end + it "Parse returns results for xml_ms and password is empty" do + results = GPP.parse(xml_ms) + results.should_not be_empty + results[0][:PASS].should be_empty + end - xmls = [] - xmls << xml_group - xmls << xml_drive - xmls << xml_schd - xmls << xml_serv - xmls << xml_datasrc + it "Parse returns results for xml_datasrc, and attributes, and password is test1" do + results = GPP.parse(xml_datasrc) + results.should_not be_empty + results[0].include?(:ATTRIBUTES).should be_true + results[0][:ATTRIBUTES].should_not be_empty + results[0][:PASS].should eq("test") + end - it "Parse returns results for all good xmls and passwords" do - xmls.each do |xml| - results = GPP.parse(xml) - results.should_not be_empty - results[0][:PASS].should_not be_empty - end - end + xmls = [] + xmls << xml_group + xmls << xml_drive + xmls << xml_schd + xmls << xml_serv + xmls << xml_datasrc - ## - # Create_Tables - ## - it "Create_tables returns tables for all good xmls" do - xmls.each do |xml| - results = GPP.parse(xml) - tables = GPP.create_tables(results, "test") - tables.should_not be_empty - end - end + it "Parse returns results for all good xmls and passwords" do + xmls.each do |xml| + results = GPP.parse(xml) + results.should_not be_empty + results[0][:PASS].should_not be_empty + end + end + + ## + # Create_Tables + ## + it "Create_tables returns tables for all good xmls" do + xmls.each do |xml| + results = GPP.parse(xml) + tables = GPP.create_tables(results, "test") + tables.should_not be_empty + end + end end