РАЗБОР Статья

Древовидные списки "TreeView" - от иерархии до уязвимостей

Marylin
Marylin Mod.Assembler · 396 сообщений
Подписаться
741
[ обложка статьи ]
Режим чтения
Элемент управления TreeView - один из самых широко используемых компонентов графического интерфейса Win. Он знаком каждому пользователю по левой панели Проводника, где папки и файлы выстроены в иерархическую структуру. Но TreeView это не просто удобная навигация - это мощный инструмент для визуализации любых данных, имеющих отношение "родитель-потомок", от организационной структуры компании, и до синтаксического дерева софта. Несмотря на кажущуюся простоту, работа с TreeView сопряжена с рядом технических нюансов, и даже ..исторических уязвимостей, которые должен знать каждый разработчик.

Оглавление:
1. Область применения​
2. Создание элемента управления​
3. Историческая уязвимость CVE-2012-0158​
4. Заключение​



1. Что такое TreeView, и где он применяется

TV представляет данные в виде иерархического списка, где каждый элемент может содержать дочерние элементы, которые можно сворачивать и разворачивать, что позволяет эффективно работать с большими объёмами структурированной информации.

Области применения:
1. Файловые менеджеры - классический пример "проводник Win"​
2. Системные утилиты - просмотр процессов, потоков, модулей​
3. Конфигурационные панели - настройки с иерархической организацией​
4. Навигация по документам - оглавление с вложенными разделами​

Окно Windows с элементом TreeView и ассемблерным кодом fasm рядом со свойствами уязвимой библиотеки mscomctl.ocx

2. Создание элемента управления

В WinAPI элемент создаётся стандартным вызовом CreateWindowEx() с указанием класса окна WC_TREEVIEW. Критически важно перед этим вызвать InitCommonControls(), иначе библиотека общих элементов управления comctl32.dll не загрузится, и план потерпит фиаско. Вот базовая последовательность действий:

• Вызвать InitCommonControls() для загрузки библиотеки​
• Создать элемент через CreateWindowEx() с классом WC_TREEVIEW​
• Опционально отправить WM_SETFONT для настройки шрифта​
• Добавить элементы через сообщение TVM_INSERTITEM​

При создании нужно указать стили внешнего вида: TVS_HASBUTTONS (кнопки сворачивания), TVS_HASLINES (соединительные линии), TVS_LINESATROOT (линии на корневом уровне), TVS_DISABLEDRAGDROP (запрет на перетаскивание drag-and-drop).

2.1. Добавление элементов и частая ошибка новичков

Для добавления элемента используется сообщение TVM_INSERTITEM, которому передаётся структура TV_INSERTSTRUCT. Внутри неё находится TV_ITEM с полями для текста, изображения, состояния и пользовательских данных. Самая распространённая ошибка: забыть установить флаг TVIF_TEXT в поле mask структуры. Без этого флага Win просто не узнает, что мы передали текст и элемент останется пустым. Это классический пример того, как невнимательность приводит к загадочному поведению.

Код:
struct TV_ITEM
  mask           dd ?,?
  hItem          dq ?
  state          dd ?
  stateMask      dd ?
  pszText        dq ?
  cchTextMax     dd ?
  iImage         dd ?
  iSelectedImage dd ?
  cChildren      dd ?
  lParam         dd ?
ends

struct TV_INSERTSTRUCT
  hParent        dq ?
  hInsertAfter   dq ?
  item           TV_ITEM
ends

Для создания иерархии:
• Корневой элемент: hParent = TVI_ROOT​
• Дочерний элемент: hParent = handle (родителя, полученный из предыдущего TVM_INSERTITEM)​
• Позиция вставки: TVI_LAST, TVI_FIRST, TVI_SORT и другие​

Вот пример простого окна с использованием элемента TreeView - всего 1 заголовок/родитель, который раскрывается при нажатии на плюс(+), и внутри в цикле можно добавить хоть сотню детей.

C-подобный:
format  pe64 gui 6.0
include 'win64ax.inc'
include 'encoding\win1251.inc'
entry start
;//-----------------
section '.data' data readable writeable
ID_TV       =   1000

hTree       dq  0
hModule     dq  0
hDlg        dq  0
hFont       dq  0
tvis        TV_INSERTSTRUCT

strRoot     db  ' Rammstein',0
strChild1   db  ' Тилль Линдеманн  ',0
strChild2   db  ' Рихард Круспе    ',0
strChild3   db  ' Кристиан Лоренц  ',0
strChild4   db  ' Кристофер Шнайдер',0
strChild5   db  ' Пауль Ландерс    ',0
strChild6   db  ' Оливер Ридель    ',0

;//-----------------
section '.text' code readable executable
start:    sub     rsp,8
          invoke  InitCommonControls   ;//<------ обязательно вызвать!
          invoke  GetModuleHandle,0
          mov     [hModule],rax
          invoke  DialogBoxParam, rax, 100, 0, DialogProc, 0
          invoke  ExitProcess, 0

proc  DialogProc  hwnd, msg, wparam, lparam
          mov     [hwnd],rcx
          mov     [wparam],r8

          cmp     rdx,WM_INITDIALOG
          je      @init
          cmp     rdx,WM_COMMAND
          je      @exit
          cmp     rdx,WM_CLOSE
          je      @close
          jmp     @next

;//******************************************************
;//****** Обработчик сообщения "WM_INITDIALOG" **********
;//******************************************************
@init:
;//------ Получить дескриптор TreeView
          invoke  GetDlgItem,[hwnd],ID_TV
          mov     [hTree],rax

;//------ Фон TreeView как у родительского окна
          invoke  GetSysColor, COLOR_BTNFACE
          invoke  SendMessage,[hTree],TVM_SETBKCOLOR,0,eax

if 0
;//------ Можно изменить шрифт в окне TreeView
          invoke  CreateFont, 14,0,0,0,FW_NORMAL,\     ;//<--- FW_BOLD
                              0,0,0,DEFAULT_CHARSET,\
                              OUT_DEFAULT_PRECIS,\
                              CLIP_DEFAULT_PRECIS,\
                              DEFAULT_QUALITY,\
                              FIXED_PITCH + FF_MODERN, "Consolas"
          mov     [hFont],rax
          invoke  SendMessage,[hTree],WM_SETFONT,[hFont],1
end if

;//------ Непосредственно работа с элементом "TreeView" -------------
;// Вставка корневого элемента (Root)
          mov     [tvis.item.mask],TVIF_TEXT    ;// мы передаём текст
          mov     [tvis.hParent],0              ;// родителя нет

          mov     eax,TVI_ROOT
          mov     [tvis.hInsertAfter],rax       ;// вставить как корень
          mov     [tvis.item.pszText],strRoot   ;// адрес строки

          invoke  SendMessage,[hTree],TVM_INSERTITEMA,0,tvis    ;// первый пошёл (заголовок)
          mov     [tvis.hParent], rax           ;// сохраняем хендл родителя для следующего шага

;// Вставка дочерних элементов (Child)
          mov     eax,TVI_LAST
          mov     [tvis.hInsertAfter],rax       ;// вставить в конец списка детей Root'а

          mov     rcx,6               ;// длина цикла
          mov     rax,strChild1       ;// первая строка
@@:       push    rax rcx             ;// запомнить

          mov     [tvis.item.pszText],rax       ;// адрес очередной строки
          invoke  SendMessage,[hTree],TVM_INSERTITEMA,0,tvis

          pop     rcx rax     ;// восстановить цикл и адрес строки
          add     rax,19      ;// переход к следующей строке
          loop    @b          ;// промотать CX-раз..
          jmp     @next

;//****************************************************
;//****** Обработчики "WM_CLOSE/COMMAND" **************
;//****************************************************
@exit:    cmp     [wparam],BN_CLICKED shl 16 + IDCANCEL
          jne     @next
@close:   invoke  EndDialog,[hwnd], 0
@next:    xor     eax,eax
          ret
endp
;//---------------
section '.idata' import data readable writeable
library  kernel32,'kernel32.DLL',user32,'user32.DLL',\
         comctl32,'comctl32.dll',gdi32,'gdi32.dll'
include  'api\kernel32.inc'
include  'api\comctl32.inc'
include  'api\user32.inc'
include  'api\gdi32.inc'
;//------------------
section '.rsrc' data resource readable
directory  RT_DIALOG, dialogs
resource dialogs,100,LANG_ENGLISH + SUBLANG_DEFAULT, mainform

dialog mainform,'TreeView example', 0,0,180,110, WS_CAPTION + WS_SYSMENU + DS_CENTER,,,'Verdana',8
  dialogitem 'Button','Выход',IDCANCEL, 020,090,070,013,WS_VISIBLE + BS_DEFPUSHBUTTON
  dialogitem  TREEVIEW_CLASS,'',ID_TV,  010,010,170,070,WS_VISIBLE + TVS_HASBUTTONS +\
                                                      TVS_HASLINES + TVS_LINESATROOT
enddialog

TreeWin.webp

2.2. Уведомления и взаимодействие

Элемент TreeView активно общается с родительским окном через механизм WM_NOTIFY. Родительское окно получает уведомления о следующих ключевых событиях внутри элемента:

• TVN_SELCHANGING / TVN_SELCHANGED - смена выделенного элемента.
• TVN_ITEMEXPANDING / TVN_ITEMEXPANDED - сворачивание/разворачивание узла.
• TVN_BEGINDRAG - начало перетаскивания элемента [I](если стиль TVS_DISABLEDRAGDROP не установлен)[/I].

Для программного управления используются сообщения:
TVM_GETITEM, TVM_SETITEM, TVM_GETNEXTITEM, TVM_SELECTITEM, TVM_EXPAND, TVM_ENSUREVISIBLE

2.3. Механизм обратного вызова "Callback"

Одна из самых интересных возможностей TreeView - ленивая загрузка данных через callback-механизм. Вместо немедленного предоставления текста, иконки или информации о наличии потомков, приложение может указать специальные значения ниже. В этом случае элемент отправляет родителю уведомление TVN_GETDISPINFO с запросом информации. Это позволяет строить огромные деревья без загрузки всех данных сразу в память (отложенная загрузка):

LPSTR_TEXTCALLBACK - для текста
I_IMAGECALLBACK - для индекса иконки
I_CHILDRENCALLBACK - для информации о дочерних элементах

Однако здесь есть важный нюанс. Если вы подставляете строку вместо копирования в предоставленный буфер, эта строка не может быть изменена до тех пор, пока не придёт как минимум два доп.уведомления TVN_GETDISPINFO, или элемент не будет удалён. Нарушение этого правила приведёт к ошибкам доступа к памяти AccessViolation.

3. Историческая уязвимость: CVE-2012-0158

Нельзя говорить об элементе TreeView, не упомянув самую известную уязвимость. CVE-2012-0158 - критическая лазейка для удалённого выполнения кода, затронувшая ActiveX-компоненты ListView и TreeView в библиотеке mscomctl.ocx. Она возникала из-за некорректной обработки входных данных и управления памятью. Злоумышленник мог создать сайт, доку Office или RTF-файл, который при открытии вызывал сбой системы, и приводил к выполнению произвольного кода.

Проблема затронула широкий спектр продуктов Microsoft:
Office 2003/2007/2010,
SQL Server 2000/2005/2008,
BizTalk Server, Commerce Server,
Visual FoxPro и Visual Basic 6.0 Runtime.

Уязвимость активно эксплуатировалась в дикой природе в апреле 2012 года. Согласно технической классификации, уязвимость относится к CWE-121: Heap-based Buffer Overflow (переполнение буфера в куче). CVE-2012-0158 это напоминание о том, что даже проверенные компоненты системы могут содержать критические уязвимости.

3.1. Защитные меры

Конечно-же Microsoft уже давно заткнула дыру своей заплаткой MS12-027, но проблема в том, что не все юзеры с завидной постоянностью обновляются. Если вы относитесь к их числу, вот несколько советов, как проверить свой периметр сподручными средствами.

Все обновления, и бюллетень MS12-027 в том числе, поставляется майкаим в виде т.н. KB или "Knowledge Base". Если зайти в апплет "Обновления Windows" комбинацией клавиш Win+R -> control update, то заплатка на CVE-2012-0158 будет числится в розысках под одной из следующих кличек:

• KB2664258 - для систем Windows
• KB2598374 - для Microsoft Office 2010
• KB2597986 - для Microsoft Office 2007
• KB2687441 - для Microsoft Office 2003

Вы можете найти её или в окне апплета, или просто ввести в ком.строке:
systeminfo | find "KB2664258"

Однако если обновление MS12-027 шло в составе пакета для Office или стороннего софта (например, Visual Studio), утилита systeminfo может не найти этот пакет, так как она ориентирована на компоненты ОС. Если команда выше не сработала, более надежный способ для поиска обновлений Office - использовать PowerShell:
get-hotfix | Where-Object {$_.HotFixID -eq "KB2664258"}

С другой стороны, все эти обновы просто заменяют уязвимый файл MSCOMCTL.OCX версии 6.01.9545 (лежит в папке win\system32), как минимум на v6.01.9833 или новее. Так-что достаточно проверить его версию в свойствах, и если сборка выше-равна 9833, то можете спать спокойно.

mscomctl.webp

3.2. Что такое ActiveX компоненты

Для тех кто в танке напомним некоторые моменты.
ActiveX - это древняя технология, которая позволяла сайтам и программам запускать мини-приложения прямо внутри себя для расширения возможностей. Она работала как конструктор из готовых деталей. Вместо того чтобы писать сложные функции с нуля, разработчики вставляли в документ или веб-пагу готовый маленький блок кода - это и есть элемент ActiveX. Благодаря этой фишке на сайтах могли работать интерактивные игры, сложные анимации, проигрыватели видео/аудио, инструменты для работы с банками, камерами видео-наблюдения и т.д.

Но у технологии был огромный минус - безопасность! Поскольку элементы ActiveX получали прямой доступ к ОС, это было открытой форточкой для всякого рода малвари. Поэтому сейчас все современные браузеры (Chrome, Edge, Firefox, Safari) полностью отказались от поддержки ActiveX, а сами майки заменили технологию на более безопасные стандарты, например HTML5, JavaScript и расширения. Сегодня элементы ActiveX изредка можно встретить только внутри старых корпоративных программ, закрытых систем, или в редких настройках макросов для Excel.

А проблема заключалась в том, как код внутри MSCOMCTL.OCX обрабатывал входящие данные. Разбор уязвимости показывает, что в одной из функций при вызове происходит выделение небольшого (~20 байт) буфера в стеке. Однако из-за ошибки в логике проверки границ, код мог продолжить запись данных так, чтобы из этого буфера торчал специальным образом сформированный хвост, который правил адрес возврата на нужный участок кода. Это давало возможность перехватить управление выполнением программы и запустить вредоносный код.

Таким образом, уязвимость была не в самом факте использования элемента управления TreeView, а во внутренней реализации этого ActiveX-компонента в конкретной версии либы MSCOMCTL.OCX.

А как на счёт примера выше с окном участников группы Rammstein? Даже если и была-бы на машине уязвимая mscomctl.ocx, программного кода WinAPI это никак не касалось-бы, поскольку хоть они и одного поля ягоды, но находятся на разных уровнях абстракций, и никак не конфликтуют между собой. TreeView из mscomctl.ocx - это ActiveX-оболочка, созданная Microsoft в основном для VB6 и VBA. Она под катом использует стандартный (и глубоко зарытый) элемент управления TreeView из системной comctl32.dll, а не наоборот.

4. Заключение

TreeView - это мощный и гибкий инструмент, который остаётся актуальным на протяжении десятилетий. Он предоставляет богатый API для управления иерархическими данными, поддерживает ленивую загрузку и drag-and-drop. Однако, как и любой сложный компонент, он требует внимательного отношения к деталям: от установки флага TVIF_TEXT до понимания исторических уязвимостей вроде CVE-2012-0158. Грамотное использование TreeView - это баланс между функциональностью, производительностью и безопасностью.
Полезно · 4

Комментарии

0