Написание плагинов к 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

Используются технологии uCoz