Search
Write a publication
Pull to refresh

Чтение сохраненных паролей в Google Chrome

Иногда бывает, что забываешь пароль от сайта, но браузер его помнит — возникает задача его достать. В настройках Google Chrome можно посмотреть пароль для каждого отдельного сайта, но иногда бывает полезно посмотреть пароли ото всех сайтов сразу (например, в случае амнезии). Кроме того, просто интересно, как Chrome хранит данные форм.



Данные форм Chrome хранит в базе SQLite по адресу C:\Users\Username\AppData\Local\Google\Chrome\User Data\Default\Web Data. В этой базе находятся таблицы, отвечающие за автозаполнение, импортированные данные из IE, данные веб-приложений и другие, но нас интересует таблица logins — именно тут хранятся логины и пароли.

Таблица logins

Названия полей говорят сами за себя, но вот пароли хранятся в зашифрованном виде. Чтобы понять, как они шифруются, стоит посмотреть на функцию в коде Chrome, которая непосредственно добавляет логин и пароль в таблицу:

bool WebDatabase::AddLogin(const PasswordForm& form) {
 SQLStatement s;
 std::string encrypted_password;
 if (s.prepare(db_,
  "INSERT OR REPLACE INTO logins "
  "(origin_url, action_url, username_element, username_value, "
  " password_element, password_value, submit_element, "
  " signon_realm, ssl_valid, preferred, date_created, "
  " blacklisted_by_user, scheme) "
  "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") != SQLITE_OK) {
  NOTREACHED() << "Statement prepare failed";
  return false;
 }

 s.bind_string(0, form.origin.spec());
 s.bind_string(1, form.action.spec());
 s.bind_wstring(2, form.username_element);
 s.bind_wstring(3, form.username_value);
 s.bind_wstring(4, form.password_element);
 Encryptor::EncryptWideString(form.password_value, &encrypted_password);
 s.bind_blob(5, encrypted_password.data(),
       static_cast<int>(encrypted_password.length()));
 s.bind_wstring(6, form.submit_element);
 s.bind_string(7, form.signon_realm);
 s.bind_int(8, form.ssl_valid);
 s.bind_int(9, form.preferred);
 s.bind_int64(10, form.date_created.ToTimeT());
 s.bind_int(11, form.blacklisted_by_user);
 s.bind_int(12, form.scheme);
 if (s.step() != SQLITE_DONE) {
  NOTREACHED();
  return false;
 }
 return true;
}


* This source code was highlighted with Source Code Highlighter.

Для зашифрования пароля функция использует класс Encryptor, реализация метода которого выглядит так:

bool Encryptor::EncryptString(const std::string& plaintext,
               std::string* ciphertext) {
 DATA_BLOB input;
 input.pbData = const_cast<BYTE*>(reinterpret_cast<const BYTE*>(plaintext.data()));
 input.cbData = static_cast<DWORD>(plaintext.length());

 DATA_BLOB output;
 BOOL result = CryptProtectData(&input, L"", NULL, NULL, NULL, 0, &output);
 if (!result)
  return false;

 // this does a copy
 ciphertext->assign(
   reinterpret_cast<std::string::value_type*>(output.pbData), output.cbData);

 LocalFree(output.pbData);
 return true;
}


* This source code was highlighted with Source Code Highlighter.

То есть в конечном счете пароль шифруется Windows-функцией
CryptProtectData — да, так просто!

Напоследок небольшая прога, записывающая сохраненные Chrome пароли в файл. Чтобы расшифровать пароль, достаточно открыть базу и применить к каждому password_value функцию CryptUnprotectData, что и было сделано:

#pragma comment(lib, "crypt32.lib")

#include <string>
#include <iostream>
#include <fstream>

#include <stdlib.h>
#include <string.h>

#include <windows.h>
#include <Wincrypt.h>

#include <sqlite3.h>

using namespace std;

bool Decrypt(const byte *ciphertext, int length, char **plaintext)
{
 DATA_BLOB input;
 input.pbData = const_cast<BYTE *>(ciphertext);
 input.cbData = static_cast<DWORD>(length);

 DATA_BLOB output;
 BOOL result = CryptUnprotectData(&input, NULL, NULL, NULL, NULL, 0, &output);
 if (!result)
  return false;

 *plaintext = static_cast<char *>(malloc(output.cbData + 1));
 strncpy_s(*plaintext, output.cbData + 1, reinterpret_cast<char *>(output.pbData), output.cbData);

 LocalFree(output.pbData);

 return true;
}

int main()
{
  sqlite3 *db;
  int rc;
  
  rc = sqlite3_open_v2("C:\\Users\\Username\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Web Data", &db, SQLITE_OPEN_READONLY, NULL);
  if (rc != SQLITE_OK)
  {
    cerr << "Can't open database: " << sqlite3_errmsg(db) << endl;
    sqlite3_close(db);
    getchar();
    return 1;
  }
  
  sqlite3_stmt *stmt;
  char *query = "SELECT origin_url AS url, username_value AS username, password_value AS password FROM logins";
  rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
  if (rc != SQLITE_OK)
  {
    cerr << "Can't prepare statement: " << sqlite3_errmsg(db) << endl;
    sqlite3_finalize(stmt);
    sqlite3_close(db);
    return 1;
  }

  ofstream fout;
  fout.open("C:\\p.csv", ios::out | ios::trunc);
  if (!fout.is_open())
  {
    cerr << "Can't open file";
    fout.close();
    sqlite3_finalize(stmt);
    sqlite3_close(db);
    return 1;
  }

  while (sqlite3_step(stmt) == SQLITE_ROW)
  {
    const unsigned char *url = sqlite3_column_text(stmt, 0);
    const unsigned char *user = sqlite3_column_text(stmt, 1);
    const byte *encrypted_password = static_cast<const byte *>(sqlite3_column_blob(stmt, 2));
    int enctypted_password_length = sqlite3_column_bytes(stmt, 2);
    char *password = "";
    Decrypt(encrypted_password, enctypted_password_length, &password);
    fout << url << "," << user << "," << password << endl;
  }

  fout.close();
  
  sqlite3_finalize(stmt);
  sqlite3_close(db);
  
  return 0;
}


* This source code was highlighted with Source Code Highlighter.

P.S. Разумеется, речи о том, чтобы таким образом взять чужую базу и прочитать из нее пароли, быть не может, потому что CryptProtectData шифрует данные так, что расшифровать их может только тот пользователь, который их шифровал.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.