Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.145.62.131] |
|
Сообщ.
#1
,
|
|
|
Итак, сегодня мы окунемся в мир MS, мир недокументированный. Обычно, для получения списка окон или свойств окна мы используем API навроде:
EnumWindows(); GetWindow(); GetWindowLong(); GetWindowText(); SetWindowLong(); SetWindowText(); SendMessage(); // (в некоторых случаях. Например, WM_CHANGEUISTATE) В данном примере мы используем всего две ф-ции из недр Windows. Одну для получения массива некоторых данных, другую - для удобства (дабы не лезть в ядро). Если погуглить, например по слову "ValidateHwnd", то можно найти много информации на эту тему. Однако она весьма разрознена и, хоть и авторы пытались ее развернуть по максимуму, получилось это слабо. Отчего-то многие, по привычке (видимо, на wasm.ru всегда идут в лоб) ищут данные дизассемблированием подсистемы win32k и user32. В принципе, даже нашли универсальные способы (для всех версий WinNT, начиная с win2k) поиска нужных участков кода и получения из него данных. ЕМНИП, способ хоть и железный, но некрасивый. Мы пойдем другим путем. Впрочем, без проверки версии ОС нам тоже не обойтись, ибо структура, коей будем пользоваться недокументирована и в ней от версии к версии добавляются/удаляются какие-то поля. Но на мой взгляд - это мелочи, - данные мы получим верные, надо будет только в зависимости от версии ОС их разобрать. Итак. Начнем с того, что нам известно. Все ф-ции по работе с окошками используют некий описатель HWND, который объявлен как opaque в MSDN и больше там информации нет. На самом деле HWND состоит из двух полей типа USHORT. Старшее слово - это уникальный индекс, который был введен разработчиками MS для внутренних целей, но потом они от него отказались, потому что много софта, писавшегося под win передавали HWND в виде USHORT и этот индекс всегда терялся, поэтому в данный момент он не используется, хотя и всегда добавляется в HWND. Более интересно младшее слово - это индекс в таблице описателей графической подсистемы win32. По этому индексу мы можем получить структуру, описывающую объект. Между прочим, в этой таблице хранятся описатели не только окон, но и: - Таймеры; (те самые, что создаются через SetTimer()) - Меню; - Хуки; - Данные буфера обмена; - Акселлераторы; Ну и прочие объекты, в данном моменте нам неинтересные (кому интересно - могут глянуть на сайте reactos, например). А вот теперь, самое интересное. Тут, как говорится, есть две новости - одна плохая, другая - хорошая. Данная таблица описателей (точнее объекты, на которые она ссылается) хранится в памяти ядра (конкретнее по адресам 0xbfXXXXXX - зависит от версии ОС, но в winxp sp2/3 там). Но, однако, некоторая часть этой таблицы отображена в user-mode пространство и может быть прочитана (но не изменена!). Этой некоторой части таблицы нам будет достаточно. Во-первых, то, что мы можем подсмотреть - это окна, меню, хуки и еще пара типов объектов. Причем, отображены не все описатели для этих объектов, а только те, которые представлены на текущей для процесса оконной станции (WinStation). Ну, для начала этих окон будет достаточно. Теперь, задачи перед нами стоят следующие: получить адрес этой таблицы (а она будет в user-mode пространстве), найти указатель на объект по HWND, а затем по этому указателю (который указывает в область ядра) найти указатель на копию объекта в user-mode. Начнем с первого: получим адрес таблицы. В гуглах обычно пишут следующее: для таких-то версий ОС адреса этой таблицы (в user32.dll) такие-то, либо по GetProcAddress ищем такую-то функцию в user32.dll, а там по сигнатурам ищем нужный участок кода и из этого участка получаем адрес этой самой таблицы. Как я уже говорил - эти методы не для нас. Мы же белые люди? Для получения адреса этой самой штуки надо подключится к серверу csrss (раньше он отвечал за всю графическую подсистему, сейчас, насколько мне известно, из него вынули б0льшую часть функционала и перенесли в другие места). Несмотря на то, что user32 при инициализации подключается к серверу, мы можем сделать это еще раз и получить свою собственную копию данных. Подключение выполняется с помощью недокументированной функции CsrClientConnectToServer. Описание найти в интернете сложно, поэтому приведу его здесь: NTSTATUS WINAPI CsrClientConnectToServer( IN PWSTR ObjectDirectory, // объектная директория, из которой получать данные IN ULONG ServerDllIndex, // тип запрашиваемой информации IN OUT PVOID ConnectionInformation, // структура, в которой мы получим необходимые данные IN OUT PULONG ConnectionInformationLength, // ее длина OUT PBOOLEAN CalledFromServer // мы - есть сервер? ); Какие поля тут имеются. Ну во-первых, ObjectDirectory. В нашем случае нас интересуют объекты графической подсистемы, а они хранятся в директории \Windows (не путать с папкой Windows!): #define WINSS_OBJECT_DIRECTORY_NAME L"\\Windows" Тип запрашиваемой информации: объекты USER #define USERSRV_SERVERDLL_INDEX 3 Структура данных: typedef struct _tagUSERCONNECT { ULONG ulVersion; // Версия подсистемы (как правило это 0x50000000. Хотя на Vista и 7 могло поменяться на 0x60000000, но данный момент меня мало интересовал). ULONG ulCurrentVersion; // Not required DWORD dwDispatchCount; // SHAREDINFO siClient; // А здесь хранится самая важная структура. В интернете упоминания о ней можно найти по названию переменной gSharedInfo. } USERCONNECT, *PUSERCONNECT; Здесь важно следующее: - во-первых, версия должна быть указана обязательно; - во-вторых, размер структуры при вызове ф-ции должен _точно_ соответствовать нашей, иначе нам покажут STATUS_UNSUCCESSFUL и больше ничего. Если все ОК, то *siClient = FALSE (как правило, ибо мы не сервер CSRSS), в ConnectionInformation запишется требуемая нами информация. Итак: // undocumented.cpp SHAREDINFO gSharedInfo; PSERVERINFO gpsi; BOOL RetreiveSharedInfo() { ULONG sz = sizeof(USERCONNECT); BOOLEAN pserv = FALSE; USERCONNECT uc = { 0 }; uc.ulVersion = 0x50000000; if ( STATUS_SUCCESS != CsrClientConnectToServer(WINSS_OBJECT_DIRECTORY_NAME, USERSRV_SERVERDLL_INDEX, &uc, &sz, &pserv) ) return FALSE; gSharedInfo = uc.siClient; gpsi = uc.psi; return TRUE; } Обратимся к структуре SHAREDINFO: typedef struct tagSHAREDINFO { PSERVERINFO psi; // здесь лежит некоторая интересующая нас информация, см. ниже struct _HANDLEENTRY *aheList; // таблица описателей PVOID *pDispInfo; // информация о текущем дисплее ULONG ulSharedDelta; // Эта дельта - разница между значением указателя на ядро и значением указателя на пользовательскую память (маппинг) // Сложно сказать, что это за дельта, но нам она не понадобится. LPWSTR pszDllList; // все нижележащие поля нам неинтересны WNDMSG awmControl[FNID_END - FNID_START + 1]; WNDMSG DefWindowMsgs; WNDMSG DefWindowSpecMsgs; } SHAREDINFO, *PSHAREDINFO; Далее приведу только часть структуры PSERVERINFO, где лежит информация, которая нам интересна: typedef struct tagSERVERINFO { // si WORD wRIPFlags; // RIPF_ flags WORD wSRVIFlags; // SRVIF_ flags WORD wRIPPID; // PID of process to apply RIP flags to (zero means all) WORD wRIPError; // Error to break on (zero means all errors are treated equal) DWORD cHandleEntries; // count of handle entries in array } SERVERINFO, *PSERVERINFO; Здесь нам интересно поле cHandleEntries, которое обозначает размер таблицы aheList. Ну и наконец, что у нас лежит в элементах массива: typedef struct _HANDLEENTRY { PVOID phead; /* указатель на объект. Указывает в область ядра!!! */ PVOID pOwner; /* владелец объекта (процесс или поток) */ BYTE bType; /* тип объекта (TYPE_WINDOW, TYPE_MENU, etc) */ BYTE bFlags; /* флаги (например, HANDLEF_DESTROY) */ WORD wUniq; /* индекс уникальности (не используется) */ } HANDLEENTRY, *PHE; Интересующая нас информация об окне лежит в памяти по указателю phead. Для окна эта структура выглядит следующим образом: typedef struct tagCOMMON_WNDCLASS { int cWndReferenceCount; // число окошек, созданных этим классом UINT style; WNDPROC_PWND lpfnWndProc; int cbclsExtra; int cbwndExtra; HANDLE hModule; PVOID spicn; PVOID spcur; HBRUSH hbrBackground; LPWSTR lpszMenuName; LPSTR lpszAnsiClassName; // самое главное - имя класса! PVOID spicnSm; } COMMON_WNDCLASS; typedef struct tagCLS { struct tagCLS * pclsNext; // указатель а следующий класс ATOM atomClassName; WORD fnid; PVOID rpdeskParent; /* десктоп */ PDCE pdce; /* объект DC (Device Context) */ WORD hTaskWow; WORD CSF_flags; /* внутренние флаги */ LPSTR lpszClientAnsiMenuName; /* имя ресурса меню ansi */ LPWSTR lpszClientUnicodeMenuName; /* имя ресурса меню unicode */ PCALLPROCDATA spcpdFirst; /* процедура обработчика */ struct tagCLS * pclsBase; /* Начало массива классов */ struct tagCLS * pclsClone; /* Начало копии массива классов */ BOOL fFlag; COMMON_WNDCLASS wc; // пользовательские данные класса (похожа на WNDCLASS, но есть некоторые отличия) } CLS, *PCLS, **PPCLS; typedef struct _WW { ULONG state1; ULONG state2; DWORD dwStyleEx; DWORD dwStyle; HMODULE hInstance; WORD hMod16; WORD fnid; } WW, *PWW; typedef struct tagWND { // wnd THRDESKHEAD head; // информация о текущем десктопе и потоке, которому принадлежит окошко WW ww; // различная оконная информация: стили, hInstance и т.п. struct tagWND * spwndNext; // указатель на объект следующего окна struct tagWND * spwndPrev; // ...предыдущего struct tagWND * spwndParent; // ...родительского struct tagWND * spwndChild; // ...дочернего struct tagWND * spwndOwner; // ..."босс" для нашего окна RECT rcWindow; // размеры окна RECT rcClient; // ...и его клиентской части PVOID lpfnWndProc; // указатель на процедуру-обработчика окна PCLS pcls; // информация о классе окна HRGN hrgnUpdate; // регион отрисовки PVOID ppropList; // Указатель на PropList (SetProp/GetProp итд) PVOID pSBInfo; // указатель на данные полос прокрутки PVOID spmenuSys; // Указатель на объект системного меню PVOID spmenu; // Menu handle or ID HRGN hrgnClip; // еще один регион для отрисовки LARGE_UNICODE_STRING strName; // текст окна (не тот, который задается через WM_SET/GETTEXT, а тот, что через Set/GetWindowText int cbwndExtra; // Размер пользовательских данных (Get/SetWindowLong). struct tagWND * spwndLastActive; // Последний активный попап HIMC hImc; // input context ULONG dwUserData; // Пользовательские данный (Get/SetWindowLong(GWL_USERDATA) } WND,*PWND; Итак, мы все знаем. Осталось только получить указатель на эту структуру в юзер-спэйсе (мы ведь помним, что они хранятся вне адресного пространства юзер-мода, но отмаплены на юзер-память). Этот указатель получается следующим образом: есть некая дельта, которую необходимо вычесть из адреса ядра и мы получим валидный адрес в юзер-моде: PWND pwnd = PWND(ULONG(p->phead) - g_uDelta); // <<-- все, здесь мы можем получить доступ к данным Несмотря на то, что сервер csrss нам дал какую-то дельту, но, как ни странно - это не она. Нужная нам дельта есть в TEB. Приводить структуру TEB я не буду (можно поглядеть на нее здесь. Там нас мало что интересует, кроме поля Win32ClientInfo. По непонятным причинам оно везде обозначено так ULONG Win32ClientInfo[62];. Однако это не массив лонгов, а самая обычная структура, которая выглядит так: #define CVKKEYCACHE 32 #define CBKEYCACHE (CVKKEYCACHE >> 2) #define CVKASYNCKEYCACHE 16 #define CBASYNCKEYCACHE (CVKASYNCKEYCACHE >> 2) typedef struct _CLIENTINFO { DWORD CI_flags; // Needs to be first because CSR DWORD cSpins; DWORD dwExpWinVer; DWORD dwUnknownFlags; DWORD dwCompatFlags; DWORD dwTIFlags; PVOID pDeskInfo; ULONG ulClientDelta; PVOID phkCurrent; DWORD fsHooks; CALLBACKWND CallbackWnd; DWORD dwHookCurrent; int cInDDEMLCallback; HANDLE hDdemlCallbackInst; PVOID pClientThreadInfo; DWORD dwHookData; DWORD dwKeyCache; BYTE afKeyState[CBKEYCACHE]; DWORD dwAsyncKeyCache; BYTE afAsyncKeyState[CBASYNCKEYCACHE]; BYTE afAsyncKeyStateRecentDown[CBASYNCKEYCACHE]; WORD CodePage; HKL hKL; BYTE achDbcsCF[2]; MSG msgDbcsCB; } CLIENTINFO, *PCLIENTINFO; Здесь тоже мало интересного, кроме поля ulClientDelta - это и есть нужная нам дельта. Получим ее так: g_uDelta = PCLIENTINFO(&PTEB(NtCurrentTeb())->Win32ClientInfo)->ulClientDelta; Вот теперь мы во всеоружии! Тут, правда, необходимо учесть вот что: не все объекты отмаплены в наше пространство, поэтому надо как-то отделить мух от котлет. Для начала вспомним, что мы можем получить информацию только о тех окнах, которые присутствуют на нашем десктопе. В THRDESKHEAD структуры tagWND есть поле rpdesk - указатель на объект десктопа (что там лежит - неважно, важен сам указатель). Будем сравнивать его с указателем окна нашего десктопа (GetDesktopWindow()). Но для того, чтобы коснуться этого, надо написать ту самую ValidateHwnd: PWND ValidateHwnd(HWND hWnd) { USHORT index = LOWORD(hWnd), wUniq = HIWORD(hWnd); if ( index >= 0 && index < gpsi->cHandleEntries ) { if ( gSharedInfo.aheList[index].bType == TYPE_WINDOW ) return PWND(ULONG(gSharedInfo.aheList[index].phead) - g_uDelta); } return NULL; } Разберем структуру THRDESKHEAD: typedef struct _HEAD { HANDLE h; // здесь h = hWnd DWORD cLockObj; } HEAD, *PHEAD; typedef struct _THROBJHEAD { HEAD head; PVOID pti; // PTHREADINFO } THROBJHEAD, *PTHROBJHEAD; typedef struct _DESKHEAD { PVOID rpdesk; PBYTE pSelf; } DESKHEAD, *PDESKHEAD; typedef struct _THRDESKHEAD { THROBJHEAD thr; DESKHEAD deskhead; } THRDESKHEAD, *PTHRDESKHEAD; И тут есть поле rpdesk, которое нам и нужно. Ну что ж, попробуем написать перечисление окошек: void MyEnumWindows() { HWND hDesktop = GetDesktopWindow(); PWND pDesktop = ValidateHwnd(hDesktop); PVOID rpdesk = pDesktop->head.deskhead.rpdesk; ULONG count = gpsi->cHandleEntries; for(ULONG c = 0; c < count; ++c) { if ( gSharedInfo.aheList[c].bType == TYPE_WINDOW ) { PWND pwnd = PWND(ULONG(gSharedInfo.aheList[c].phead) - g_uDelta); __try // здесь поступаем не очень красиво. Если объект не отмаплен в юзер-спейс - он может указать на мусор { if ( pwnd->head.deskhead.rpdesk == rpdesk ) // наше окно! (и здесь возможно исключение, либо какой-нибудь NULL, который будет отсеян) { LPWSTR strName = LPWSTR(ULONG(pwnd->strName.Buffer) - g_uDelta); PCLS pcls = PCLS(ULONG(pwnd->pcls) - g_uDelta); // да-да! Нам придется вычислять смещения для всех указателей структуры! LPSTR strClassname = LPSTR(ULONG(pcls->lpszAnsiClassName) - g_uDelta); printf("Window: hwnd = 0x%08x; class: `%s`; title: `%ws`\n", pwnd->head.thr.h, strClassname, strName); } __except(EXCEPTION_EXECUTE_HANDLER) { // не наше окно :( continue; } } } } Реализация перечисления окон не очень хороша. В данном случае, подход в последней процедуре не очень красивый, но хочется сделать побыстрее и покороче. В действительности нужно было бы весьма заковыристыми путями получить десктоп потока (pOwner в HANDLEENTRY, который, кстати, тоже указывает в ядро) и сравнить с десктопом DesktopWindow. Пока все. (позже добавлю информацию на тему структуры WW из tagWND, там тоже есть интересная информация). Исходники добавлю позже! © ALXR Сообщения были разделены в тему "spam" |