PDA

Просмотр полной версии : Заменить пункт меню на свой через HANDLE



char_ser
18.11.2004, 15:10
Есть программа N, написанная сторонним производителем *.exe .

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

Могу я как-нибудь узнать HANDLE меню и HANDLE самого элемента меню, чтобы этот элемент меню удалить, а свой добавить? Как это сделать?

Eugie
18.11.2004, 16:41
Узнать-то в принципе можно, если есть хэндл соотв.окна: GetMenu и далее рекурсивно GetSubMenu, GetMenuInfo пока не найдем что нужно (напр., по тексту menu item). И удалить/добавить свой menu item для чужого процесса, скорее всего, тоже можно, хотя и не проверял. Но вот внедрить свой обработчик в чужую программу нельзя.

Romeo
18.11.2004, 18:11
Можно. Всего надо:

1. Открыть exe-файл.
2. Найти адрес начала и адрес конца обработчка.
3. Залить всё NOP-ами.
4. Поверх NOP-ов написать свой код (на асме, конечно)
5. Сохранить exe-файл.

:)

char_ser
18.11.2004, 18:46
2Eugie: мне кажется, что это реально, т.к. этот процесс пользуется моей dll -> я нахожусь тоже в этом процессе -> подменить обработчик элемента меню несложно

2Romeo: тоже вариант, но мне он не подходит:)

Romeo
19.11.2004, 11:00
Верно, обработчик действительно реально заменить программно, но всё будет работать только до того момента, пока твоя DLL подгружена. После её выгрузки код, на который ссылается обработчик станет невалидным и первый же клик на соответствующем айтеме меню приведёт к крашу всего комплекса.

char_ser
19.11.2004, 11:13
Пока обработчик ссылается на мой код, длл выгружена не будет.

Romeo
19.11.2004, 15:02
Так бы оно и было, если бы DLL была COM-овской. Что-то у меня необъяснимое ощущение, что DLL-ка загружается комплексом, как плагин. Т.е. делает LoadLibrary, потом GetProcAddress, вызывает полученный метод, а потом делает FreeLibrary. Это, конечно, утрированный пример, но технология, думаю, именно такова. А, поскольку, меняя стандартный обработчик на свой, dllcounter ты не изменяешь, то при вызове FreeLibrary произойдёт выгрузка и никаких других вариантов быть просто не может.

Eugie
19.11.2004, 16:23
2Eugie: мне кажется, что это реально, т.к. этот процесс пользуется моей dll -> я нахожусь тоже в этом процессе -> подменить обработчик элемента меню несложно

В этом случае действительно можно: при загрузке dll ставить хук типа WH_GETMESSAGE и ловить сообщение WM_COMMAND с заданным id. Ессно, при выгрузке dll хук нужно удалять.

char_ser
22.11.2004, 12:34
Теперь другая проблема вылезла:(
В общем заменил я пункт меню на свой, а вот чтобы теперь к нему добавить вызываемую функцию по этому пункту меню...
Ведь существует карта сообщений у программы. Я ее не знаю.. Мой новый элемент меню имеет свой ID. Как мне изменить эту карту так, чтобы она по этому ID вызывала мою функцию??? Как к этой карте получить доступ?

Eugie
22.11.2004, 13:39
Вот пример, как это можно сделать (код для dll: когда она загружена, перехватывается обращение к пункту меню с именем "&About ...", вместо него вызывается MessageBox):


HHOOK hkMsg;
int nTargetCmdID;

// Searching for the main window of the current app.
BOOL CALLBACK EnumWindowsProc(
HWND hwnd, // handle to parent window
LPARAM lParam // application-defined value
)
{
DWORD dwPID, dwTID = GetWindowThreadProcessId(hwnd, &dwPID);
DWORD dwCurPID = GetCurrentProcessId();
BOOL bProceed = dwCurPID != dwPID;
if (!bProceed)
*(HWND*)lParam = hwnd;
return bProceed;
}


HWND GetAppTopWindow()
{
HWND hwnd = NULL;
EnumWindows(EnumWindowsProc, (LPARAM)&hwnd);
return hwnd;
}

int FindMenuItemID(HMENU hmMenu, LPCTSTR lpszMenuItemText)
{
int id = -1;
TCHAR buf[100];
MENUITEMINFO mii;
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STRING;
mii.dwTypeData = buf;
mii.cch = sizeof(buf)/sizeof(TCHAR);

for(int i = 0; i < GetMenuItemCount(hmMenu); i++)
{
GetMenuItemInfo(hmMenu, i, TRUE, &mii);
if (strcmp(mii.dwTypeData, lpszMenuItemText) == 0)
return GetMenuItemID(hmMenu, i);
mii.fMask = MIIM_SUBMENU;
GetMenuItemInfo(hmMenu, i, TRUE, &mii);
if (mii.hSubMenu)
id = FindMenuItemID(mii.hSubMenu, lpszMenuItemText);
}

return id;
}

int GetMenuCmdMsgID(LPCTSTR lpszMenuItemText)
{
HWND hwnd = GetAppTopWindow();
HMENU hmenu = GetMenu(hwnd);
return FindMenuItemID(hmenu, lpszMenuItemText);
}

LRESULT CALLBACK GetMsgProc(
int code, // hook code
WPARAM wParam, // removal option
LPARAM lParam // message
)
{
if (code == HC_ACTION)
{
MSG* pmsg = (MSG*)lParam;
wParam = PM_REMOVE;

nTargetCmdID = GetMenuCmdMsgID("&About ...");
if(pmsg->hwnd == GetAppTopWindow() &&
pmsg->message == WM_COMMAND &&
HIWORD(pmsg->wParam) == 0 &&
LOWORD(pmsg->wParam) == nTargetCmdID)
{
pmsg->wParam = -1; // to prevent passing to standard handler
MessageBox(NULL, "Target menu command called.", "Info", MB_OK);
return 0;
}

}
return CallNextHookEx(hkMsg, code, wParam, lParam);
}

void InstallHook()
{
hkMsg = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, NULL, GetCurrentThreadId());
}

void UninstallHook()
{
UnhookWindowsHookEx(hkMsg);
}

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
InstallHook();
break;
case DLL_PROCESS_DETACH:
UninstallHook();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}

char_ser
22.11.2004, 14:14
ОГРОМНОЕ СПАСИБО!!!

char_ser
22.11.2004, 15:33
А что делать в случае с контекстным меню? Ведь оно не посылает сообщения WM_COMMAND...

Eugie
24.11.2004, 13:45
Видимо, вопрос естественным образом закрылся, но на всякий случай: 1) если контекстное меню системное (system menu, отображается при клике в левом вернем углу заголовка), оно посылает сообщения WM_SYSCOMMAND, их тоже можно ловить; 2) если оно пользоватьское - все зависит от способа вызова - указан или нет флажок TPM_NONOTIFY в TrackPopupMenu (в последнем случае, действительно, ловить нечего)

Romeo
24.11.2004, 14:54
В последнем случае нужно ловить WM_COMMAND, который отсылает сэмулированный айтем главного меню. Ведь если разработчики сэмулировали выпадение подменю, то соответствующий айтем уже не popup, а обычный, как следствие он шлёт WM_COMMAND. Именно в command xэндлере этого айтема происходит вызов TrackPopupMenu.

Eugie
24.11.2004, 16:17
Речь об обычном контекстном меню, в эмулированном, ессно, все может быть

char_ser
24.11.2004, 16:33
В общем, сделаю наверное так:
Есть такая фигня "Detours": http://research.microsoft.com/sn/detours/
Она позволяет перехватывать любые Win32 API функции, она специально для этого и разработана. (COOL)
Я перехватываю TrackPopupMenu(), в параметре hMenu у нее убиваю нужный мне пункт и передаю управление оригинальной TrackPopupMenu() :)