Написание плагинов к API Logger
Перехват динамически подгружаемых DLL
sendersu @11.11.2010
review by black_ninja
Инструментарий
ApiLogger v1.4 (брать с сайта автора - www.blackninja2000.narod.ru)
VisualStudio 200x
Драйвер «прямые руки» :)
Уже сам по себе ApiLogger (дальше шпион) умеет очень много. Это и база данных функций, что будут мониториться (файл db.txt), это и установки утилиты (settings.ini), это и развитая система плагинов + СДК к ней!
Давеча стала предо мною следующая задача: прошпионить за параметрами функций и кодами возврата одной интересной длл. Она занимаеться рисованием эффекта воды и найдена в инсталлере к портабл версии Дельфи7:
Сразу скажу что длл эта пишеться в темп каталог с рандомным названием и грузиться соответсвенно динамически – тоесть отловить это дело стандартно с помощью db.txt не выйдет (а вообще со слов автора это можно только если путь статический, неизменный – просто добавляем новую секцию и набор функций).
Дело усложняеться еще и тем, что длл эта грузиться не запущенным процессом, а порожденным (дочерним, или child).
Итак, приступим-с.
Мой файл db.txt (все лишние функи убраны, оставил только то что интересно видеть – рекомендую кстати держать этот список минимальным, бывает что через большой проги вылетают)
;---------------- ;Format of DB ;[DLL_NAME] ;Function, ParamsCnt, Options ;!Ordinal,ParamsCnt,Options ;+RVA,ParamsCnt,Options ;@Address,ParamsCnt,Options ;Options: ;#reg - general registers ;#tacts- function tacts ;#fpu - fpu registers ;#xmm - xmm registers ;#mmx - mmx registers [kernel32.dll] LoadLibraryA ,1 LoadLibraryExA ,3 LoadLibraryExW ,3 LoadLibraryW ,1 GetProcAddress ,2 CreateProcessA,10 CreateProcessW,10
Запускаем шпион, и вот что видно в логе – только первый процесс:
API Logger v1.4 (C)2004-2010 black_ninja Thu Nov 11 16:10:29 2010 Functions in DB: 7 Exclude modules: MSCTF.DLL, POWERME~.DLL, QDHOOK.DLL, COMCTL32.DLL, COMCTL32.OCX, COMDLG32.OCX, OLE32.DLL, OLEAUT32.DLL, COMDLG32.DLL, SXS.DLL, RPCRT4.DLL, GDI32.DLL, SHLWAPI.DLL, UXTHEME.DLL, BROWSEUI.DLL, SHDOCVW.DLL, MSI.DLL, WINMM.DLL, IMM32.DLL, MSCTFIME.IME, MFC42.DLL, USP10.DLL, VERSION.DLL, SHFOLDER.DLL, URLMON.DLL, IERTUTIL.DLL, CLBCATQ.DLL LOG START Delphi7~.exe 00404568 0D0C GetProcAddress(7C800000, 0040459C: "SetDllDirectoryW") ret: 7C85FBB8 Delphi7~.exe 0040457E 0D0C GetProcAddress(7C800000, 004045B4: "SetSearchPathMode") ret: 00000000 Delphi7~.exe 0040907B 0D0C GetProcAddress(7C800000, 004090F8: "Wow64DisableWow64FsRedirection") ret: 00000000 Delphi7~.exe 00409095 0D0C GetProcAddress(7C800000, 00409128: "Wow64RevertWow64FsRedirection") ret: 00000000 Delphi7~.exe 00406F8A 0D0C LoadLibraryA(00409150: "shell32.dll") ret: 7C9C0000 kernel32.dll 7C840A63 0D0C LoadLibraryExW(7C840AF4: "netmsg.dll", 00000000, 00000002) ret:00C20001 Delphi7~.exe 00407004 0D0C GetProcAddress(7C800000, 004070E8: "GetUserDefaultUILanguage")ret: 7C813100 user32.dll 7E428055 0D0C LoadLibraryExW(0012F958: "C:\WINDOWS\system32\MSCTF.dll", 00000000, 00000008) ret: 74720000 user32.dll 7E428055 0D0C LoadLibraryExW(0012F964: "C:\prg\QDict\qdhook.dll", 00000000, 00000008) ret: 22000000 kernel32.dll 7C80AEEC 0D0C LoadLibraryExW(763986FC: "version.dll", 00000000, 00000000) ret: 77C00000 kernel32.dll 7C80AEEC 0D0C LoadLibraryExW(0012F59C: "C:\WINDOWS\system32\msctfime.ime",00000000, 00000000) ret: 755C0000 kernel32.dll 7C80AEEC 0D0C LoadLibraryExW(0012F310: "C:\WINDOWS\system32\msctfime.ime",00000000, 00000000) ret: 755C0000 kernel32.dll 7C801DA8 0D0C LoadLibraryExA(773D30C0: "UxTheme.dll", 00000000, 00000000) ret:5AD70000 Delphi7~.exe 004099C5 0D0C CreateProcessA(00000000, 00B1217C: ""C:\DOCUME~1\ALEXS~1.PC2\LOCALS~1\Temp\is-PUA4E.tmp\Delphi7_Lite_Full_Edition_Setup_7.3.3.5v3_(Build_20100801).tmp"/SL5="$5407C...", 00000000, 00000000, 00000000, 00000000,00000000, 00000000, 0012FF04, 0012FEF4) ret: 00000001 kernel32.dll 7C80AEEC 0D0C LoadLibraryExW(0012FB40: "C:\WINDOWS\system32\Msctf.dll",00000000, 00000000) ret: 74720000 shell32.dll 7CA29F3B 0D0C GetProcAddress(7C800000, 7C9D3B9C: "ReleaseActCtx") ret: 7C8130EF intruder.dll 100042F8 0D0C LoadLibraryA(100016A4: "psapi.dll") ret: 76BF0000 intruder.dll 10004310 0D0C GetProcAddress(76BF0000, 10001690: "EnumProcessModules") ret: 76BF1EF4 intruder.dll 1000431A 0D0C GetProcAddress(76BF0000, 10001678: "GetModuleFileNameExA") ret: 76BF204D .......... LOG ENDЗначит нам надо как-то продолжить мониторинг….. На помощь приходит система плагинов апи шпиона.
Надо перехватить вызов CreateProcessA ! Поскольку автор поставляет шпион с примером плагина, который уже делает то что нам надо – воспользуемься этим любезно!
Еще до сборки надо подкорректировать путь ко второму экземпляру шпиона в коде плагина, пример:
void KInject::Inject2(HANDLE hProcess, HANDLE hThread){ DWORD ret; char szDLL[MAX_PATH]; strcpy(szDLL, "C:\\intruder.dll"); int len = strlen(szDLL) + 1; PVOID param = VirtualAllocEx(hProcess, NULL, len, MEM_COMMIT | MEM_TOP_DOWN, PAGE_READWRITE); if (param != NULL){ if (WriteProcessMemory(hProcess, param, (LPVOID)szDLL, len, &ret)){ InjectDll(hProcess, hThread, (DWORD)param); } } }
Cобираем плагин в VS2008
Также обратите внимание на то, что для второго процесса надо будет создать свою среду (в отдельном, не том же каталоге! У меня – это корень диска С:), а именно второй среде шпиона надо:
Свою длл intruder.dll
Свой файл db.txt (и свой набор функций, если надо)
Свой файл settings.ini (тут важно прописать отдельный output файл!), пример:
OutLogFile=C:\plugin.apilog.txt
иначе будут чудеса (два шпиона будут писать в 1 файл)
Дальше, собранный файл плагина - plugin.dll ложим рядышком с 1-ым шпионом (injector.exe)
И запускаемся:
injector.exe .\Delphi7_Lite_Full_Edition_Setup_7.3.3.5v3_(Build_20100801).exe
Вот пример лога (Delphi7_Lite_Full_Edition_Setup_7.3.3.5v3_(Build_20100801).exe.apilog.txt) с перехваченным CreateProcessA (внимание на подсветку красным цветом!)
API Logger v1.4 (C)2004-2010 black_ninja Thu Nov 11 16:13:37 2010 Functions in DB: 7 Plugin: "ChildProcess Logger Plugin" ------------------------------ Set to "CreateProcessA" ok Set to "CreateProcessW" ok ------------------------------ Exclude modules: MSCTF.DLL, POWERME~.DLL, QDHOOK.DLL, COMCTL32.DLL, COMCTL32.OCX, COMDLG32.OCX, OLE32.DLL, OLEAUT32.DLL, COMDLG32.DLL, SXS.DLL, RPCRT4.DLL, GDI32.DLL, SHLWAPI.DLL, UXTHEME.DLL, BROWSEUI.DLL, SHDOCVW.DLL, MSI.DLL, WINMM.DLL, IMM32.DLL, MSCTFIME.IME, MFC42.DLL, USP10.DLL, VERSION.DLL, SHFOLDER.DLL, URLMON.DLL, IERTUTIL.DLL, CLBCATQ.DLL LOG START Delphi7~.exe 00404568 0C5C GetProcAddress(7C800000, 0040459C: "SetDllDirectoryW") ret: 7C85FBB8 .......... Target Detected Inject OK Delphi7~.exe 004099C5 0C5C CreateProcessA(00000000, 00B4217C: ""C:\DOCUME~1\ALEXS~1.PC2\LOCALS~1\Temp\is-6R7PP.tmp\Delphi7_Lite_Full_Edition_Setup_7.3.3.5v3_(Build_20100801).tmp" /SL5="$4C07A...", 00000000, 00000000, 00000000, 00000000, 00000000, 00000000, 0012FF04, 0012FEF4) ret: 00000001 kernel32.dll 7C80AEEC 0C5C LoadLibraryExW(0012FB40:"C:\WINDOWS\system32\Msctf.dll", 00000000, 00000000) ret: 74720000 ..........
Вот пример лога от второго шпиона (plugin.apilog.txt) с перехваченным CreateProcessA
Версия укорочена, показано только то что нас интересует
API Logger v1.4 (C)2004-2010 black_ninja Thu Nov 11 16:37:22 2010 Functions in DB: 7 Plugin: "ChildProcess Logger with Dll Hooking Plugin v2" ------------------------------ Set to "CreateProcessA" ok ------------------------------ Exclude modules: MSCTF.DLL, POWERME~.DLL, QDHOOK.DLL, COMCTL32.DLL, COMCTL32.OCX, COMDLG32.OCX, OLE32.DLL, OLEAUT32.DLL, COMDLG32.DLL, SXS.DLL, RPCRT4.DLL, GDI32.DLL, SHLWAPI.DLL, UXTHEME.DLL, BROWSEUI.DLL, SHDOCVW.DLL, MSI.DLL, WINMM.DLL, IMM32.DLL, MSCTFIME.IME, MFC42.DLL, USP10.DLL, VERSION.DLL, SHFOLDER.DLL, URLMON.DLL, IERTUTIL.DLL, CLBCATQ.DLL LOG START Delphi7~.tmp 004064E0 0E80 GetProcAddress(7C800000, 00406514: "SetDllDirectoryW") ret: 7C85FBB8 Delphi7~.tmp 004064F6 0E80 GetProcAddress(7C800000, 0040652C: "SetSearchPathMode") ret: 00000000 Delphi7~.tmp 0041B388 0E80 LoadLibraryA(0041B6EC: "uxtheme.dll") ret: 5AD70000 ..... Delphi7~.tmp 00438343 0E80 LoadLibraryA(01422098: "C:\DOCUME~1\ALEXS~1.PC2\LOCALS~1\Temp\is-OBMGC.tmp\waterctrl.dll") ret: 011E0000 Delphi7~.tmp 004383B2 0E80 GetProcAddress(011E0000, 014220E8: "enablewater") ret: 011E1FF3 kernel32.dll 7C801DA8 0E80 LoadLibraryExA(73E77758: "COMCTL32.DLL", 00000000, 00000000) ret: 773D0000 kernel32.dll 7C801DA8 0E80 LoadLibraryExA(73E88AD0: "COMCTL32.dll", 00000000, 00000000) ret: 773D0000 kernel32.dll 7C801DA8 0E80 LoadLibraryExA(73E77758: "COMCTL32.DLL", 00000000, 00000000) ret: 773D0000 kernel32.dll 7C801DA8 0E80 LoadLibraryExA(73E77758: "COMCTL32.DLL", 00000000, 00000000) ret: 773D0000 kernel32.dll 7C801DA8 0E80 LoadLibraryExA(73E77758: "COMCTL32.DLL", 00000000, 00000000) ret: 773D0000 kernel32.dll 7C801DA8 0E80 LoadLibraryExA(73E77758: "COMCTL32.DLL", 00000000, 00000000) ret: 773D0000 kernel32.dll 7C801DA8 0E80 LoadLibraryExA(73E88A90: "OLEPRO32.DLL", 00000000, 00000000) ret: 5EDD0000 kernel32.dll 7C801DA8 0E80 LoadLibraryExA(774EC038: "oleaut32.dll", 00000000, 00000000) ret: 77120000 Delphi7~.tmp 004383B2 0E80 GetProcAddress(7E410000, 01422100: "SetTimer") ret: 7E418C2E Delphi7~.tmp 004643AB 0E80 GetProcAddress(7C800000, 004644B8: "GetDiskFreeSpaceExA") ret: 7C83038B Delphi7~.tmp 004383B2 0E80 GetProcAddress(7E410000, 01422FF8: "GetSystemMenu") ret: 7E42B222 Delphi7~.tmp 004383B2 0E80 GetProcAddress(7E410000, 01422FF8: "AppendMenuA") ret: 7E431B0E Delphi7~.tmp 004383B2 0E80 GetProcAddress(011E0000, 013E71FC: "setwaterparent") ret: 011E20DF Delphi7~.tmp 004383B2 0E80 GetProcAddress(011E0000, 013E71FC: "waterblob") ret: 011E20F9 Delphi7~.tmp 00420F42 0E80 GetProcAddress(7E410000, 00420FF4: "MonitorFromWindow") ret: 7E42A679 Delphi7~.tmp 00420F4F 0E80 GetProcAddress(7E410000, 00421008: "GetMonitorInfoA") ret: 7E42A84A Delphi7~.tmp 004383B2 0E80 GetProcAddress(011E0000, 0142207C: "disablewater") ret: 011E2129 .......... LOG END
Длл нашли – она расспакована инсталлером в темп дир и динамически загружена через LoadLibrary, можем еще посмотреть для интереса ее экспорт в Hiew:
Но! Самое интересное все еще скрыто от наших глаз – не видно вызовов функций нашей длл, аргументов и результатов возврата
Что делать….. пошли в рукопашку! Добавляем перехват функции LoadLibraryA + ищем и хучим экспортируемые функции нашей длл
Вот как это сделано в коде (плагин тот же)
Добавляем новый описатель плагина
PLUGIN_DAT plugins[]= { { "CreateProcessA", before_CreateProcessA, 0 }, { "LoadLibraryA", before_LoadLibraryA, 0 } };
Реализуем свой обработчик
DWORD __stdcall before_LoadLibraryA(SPROXYENTRYSTRUCT* pEntry){ DWORD retv; if(StrStrIA((char*)pEntry->parameters[0], "waterctrl.dll")){ retv = ((PFN_F1)GetUnhookedFuncAddrById(pEntry->funcid))( pEntry->parameters[0] //LPCTSTR lpFileName ); hMyDll = (HMODULE)retv; Str2Log("DLL Detected\r\n"); //hook exported API Hook_WaterDll(hMyDll); } else{ retv = ((PFN_F1)GetUnhookedFuncAddrById(pEntry->funcid))( pEntry->parameters[0] //LPCTSTR lpFileName ); } //Logger(">>before_LoadLibraryA called for %s, retv: %#X\r\n", pEntry->parameters[0], retv); pEntry->eax=retv; return (DWORD)stub_1; }
Как узнать какой тип функции и что возращать в конце? Очень просто.
Идем в МСДН (msdn.com), смотрим описание аргументов функции
Видим что один аргумент, значит будем использовать PFN_F1 и stub_1 (если будет 5 аргументов – тогда PFN_F5 и stub_5 и т.д.)
Кодируем хукер:
struct FUNCDATA{ char* pName; DWORD Params; }; FUNCDATA table_WaterDll[]={ {"enablewater",1}, {"disablewater",1}, {"waterblob",1}, {"flattenwater",1}, {"setwaterparent",1}, }; void Hook_WaterDll(HMODULE hHookedDll){ static int fNotFirst = 0; if(!fNotFirst){ fNotFirst = 1; for(DWORD i = 0; i < sizeof(table_WaterDll)/sizeof(table_WaterDll[0]); i++){ SFUNCTION finfo={0}; DWORD addr = (DWORD)GetProcAddress(hHookedDll, table_WaterDll[i].pName); Logger(">>Hook_WaterDll called for %s, addr: %#X\r\n", table_WaterDll[i].pName, addr); finfo.pName = table_WaterDll[i].pName; finfo.params= table_WaterDll[i].Params; SetHook(addr, &finfo); } } }
Собираем проект и копируем новую длл в место второго шпиона (в моем случае сюда - c:\plugin.dll)
Запускаемся, и смотрим лог второго шпиона (не весь),
уррааааа, есть вызов! Бой выигран, пошли пить кофе (или кто что любит в таких случаях)
API Logger v1.4 (C)2004-2010 black_ninja Thu Nov 11 16:59:13 2010 Functions in DB: 7 Plugin: "ChildProcess Logger with Dll Hooking Plugin v2" ------------------------------ Set to "CreateProcessA" ok Set to "LoadLibraryA" ok ------------------------------ Exclude modules: MSCTF.DLL, POWERME~.DLL, QDHOOK.DLL, COMCTL32.DLL, COMCTL32.OCX, COMDLG32.OCX, OLE32.DLL, OLEAUT32.DLL, COMDLG32.DLL, SXS.DLL, RPCRT4.DLL, GDI32.DLL, SHLWAPI.DLL, UXTHEME.DLL, BROWSEUI.DLL, SHDOCVW.DLL, MSI.DLL, WINMM.DLL, IMM32.DLL, MSCTFIME.IME, MFC42.DLL, USP10.DLL, VERSION.DLL, SHFOLDER.DLL, URLMON.DLL, IERTUTIL.DLL, CLBCATQ.DLL LOG START Delphi7~.tmp 004064E0 12D4 GetProcAddress(7C800000, 00406514: "SetDllDirectoryW") ret: 7C85FBB8 ....... Delphi7~.tmp 00438343 12D4 LoadLibraryA(01421718: "C:\DOCUME~1\ALEXS~1.PC2\LOCALS~1\Temp\is-3DCOK.tmp\waterctrl.dll") ret: 011E0000 Delphi7~.tmp 004383B2 12D4 GetProcAddress(011E0000, 01421768: "enablewater") ret: 011E1FF3 Delphi7~.tmp 00430EC6 12D4 enablewater(004E0C64) ret: 00000001 Delphi7~.tmp 004383B2 12D4 GetProcAddress(7E410000, 01421780: "SetTimer") ret: 7E418C2E Delphi7~.tmp 004643AB 12D4 GetProcAddress(7C800000, 004644B8: "GetDiskFreeSpaceExA") ret: 7C83038B Delphi7~.tmp 004383B2 12D4 GetProcAddress(7E410000, 01422974: "GetSystemMenu") ret: 7E42B222 Delphi7~.tmp 004383B2 12D4 GetProcAddress(7E410000, 01422974: "AppendMenuA") ret: 7E431B0E Delphi7~.tmp 004383B2 12D4 GetProcAddress(011E0000, 0141B098: "setwaterparent") ret: 011E20DF Delphi7~.tmp 00430EC6 12D4 setwaterparent(004E0C64) ret: 00000001 Delphi7~.tmp 004383B2 12D4 GetProcAddress(011E0000, 0141B098: "waterblob") ret: 011E20F9 Delphi7~.tmp 00430EC6 12D4 waterblob(0000006E) ret: 00000001 Delphi7~.tmp 00430EC6 12D4 waterblob(00000070) ret: 00000001 Delphi7~.tmp 00420F42 12D4 GetProcAddress(7E410000, 00420FF4: "MonitorFromWindow") ret: 7E42A679 Delphi7~.tmp 00420F4F 12D4 GetProcAddress(7E410000, 00421008: "GetMonitorInfoA") ret: 7E42A84A Delphi7~.tmp 004383B2 12D4 GetProcAddress(011E0000, 01421238: "disablewater") ret: 011E2129 Delphi7~.tmp 00430EC6 12D4 disablewater(0141A2C8) ret: 00000001 .......... LOG END
P.S. Большое спасибо автору за его труд и помощь при создании платина, а также за вычитку сего опуса.
Примечание black_ninja
После написания статьи, API Logger обновлися до версии 1.5
Загрузка DLL сделана другим образом и LoadLibraryA для перехвата функций из water.dll писать не надо, достаточно просто указать для копии шпиона (который внедряется в дочерний процесс) эту базу как и шпион сам перехватит функции:
[waterctrl.dll] enablewater,3 disablewater,3 waterblob,3 flattenwater,3 setwaterparent,3Скачать пример плагина и исходник для API Logger / Download