Attack on domain controller database (NTDS.DIT)
Table of contents:
So, as I have promised, we start the process of analyzing separate Cyber Kill Chain stages of the previously described attack. Today we will review one of the attack vectors on the Company infrastructure, which we can count as two stages: «Actions on Objectives» and «Reconnaissance». Our goals are:
- To gain a foothold by using obtained accounts;
- To get maximum information about Company (email, department names, phone numbers, Functional titles etc) for further attacks or even selling access to its infrastructure.
SYSTEM and NTDS.DIT
Adversaries, once they are in the corporate network, will encroach on one of the “tastiest” pieces of ActiveDirectory’s information, which are SYSTEM and NTDS.DIT files. These files are databases, which contain all data from ActiveDirectory. NTDS.DIT contains information about all users in domain including password hashes. File SYSTEM is needed to decrypt data from NTDS.DIT.
I won’t describe methods of hash retrieval in this article, because many articles on that subject have already been written. I will concentrate on the method that shows infrastructure vulnerability by password exposure (if LM hashes are available), and automation of the searching process that makes it extremely fast.
So we need only LM and NTLM hashes:
After receiving hashes we start with LM hash.
About LM-hash
Some information about LM hash.
It is based on the DES block cipher and can be easily attacked for password cracking because of two vulnerabilities in its realization. Firstly, passwords that are longer than 7 characters are divided into two parts and each part is hashed separately, allowing to attack each part separately. Secondly, all lowercase characters are switched to uppercase.
So, we have:
LM hash, which, as we have figured out before, has two parts.
9196B21FEF3C8906AAD3B435B51404EE
AAD3B435B51404EE – this hash is from an empty password (such sequence of numerals means that one half of hash is an empty password).
We conclude that password consists of less than seven characters, because second part of hash matches with empty character set.
Now to brute-forcing the first part
For that purpose we use hashcat, which allows strong parallelized GPU brute-forcing. Using next keys:
-a => attack mode
-m => hash type
?d = all decimals (0–9).
?l = lowercase characters (a–z).
?u = uppercase characters (A–Z).
?s = symbols.
We receive password in 24 seconds.
Now it is the turn of NTLM
1BC10704ED604747D7D827FC7A43D555
It is much easier to brute-force it, because we know the following information:
- What characters are used in the password;
- Order of these characters.
We only have to check whether a character is uppercase or lowercase.
Thus because we have a LM hash all the work took us slightly more than 32 seconds.
We will further complicate the task:
Suppose we have a 14-character password with the third level of difficulty (classic corporate password):
29355BC0D45C341B51ACD8D3923F7C21
We use the same trick:
Brute-force gave us the first approximation (password in uppercase characters) in 31 second.
29355BC0D45C341B51ACD8D3923F7C21
Now all we have to do is
NTLM:
E06E777CD11F495E9B9098B5576A4343
Increasing complexity of the password even up to 14 characters isn’t making our task harder – if we have LM hash, cracking only takes 40 seconds!
We processed one hash. If we needed to process a big amount of hashes, building masks for every password would be too labor-intensive. That’s why I have developed a tool which would do next work for us – ntlm_mask_gen.exe
(you can download it here тут, MD5: c2a9dac79c51e14c84f35febd72d05ba
or take python code in Annex 1) :
- It forms wordlists for every single password;
- It forms mask for every single password inclusive of formed wordlists;
- It forms one single file with strings for every separate hash, mask and wordlist.
All you have to do then is run this file and watch hashcat cracking passwords in seconds.
Demonstration of the described methodology
Let us carry out one more experiment to demonstrate the described technique:
We will use 10 passwords with 10 characters, fourth level of difficulty with the following settings (traditional requirements for technologic/administrative/system account from corporate password policy):
- We create a file with hashes
csv
:
- Then we create a file for our tool, which will generate wordlists and masks for us. We take NTLM hash and password for hacking LM, for example
test_ntlm_input.txt
:
- Run our tool:
Immediately we receive a file that is ready for password searching by masks and automatically formed wordlists:
- We run this file and get our passwords in 14 seconds:
That’s all.
Clarification
As you know, starting from version 2008, storage of LM hashes is disabled by default, but in case of migration from version 2003 (or lower), accounts that haven’t changed passwords after migration, stay with LM hash in the database. Usually these are technological accounts with passwords that are not changed for several reasons, for example, they can cause crashing of some critical services, etc. Such accounts are a serious threat because their passwords are often «Never Expire», their access is not monitored and they frequently have high privilege up to administrative access. As a result using such accounts for breaking-in will be the most discreet.
Conclusion
It is critically important to give up the domains based on 2003 or lower and use newer versions. In case of migration you have to assure that all accounts have changed their passwords and there is no LM hashes in the database. And one more advice for domain admins and information security experts, you should work proactively and brute-force your passwords at least once a quarter! You should control access to AD especially shadowing operations with SYSTEM and NTDS.DIT files.
I hope this article will be useful for information security experts, who can use described techniques as arguments while communicating with IT specialists. Or it will be useful for admins for the purpose of wider understanding of risks related to attacks on ActiveDirectory.
Annex 1
import math alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' exception = ' &%{}<>^|"' file_name = open('D:\hashcat\kd_input.new','r') main_len = len(file_name.readlines()) file_name.close() file_name = open('D:\hashcat\kd_input.new','r') file_name_out = open('D:\hashcat\kd_input.cmd','w') hashes = [] passwords = [] masks = [[] for i in range(main_len)] dicts = [] char_sets_count = 0 char_sets = [[] for i in range(main_len)] ind_main = 0 ch1 = [] ch2 = [] ch3 = [] ch4 = [] ch_sets = [ch1, ch2, ch3, ch4] def new_init(pass_letter): for ind_0 in range(4): ch_sets[ind_0] = [] return predict(pass_letter) def predict(new_password): pass_letter = '' for let in new_password: if let in alphabet and not (let in pass_letter): pass_letter += let return dict_generator(pass_letter) def dict_generator(pass_letter): global char_sets_count dict_new = '' pass_len = len(pass_letter) if pass_len < 4: for ind1 in range(pass_len): ch_sets[ind1].append(pass_letter[ind1]) ch_sets[ind1].append(pass_letter[ind1].lower()) else: filler(pass_letter, spreader(pass_len)) for ind2 in range(4): if len(ch_sets[ind2]) > 0: dict_new += '-' + str(ind2+1) + ' ' + ''.join(ch_sets[ind2]) + ' ' char_sets[char_sets_count] = [ch_sets[0], ch_sets[1], ch_sets[2], ch_sets[3]] char_sets_count += 1 return dict_new def spreader(letter_num): spread_counter = [0, 0, 0, 0] for ind3 in range(4): spread_counter[ind3] = int(math.ceil(letter_num/float(4-ind3))) letter_num -= spread_counter[ind3] return spread_counter def filler(pass_letter, counter_letter): count_l = 0 for ind_ch in range(4): for ind_set in range(counter_letter[ind_ch]): ch_sets[ind_ch].append(pass_letter[count_l]) ch_sets[ind_ch].append(pass_letter[count_l].lower()) count_l += 1 def mask_generator(password,pwd_idx): mask_new = '' for pw_let in password: if pw_let in alphabet: for ind_array in range(4): if pw_let in char_sets[pwd_idx][ind_array]: mask_new += '?'+str(ind_array+1) elif pw_let in exception: mask_new += '?s' else: mask_new += pw_let masks[pwd_idx] = mask_new # ---------------------- Main -------------------------------- for line in file_name: hashes.append(line[:32]) passwords.append(line[33:len(line)-1]) dicts.append(new_init(passwords[ind_main])) ind_main += 1 for pwd_idx in range(len(passwords)): mask_generator(passwords[pwd_idx],pwd_idx) for index in range(len(hashes)): text = 'cudaHashcat64.exe -m 1000 -a 3 -o ntlm_tesult.hash ' + str(hashes[index]) + ' ' + str(dicts[index]) + ' ' + str(masks[index]) + '\n' file_name_out.write(text) file_name.close() file_name_out.close()