Отримуємо доступ до історії листування і контактам в популярному мессенджере

  1. Зміст статті Ти навряд чи користуєшся Mail.Ru Агентом, але це шалено популярний сервіс, який з кожним...
  2. Історія злому
  3. Rich Text Format (RTF)
  4. Як добути файл mra.dbs?
  5. Розкриваємо секрети mra.dbs
  6. INFO
  7. Типи повідомлень mra.dbs
  8. Кодима
  9. 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 року після довгих пошуків формат все-таки скорився.

До кінця 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; } 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]); Інтерфейс моєї читалки

Кодима

Зараз я покажу тобі лише самі основні моменти. Отже, файл 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, і як його добути?
Але звідки взагалі дізнатися, де знаходиться ця хеш-таблиця, і як знайти початок записів історії?
IRC (Internet Relay Chat)