Отримуємо доступ до історії листування і контактам в популярному мессенджере
- Зміст статті Ти навряд чи користуєшся Mail.Ru Агентом, але це шалено популярний сервіс, який з кожним...
- Історія злому
- Rich Text Format (RTF)
- Як добути файл mra.dbs?
- Розкриваємо секрети mra.dbs
- INFO
- Типи повідомлень mra.dbs
- Кодима
- PS
Зміст статті
Ти навряд чи користуєшся Mail.Ru Агентом, але це шалено популярний сервіс, який з кожним днем набирає обертів. За офіційними даними місячна аудиторія цього месенджера в кінці минулого року становила божевільну цифру в 21,4 мільйона чоловік. Це легко пояснити, - продукт дійсно вдалий. Але сьогодні я хочу розповісти про те, як був разреверсен файл з історією повідомлень користувача.
WARNING
Не забувай про статтю 138 - «Порушення таємниці листування, телефонних переговорів, поштових, телеграфних або інших повідомлень» КК РФ, а також про наявність в ній глави 28 - «Злочини у сфері комп'ютерної інформації» (ст. 272, 273, 274).
Історія злому
Експеримент почався для мене ще в далекому 2008 році, коли друг попросив перевірити листування його дівчини в Mail.ru Агента. Тоді файл історії вдавав із себе простий текстовік з назвою email history.txt і мав порівняно з mra.dbs (файл, в якому в даний час зберігається історія листування і дані про контакти) примітивну структуру. За пару годин був написаний простий, але ефективний RTF-конвертер, який і робив всю брудну роботу по витягуванню листування з Агента. Друг був у захваті. Далі, в ході вивчення програмування на компільованих мовах, я як практики написав програму Mail.ru History Reader, опис якої потрапило на сторінки] [в серпні 2009 року. Отримавши велику кількість позитивних відгуків, я опублікував структуру формату тодішнього файлу історії (див. Посилання в бічному виносі) і вихідні читалки. Однак Mail.ru Агент продовжував розвиватися, і правити балом став новий просунутий файл mra.dbs. Після цієї події до мене посипалися тонни повідомлень від різних людей з проханнями зайнятися ім. У компанії з SOLON7 ми колупали цей файл в HEX-редакторі, намагаючись знайти структури, посилання на зміщення і всілякі зміни після запуску Mail.ru Агента. До кінця 2010 року після довгих пошуків формат все-таки скорився.
Ідентифікатори початку листування
Rich Text Format (RTF)
RTF, що використовується в mra.dbs, представляє з себе формат зберігання розмічених документів, запропонований ще в 1982 році бородатими програмістами з Microsoft і Adobe. Для його парсинга зовсім не обов'язково винаходити велосипед, а досить лише відправити повідомлення EM_STREAMIN з прапором SF_RTF для запису і EM_STREAMOUT з прапором SF_TEXT для читання:
EDITSTREAM es = {0}; es.pfnCallback = EditStreamCallback; es.dwCookie = (DWORD_PTR) & lps; SendMessage (hRich, EM_STREAMIN, SF_RTF, (LPARAM) & es);
Цей нехитрий прийом і використаний в моїй читалки.
Як добути файл mra.dbs?
Ти, звичайно, задашься питанням: а де, власне, зберігається цей горезвісний mra.dbs, і як його добути? Файл mra.dbs зберігається в папці «% APPDATA% \ Mra \ Base \ mra.dbs» (наприклад «C: \ Documents and Settings \ user \ Application Data \ Mra \ Base \ mra.dbs»), і отримати його при вимкненому агента не так вже й складно, достатньо лише використовувати функції ExpandEnvironmentStrings і CopyFile. Однак при включеному Агента файл mra.dbs є зайнятим і система просто не дозволить його використовувати. Для вирішення цієї проблеми можна, наприклад, тимчасово відключити Агент (для цього дії тобі знадобляться привілеї отладчика, які можна отримати тільки з правами адміністратора) або знайти відкритий хендл файлу в системі, а потім продублювати його в адресний простір свого процесу. Також можна прочитати файл безпосередньо з диска (правда, для цього потрібно знати, що таке кластер і як працювати безпосередньо з драйвером файлової системи) або ж написати власний файловий драйвер (це практично нереально). Все б добре, але на практиці у всіх перерахованих вище методів є свої недоліки. При перерахуванні хендлом за допомогою ZwQuerySystemInformation і їх копіюванні до себе в процес за допомогою DuplicateHandle можна зіткнутися з двома проблемами. Перша полягає в тому, що при виклику ZwQueryInformationFile потік може повиснути, чекаючи відгуку від блокуючого іменованого каналу. Друга - після копіювання обидва хендлом (наш і відкрив файл процесу) будуть вказувати на один FileObject, а отже - поточний режим введення-виведення. Позиція в файлі і інша пов'язана з файлом інформація будуть загальними у двох процесів, тому навіть читання файлу викличе зміна позиції читання і порушення нормальної роботи програми, яка відкрила файл. Звичайно, можна призупинити на час все потоки процесу файлу, а після копіювання відновлювати позиції читання і запускати процес власника знову, але це пов'язано з великими витратами часу і сил. Здавалося б, ідеальним методом може бути пряме читання з диска, але і тут є недоліки. Таким способом можна читати тільки файли, які відкриваються з доступом FILE_READ_ATTRIBUTES (крім файлів підкачки), файл обов'язково повинен бути не стиснутий, що не зашифрований (інакше ми прочитаємо нісенітницю) і мати свій кластер (маленькі файли в NTFS можуть цілком розміщуватися в MFT). Також слід врахувати, що під час читання файл може бути змінений (і ми отримаємо в результаті незрозуміло що). Тому розберемо найпростіший метод з тимчасовим відключенням процесу Агента.
Отже, щоб вбити процес Mail.ru Агента, для початку необхідно дізнатися його ідентифікатор (ProcessID). Зробити це можна різними способами: через ToolHelp API, через Native API (використовуючи функцію ZwQuerySystemInformation), прошерстів список відкритих хендлом або за списком відкритих процесом вікон (GetWindowThreadProcessId). Найлегший варіант - це використання ToolHelp API і пошук по імені exe-файлу. Для цього досить викликати функції CreateToolhelp32Snapshot> Process32First> Process32Next, а потім в тілі циклу звіряти значення поля szExeFile структури PROCESSENTRY32 c magent.exe. Необхідний нам ProcessID знаходиться в цій же структурі, поле th32ProcessID:
hProcessSnap = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0); if (INVALID_HANDLE_VALUE! = hProcessSnap) {pe32.dwSize = sizeof (PROCESSENTRY32); if (Process32First (hProcessSnap, & pe32)) {do {if (0 == lstrcmp (pe32.szExeFile, _TEXT ( "magent.exe"))) {pid = pe32.th32ProcessID; break; }} While (Process32Next (hProcessSnap, & pe32)); } CloseHandle (hProcessSnap); }
Після того як ми знайдемо PID, нам необхідно отримати привілеї отладчика SeDebugPrivilege (OpenProcessToken> LookupPrivilegeValue> AdjustTokenPrivileges) і вбити процес (OpenProcess> TerminateProcess), а потім знову спробувати викликати CopyFile. Привілеї можна отримати і більш елегантним шляхом - через Native API:
void GetPrivilege (IN ULONG Privilege) {BOOLEAN OldValue; RtlAdjustPrivilege (Privilege, TRUE, FALSE, & OldValue); }
Все, mra.dbs у нас в руках. Тепер перейдемо до його патранню :).
Розкриваємо секрети mra.dbs
Файл mra.dbs вдає із себе дамп пам'яті Mail.ru Агента, тому відкрити його для читання при працюючій програмі не представляється можливим (для рядового програміста, але у нас свої секрети :), також завдання ускладнює той факт, що в пам'яті все числа зберігаються в перевернутому вигляді. Однак давай трохи заглибимося в реверс-інжиніринг.
Структура запису повідомлення
Отже, в надрах mra.dbs існує хеш-таблиця, в якій описані зміщення на 4-байтниє ідентифікатори. Ідентифікатори служать для визначення початку запису різних структур і сегментів дампа, серед яких і знаходяться потрібні нам записи історії листування (зверни увагу на відповідну ілюстрацію):
typedef struct _ids {unsigned int id1; unsigned int id2; unsigned int count; } _Ids;
INFO
Хеш-таблиця - це структура даних, що реалізує інтерфейс асоціативного масиву, вона дозволяє зберігати пари «ключ-значення». Двусвязний список складається з елементів даних, кожен з яких містить посилання як на наступний, так і на попередній елементи.
Початок історії характеризується ключовим словом mrahistory_, за яким слід e-mail господаря файлу mra.dbs і e-mail контакту, з яким ведеться листування. У випадку з історією ідентифікатори утворюють двусвязний список: перший веде до першого відправленому з повідомленням, а другий - до останнього прийнятим повідомленням. Кількість повідомлень можна дізнатися, вивчивши чотири байти після ідентифікаторів (структура _ids). Пройшовши по зсуву ідентифікатора (його можна дізнатися з хеш-таблиці) ми потрапимо на запис повідомлення (знову вся увага на відповідний малюнок):
Пошук хеш-таблиці struct _message {unsigned int size; unsigned int prev_id; unsigned int next_id; unsigned int xz1; FILETIME time; unsigned int type_mesage; char flag_incoming; char byte [3]; unsigned int count_nick; unsigned int magic_num; // 0x38 unsigned int count_message; unsigned int xz2; unsigned int size_lps_rtf; unsigned int xz3; };
Рядки в дампі зберігаються в кодуванні Unicode (wchar_t) різними способами:
- з завершальним нулем в кінці рядка;
- в структурі LPS (назва структури взято з опису формату протоколу MMP), де перші чотири байти вказують на довжину наступного рядка;
- в форматі RTF.
Знаючи кількість повідомлень, нам не важко буде пробігтися по всьому ланцюжку. Але звідки взагалі дізнатися, де знаходиться ця хеш-таблиця, і як знайти початок записів історії? Над пошуками відповідей до цих питань ми з SOLON7 провели чимало безсонних ночей.
Типи повідомлень mra.dbs
- 2 - неавторизовані користувачі;
- 4 - запити авторизації;
- 7 - звичайні повідомлення;
- 10 - передача файлів;
- 35 - записи в мікроблог;
- 46 - зміна геоположенія.
трохи магії
По зсуву 0x10 від початку файлу mra.dbs, як виявилося, і зберігається адреса заповітної хеш-таблиці. Пройшовши по зсуву першого індексу з хеш-таблиці, ми натикаємося на структуру початкових даних. Можливо, там знаходиться взагалі вся інформація, закладена в mra.dbs. Йдемо далі. По зсуву 0x20 в цій структурі зберігається кількість записів історії або, простіше кажучи, кількість листувань. Так як файл дампа постійно розширюється, то по зсуву 0x2C лежить ідентифікатор останньої записаної історії, - це все, що нам потрібно знати, щоб почати шукати ідентифікатори листувань. В цілому ж алгоритм такий:
- проходимся по ідентифікаторів записів історії за допомогою циклу (починаючи від останньої доданої записи);
- якщо в цьому записі від зсуву 0x190 присутнє слово «mrahistory_», то це означає, що по зсуву 0x24 лежать ідентифікатори ланцюжка повідомлень даної листування.
Щоб стало трохи зрозуміліше, поглянь на цей код:
DWORD * offset_table = (DWORD *) (mra_base + * (DWORD *) (mra_base + 0x10)); DWORD end_id_mail = * (DWORD *) (mra_base + 0x20 + offset_table [1]); DWORD count_emails = * (DWORD *) (mra_base + 0x2C + offset_table [1]); ... for (int i = 0; i <count_emails; i ++) {_ids * mail_data = (struct _ids *) (mra_base + offset_table [end_id_mail] +4); if (memmem (((unsigned char *) mail_data + 0x190), mrahistory, ...)) {emails [k] .id = (_ ids *) ((unsigned char *) mail_data + 0x24); ...} end_id_mail = mail_data-> id2; } Інтерфейс моєї читалки
Кодима
Зараз я покажу тобі лише самі основні моменти. Отже, файл mra.dbs є дампом пам'яті, тому ми не будемо перекручуватися і використовувати функції для роботи з файловими зсувами, а відразу помістимо його в пам'ять нашої програми. Для цього заюзать ресурси ОС Windows і створимо Memory Mapped файл:
CreateFile CreateFileMap MapViewOfFile VirtualFree CloseHandle CloseHandle
Так як нам не потрібно зберігати внесені зміни назад в файл, то тут замість UnmapViewOfFile використовується VirtualFree. Перше, що ми зробимо, це знайдемо всі контакти з історії листування. Зберігати знайдене добро будемо в структурі emails:
typedef struct _emails {wchar_t * email; _ids * id; }; ... struct _emails * emails; ... emails = VirtualAlloc (NULL, count_emails * sizeof (struct _emails), ..); ...
Після проходу по ідентифікаторів і пошуку рядка «mrahistory_» наша структура буде заповнена адресами ідентифікаторів. Зауваж, при цьому ми не скопіювали навіть байта і витратили всього лише 16 * count_emails байт (наприклад, при 1 000 контактів ми використовуємо всього ~ 15 кілобайт пам'яті). Тепер, маючи на руках ідентифікатори початку листування з конкретним користувачем, ми можемо прочитати повідомлення:
int id_message = emails [k] .id-> id1; for (int i = 0; i <emails [k] .id-> count_messages; i ++) {_message * mes = (_ message *) (mra_base + offset_table [id_message]); wchar_t * str = (wchar_t *) ((unsigned char *) mes + sizeof (_message)); ... id_message = mes-> prev_id; }
Дата повідомлення зберігається в форматі FILETIME, для зручності її можна перевести в легкий для читання вигляд за допомогою функції FileTimeToSystemTime. Формат RTF відмінно сприймається Rich Edit'ом і будь-якими іншими стандартними редакторами типу WordPad. Але з цим можна і не морочитися, так як повідомлення зберігаються в неотформатированним вигляді відразу після ника, а їх розмір вказаний в структурі message. Це все, що тобі потрібно знати, щоб отримати більш-менш прийнятний список мессаг з Агента.
PS
На жаль, формат журналу не дозволяє привести тут мої хардкорні вишукування повністю, тому поспішає заглянути на диск. Сподіваюся, приклад коду читалки (exe'шнік якої, до речі, за допомогою невеликої оптимізації вмістився всього в 2 кілобайт без жодних пакеров) допоможе тобі в написанні швидкого і крутого C-коду, а також у вивченні hex-редакторів та інших низькорівневих речей. До речі, незачепленою залишилася не менше захоплююча тема читання історії ICQ-листування, яка також зберігається в файлі mra.dbs. Спасибі компанії Mail.Ru, по-перше, за розробку Mail.Ru Агента, по-друге, за помітний розвиток улюбленої аськи, і по-третє, за цікавий квест, про який я тобі сьогодні розповів.
Dbs?Dbs?
Dbs, і як його добути?
Але звідки взагалі дізнатися, де знаходиться ця хеш-таблиця, і як знайти початок записів історії?