На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
! Правила раздела FAQ в группе разделов С++.
1. Раздел FAQ предназначен для публикации готовых статей.
2. Здесь нельзя задавать вопросы, для этого существуют соответствующие разделы:
Чистый С++
Visual C++ / MFC / WTL / WinApi
Borland C++ Builder
COM / DCOM / ActiveX / ATL
Сопутствующие вопросы
3. Внимание, все темы и сообщения в разделе премодерируются. Любое сообщение или тема будут видны остальным участникам только после одобрения модератора.
Модераторы: B.V., Qraizer
  
> Список окон, Без использования Winapi :-)
    Итак, сегодня мы окунемся в мир MS, мир недокументированный. Обычно, для получения списка окон или свойств окна мы используем API навроде:
    ExpandedWrap disabled
      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. Описание найти в интернете сложно, поэтому приведу его здесь:
    ExpandedWrap disabled
      NTSTATUS
      WINAPI
      CsrClientConnectToServer(
          IN PWSTR ObjectDirectory,  // объектная директория, из которой получать данные
          IN ULONG ServerDllIndex,   // тип запрашиваемой информации
          IN OUT PVOID ConnectionInformation,  // структура, в которой мы получим необходимые данные
          IN OUT PULONG ConnectionInformationLength, // ее длина
          OUT PBOOLEAN CalledFromServer // мы - есть сервер?
          );

    Какие поля тут имеются. Ну во-первых, ObjectDirectory. В нашем случае нас интересуют объекты графической подсистемы, а они хранятся в директории \Windows (не путать с папкой Windows!):
    ExpandedWrap disabled
      #define WINSS_OBJECT_DIRECTORY_NAME     L"\\Windows"

    Тип запрашиваемой информации: объекты USER
    ExpandedWrap disabled
      #define USERSRV_SERVERDLL_INDEX         3


    Структура данных:
    ExpandedWrap disabled
      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 запишется требуемая нами информация.

    Итак:
    ExpandedWrap disabled
      // 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:
    ExpandedWrap disabled
      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, где лежит информация, которая нам интересна:
    ExpandedWrap disabled
      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.

    Ну и наконец, что у нас лежит в элементах массива:
    ExpandedWrap disabled
      typedef struct _HANDLEENTRY {
          PVOID   phead;                  /* указатель на объект. Указывает в область ядра!!! */
          PVOID   pOwner;                 /* владелец объекта (процесс или поток) */
          BYTE    bType;                  /* тип объекта (TYPE_WINDOW, TYPE_MENU, etc) */
          BYTE    bFlags;                 /* флаги (например, HANDLEF_DESTROY) */
          WORD    wUniq;                  /* индекс уникальности (не используется) */
      } HANDLEENTRY, *PHE;


    Интересующая нас информация об окне лежит в памяти по указателю phead. Для окна эта структура выглядит следующим образом:
    ExpandedWrap disabled
      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;


    Итак, мы все знаем. Осталось только получить указатель на эту структуру в юзер-спэйсе (мы ведь помним, что они хранятся вне адресного пространства юзер-мода, но отмаплены на юзер-память).
    Этот указатель получается следующим образом: есть некая дельта, которую необходимо вычесть из адреса ядра и мы получим валидный адрес в юзер-моде:
    ExpandedWrap disabled
      PWND pwnd = PWND(ULONG(p->phead) - g_uDelta); // <<-- все, здесь мы можем получить доступ к данным

    Несмотря на то, что сервер csrss нам дал какую-то дельту, но, как ни странно - это не она. :) Нужная нам дельта есть в TEB. Приводить структуру TEB я не буду (можно поглядеть на нее здесь. Там нас мало что интересует, кроме поля Win32ClientInfo. По непонятным причинам оно везде обозначено так ULONG Win32ClientInfo[62];. Однако это не массив лонгов, а самая обычная структура, которая выглядит так:
    ExpandedWrap disabled
      #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 - это и есть нужная нам дельта. Получим ее так:
    ExpandedWrap disabled
      g_uDelta = PCLIENTINFO(&PTEB(NtCurrentTeb())->Win32ClientInfo)->ulClientDelta;


    Вот теперь мы во всеоружии!
    Тут, правда, необходимо учесть вот что: не все объекты отмаплены в наше пространство, поэтому надо как-то отделить мух от котлет. Для начала вспомним, что мы можем получить информацию только о тех окнах, которые присутствуют на нашем десктопе. В THRDESKHEAD структуры tagWND есть поле rpdesk - указатель на объект десктопа (что там лежит - неважно, важен сам указатель). Будем сравнивать его с указателем окна нашего десктопа (GetDesktopWindow()). Но для того, чтобы коснуться этого, надо написать ту самую ValidateHwnd:
    ExpandedWrap disabled
      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:
    ExpandedWrap disabled
      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, которое нам и нужно.
    Ну что ж, попробуем написать перечисление окошек:
    ExpandedWrap disabled
      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"
    user posted image
    ЩИТО?
    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
    0 пользователей:


    Рейтинг@Mail.ru
    [ Script Execution time: 0,1166 ]   [ 17 queries used ]   [ Generated: 13.11.19, 06:39 GMT ]