Pull to refresh

Дизассемблируем Windows Explorer — отключаем группировку на панели задач

Reading time6 min
Views92K
Однажды, в 2009 году, вышла Windows 7. В то время я сидел на Висте, которая притормаживала на моем стареньком компьютере, и я решил пересесть на семерку сразу после ее выхода.

Первое, на что я обратил внимание после установки — новая панель задач. А конкретнее — тот факт, что она группирует кнопки по программе, к которой они принадлежат.



Сразу же полез в настройки, чтобы отключить это безобразие, и с удивлением обнаружил, что группировка не отключается. Наиболее близкий к желаемому вариант, Never combine, кнопки все же группирует.



Я думал, что как и мне, многим это не понравится, и был уверен, что спустя неделю-две в интернете всплывет решение этой проблемы. Но я так ничего путного и не нашел, и понял, что придется действовать самому.

Под катом:
  • Подробная демонстрация того, как можно самостоятельно подправить принцип работы такого системного процесса, как explorer, под себя, используя отладчик OllyDbg.
  • Готовое решение описанной выше проблемы.

Приступаем


Для понимания описанного ниже процесса, желательно иметь базовое знание ассемблера.

Для того, чтобы отучить панель задач от группировки, нам понадобится OllyDbgбесплатный проприетарный 32-битный отладчик уровня ассемблера для операционных систем Windows, предназначенный для анализа и модификации откомпилированных исполняемых файлов и библиотек, работающих в режиме пользователя (ring-3) © Wikipedia.

Повторить процесс можно на ОС Windows 7 или Windows 8. К сожалению, на данный момент имеется только 32-битная версия OllyDbg, так что если у вас 64-битная ОС, повторить следующие действия у вас не получится (в таком случае можно использовать виртуальную машину).

Инсталляция OllyDbg

OllyDbg — портативная программа, и в инсталляции не нуждается. Создайте папку с правами записи, и перепишите туда файлы с этого архива.

Настройка Microsoft Symbol Server

Microsoft Symbol Server — Майкрософтовский сервер отладочных символов, благодаря которым мы, кроме чистого ассемблера, будем видеть еще и названия функций и переменных. Это очень помогает при анализе кода.

Для этого нужно сделать следующее:
  • Скачайте этот архив, и поместите его содержание в папку программы OllyDbg.
    Что это за файлы?
    • symsrv.dll — DLL файл Майкрософта, предназначен для работы с их сервером отладочных символов. Подписан Майкрософтом.
    • symsrv.yes — пустой файл. Обозначает, что вы согласны с условиями предоставленных услуг.
    • symsrv.ini — исключает все файлы кроме тех, которые начинаются на exp — для нашего explorer.exe. Иначе, если будут качаться все символы, придется очень долго ждать, да и не нужно оно нам для данной демонстрации.
  • Включите OllyDbg, откройте настройки (Alt+O), и пометьте опцию Demangle symbolic names. Эта опция декодирует символических имена, делая их более читаемые для нас, людей.
    Скриншот
  • В настройках, откройте страницу Debugging data, и пометьте Allow access to Microsoft Symbol Server. Затем создайте папку для символов, и выберете ее в настройках, как на скриншоте.
    Скриншот
  • Перезапустите OllyDbg.

Присоединение процесса explorer

Как известно, панель задач — часть процесса Windows Explorer. Запускаем OllyDbg, и выбираем File -> Attach.... Затем, выбираем explorer и жмем на Attach.
Скриншоты


Attach, с английского — присоединять — то есть мы присоединяем наш отладчик к проводнику для его отладки.

Затем ждем, пока все модули загрузятся. Это может занять несколько минут, и в это время проводник не будет реагировать. После завершения загрузки в строке состояния справа будет написано Paused на желтом фоне — то есть процесс приостановлен. Жмем F9 чтобы возобновить работу процесса.

Внимание, исключения: если процесс приостанавливается, и в строке состояния появляется надпись Exception xxxxxxxx (как на скриншоте ниже), нажмите Shift+F9 чтобы передать исключение по назначению.
Скриншот

Обзор функций проводника

Теперь проводник работает под надзором нашего отладчика, OllyDbg. Если связь с сервером отладочных символов прошла как следует, мы можем взглянуть на функции проводника.

Сначала, откроем модуль процесса explorer в окошке CPU отладчика. Для этого откроем окошко модулей (буква E на голубом фоне), нажмем правой кнопкой на Explorer, и выберем View code in CPU.
Скриншот

Затем, нажмем правой кнопкой на код, и выберем Search for -> Names. Перед нами появится список функций, присутствующих в проводнике. Для более удобной работой со списком, отсортируем его по имени, скопируем и вставим в текст в любимый текстовой редактор.
Скриншоты


Итак, нам нужно отключить группировку. Логично начать поиск со слова group. В глаза сразу бросаются два класса: CTaskBtnGroup и CTaskGroup. Вместе у этих двух классов 131 функций — не очень много для беглого просмотра названий.

Мне сложно сказать, можно ли найти нужную нам функцию, всего лишь изучая названия, так как я уже неплохо с многими из них знаком. В любом случае, нужная нам функция — CTaskGroup::DoesWindowMatch, название которой переводится как совпадает ли окошко, и делает она именно это — отвечает, подходит ли окошко к определенной группе.
Скриншот

Просмотр выбранной функции

Мы нашли функцию с интересным названием, теперь давайте посмотрим, чего она из себя представляет. Вернемся в OllyDbg, нажмем на Ctrl+G и перейдем на адрес функции (тот восьмизначный номер в начале строки, в нашем случае 00973629).

P.S. Советую включить подсветку Jumps and calls, благодаря которой четко видны вызовы функций и условные/безусловные прыжки.
Скриншот

Итак, вот наша функция:


Столбцы, слева на право: адрес, байты, команды (ассемблер), комментарии.
Голубым цветом помечены функции, желтым — переходы. На них мы и сосредоточимся.

Первым делом, вызывается какая-то функция, и в зависимости от результата происходит переход. Здесь я на них задерживаться не буду, замечу только, что к нашей задачи они дело не имеют.

Дальше мы видим три перехода. Первый перепрыгивает через второго и третьего. Второй и третий переходят куда-то подальше — давайте посмотрим, куда…

Первый
В глаза сразу бросается WinAPI функция ILIsEqual, которая сравнивает две структуры. После некоторого анализа становится ясно, что сравниваемые структуры связаны с группами. Наша цель — отменить любую группировку, так что пропатчим код так, чтобы проводник думал, что структуры не равны.



Для этого, пропишем безусловный прыжок JMP, как на скриншоте сверху.

Второй
Как и в первом разе, сразу видим WinAPI функцию для сравнения, на этот раз текста — CompareStringOrdinal. Опять же, после экспериментирования становится ясно, что и это сравнение связано с группами. На этот раз сравниваются так называемые Application ID — идентификатор аппликации, по которым панель задач группирует кнопки.



Опять же, ставим безусловный прыжок, заставляя проводника думать, что все идентификаторы разные.

Пробуем

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

Попробуем открыть несколько копий блокнота, и увидим, что они не группируются:



Получилось :)

Так как мы изменили код только в памяти запущенного процесса, наша модификация продержится только до завершения процесса.

Бонус — декомпилированная версия функции CTaskGroup::DoesWindowMatch

Несмотря на то, что оригинальный язык данного кода — C++, ниже превидена самописная декомпиляция на чистом C.

/*
return:
S_OK if there's a match, E_FAIL otherwise

*pnMatch:
1: AppId matches, ITEMIDLIST is missing
2: AppId matches, ITEMIDLIST doesn't match
3: ITEMIDLIST matches
4: hWnd already exists (pTaskItem recieves the CTaskItem)
*/

HRESULT CTaskGroup_DoesWindowMatch(void /*CTaskGroup*/ *this, 
	/*IN*/ HWND hCompareWnd, /*IN*/ ITEMIDLIST *pCompareItemIdList, /*IN*/ WCHAR *pCompareAppId, 
	/*OUT*/ int *pnMatch, /*OUT*/ void /*CTaskItem*/ **pTaskItem)
{
	void /*CTaskItem*/ *CompareWndTaskItem;
	int nMatch;
	HRESULT hr;

	nMatch = 0;

	hr = this->lpVtbl->GetItemFromWindow(this, hCompareWnd, &CompareWndTaskItem);
	if(SUCCEEDED(hr)) // if an item for this window already exists
	{
		if(pTaskItem)
		{
			*pTaskItem = NULL;
			IUnknown_Set(pTaskItem, CompareWndTaskItem);
		}

		nMatch = 4;
		CompareWndTaskItem->lpVtbl->Release(CompareWndTaskItem);
	}
	else
	{
		if(!(this->SomeFlags & 0x80000000))
		{
			if(pCompareItemIdList && this->pItemIdList && ILIsEqual(pCompareItemIdList, this->pItemIdList) != 0)
			{
				nMatch = 3;
				hr = S_OK;
			}
			else if(pCompareAppId && CompareStringOrdinal(this->pAppId, -1, pCompareAppId, -1, TRUE) == CSTR_EQUAL)
			{
				hr = S_OK;

				if(pCompareItemIdList && this->pItemIdList)
					nMatch = 2;
				else
					nMatch = 1;
			}
		}
	}

	*pnMatch = nMatch;

	return hr;
}


Готовое решение


Программа 7+ Taskbar Tweaker, автором которой являюсь я, умеет отключать группировку как глобально, так и выборочно по Application ID. Кроме этого, имеются в наличие еще несколько интересных настроек панели задач.



Заключение


Иногда я открываю статью на хабре, которая кажется мне интересной, но после того как начинаю читать, понимаю что она не такая уж и интересная (или не очень понятная). В таких случаях я часто сразу перелистываю вниз к заключению/выводам.

Если вы находитесь здесь по этой же причине, расскажу вкратце о чем шла речь:
Я взял отладчик, и присоединил его к проводнику. Затем я нашел нужную функцию, и изменил ее так, чтобы проводник думал, что любое созданное окошко не подходит ни к одной из существующих групп. В связи с этим изменением, проводник перестал группировать кнопки на панели задач.
Tags:
Hubs:
Total votes 202: ↑176 and ↓26+150
Comments117

Articles