Logo

Chrome

Let us place all of our trust in a password manager.

Chrome

TryHackMe: Прохождение комнаты Chrome.

Дан файл с записью перехваченных пакетов сетевого трафика в формате .pcapng (Packet CAPture Next Generation). Необходимо разобраться, что именно украл злоумышленник.

Обзор

Работать с данными файлами можно различными инструментами, например популярным сниффером и анализатором Wireshark или фреймворком pyshark. Здесь будет использован tshark.

Взглянем на общую картину перехваченного трафика, а именно на статистику по протоколам:

$ tshark -r traffic.pcapng -z io,phs -q
Статистика по протоколам

Можно заметить активный трафик по SMB, а так же 60 Мб трафика данных (data), что говорит о передаче файлов.

Извлечем эти файлы:

$ tshark -r traffic.pcapng -q --export-objects smb,files
Извлечение файлов

Получен ряд файлов, 71 Мб занимает файл encrypted_files, файлы же transfer*.exe идентичны.

Изучим transfer.exe. Если глянуть на строки в файле, легко обнаружить, что это некое .net приложение.

$ strings "files/%5ctransfer.exe"
FrameworkDisplayName
.NET Framework 4.7.2
RSDS
C:\Users\hadri\Desktop\Chrome\transfer\obj\Debug\transfer.pdb
_CorExeMain
mscoree.dll

Приложения под .net компилируются в промежуточный язык который транслируется в машинный код уже только во время выполнения, поэтому их можно крайне эффективно декомпилировать. Существует множество инструментов для этого, например десктопный AvaloniaILSpy, либо онлайн сервис decompiler.com

Декомпилированный код приложения:

private static void Main()
{
    try
    {
        byte[] bytes = Encoding.UTF8.GetBytes("PjoM95MpBdz85Kk7ewcXSLWCoAr7mRj1");
        byte[] bytes2 = Encoding.UTF8.GetBytes("lR3soZqkaWZ9ojTX");
        string path = "C:\\Users\\hadri\\Downloads\\files.zip";
        byte[] array = File.ReadAllBytes(path);
        byte[] bytes3;
        using (Aes aes = Aes.Create())
        {
            aes.Key = bytes;
            aes.IV = bytes2;
            ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);
            using MemoryStream memoryStream = new MemoryStream();
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
            {
                cryptoStream.Write(array, 0, array.Length);
            }
            bytes3 = memoryStream.ToArray();
        }
        string path2 = "C:\\Users\\hadri\\Downloads\\encrypted_files";
        File.WriteAllBytes(path2, bytes3);
        Console.WriteLine("File encrypted and saved successfully.");
    }
    catch (Exception ex)
    {
        Console.WriteLine("Error: " + ex.Message);
    }
}

Приложение довольно примитивно, оно берёт некий файл files.zip и зашифровав его с помощью AES сохраняет под видом encrypted_files. Нам доступны ключ и вектор исполнения (инициализации, IV). Размер ключа равен 32 байтам (256 бит):

$ echo -n PjoM95MpBdz85Kk7ewcXSLWCoAr7mRj1 | wc -c
      32

Такой размер ключа означает использование AES-256. Расшифруем файл encrypted_files с помощью openssl:

  • enc – режим шифрования/дешифрования.

  • -in – входной (зашифрованный) файл.

  • -out – расшифрованный файл.

  • -aes-256-cbc – алгоритм AES-256, CBC.

  • -d – задача расшифровать файл.

  • -iv – вектор исполнения, он задается в шестнадцатеричном формате. Для перевода строки в hex можно воспользоваться xxd:

    • -p – перевод строки в hex.

    • -c0 – убрать разбиение на столбцы.

  • -K – ключ, так же задается в шестнадцатеричном формате.

$ openssl enc -in "files/%5cencrypted_files" -out "files.zip" -aes-256-cbc -d -v 
  -iv `echo -n lR3soZqkaWZ9ojTX | xxd -p -c0` 
  -K `echo -n PjoM95MpBdz85Kk7ewcXSLWCoAr7mRj1 | xxd -p -c0`
Расшифровка архива

Теперь можно распаковать полученный архив:

$ unzip -q files.zip
Распаковка архива

Очевидно злоумышленник скопировал папку AppData, она предназначена для хранения приложениями своих служебных данных. Например, можно заметить папку с профайлом браузера Chrome. Именно здесь браузеры на основе Chromium хранят пароли от сайтов. Проверим наличие паролей, они хранятся в файле User Data > Default > Login Data. Данный файл представляет ни что иное, как обычную базу данных SQLite:

В качестве альтернативы sqlite3 можно использовать куда более удобную утилиту litecli. Она поддерживает автодополнение и правильное форматирование выводимых данных.

$ litecli --auto-vertical-output -R ">" "AppData/Local/Google/Chrome/User Data/Default/Login Data"
Просмотр таблицы с паролями

В таблице logins находятся две записи с паролями. Пароли, конечно же, зашифрованы.

Во всех браузерах на основе Chromium (кроме Яндекс.Браузера) применяется один и тот же алгоритм: пароли зашифрованы с помощью AES GCM единым ключом. Данный ключ же в свою очередь зашифрован с помощью стандартного API Windows - DPAPI. API предоставляет простые функции CryptProtectData/CryptUnprotectData основная идея которых заключается в том, что расшифровать данные может только пользователь с той же учётной записью, что и пользователь зашифровавший данные. Достигается это за счёт того, что данные шифруются с помощью случайно сгенерированного мастер-ключа который в свою очередь зашифрован с помощью предварительного ключа (pre-key) что генерируется на основе пользовательского пароля в системе (NT-хэша) и его уникального идентификатора безопасности (SID); так же используется определенное число итераций, соль и код проверки подлинности (HMAC).

Зашифрованный мастер-ключ и параметры генерации (а разные версии ОС используются различные алгоритмы шифрования и хеширования) хранятся в специальном файле. Имея данный файл, SID пользователя и пароль в системе, можно сгенерировать предварительный ключ и расшифровать мастер-ключ, а с ним и расшифровать любые данные зашифрованные с помощью CryptProtectData().

А что произойдёт, если пользователь сменил пароль, ведь в этом случае предварительный ключ так же изменится и станет невозможно расшифровать мастер-ключ. Станут ли пользовательские данные зашифрованные старым ключом невалидны? На этот случай Windows хранит хеши (NT/SHA1) всех прошлых паролей пользователя в специальном файле CREDHIST, равно как и все зашифрованные мастер-ключи, каждый в своём отдельном файле. Таким образом, если система не может расшифровать текущий мастер-ключ, она последовательно будет перебирать все записи из данного файла.

Мастер-ключ хранится в %USER%\AppData\Roaming\Microsoft\Protect\<SID>\. Если пользователь менял пароли, то там будет несколько файлов, указатель на текущий мастер-ключ будет записан в файле Preferred. <SID> – обозначает идентификатор безопасности.

В полученном архиве имеются все данные файлы:

  • SID пользователя: S-1-5-21-3854677062-280096443-3674533662-1001

  • файл с зашифрованным мастер-ключом: 8c6b6187-8eaa-48bd-be16-98212a441580

  • история паролей: CREDHIST

What is the first password that we find?

Чтобы расшифровать ключ из браузера Chrome которым зашифрованы пароли, можно воспользоваться приложением DataProtectionDecryptor. Оно требует указать путь к папке Protect с данными для DPAPI, а так же пароль пользователя в системе. Пароль пользователя нам не известен, но имея SID и файл с зашифрованным мастер-ключом можно провести брутфорс атаку на пароль.

Если у вас в системе нет словарей паролей, то их можно скачать с репозитория SecLists. В Linux дистрибутивах Parrot и Kali они предустановлены в /usr/share/wordlist.

С помощью DPAPImk2john.py достанем хеш и проведем атаку по словарю:

$ DPAPImk2john -S S-1-5-21-3854677062-280096443-3674533662-1001 
  -mk "AppData/Roaming/Microsoft/Protect/S-1-5-21-3854677062-280096443-3674533662-1001/
  8c6b6187-8eaa-48bd-be16-98212a441580" -c local > hash.txt
$ john hash.txt --no-log --wordlist=/usr/share/wordlists/rockyou.txt 2>/dev/null
  • -S – SID пользователя, берется из имени папки с мастер-ключами.

  • -mk – файл с данными мастер-ключа.

  • -c local – контекст определяющий откуда ключ: локальный (local) или контроллера домена (domain).

Брутфорс пароля пользователя

Пароль успешно подобран. Это ответ на первый вопрос.



What is the URL found in the first index? What is the password found in the first index?

Теперь имея пароль пользователя в системе, можно расшифровать ключ для паролей Chrome. Данный ключ находится в json файле Local State:

"os_crypt": {
  "encrypted_key": "RFBBUEkBAAAA0Iyd3w...TOblLRNtB8YTwhm3wCQSi"
  },

Значение закодировано в base64. Особо стоит отметить, что в самом начале ключа добавлен суффикс DPAPI, поэтому первые 5 байт необходимо пропустить (ключ -s +5 для xxd):

$ cat "AppData/Local/Google/Chrome/User Data/Local State" | 
    jq .os_crypt.encrypted_key -r | base64 --decode | xxd -s +5 -p -c0
Просмотр таблицы с паролями

Получив зашифрованный ключ, теперь можно приступить к его расшифровке. Для DataProtectionDecryptor необходимо в настройках “DPAPI Decryption Options” выбрать режим “Decrypt DPAPI data from external drive or another user”, указать путь к AppData, пароль пользователя, выбрать опцию “Decrypt DPAPI data from the specified string” и ввести данные полученные выше. Программа расшифрует ключ:

Дешифрация мастер-ключа для Chrome

В качестве альтернативы DataProtectionDecryptor можно использовать mimikatz, pypykatz, Impacket, DPAPI-NG или DPAPIck.

Получив расшифрованный ключ, можно расшифровать и пароли, для удобства написан скрипт на Go.

Дополнительно необходимо уточнить структуру паролей: первые три байта это сигнатура (версия), следующие 12 байт это вектор инициализации, далее собственно сам пароль.

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "database/sql"
    "encoding/hex"
    "errors"
    "flag"
    "fmt"
    _ "github.com/mattn/go-sqlite3"
    "os"
    "text/tabwriter"
)

var dbfile string
var aeskey string
var key []byte

func init() {
    flag.StringVar(&dbfile, "ldata", "", "path to Chromium based browser \"Login Data\" file")
    flag.StringVar(&aeskey, "aeskey", "", "decrypted AES key (in hex)")
    flag.Parse()

    if dbfile == "" {
        fmt.Println("No arguments defined")
        flag.PrintDefaults()
        os.Exit(0)
    }

    var err error
    key, err = hex.DecodeString(aeskey)
    if err != nil || len(key) != 32 /*AES-256*/ {
        fmt.Println("Key must be 32 bytes (AES-256), in hex")
        os.Exit(0)
    }
}

func main() {
    db, err := sql.Open("sqlite3", dbfile)
    checkErr(err)

    defer func(db *sql.DB) {
        _ = db.Close()
    }(db)

    rows, err := db.Query("select origin_url, username_value, password_value, id from logins")
    checkErr(err)

    defer func(rows *sql.Rows) {
        _ = rows.Close()
    }(rows)

    var pass []byte
    block, err := aes.NewCipher(key)
    checkErr(err)

    w := new(tabwriter.Writer)

    w.Init(os.Stdout, 0, 0, 2, ' ', 0)
    _, _ = fmt.Fprintln(w, "ID\tURL\tUSER\tPASSWORD\t")

    var count int
    for rows.Next() {
        count++
        var url, user, passblob string
        var id int64
        if err = rows.Scan(&url, &user, &passblob, &id); errors.Is(err, sql.ErrNoRows) {
            fmt.Println("Database is empty")
            os.Exit(0)
        }

        // struct WebPassword {
        //    BYTE signature[3] = "v10";
        //    BYTE iv[12];
        //    BYTE encPassword[...]
        // }
        if passblob[0:3] != "v10" {
            panic("Unknown version of password")
        }
        var iv = passblob[3:15]
        var encpass = passblob[15:]

        gcm, err := cipher.NewGCM(block)
        if err == nil {
            pass, err = gcm.Open(nil, []byte(iv), []byte(encpass), nil)
        }
        if err != nil {
            pass = []byte("<failed>")
        }

        _, _ = fmt.Fprintf(w, "%2d\t%s\t%s\t%s\n", id, url, user, string(pass))
    }

    if count > 0 {
        _ = w.Flush()
    } else {
        fmt.Println("No records found")
    }
}

func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}
Пароли Chrome

Пароли успешно расшифрованы. Получены ответы на все вопросы комнаты.


Дополнительно

Wireshark

Среди перехваченных пакетов сетевого трафика можно обнаружить пакеты аутентификации протокола NTLMSSP используемого smb:

$ tshark -r traffic.pcapng -Y ntlmssp | awk '{$2 = ""; print}'
Пакеты NTLMSSP

В этих пакетах достаточно данных для попытки подобрать пароль. Создадим файл с данными в следующем формате:

пользователь::домен:NTLM Server Challenge:NTProofStr:остаток NTLMV2 Response

Выведем все необходимые данные:

  • -Y "ntlmssp.messagetype == 0x3" – отфильтруем пакет NTLMSSP_AUTH

  • -Tfields – вывести значения полей

    • -e ntlmssp.auth.username – имя пользователя
    • -e ntlmssp.auth.domain – домен
    • -e ntlmssp.ntlmv2_response.ntproofstr – NTProofStr
    • -e ntlmssp.ntlmv2_response – ответ NTLMV2 (ответ будет включать, в том числе и NTProofStr, их нужно разделить двоеточием, поэтому для удобства где разделить, NTProofStr выводится выше отдельно)
  • tr -s "\t" "\n" – замена табуляторов на символ новой строки, чтобы все поля были выведены не последовательно, а каждое на новой строке.

$ tshark -r traffic.pcapng -Y "ntlmssp.messagetype == 0x3" -Tfields
  -e ntlmssp.auth.username
  -e ntlmssp.auth.domain 
  -e ntlmssp.ntlmv2_response.ntproofstr 
  -e ntlmssp.ntlmv2_response | tr -s "\t" "\n"
  • -Y "ntlmssp.messagetype == 0x2" – отфильтруем пакет NTLMSSP_CHALLENGE

  • -Tfields – вывести значения полей

    • -e ntlmssp.ntlmserverchallenge – NTLM Server Challenge
$ tshark -r traffic.pcapng -Y "ntlmssp.messagetype == 0x2" -Tfields 
  -e ntlmssp.ntlmserverchallenge
Данные NTLMSSP

Получим следующий файл:

hacked::MicrosoftAccount:aaaaaaaaaaaaaaaa:244685bd249979d4bd166a0abdfa27d9:
010100000000000080a583db9f04da01b18aaf3d35164a6f000000000100100048006200580
06a004400450052006600030010004800620058006a00440045005200660002001000780053
006c004e006500720054006b0004001000780053006c004e006500720054006b00070008008
0a583db9f04da0106000400020000000800300030000000000000000100000000200000553d
e98b4e7403dd7106431cb8cc73ab2e76205f81fdc4bede196950942fb3020a0010000000000
000000000000000000000000009001c0063006900660073002f00310030002e0030002e0032
002e00330036000000000000000000

Теперь можно попробовать в hashcat подобрать пароль:

  • -m 5600 – NetNTLMv2

  • -a 0 – подбор по словарю

$ hashcat -m 5600 netntlmv2.txt -a 0 /usr/share/wordlists/rockyou.txt
Пароль

На этом всё.