[Работа с буфером обмена в ядре.] Решила написать такую вот небольшую заметку о работе с буфером обмена в ядре. Для начала рассмотрим типичный код для работы с буфером обмена в юзермоде. Code: if(OpenClipboard(GetForegroundWindow())) { EmptyClipboard(); hGlobalData = GlobalAlloc(GHND,150); if(hGlobalData) { lstrcpy((LPWSTR)GlobalLock(hGlobalData), L"Hello from clipboard"); GlobalUnlock(hGlobalData); SetClipboardData(CF_UNICODETEXT,hGlobalData); } CloseClipboard(); } Из этого становиться ясна последовательность действий. Сначала открыть буфер обмена, очистить его, выделить память, передать ее хендл в функцию SetClipboardData. И закрыть буфер обмена. OpenClipboard,EmptyClipboard, GetForegroundWindow сводятся к вызову сервисов win32k.sys. И с вызовом в ядре проблем не возникнет, так как мы тестировать драйвер будем DeviceIoControl - ом. В общем случае можно приаттачиться к какому-нибудь процессу, что тоже известно как делать из драйвера. Теперь к ресечу непосредственно. Каким образом вставить данные в буфер обмена? Обратимся к коду функции SetClipboardData. Code: 7E380F83 6A 00 PUSH 0 7E380F85 53 PUSH EBX 7E380F86 8975 FC MOV DWORD PTR SS:[EBP-4],ESI 7E380F89 E8 BB000000 CALL USER32._ConvertMemHandle@8 7E380F8E 8BF8 MOV EDI,EAX ... 7E380FCC 50 PUSH EAX 7E380FCD 57 PUSH EDI 7E380FCE FF75 08 PUSH DWORD PTR SS:[EBP+8] 7E380FD1 8975 F8 MOV DWORD PTR SS:[EBP-8],ESI 7E380FD4 E8 5CFFFFFF CALL USER32._NtUserSetClipboardData@12 7E380FD9 85C0 TEST EAX,EAX Первым делом вызывается неэкспортируемая функция ConvertMemHandle, возвращаемое значение которой и будет передано в NtUserSetClipboardData.Надо сказать, что передаваемые адреса должны быть юзермодные. Посмотрим на код NtUserConvertMemHandle Code: .text:BF8ECE5C mov esi, [ebp+arg_4] .text:BF8ECE5F test esi, esi .text:BF8ECE61 jz short loc_BF8ECE75 .text:BF8ECE63 mov ecx, [ebp+arg_0] .text:BF8ECE66 lea eax, [ecx+esi] .text:BF8ECE69 cmp eax, ecx .text:BF8ECE6B jb short loc_BF8ECE93 .text:BF8ECE6D cmp eax, _Win32UserProbeAddress .text:BF8ECE73 ja short loc_BF8ECE93 .text:BF8ECE75 .text:BF8ECE75 loc_BF8ECE75: ; CODE XREF: NtUserConvertMemHandle(x,x)+1Aj .text:BF8ECE75 ; NtUserConvertMemHandle(x,x)+52j .text:BF8ECE75 or [ebp+ms_exc.disabled], 0FFFFFFFFh .text:BF8ECE79 push esi В NtUserSetClipboardData Code: .text:BF8ECD89 xor esi, esi .text:BF8ECD8B cmp eax, esi .text:BF8ECD8D jnz short loc_BF8ECDC2 .text:BF8ECD8F mov [ebp+ms_exc.disabled], esi .text:BF8ECD92 mov edx, [ebp+arg_8] .text:BF8ECD95 mov eax, _Win32UserProbeAddress .text:BF8ECD9A cmp edx, eax .text:BF8ECD9C jnb short not_usermode Как видим, адрес сверяется с Win32UserProbeAddress - наибольшим адресом для юзермода. Значит в драйвере заюзаем функции RtlCreateHeap и RtlAllocateHeap. Code: hHeap = RtlCreateHeap(0, 0, 0, 0, 0, 0); pMem = RtlAllocateHeap(hHeap,HEAP_ZERO_MEMORY,30); if(pMem) { .... ... RtlFreeHeap(hHeap,0,pMem); } RtlDestroyHeap(hHeap); Теперь о том, как получить данные из буфера обмена. Все дело в том, что просто так, вызовом NtUserGetClipboardData нам данные из буфера обмена не получить. Так как эта функция возвращает хендл. Чтобы по хендлу получить нужные данные нужна функция CreateLocalMemHandle. Код внутренней функции CreateLocalMemHandle. Code: 7E380ECC > 8BFF MOV EDI,EDI 7E380ECE 55 PUSH EBP 7E380ECF 8BEC MOV EBP,ESP 7E380ED1 51 PUSH ECX 7E380ED2 56 PUSH ESI 7E380ED3 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4] 7E380ED6 50 PUSH EAX 7E380ED7 6A 00 PUSH 0 7E380ED9 6A 00 PUSH 0 7E380EDB FF75 08 PUSH DWORD PTR SS:[EBP+8] 7E380EDE E8 3E000000 CALL USER32._NtUserCreateLocalMemHandle@> 7E380EE3 3D 230000C0 CMP EAX,C0000023 //STATUS_BUFFER_TOO_SMALL 7E380EE8 75 2E JNZ SHORT USER32.7E380F18 7E380EEA FF75 FC PUSH DWORD PTR SS:[EBP-4] 7E380EED 6A 00 PUSH 0 7E380EEF FF15 2C13367E CALL DWORD PTR DS:[<&KERNEL32.GlobalAllo>; kernel32.GlobalAlloc 7E380EF5 8BF0 MOV ESI,EAX 7E380EF7 85F6 TEST ESI,ESI 7E380EF9 74 1D JE SHORT USER32.7E380F18 7E380EFB 6A 00 PUSH 0 7E380EFD FF75 FC PUSH DWORD PTR SS:[EBP-4] 7E380F00 56 PUSH ESI 7E380F01 FF75 08 PUSH DWORD PTR SS:[EBP+8] 7E380F04 E8 18000000 CALL USER32._NtUserCreateLocalMemHandle@> 7E380F09 85C0 TEST EAX,EAX 7E380F0B 0F8C 4E6F0100 JL USER32.7E397E5F 7E380F11 8BC6 MOV EAX,ESI 7E380F13 5E POP ESI 7E380F14 C9 LEAVE 7E380F15 C2 0400 RETN 4 То есть сначала по идее надо запросить количество байт, требуемых под буфер, потом выделить память и снова вызвать сервис NtUserCreateLocalMemHandle. Из этого кода можно уже сказать, что возвращаемое значение типа NTSTATUS. Первый параметр - возвращенный NtUserGetClipboardData хендл, второй - буфер, куда надо поместить данные, третий - длина буфера, четвертый - опциональный параметр - указатель на переменную, куда следуемт поместить количество требуемых байт. Теперь для вызова функций по указателю напишем в драйвере Code: typedef HANDLE (__stdcall*_NtUserGetForegroundWindow_)(VOID); typedef ULONG (__stdcall*_NtUserOpenClipboard_)(HANDLE,PHANDLE); typedef ULONG (__stdcall*_NtUserEmptyClipboard_)(VOID); typedef HANDLE (__stdcall*_NtUserGetClipboardData_)(ULONG, PVOID); typedef ULONG (__stdcall*_NtUserSetClipboardData_)(ULONG, HANDLE, PULONG); typedef ULONG (__stdcall*_NtUserCloseClipboard_)(VOID); typedef HANDLE (__stdcall*_NtUserConvertMemHandle_)(PVOID,ULONG); typedef NTSTATUS (__stdcall*_NtUserCreateLocalMemHandle_)(HANDLE,PVOID,ULONG,PULONG); А вызывать функции будем так Code: pNtGetForegroundWindow = (_NtUserGetForegroundWindow_)ShadowSsdtTable[1].ServiceTable[_puServiceTable[NtUserGetForegroundWindow]]; ... hWindow = pNtGetForegroundWindow(); Нужные нам сервисы находятся в win32k.sys, поэтому найдем сначала shadow ssdt. Я думаю, приводить его уже не надо, так как это всем известный метод. Хорошо, вроде как разобрались. Теперь другой вопрос - получение номеров нужных системных сервисов. Получать их будем конечно же в юзермодной проге. Я очень не люблю привязываться к версии оси, это крайний случай. Однако, я уже почти согласилась на него. Почему сейчас расскажу. Из всего вышеизложенного понятно, что нам надо добраться до номеров сервисов NtUserGetForegroundWindow - так как я не хотела создавать свое окно. В общем случае лучше создавать свое, конечно NtUserOpenClipboard NtUserEmptyClipboard NtUserCloseClipboard NtUserConvertMemHandle NtUserSetClipboardData NtUserGetClipboardData NtUserCreateLocalMemHandle Со списком определились. Посмотрим на код юзермодной GetForegroundWindow. Code: 7E379823 > B8 94110000 MOV EAX,1194 7E379828 BA 0003FE7F MOV EDX,7FFE0300 7E37982D FF12 CALL DWORD PTR DS:[EDX] 7E37982F C3 RETN То есть вот отсюда спокойно можно откопировать нужный номер. В функции OpenClipboard чуть больше кода и номер системного сервиса надо взять из подпроцедуры. Что тоже не проблема. Code: 7E380277 > 8BFF MOV EDI,EDI 7E380279 55 PUSH EBP 7E38027A 8BEC MOV EBP,ESP 7E38027C 56 PUSH ESI 7E38027D 8D45 08 LEA EAX,DWORD PTR SS:[EBP+8] 7E380280 50 PUSH EAX 7E380281 FF75 08 PUSH DWORD PTR SS:[EBP+8] 7E380284 E8 18000000 CALL USER32._NtUserOpenClipboard@8 7E380289 837D 08 00 CMP DWORD PTR SS:[EBP+8],0 7E38028D 8BF0 MOV ESI,EAX 7E38028F 0F85 F6090000 JNZ USER32.7E380C8B 7E380295 8BC6 MOV EAX,ESI 7E380297 5E POP ESI 7E380298 5D POP EBP 7E380299 C2 0400 RETN 4 Не проблема, если есть дизассемблер длин. Я сначала думала как обычно продизасмить, дойти до инструкции call адрес и скопировать номер сервиса. Но потом взглянула на функцию SetClipboardData и поняла, что так не получиться. Так как на разных системах код сильно отличается и универсального способа, вроде как нет. Но только вроде как. Я точно знаю, что первый вызов в SetClipboardData - NtUserConvertMemHandle, а второй - NtUserSetClipboardData, так воспользуемся этим. Трассировку ведь никто не отменял. Дада, самую обычную трассировку с флагом TF. Загрузить копию user32 (чтобы избежать хуков в этой либе), найти адреса нужных функций и потрейсить их в поисках нужных адресов сервисов. Алгоритм поиска прост. Устанавливаем векторный обработчик исключений. Находим адреса соответствующих функций в копии user32.dll, потом ставим int 3 на начало функции. Вызываем эту функцию в копии библиотеки. Первая инструкция вызываемой функции будет int 3 и нас перебросит в векторый обработчик исключений. Он, в свою очередь проверит, какой эксепшн его вызвал, если это было 0x80000003, то восставливаем затертый int 3 байт. Далее трассировка идет уже с использованием флага TF. Писать про то, что эксепшн может быть и не мой (в смысле int 3 сработало, но не там, где я его поставила), не надо, я в курсе. По идее да, надо проверять, там ли мы брякнулись и в случае, если не там возвращать EXCEPTION_CONTINUE_SEARCH. Добавить код проверок дополнительных не проблема. В качестве демонстрации итак норм. Для вызова функций удобно сделать такие объявления Code: typedef HWND (__stdcall*_GetForegroundWindow_)(VOID); typedef BOOL (__stdcall*_OpenClipboard_)(HWND); typedef BOOL (__stdcall*_EmptyClipboard_)(VOID); typedef HANDLE (__stdcall*_SetClipboardData_)(UINT,HANDLE); typedef HANDLE (__stdcall*_GetClipboardData_)(UINT); typedef BOOL (__stdcall*_CloseClipboard_)(VOID); Функция, которая подготавливает к началу трассировки Code: DWORD SearchServiceNumberStart(DWORD DllBase,LPCSTR FunctionName) { dwAddress4Disasm = (DWORD)GetProcAddress((HMODULE)DllBase,FunctionName); // вычисляем новый адрес функции в смэппленом образе. rva не меняется, поэтому просто меняем базу dwAddress4Disasm = dwAddress4Disasm - DllBase + (DWORD)hUsrDll; // сохраняем переписанный int 3 байт в глобальной переменной bSavedByte = *(PUCHAR)dwAddress4Disasm; // ставим int 3 на начало функции *(PUCHAR)dwAddress4Disasm = 0xCC; // возвращаем адрес функции в смэппленом образе return dwAddress4Disasm; } Сам обработчик проверяет, на какой инструкции мы находимся и если номер сервиса найден, то заканчивает трассировку Code: // собственно трейсер, юзающий VEH LONG WINAPI HandlerSearch(PEXCEPTION_POINTERS ExceptionInfo) { DWORD dwCurrentAddress; dwCurrentAddress = (DWORD)ExceptionInfo->ExceptionRecord->ExceptionAddress; // если мы в самом начале функции, то переписываем затертый байт обратно if(ExceptionInfo->ExceptionRecord->ExceptionCode==EXCEPTION_BREAKPOINT) { *(PUCHAR)dwCurrentAddress = bSavedByte; } // мы на инструкции mov eax,imm32, а следующая mov edx,imm32? if((*(PUCHAR)dwCurrentAddress==0xB8)&(*((PUCHAR)dwCurrentAddress+5)==0xBA)&(*(PULONG)(dwCurrentAddress+1)>=0x1000)) { if(!dwNested) { // записываем номер сервиса - 0x1000 ServiceNumberTable[dwIndexService] = *(PULONG)(dwCurrentAddress+1) - 0x1000; }else { // если надо пропустить какой-либо сервис dwNested -= 1; // продолжаем трейсить // устанавливаем TF ExceptionInfo->ContextRecord->EFlags |=0x100; } // продолжаем трейсить }else ExceptionInfo->ContextRecord->EFlags |=0x100; return EXCEPTION_CONTINUE_EXECUTION; } Пример использования Code: // dwNested - сколько вызовов пропускать //NtUserGetForegroundWindow dwNested = 0; dwIndexService = NtUserGetForegroundWindow; _GetForegroundWindow_ MapGetForegroundWindow = (_GetForegroundWindow_)SearchServiceNumberStart((DWORD)pUser32Base, "GetForegroundWindow"); // вызываем функцию, запускаем трейс HWND hwndCurr = MapGetForegroundWindow(); dwIndexService - это переменная, в которой храниться номер в массиве сервисов, который мы передадим в драйвер. Я все проименовала, чтобы было читабельней. Code: #define NtUserGetForegroundWindow 0 #define NtUserOpenClipboard 1 #define NtUserEmptyClipboard 2 #define NtUserSetClipboardData 3 #define NtUserGetClipboardData 4 #define NtUserCloseClipboard 5 #define NtUserConvertMemHandle 6 #define NtUserCreateLocalMemHandle 7 Если вы читали внимательно комменты в коде, то уже наверно поняли, что я пропускаю нужное количество вызовов. В случае с SetClipboardData Code: //NtUserConvertMemHandle dwNested = 0; dwIndexService = NtUserConvertMemHandle; _SetClipboardData_ MapSetClipboardData= (_SetClipboardData_)SearchServiceNumberStart((DWORD)pUser32Base, "SetClipboardData"); HANDLE hGlobalData = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,10); RtlCopyMemory(hGlobalData,L"Run",6); MapSetClipboardData(CF_UNICODETEXT,hGlobalData); //NtUserSetClipboardData dwNested = 1; dwIndexService = NtUserSetClipboardData; MapSetClipboardData= (_SetClipboardData_)SearchServiceNumberStart((DWORD)pUser32Base, "SetClipboardData"); MapSetClipboardData(CF_UNICODETEXT,hGlobalData); dwNested для первого вызова -0, для второго (1 пропускаем) - 1. Насчет другой функции - GetClipboardData. Первый вызов NtUserGetClipboardData, а второй - NtUserCreateLocalMemHandle, в общем по аналогии делается (dwNested = 0 для NtUserGetClipboardData и 1 для NtUserCreateLocalMemHandle). Только не говорите, что надо мэппить все остальные либы, учитывать все возможные хуки еще и в других либах, а в коде хука может быть антитрассировка и прочее, уже не хотелось с этим заморачиваться просто. У меня вот стояли хуки комодо в ntdll, через них трейс нормально прошел. Как видите, дизассемблер тут даже не нужен. Драйвер использует функции RtlCreateHeap, RtlAllocateHeap, RtlFreeHeap. А если мы посмотрим в msdn http://msdn.microsoft.com/en-us/library/ms797739.aspx "This routine is available on Microsoft Windows XP and later." + я использую векторную обработку исключений. Так что кодес для систем начиная с XP. Этот код я протестировала на висте, своей XP SP3, SP2 и win7. Работает нормально. На этом пока прощаюсь, надеюсь кому-то было познавательно. ссылка на сорс http://dump.ru/file/2315384
ммм... а как тебе такой метод определения номера сервиса: вспоминаем, что в KTHREAD есть ServiceTable, в юзермоде это Shadow SDT. узнаём размер SST и создаём SST такого же размера где-нибудь, записываем где-то по адресу X UD2 столько раз, сколько записей в SST, забиваем её целиком адресами этих разных уд-шек. После чего вызываем функу, которая переводит стрелки на системный сервис и вуаля получаем инвалид опкод. Адрес, где этот опкод выполнился, узнать нетрудно, а зная адрес получаем номер как (адрес-X)/2 (2 - длина UD2) (кста можно вообще без исключений - вместо UD2 поставить инструкцию типа mov [], 1) //edit: надеюсь, все поняли, что под юзермодом следует понимать гуй
0x0c0de спасибо за исходники, класные =) desTiny интересная идея, а у тебя негде примерчика на завалялось? =). Насколько я понял, надо будет вызвать соответсвующюю юзермодную api в нашем треде (с фэйковой табличкой сервисов)? Т.е. определить номер сериса не выйдет не выходя из ядра? И смещение указателя на таблицу сервисов в KTHREAD изменяеться в разных версиях, как и номера сервисов.
>> примерчика на завалялось? нет.. просто такой вот крестьянский способ с трассировкой мне не понравился, и такой в голову пришёл. Реализацию (от меня), если раньше не будет, ждать можно не раньше июня. >> Т.е. определить номер сериса не выйдет не выходя из ядра? ну можно и из ядра, если юзермодная функция не оперирует никакими юзермодными адресами - то просто её и в ядре вызвать можно) если юзает - то поймать ошибки тоже можно - нам почти наплевать на большую часть их кода - нам только перед сисэнтером интересно, какая константа записывается >>И смещение указателя на таблицу сервисов в KTHREAD изменяеться в разных версиях разве меняется? вроде это документированная структура... а даже если и меняется... вот я только что такой чит придумал - берём какой-нить тред, для которого гарантировано сдт - это не шадоу и сканим всю структурку на известный адрес - вот и смещение) >>номера сервисов вот их-то мы-то и хотим найти-то)
Сори, что так долго молчала. Решаю проблемы с учебой. desTiny: крестьянский способ. Нормальный способ и рабочий (но долгий). Как я уже сказала тебе в жабере, что пока практической реализации нет, по поводу твоего способа ничего не хочу писать. В выходные постараюсь собраться и накодить если что. Сегодня реализовала через хук KiFastSystemCall. Все отлично. Никакой трассировки нет, работает быстрее. //8.05.09: исправила, KiFastSystemCall конечно же, а не KiFastSystemCallRet=\
ато! ещё как меняется =), вот например xp sp2: kd> dt _KTHREAD +0x0e0 ServiceTable : Ptr32 Void а вот виста sp1: +0x12c ServiceTable : Ptr32 Void Ну и ещё она не документированная. Впрочем, вижу что тебя это не смущает =)
А я и не говорю, что не нормальный) Просто он.. как бы сказать.. не изящен.. а когда я писал, мне что-то вспомнился препод по матану из первого семестра, а он любил подобные способы крестьянскими называть) Да, такая идея тоже напрашивается. Но всё-таки не хочется ничего глобального портить.. а свой поток положить на жертвенный алтарь не так страшно. Но это действительно самый лаконичный вариант.. Обидно.. но тогда можно считать, что я предложил новый способ нахождения shadow sdt )
Finito: В результате продолжительного обсуждения было принято решение о том, что оказывается KiFastSystemCall и KiFastSystemCallRet экспортируются. Вроде при таком допущении номер сервиса выводится в одно действие )
Как и обещала, метод я реализовала. Время на радостях после успешно завершенной зачетной недели выкроилось. Создаем свою SDT, ntoskrnl SST оставляем прежней (копируем из оригинальной SST), создаем свою таблицу вместо win32k.sys SST (при этом указатель на argument table берем из оригинальной таблицы), а адреса всех функций заполняются адресом моего обработчика, затем прописываем новую SDT в KTHREAD.ServiceTable. Обработчик для всех функций, логирующий номера вызываемых сервисов. Т.к. приложение, юзающее такое определение номеров сервисов (отсылающее драйверу ioctl запросы) консольное- проблем не возникает. Code: VOID _declspec(naked) MyHandlerServiceSearch(VOID) { _asm { pushad mov uEax,eax } KdPrint(("MyHandlerServiceSearch-> Service number = %X\n",uEax)); if(uServiceCount<6) { // сохраняем номер сервиса uServTable[uServiceCount] = uEax; uServiceCount++; } else KdPrint(("Too many calls")); // вычисляем адрес оригинальной функции uOriginalCallAddress = (ULONG)uShadow[1].ServiceTable[uEax]; _asm { popad // вызываем оригинальную функцию jmp [uOriginalCallAddress] } } Надо сказать, что на win7 там иногда не с первого раза определяются номера. причина этого мне неизвестна. Я сделала цикл до тех пор, пока не определится. Кстати, на скрине с семеркой видно, что первая попытка неуспешна, зато на второй раз все определилось. Однако, все равно остаюсь при своем способе. этот пускай будет