Форум АНТИЧАТ

Форум АНТИЧАТ (https://forum.antichat.xyz/index.php)
-   Реверсинг (https://forum.antichat.xyz/forumdisplay.php?f=94)
-   -   Работа с буфером обмена в kernel-mode (https://forum.antichat.xyz/showthread.php?t=113530)

0x0c0de 29.03.2009 12:53

Работа с буфером обмена в kernel-mode
 
[Работа с буфером обмена в ядре.]

Решила написать такую вот небольшую заметку о работе с буфером обмена в ядре.

Для начала рассмотрим типичный код для работы с буфером обмена в юзермоде.

Код:

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.

Код:


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

Код:

.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

Код:

.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.

Код:

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.

Код:


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 хендл, второй - буфер, куда надо поместить данные, третий - длина буфера, четвертый - опциональный параметр - указатель на переменную, куда следуемт поместить количество требуемых байт.

Теперь для вызова функций по указателю напишем в драйвере

Код:

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);

А вызывать функции будем так

Код:

pNtGetForegroundWindow =

(_NtUserGetForegroundWindow_)ShadowSsdtTable[1].ServiceTable[_puServiceTable[NtUserGetForegroundWindow]];

...

hWindow = pNtGetForegroundWindow();

Нужные нам сервисы находятся в win32k.sys, поэтому найдем сначала shadow ssdt. Я думаю, приводить его уже не надо, так как это всем известный метод.

Хорошо, вроде как разобрались. Теперь другой вопрос - получение номеров нужных системных сервисов. Получать их будем конечно же в юзермодной проге. Я очень не люблю привязываться к версии оси, это крайний случай. Однако, я уже почти согласилась на него.

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

NtUserGetForegroundWindow - так как я не хотела создавать свое окно. В общем случае лучше создавать свое, конечно
NtUserOpenClipboard
NtUserEmptyClipboard
NtUserCloseClipboard
NtUserConvertMemHandle
NtUserSetClipboardData
NtUserGetClipboardData
NtUserCreateLocalMemHandle

Со списком определились. Посмотрим на код юзермодной GetForegroundWindow.

Код:

7E379823 > B8 94110000      MOV EAX,1194
7E379828  BA 0003FE7F      MOV EDX,7FFE0300
7E37982D  FF12            CALL DWORD PTR DS:[EDX]
7E37982F  C3              RETN

То есть вот отсюда спокойно можно откопировать нужный номер. В функции OpenClipboard чуть больше кода и номер системного сервиса надо взять из подпроцедуры. Что тоже не проблема.

Код:

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. Добавить код проверок дополнительных не проблема. В качестве демонстрации итак норм.

Для вызова функций удобно сделать такие объявления

Код:

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);

Функция, которая подготавливает к началу трассировки

Код:

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;
}

Сам обработчик проверяет, на какой инструкции мы находимся и если номер сервиса найден, то заканчивает трассировку

Код:

// собственно трейсер, юзающий  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;
}

Пример использования

Код:

// dwNested - сколько вызовов пропускать
                        //NtUserGetForegroundWindow
                        dwNested = 0;
                       
                        dwIndexService = NtUserGetForegroundWindow;
                       
                        _GetForegroundWindow_  MapGetForegroundWindow =

(_GetForegroundWindow_)SearchServiceNumberStart((DWORD)pUser32Base,
                                "GetForegroundWindow");
                       
                        // вызываем функцию, запускаем трейс
                        HWND hwndCurr = MapGetForegroundWindow();

dwIndexService - это переменная, в которой храниться номер в массиве сервисов, который мы передадим в драйвер. Я все проименовала, чтобы было читабельней.

Код:

#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

Код:


                        //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

Qws 29.03.2009 16:51

Молодца... так держать.

0verbreaK 29.03.2009 22:14

Спасибо за статью, по больше бы таких, будет ли еще про ядро?

desTiny 25.04.2009 19:46

ммм... а как тебе такой метод определения номера сервиса:
вспоминаем, что в KTHREAD есть ServiceTable, в юзермоде это Shadow SDT.
узнаём размер SST и создаём SST такого же размера где-нибудь, записываем где-то по адресу X UD2 столько раз, сколько записей в SST, забиваем её целиком адресами этих разных уд-шек.
После чего вызываем функу, которая переводит стрелки на системный сервис и вуаля получаем инвалид опкод. Адрес, где этот опкод выполнился, узнать нетрудно, а зная адрес получаем номер как (адрес-X)/2 (2 - длина UD2) (кста можно вообще без исключений - вместо UD2 поставить инструкцию типа mov [], 1)

//edit: надеюсь, все поняли, что под юзермодом следует понимать гуй :)

AlexGT 25.04.2009 21:45

0x0c0de ты ассемблерная террористка :)

winterfrost 06.05.2009 03:39

0x0c0de спасибо за исходники, класные =)
desTiny интересная идея, а у тебя негде примерчика на завалялось? =). Насколько я понял, надо будет вызвать соответсвующюю юзермодную api в нашем треде (с фэйковой табличкой сервисов)? Т.е. определить номер сериса не выйдет не выходя из ядра? И смещение указателя на таблицу сервисов в KTHREAD изменяеться в разных версиях, как и номера сервисов.

desTiny 07.05.2009 22:19

>> примерчика на завалялось?
нет.. просто такой вот крестьянский способ с трассировкой мне не понравился, и такой в голову пришёл. Реализацию (от меня), если раньше не будет, ждать можно не раньше июня.
>> Т.е. определить номер сериса не выйдет не выходя из ядра?
ну можно и из ядра, если юзермодная функция не оперирует никакими юзермодными адресами - то просто её и в ядре вызвать можно) если юзает - то поймать ошибки тоже можно - нам почти наплевать на большую часть их кода - нам только перед сисэнтером интересно, какая константа записывается
>>И смещение указателя на таблицу сервисов в KTHREAD изменяеться в разных версиях
разве меняется? вроде это документированная структура... а даже если и меняется... вот я только что такой чит придумал - берём какой-нить тред, для которого гарантировано сдт - это не шадоу и сканим всю структурку на известный адрес - вот и смещение)
>>номера сервисов
вот их-то мы-то и хотим найти-то)

0x0c0de 08.05.2009 00:06

Сори, что так долго молчала. Решаю проблемы с учебой.

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

Сегодня реализовала через хук KiFastSystemCall. Все отлично. Никакой трассировки нет, работает быстрее.

//8.05.09: исправила, KiFastSystemCall конечно же, а не KiFastSystemCallRet=\

winterfrost 08.05.2009 18:02

Цитата:

разве меняется? вроде это документированная структура...
ато! ещё как меняется =), вот например xp sp2:
kd> dt _KTHREAD
+0x0e0 ServiceTable : Ptr32 Void

а вот виста sp1:
+0x12c ServiceTable : Ptr32 Void
Ну и ещё она не документированная. Впрочем, вижу что тебя это не смущает =)

desTiny 08.05.2009 19:14

Цитата:

Сообщение от 0x0c0de
desTiny: крестьянский способ. Нормальный способ и рабочий (но долгий).

А я и не говорю, что не нормальный) Просто он.. как бы сказать.. не изящен.. а когда я писал, мне что-то вспомнился препод по матану из первого семестра, а он любил подобные способы крестьянскими называть)

Цитата:

Сегодня реализовала через хук KiFastSystemCallRet. Все отлично. Никакой трассировки нет, работает быстрее.
Да, такая идея тоже напрашивается. Но всё-таки не хочется ничего глобального портить.. а свой поток положить на жертвенный алтарь не так страшно. Но это действительно самый лаконичный вариант..

Цитата:

ещё как меняется =)
Обидно.. но тогда можно считать, что я предложил новый способ нахождения shadow sdt )


Время: 02:35