[Reverseme09 by 0x0c0de] Итак, моя девятая работа. Активности в разделе мало, да и как-то вяло реверсится. Ну да ладно.. Расскажу как это должно было быть. В первый раз так, что ни одного дельного поста за несколько суток. Обычно 1 дня хватает для первого решения ну или хотя бы признаков того, что кто-то решает и двигается в ВЕРНОМ направлении. Я тут не стану описывать алгоритм генерации ключа - там и разбирать-то нечего. Объясню самое интересное. [Приступим] Загрузим кодес в иду. Именно в иду, не нужно это отлаживать. Точнее, совсем не обязательно. В начале WinMain видим Code: .text:004017AB push ebp ; hTemplateFile .text:004017AC push 80h ; dwFlagsAndAttributes .text:004017B1 push 3 ; dwCreationDisposition .text:004017B3 push ebp ; lpSecurityAttributes .text:004017B4 push 3 ; dwShareMode .text:004017B6 push 0C0000000h ; dwDesiredAccess .text:004017BB push offset a_Reverseme0 ; "\\\\.\\reverseme0" Ага, значит будем иметь дело с драйвером. Но это не значит, что надо хватать ядерный отладчик и ставить бряки на загрузку драйвера. Здесь мы пойдем другим путем. Посмотрим чуть дальше Code: .text:004017C6 cmp eax, 0FFFFFFFFh .text:004017C9 mov hDevice, eax .text:004017CE jnz short loc_4017E1 .text:004017D0 call sub_401289 Здесь, в процедуре по адресу sub_401289 извлекается из ресурсов драйвер и его файл создается на диске. Драйвер можно вытащить прямо из ресурсов, так как они не криптованы ничем . Что сейчас и сделаем. Думаю, как пользоваться Restorator знают все, поэтому на этом не останавливаюсь. Итак, загружаем драйвер в дизассемблер Code: INIT:00012000 sub esp, 10h INIT:00012003 push esi INIT:00012004 mov esi, [esp+14h+DeviceObject] INIT:00012008 push edi INIT:00012009 mov edi, ds:RtlInitUnicodeString INIT:0001200F push offset SourceString ; "\\Device\\reverseme0" INIT:00012014 lea eax, [esp+1Ch+DestinationString] INIT:00012018 push eax ; DestinationString INIT:00012019 mov dword ptr [esi+34h], offset loc_11160 INIT:00012020 call edi ; RtlInitUnicodeString INIT:00012022 lea ecx, [esp+18h+DeviceObject] INIT:00012026 push ecx ; DeviceObject INIT:00012027 push 0 ; Exclusive INIT:00012029 push 0 ; DeviceCharacteristics INIT:0001202B push 22h ; DeviceType INIT:0001202D lea edx, [esp+28h+DestinationString] Все бы ничего и все стандартно, на первый взгляд. DriverEntry как DriverEntry. Но тут-то надо быть внимательней. Прокрутите чуть ниже листинг и вы увидите, что Code: INIT:00012099 loc_12099: INIT:00012099 call sub_110C0 INIT:0001209E test eax, eax INIT:000120A0 jz short loc_120A7 INIT:000120A2 mov esi, 40010003h // хм, возвращаемое значение? INIT:000120A7 INIT:000120A7 loc_120A7: INIT:000120A7 mov eax, esi INIT:000120A9 INIT:000120A9 loc_120A9: INIT:000120A9 pop edi INIT:000120AA pop esi INIT:000120AB add esp, 10h INIT:000120AE retn 8 То, что DriverEntry может вернуть 40010003h в зависимости от того, как выполниться процедура по адресу sub_110C0 - уже должно насторожить. Посмотрим, что это за процедура. Code: .text:000110C0 push ebp .text:000110C1 mov ebp, esp .text:000110C3 sub esp, 40h .text:000110C6 push esi .text:000110C7 push edi .text:000110C8 xor eax, eax .text:000110CA push 31h ; size_t .text:000110CC push eax ; int .text:000110CD mov [ebp+var_4], eax .text:000110D0 mov [ebp+var_40], al .text:000110D3 lea eax, [ebp+var_3F] .text:000110D6 push eax ; void * .text:000110D7 call memset .text:000110DC add esp, 0Ch .text:000110DF sidt fword ptr [ebp+var_C] Опа, sidt. Инструкция, получающая содержимое регистра idtr. Посмотрим что происходит дальше. Я облегчила задачу в разы и не замусоривала код вообще. Поэтому можно заюзать Hex-Rays. жмем F5 и смотрим псевдокод. Немного забегая вперед я обозначила имена функций. Читайте комментарии ниже и все станет ясно. Code: int __cdecl CheckIdtHook() { …. __asm { sidt fword ptr [ebp+var_C] } v0 = *(unsigned __int16 *)&v5[2] | (*(unsigned __int16 *)&v5[4] << 16); GetIdtModuleName(*(_WORD *)((*(unsigned __int16 *)&v5[2] | (*(unsigned __int16 *)&v5[4] << 16)) + 0x168) | (*(_WORD *)((*(unsigned __int16 *)&v5[2] | (*(unsigned __int16 *)&v5[4] << 16)) + 0x16E) << 16), &v3); if ( *(_DWORD *)&v3 == 'esyS' ) // первая часть имени { if ( v6 == 'ys.r' ) // вторая часть ++v2; } GetIdtModuleName(*(_WORD *)(v0 + 104) | (*(_WORD *)(v0 + 110) << 16), &v3); if ( *(_DWORD *)&v3 == 'stdr' ) // первая часть имени { if ( v6 == 'ys.c' ) // вторая часть ++v2; } return v2; } И? ) 'ys.r'+'esyS' - "Syser.sy". хм. Проверка на драйвер Syser? Да, именно она. Syser.sys перехватывает довольно много прерываний (вы можете это глянуть в каком-нибудь RKU на вкладке Code Hooks): int 1,2,3,6,B,C,D,E,2D... Я проверяю конкретно int 0x2d. KiDebugService )). Смотрим дальше. 'ys.c'+'stdr' - "rdtsc.sy" - проверка на ольгу. Проверяю вход 0xD в идт. Намек на то, что значение, возвращаемое DriverEntry используется где-то. И логично предположить, что это где-то будет в юзермодной части реверсми. Функция получает имя модуля по заданному адресу. И надо сказать вызываться она будет не только в DriverEntry.. На время вернемся в юзермодную часть. Посмотрим внимательней на процесс загрузки драйвера Code: .text:004011FB LoadDriver: ; CODE XREF: sub_4010F4+9E j .text:004011FB push offset unk_40CC68 .text:00401200 call ds:NtLoadDriver .text:00401206 pop edi .text:00401207 pop esi .text:00401208 mov NtstatusSave, eax // это будет играть роль при генерации ключа .text:0040120D pop ebx .text:0040120E leave .text:0040120F retn .text:0040120F sub_4010F4 endp Я сразу переименовала в иде переменную, куда сохраняется значение NTSTATUS, возращенное ZwLoadDriver. Хорошо, теперь снова вернемся в WinMain из процедуры создания файла драйвера Code: .text:004017E1 loc_4017E1: ; CODE XREF: wWinMain(x,x,x,x)+C7 j .text:004017E1 ; wWinMain(x,x,x,x)+D0 j .text:004017E1 push ebp ; lpOverlapped .text:004017E2 lea eax, [esp+18h+BytesReturned] .text:004017E6 push eax ; lpBytesReturned .text:004017E7 push 8 ; nOutBufferSize .text:004017E9 push offset byte_40CCA0 ; lpOutBuffer .text:004017EE push ebp ; nInBufferSize .text:004017EF push ebp ; lpInBuffer .text:004017F0 push 222014h ; dwIoControlCode .text:004017F5 push hDevice ; hDevice .text:004017FB call ds:DeviceIoControl Итак, первое обращение к дрову. Хм... IOCTL запрос 222014h. Теперь, нужно найти функцию обработки IOCTL запросов в драйвере. И ее расковырять. Определять местоположение процедур обработки можно и нужно из DriverEntry Типичный код в DriverEntry. Code: DriverObject->MajorFunction[IRP_MJ_CREATE]= OpenDeviceHandler; DriverObject->MajorFunction[IRP_MJ_CLOSE] = CloseHandler; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]= DeviceControlRoutine; DriverObject->MajorFunction[IRP_MJ_READ] = ReadHandler; DriverObject->MajorFunction[IRP_MJ_WRITE] = WriteHandler; Аналог дизассемблированном листинге DriverEntry Code: INIT:00012055 mov dword ptr [esi+38h], offset loc_111E0 INIT:0001205C mov dword ptr [esi+40h], offset loc_111E0 INIT:00012063 mov dword ptr [esi+70h], offset sub_115B0 INIT:0001206A mov dword ptr [esi+44h], offset loc_117E0 INIT:00012071 mov dword ptr [esi+48h], offset loc_11800 Все, считайте, адреса основных обработчиков у нас в кармане. На данный момент нужный нам - 000115B0 - это и есть обработчик IOCTL запросов. Я кое-что из стандартных фрагментов переименовала сразу. Что выдал HexRays по F5 [в общем виде] Code: int __stdcall DeviceControlRoutine(int a1, PIRP Irp) { code = *((_DWORD *)v14 + 3) - 0x222004; // то, что мы отсылаем из юзермода - 0x222004 status = 0; v17 = 0; switch ( code ) { case 20: … case 16: … case 4: …. } } Пока вот так. Нам нужен первый ioctl запрос. 222014h. Отлично, вычитаем 0x222004. Получаем 10h или 16. Нам HexRays как раз-таки и выдал 16-й кейз. Разберемся с ним. На первый взгляд выполняется куча каких-то непонятных операций Code: cmp ecx, 8 ; jumptable 000115E7 case 16 .text:0001161D jb short loc_115F3 .text:0001161F mov eax, dword_14004 .text:00011624 cmp eax, ebx .text:00011626 jz short loc_1162F .text:00011628 mov [esi], eax .text:0001162A jmp loc_116BA .text:0001162F ; --------------------------------------------------------------------------- .text:0001162F .text:0001162F loc_1162F: ; CODE XREF: DeviceControlRoutine+76 j .text:0001162F mov cl, [esp+20h+var_A] .text:00011633 mov al, [esp+20h+var_B] .text:00011637 or cl, 40h .text:0001163A and cl, 0EFh .text:0001163D or cl, 0Fh .text:00011640 and al, 9Fh .text:00011642 or cl, 80h .text:00011645 or al, 10h .text:00011647 and cl, 0DFh .text:0001164A and al, 0FBh .text:0001164C mov [esp+20h+var_A], cl .text:00011650 lea ecx, [esp+20h+var_10] .text:00011654 or al, 8Bh .text:00011656 push ecx .text:00011657 mov word ptr [esp+24h+var_10+2], bx .text:0001165C mov [esp+24h+var_C], bl .text:00011660 mov [esp+24h+var_9], bl .text:00011664 mov word ptr [esp+24h+var_10], 0FFFFh .text:0001166B mov [esp+24h+var_B], al .text:0001166F call sub_11250 Однако, заглянем, что у нас по адресу sub_11250 и тогда все станет ясно. Code: .text:00011250 sub_11250 proc near ; CODE XREF: DeviceControlRoutine+BF p .text:00011250 ; DeviceControlRoutine+1A6 p .text:00011250 .text:00011250 var_10 = dword ptr -10h .text:00011250 var_C = dword ptr -0Ch .text:00011250 var_8 = byte ptr -8 .text:00011250 arg_0 = dword ptr 4 .text:00011250 .text:00011250 sub esp, 10h .text:00011253 mov ecx, ds:KeNumberProcessors .text:00011259 xor eax, eax .text:0001125B cmp byte ptr [ecx], 1 .text:0001125E mov [esp+10h+var_C], eax .text:00011262 mov [esp+10h+var_10], 1 .text:00011269 jl loc_1131A .text:0001126F push ebx .text:00011270 mov ebx, [esp+14h+arg_0] .text:00011274 push ebp .text:00011275 push esi .text:00011276 push edi .text:00011277 jmp short loc_11280 .text:00011277 ; --------------------------------------------------------------------------- .text:00011279 align 10h .text:00011280 .text:00011280 loc_11280: ; CODE XREF: sub_11250+27 j .text:00011280 ; sub_11250+BC j .text:00011280 mov edx, [esp+20h+var_10] .text:00011284 push edx .text:00011285 call KeGetCurrentThread .text:0001128A push eax .text:0001128B call KeSetAffinityThread .text:00011290 sgdt fword ptr [esp+20h+var_8] А тут вы должны были насторожиться второй раз. KeNumberProcessors, вызов KeGetCurrentThread и KeSetAffinityThread, получение gdtr инструкцией sgdt. А если присмотреться к коду далее - уже явно идет поиск свободного дескриптора в gdt Code: t:000112B0 mov eax, dword ptr [esp+20h+var_8+2] .text:000112B4 lea esi, [eax+edi*8] .text:000112B7 push esi ; VirtualAddress .text:000112B8 call ds:MmIsAddressValid .text:000112BE test al, al .text:000112C0 jz short loc_112CD .text:000112C2 cmp dword ptr [esi], 0 // чекаем первый дворд .text:000112C5 jnz short loc_112CD .text:000112C7 cmp dword ptr [esi+4], 0 // чекаем второй дворд .text:000112CB jz short loc_112D6 .text:000112CD .text:000112CD loc_112CD: ; CODE XREF: sub_11250+70 j .text:000112CD ; sub_11250+75 j .text:000112CD add edi, 1 .text:000112D0 cmp edi, ebp .text:000112D2 jb short loc_112B0 .text:000112D4 jmp short loc_112F9 Проверка 2-х частей дескриптора. Как известно дескриптор равен 8-ми байтам, вот и проверяется по двордам. Как только мы нашли свободный дескриптор Code: .text:000112D6 add_descriptor: ; CODE XREF: AddDescriptorIntoGdt+7B j .text:000112D6 cli .text:000112D7 mov ecx, [ebx] .text:000112D9 mov [esi], ecx .text:000112DB mov edx, [ebx+4] .text:000112DE mov [esi+4], edx .text:000112E1 sti добавляем новый. И этот дескриптор заюзается на следующем этапе, но об этом позже. То есть все те действия, которые выполнялись перед вызовом исследуемой процедуры - это было формирование структуры дескриптора. Исходники, как я говорила, я не утаиваю и кому надо могут их увидеть. Саму процедуру, после того, как поняли, что она делает обзовем AddDescriptorIntoGdt. Она принимает 1 параметр – указатель на структуру дескриптора и возвращает селектор на созданный дескриптор. Итак, функция AddDescriptorIntoGdt добавит в GDT свой дескриптор и вернет на него селектор (селектор – 16 бит, первые 2 бита – RPL, второй бит TI, определяющий таблицу (ldt/gdt) и 3:15 биты – индекс в таблице дескрипторов). Забегая вперед, скажу, что добавление своего дескриптора в gdt конкретно в этой части кодеса в принципе бессмысленно [с точки зрения кодера, конечно, бессмысленно, неподготовленного реверсера сбивают все эти манипуляции]. Это - атавизм первых вариантов. У меня первоначально была пара хороших идей, но, к сожалению, тут мне помешали некоторые обстоятельства осуществить свой план. Окей, выходим из этой подпроцедуры и двигаемся дальше Code: .text:0001166F call AddDescriptorIntoGdt .text:00011674 cmp eax, ebx .text:00011676 jz short zero_selector_erro .text:00011678 mov [esp+20h+var_6], ax .text:0001167D mov eax, offset sub_113E0 .text:00011682 lea ecx, [esp+20h+var_8] .text:00011686 mov edx, offset sub_113E0 .text:0001168B shr eax, 10h .text:0001168E push ecx .text:0001168F mov [esp+24h+var_8], dx .text:00011694 mov [esp+24h+var_2], ax .text:00011699 mov [esp+24h+var_3], 0EEh .text:0001169E call sub_114E0 sub_114E0 - разберем ее. Code: .text:000114E0 sub_114E0 proc near ; CODE XREF: DeviceControlRoutine+EE p .text:000114E0 .text:000114E0 var_8 = byte ptr -8 .text:000114E0 arg_0 = dword ptr 4 .text:000114E0 .text:000114E0 mov eax, ds:KeNumberProcessors .text:000114E5 sub esp, 8 .text:000114E8 push ebx .text:000114E9 push esi .text:000114EA mov ebx, 1 .text:000114EF xor esi, esi .text:000114F1 cmp [eax], bl .text:000114F3 jl loc_115A2 .text:000114F9 push edi .text:000114FA mov edi, [esp+14h+arg_0] .text:000114FE mov edi, edi .text:00011500 .text:00011500 loc_11500: ; CODE XREF: sub_114E0+BB j .text:00011500 push ebx .text:00011501 call KeGetCurrentThread .text:00011506 push eax .text:00011507 call KeSetAffinityThread .text:0001150C sidt fword ptr [esp+14h+var_8] .text:00011511 movzx eax, word ptr [esp+14h+var_8+4] .text:00011516 movzx ecx, word ptr [esp+14h+var_8+2] .text:0001151B shl eax, 10h .text:0001151E or eax, ecx .text:00011520 mov ecx, 20h .text:00011525 lea edx, [eax+105h] .text:0001152B jmp short loc_11530
Ага, опять похожий код на предыдущую функцию. только на этот раз sidt. Эта функция добавляет вход в idt. Причем, поиск свободного входа осуществляется только среди 0x20 - 0x29 прерываниями. Почему? Да потому что они обычно свободны. Смысла первые чекать нет - они стандартны и уже заняты системой. Хорошо, раз это прерывание новое, то надо бы найти адрес обработчика. Да его и искать не надо. смотрим снова Code: .text:0001166F call AddDescriptorIntoGdt .text:00011674 cmp eax, ebx .text:00011676 jz short zero_selector_erro .text:00011678 mov [esp+20h+var_6], ax .text:0001167D mov eax, offset InterruptHandler .text:00011682 lea ecx, [esp+20h+var_8] .text:00011686 mov edx, offset InterruptHandler .text:0001168B shr eax, 10h .text:0001168E push ecx .text:0001168F mov [esp+24h+var_8], dx .text:00011694 mov [esp+24h+var_2], ax .text:00011699 mov [esp+24h+var_3], 0EEh .text:0001169E call AddDescriptorIntoIdt В исходном коде это было так Code: intEntry.wSelector = uSel; intEntry.wLowOffset = LOWORD(&InterruptHandler); intEntry.wHiOffset = HIWORD(&InterruptHandler); intEntry.DPL = 3; intEntry.P = 1; // 32 битный шлюз прерывания intEntry.unused_hi = 14; puInfoBuf[0] = CreateInterruptGateIntoIdt(&intEntry); В общем вот так . Хендлер прерывания мы нашли. Селектор на добавленный нами дескриптор (как вы уже могли заметить), используется при составлении дескриптора шлюза в idt. Теперь снова в юзермод, разбираться что где юзоется и где вызывается это наше прерывание. Code: .text:00401805 lea eax, [esp+14h+flOldProtect] .text:00401809 push eax ; lpflOldProtect .text:0040180A push 40h ; flNewProtect .text:0040180C push 2 ; dwSize .text:0040180E mov ebx, offset sub_401388 .text:00401813 push ebx ; lpAddress .text:00401814 call ds:VirtualProtect .text:0040181A test eax, eax .text:0040181C jz short loc_401860 .text:0040181E push ebp ; dwInitParam .text:0040181F push offset DialogFunc ; lpDialogFunc .text:00401824 push ebp ; hWndParent .text:00401825 mov eax, ebx .text:00401827 push 67h ; lpTemplateName .text:00401829 push hModule ; hInstance .text:0040182F mov byte ptr [eax], 0CDh // опкод команды int .text:00401832 mov al, byte_40CCA0 // то, что вернул драйвер [вектор добавленного прерывания] .text:00401837 mov [ebx+1], al .text:0040183A call ds:DialogBoxParamW Окей, кажется, мы нашли место, где будет юзотся прерывание. Обзовем эту процедуру int_call. Теперь, самое интересное. Отправляемся в DialogFunc и начинаем непосредственно изучать что происходит после того, как мы нажали кнопку Try!. Ковырять DialogFunc все умеют, думаю, поэтому я не останавливаюсь на поиске обработки конкретной кнопки. Code: .text:0040191D push 0 ; bEnable .text:0040191F push dword_40CCC8 ; hWnd .text:00401925 call ds:EnableWindow .text:0040192B push offset sub_4016A4 .text:00401930 push large dword ptr fs:0 .text:00401937 mov large fs:0, esp .text:0040193E mov edi, 'bran' .text:00401943 call int_call Итак, как видите вызывается та процедура, которая патчится в WinMain. Устанавливается обработчик исключений, потом что-то ложится в edi и вызывается прерывание. Итак, вызываем в первый раз прерывание. Инструкция sub eax, eax будет изменена на int xx. Далее, устанавливается флаг трассировки и все бы ничего. Но это пока так кажется, что ничего и мы должны отправиться в сех с первого нопа. Code: .text:00401388 int_call proc near ; CODE XREF: DialogFunc+B5 p .text:00401388 ; DATA XREF: wWinMain(x,x,x,x)+107 o .text:00401388 sub eax, eax .text:0040138A pushf .text:0040138B pop edi .text:0040138C bts edi, 8 .text:00401390 push edi .text:00401391 popf .text:00401392 nop // в SEH отсюда? Нет! Читаем комменты ниже .text:00401393 nop .text:00401394 nop .text:00401395 nop .text:00401396 nop .text:00401397 nop .text:00401398 nop .text:00401399 xor eax, eax .text:0040139B .text:0040139B infinite_loop: ; CODE XREF: int_call:infinite_loop j .text:0040139B jmp short infinite_loop .text:0040139B int_call endp .text:0040139B .text:0040139D ; --------------------------------------------------------------------------- .text:0040139D retn А теперь разберем хендлер прерывания. Ага, значит все-таки значение в edi имеет смысл. Что же происходит? Если в edi - 'bran', то начинается манипуляция с msr регистром MSR_DEBUGCTLA (1D9h). Как вы можете помнить 0 –бит там LBR, а 1-й BTF. В результате копипаста со своего пробного кодеса, устанавливаю 2 бита. Реально же, нужен нам BTF. Code: .text:000113E0 InterruptHandler proc near ; DATA XREF: DeviceControlRoutine+CD o .text:000113E0 ; DeviceControlRoutine+D6 o .text:000113E0 .text:000113E0 arg_4 = dword ptr 8 .text:000113E0 .text:000113E0 cmp edi, 'bran' .text:000113E6 jz short loc_11453 …. .text:00011453 loc_11453: ; CODE XREF: InterruptHandler+6 j .text:00011453 mov ecx, 1D9h // MSR_DEBUGCTLA .text:00011458 rdmsr .text:0001145A or eax, 3 // LBR BTF .text:0001145D wrmsr .text:0001145F .text:0001145F locret_1145F: ; CODE XREF: InterruptHandler+30 j .text:0001145F ; InterruptHandler+41 j ... .text:0001145F iret .text:0001145F InterruptHandler endp Как это работает? Когда мы устанавливаем в MSR регистре MSR_DEBUGCTLA бит BTF установленный в EFLAGS бит TF начинает интерпретироваться как BTF – то есть мы будем останавливаться только на переходах, а не на любой инструкции. То есть, в данном случае, после вызова прерывания и установки TF флажка мы остановимся не на первом нопе, а именно на прыжке Code: .text:0040139B infinite_loop: ; CODE XREF: int_call:infinite_loop j .text:0040139B jmp short infinite_loop Дальше, конечно, возникнет исключение и мы благополучно отправимся в SEH –обработчик, нами установленный. Что же там? Code: text:004016A4 SEHFirstHandler proc near ; DATA XREF: DialogFunc+9D o .text:004016A4 .text:004016A4 arg_0 = dword ptr 8 .text:004016A4 arg_8 = dword ptr 10h .text:004016A4 .text:004016A4 push ebp .text:004016A5 mov ebp, esp .text:004016A7 mov eax, [ebp+arg_0] .text:004016AA cmp dword ptr [eax+0Ch], 4013C0h // это не играет роли .text:004016B1 jz short loc_4016DC .text:004016B3 str ax // если мы на варе в eax 4000h .text:004016B6 cmp ax, 4000h .text:004016BA jnz short loc_4016DC .text:004016BC .text:004016BC loc_4016BC: ; CODE XREF: SEHFirstHandler+36 j .text:004016BC push 'cann' .text:004016C1 push 'ot w' .text:004016C6 push 'ork ' .text:004016CB push 'with' .text:004016D0 push 'vmwa' .text:004016D5 push 're ' .text:004016DA jmp short loc_4016BC .text:004016DC ; --------------------------------------------------------------------------- .text:004016DC .text:004016DC loc_4016DC: ; CODE XREF: SEHFirstHandler+D j .text:004016DC ; SEHFirstHandler+16 j .text:004016DC push offset ThreadId ; lpThreadId .text:004016E1 xor eax, eax .text:004016E3 push eax ; dwCreationFlags .text:004016E4 push eax ; lpParameter .text:004016E5 push offset StartAddress ; lpStartAddress .text:004016EA push eax ; dwStackSize .text:004016EB push eax ; lpThreadAttributes .text:004016EC call ds:CreateThread .text:004016F2 mov dword_40CC94, eax .text:004016F7 mov eax, [ebp+arg_8] .text:004016FA add dword ptr [eax+0B8h], 2 .text:00401701 xor eax, eax .text:00401703 pop ebp .text:00401704 retn 10h На самом деле, тут я поленилась немного. Значение 4013C0h было когда-то адресом джампа, посредством которого, после установки BTF мы окажемся в хендлере. Понятное дело, что после перекомпиляций этот код сместился. Потом я порывалась поправить, а потом подумала, что нахер его исправлять, если следующий код все равно проверяет на варю, а на варе полюбому не пашет трассировка ветвлений. Далее я просто устраиваю переполнение стека и все слетает. Ну метод с str ax в общем-то немудреный и практически всем известен. Далее, если мы не на варе и все круто мы создаем тред, который дальше будет работать. Давайте заглянем что как в этом треде. Помимо стандартного кода чтения имени с эдита, там есть 1 подъ№б с KiGetTickCount (int 0x2a). Расчет сделан та тех, кто опрометчиво начнет останавливаться на всяких GetWindowText и тут-то время выполнения увеличиться и далее реверсми упадет после возврата из первого же исключения. Ну это я опять забежала вперед. Зайдем в первый, не связанный с различными манипуляциями с именем call Code: .text:004013AB sub_4013AB proc near ; CODE XREF: StartAddress+EA p .text:004013AB .text:004013AB flOldProtect = dword ptr -8 .text:004013AB var_4 = dword ptr -4 .text:004013AB .text:004013AB push ebp .text:004013AC mov ebp, esp .text:004013AE sub esp, 14h .text:004013B1 push ebx .text:004013B2 push esi .text:004013B3 push edi .text:004013B4 lea eax, [ebp+flOldProtect] .text:004013B7 push eax ; lpflOldProtect .text:004013B8 push 40h ; flNewProtect .text:004013BA push 3 ; dwSize .text:004013BC mov esi, offset int_call2 .text:004013C1 xor edi, edi .text:004013C3 push esi ; lpAddress .text:004013C4 mov [ebp+var_4], edi .text:004013C7 call ds:VirtualProtect .text:004013CD test eax, eax .text:004013CF jz short loc_4013E8 .text:004013D1 mov eax, esi .text:004013D3 mov byte ptr [eax], 0CDh // опкод int XX .text:004013D6 mov cl, byte_40CCA0 .text:004013DC mov [eax+1], cl .text:004013DF mov byte ptr [eax+2], 90h // nop .text:004013E3 call int_call2 Как видим, снова VirtualProtect и снова запись каких-то инструкций прерыванием. Обзовем модифицируемый код int_call2 Code: .text:0040139E int_call2 proc near ; CODE XREF: sub_4013AB+38 p .text:0040139E ; sub_4013AB+46 p ... .text:0040139E push 4 // это будет перезаписано прерыванием .text:004013A0 pop eax .text:004013A1 add eax, 0FFFFFFFCh // + (-4) .text:004013A4 jz short loc_4013AA .text:004013A6 .text:004013A6 loc_4013A6: ; CODE XREF: int_call2+A j .text:004013A6 push 0FFFFFFFFh // переполнение стека .text:004013A8 jmp short loc_4013A6 .text:004013AA ; --------------------------------------------------------------------------- .text:004013AA .text:004013AA loc_4013AA: ; CODE XREF: int_call2+6 j .text:004013AA retn В чем тут фишка? Снова смотрим выше на хендлер прерывания. Если в edi не передано никаких значений, то проверяем флаг трассировки. Code: .text:00011412 mov eax, [esp+arg_4] // EFLAGS .text:00011416 bt eax, 8 // 8-й бит .text:0001141A jnb short loc_1144C .text:0001141C mov eax, 0FFFFFFFCh .text:00011421 jmp short locret_1145F .text:00011423 ; --------------------------------------------------------------------------- … .text:0001144C loc_1144C: ; CODE XREF: InterruptHandler+3A j .text:0001144C mov eax, 4 // нет трассировки .text:00011451 jmp short locret_1145F .text:00011453 ; --------------------------------------------------------------------------- .text:00011453 … .text:0001145F iret .text:0001145F InterruptHandler endp
В случае, если по прерыванию прошлись по F7 в ольге то в eax положим -4, если все ок, то 4. А значит, в юзермоде эта проверка Code: .text:004013A1 add eax, 0FFFFFFFCh // + (-4) .text:004013A4 jz short loc_4013AA .text:004013A6 .text:004013A6 loc_4013A6: ; CODE XREF: int_call2+A j .text:004013A6 push 0FFFFFFFFh // переполнение стека .text:004013A8 jmp short loc_4013A6 приобрела много больше смысла. 4-4 = 0 и нас не трассирут. -4-4!=0 => нас трассируют. Окей. Двигаемся дальше Code: .text:004013E8 loc_4013E8: ; CODE XREF: sub_4013AB+24 j .text:004013E8 and eax, 0 .text:004013EB mov ax, fs .text:004013EE mov [ebp+var_4], eax .text:004013F1 call int_call2 .text:004013F1 sub_4013AB endp .text:004013F1 .text:004013F6 ; --------------------------------------------------------------------------- .text:004013F6 push edi .text:004013F7 lea eax, [ebp-10h] .text:004013FA push eax .text:004013FB push 0Ch .text:004013FD push offset dword_40CCB4 .text:00401402 push 4 .text:00401404 lea eax, [ebp-4] .text:00401407 push eax .text:00401408 push 222004h .text:0040140D push hDevice .text:00401413 call ds:DeviceIoControl .text:00401419 test eax, eax .text:0040141B jz loc_4014FE .text:00401421 call int_call2 .text:00401426 ; --------------------------------------------------------------------------- .text:00401426 call int_call2 // проверка на трассировку .text:0040142B ; --------------------------------------------------------------------------- .text:0040142B push offset loc_40135D .text:00401430 mov eax, dword_40CCB4 .text:00401435 push dword ptr [eax] .text:00401437 mov [eax], esp .text:00401439 ud2 Всматриваемся в код внимательно и видим, что в драйвер передается содержание сегментного регистра fs. Окей. Ну тогда снова в драйвер. В процедуру обработки ioctl запросов. На этот раз запрос 222004h. Вычитаем как обычно 222004h. Получаем нулевой кейз. Ай-да его ковырять. Всякие проверки, имеющие отношение только к стабильности я опускаю. Перейдем сразу к делу. Code: .text:00011728 sgdt fword ptr gdtr__ // опять? ))))))) .text:0001172F mov eax, [esi] .text:00011731 lea edx, [esp+20h+var_8] .text:00011735 push edx .text:00011736 push eax .text:00011737 push offset gdtr__ .text:0001173C call sub_11200 Зайдем в sub_11200. Судя по всему этой процедуре зачем-то нужна gdt. Code: .text:00011200 GetFsBase proc near ; CODE XREF: DeviceControlRoutine+18C p .text:00011200 .text:00011200 arg_0 = dword ptr 4 .text:00011200 selector = dword ptr 8 .text:00011200 arg_8 = dword ptr 0Ch .text:00011200 .text:00011200 mov ecx, [esp+arg_0] .text:00011204 mov eax, [esp+selector] .text:00011208 mov edx, [ecx+2] .text:0001120B push esi .text:0001120C shr eax, 3 .text:0001120F lea esi, [edx+eax*8] .text:00011212 push esi ; VirtualAddress .text:00011213 call ds:MmIsAddressValid .text:00011219 test al, al .text:0001121B jnz short valid .text:0001121D xor eax, eax .text:0001121F pop esi .text:00011220 retn 0Ch .text:00011223 ; --------------------------------------------------------------------------- .text:00011223 .text:00011223 valid: ; CODE XREF: GetFsBase+1B j .text:00011223 mov eax, [esp+4+arg_8] .text:00011227 mov ecx, [esi] .text:00011229 mov [eax], ecx .text:0001122B mov edx, [esi+4] .text:0001122E xor ecx, ecx .text:00011230 mov [eax+4], edx .text:00011233 mov ch, [eax+7] .text:00011236 pop esi .text:00011237 mov cl, dl .text:00011239 movzx edx, word ptr [eax+2] .text:0001123D shl ecx, 10h .text:00011240 or ecx, edx .text:00011242 mov eax, ecx .text:00011244 retn 0Ch .text:00011244 GetFsBase endp Объясняю суть того, что здесь происходит. Как мы помним, передан был селектор на дескриптор. Я уже написала как по селектору высчитать индекс в дескрипторной таблице. Ну так вот. Сначала получаем указатель на сам дескриптор, а потом копируем его содержимое. Code: .text:0001173C call GetFsBase .text:00011741 add [esp+20h+var_6], 0F000h .text:00011748 add [esp+20h+var_8], 3E8h .text:0001174F lea ecx, [esp+20h+var_8] .text:00011753 push ecx .text:00011754 mov edi, eax .text:00011756 call AddDescriptorIntoGdt .text:0001175B cmp eax, ebx .text:0001175D jnz short all_good_ .text:0001175F mov ebp, [esp+20h+var_10] .text:00011763 mov ebx, STATUS_UNSUCCESSFUL .text:00011768 jmp short loc_11791 .text:0001176A ; --------------------------------------------------------------------------- .text:0001176A .text:0001176A all_good_: ; CODE XREF: DeviceControlRoutine+1AD j .text:0001176A mov uFsDescr, eax .text:0001176F mov [esi+4], eax .text:00011772 mov [esi], edi .text:00011774 mov ebp, 8 .text:00011779 call CheckIdtHook // снова проверка на хуки в итд, это все будет иметь смысл при генерации ключа .text:0001177E neg eax .text:00011780 sbb eax, eax .text:00011782 and eax, DBG_TERMINATE_THREAD .text:00011787 mov [esi+8], eax .text:0001178A jmp short loc_11791 .text:0001178C ; -------------------------------------------------- Значит скопировали дескриптор, на который указывает селектор в fs. Потом мы его редактируем, а именно новая база нового сегмента будет на 0x1000 меньше. Окей, потом пересчитываем лимит (причем я пересчитываю его не точно, но реально это не имеет значения, так как нужный диапазон это с лихвой покрывает). Скажу тут сразу, что селектор на новый дескриптор будет использован мной для установки seh обработчиков через gs. Так же как и база старого fs. Так что подробно уже на этом не останавливаюсь. Покажу как это в юзермоде Code: 0040142B 68 5D134000 PUSH reversem.0040135D 00401430 A1 B4CC4000 MOV EAX,DWORD PTR DS:[40CCB4] 00401435 FF30 PUSH DWORD PTR DS:[EAX] 00401437 8920 MOV DWORD PTR DS:[EAX],ESP 00401439 0F0B UD2 0040143B 66:8EE8 MOV GS,AX ; Modification of segment register 0040143E 59 POP ECX 0040143F 59 POP ECX 00401440 68 DD124000 PUSH reversem.004012DD 00401445 65:FF35 00100000 PUSH DWORD PTR GS:[1000] 0040144C 65:8925 00100000 MOV DWORD PTR GS:[1000],ESP 00401453 0F0B UD2 Видите? Сначала устанавливается обработчик по имеющейся базе. Вот его код Code: .text:0040135D loc_40135D: ; DATA XREF: .text:0040142B o .text:0040135D mov ecx, [esp+0Ch] .text:00401361 lea eax, [ecx+0B8h] .text:00401367 add dword ptr [eax], 2 // модификация адреса возврата из seh .text:0040136A mov edx, [eax] .text:0040136C push esi .text:0040136D mov esi, dwCount // а вот тут мы при возврате из seh добавляем еще наш счетчик между int 2a в начале .text:00401373 add edx, esi .text:00401375 mov [eax], edx .text:00401377 mov eax, dword_40CCB8 .text:0040137C mov [ecx+0B0h], eax .text:00401382 xor eax, eax .text:00401384 pop esi .text:00401385 retn 10h Этот обработчик не делает ничего интересного, кроме как неявно проверяет счетчки между вызовами KiGetTickCount и кладет в ax селектор на новый созданный дескриптор. Окей! Разобрались. Двигаемся дальше. Второй обработчик Code: .text:004012DD ; int __cdecl SEH2Handler(DWORD NumberOfBytesWritten, int, int) .text:004012DD SEH2Handler proc near .text:004012DD .text:004012DD NumberOfBytesWritten= dword ptr 8 .text:004012DD arg_8 = dword ptr 10h .text:004012DD .text:004012DD push ebp .text:004012DE mov ebp, esp .text:004012E0 mov eax, [ebp+NumberOfBytesWritten] .text:004012E3 mov eax, [eax] .text:004012E5 cmp eax, EXCEPTION_BREAKPOINT .text:004012EA jz short loc_401329 .text:004012EC cmp eax, EXCEPTION_ILLEGAL_INSTRUCTION .text:004012F1 jnz short loc_401342 .text:004012F3 push 0 ; lpOverlapped .text:004012F5 lea eax, [ebp+NumberOfBytesWritten] .text:004012F8 push eax ; lpNumberOfBytesWritten .text:004012F9 push nNumberOfBytesToWrite ; nNumberOfBytesToWrite .text:004012FF push offset byte_40CC70 ; lpBuffer .text:00401304 push hDevice ; hFile .text:0040130A call ds:WriteFile .text:00401310 push 0 ; lpOverlapped .text:00401312 push offset NumberOfBytesRead ; lpNumberOfBytesRead .text:00401317 push 0 ; nNumberOfBytesToRead .text:00401319 push 0 ; lpBuffer .text:0040131B push hDevice ; hFile .text:00401321 call ds:ReadFile .text:00401327 jmp short loc_401342 .text:00401329 ; --------------------------------------------------------------------------- … … … .text:0040135C SEH2Handler endp Итак, инструкция ud2 спровоцирует EXCEPTION_ILLEGAL_INSTRUCTION => Начинается процесс обращения к драйверу через ReadFile/WriteFile. Скажу сразу, что тут только калькуляция серийного номера. WriteFile считает серийник, ReadFile заканчивает калькуляцию вот так Code: .text:000117E0 ReadHandler: ; DATA XREF: DllEntryPoint+6A o .text:000117E0 mov eax, [esp+8] .text:000117E4 xor serialnumb, 33223322h .text:000117EE push 0 .text:000117F0 push 0 .text:000117F2 push eax .text:000117F3 call CompleteIrp .text:000117F8 retn 8 Переменная serialnumb сохранена в драйвере и будет использоваться при заключительной проверке. WriteHandler я оставляю интересующимся. Отлично, мы отработали в seh – хендлере. Теперь Code: .text:00401342 return: ; CODE XREF: SEH2Handler+14 j .text:00401342 ; SEH2Handler+4A j .text:00401342 mov eax, [ebp+arg_8] .text:00401345 add eax, 0B8h .text:0040134A add dword ptr [eax], 2 // eip+2 .text:0040134D mov ecx, [eax] .text:0040134F mov edx, dwCount // снова учет времени между int 0x2a .text:00401355 add ecx, edx .text:00401357 mov [eax], ecx .text:00401359 xor eax, eax .text:0040135B pop ebp .text:0040135C retn .text:0040135C SEH2Handler endp Возврат из хендлера. Code: .text:0040145A push edi .text:0040145B lea eax, [ebp-0Ch] .text:0040145E push eax .text:0040145F push 3EAh .text:00401464 push dword_40CCCC .text:0040146A call ds:GetDlgItemInt // серийник- число онли! .text:00401470 cmp [ebp-0Ch], edi .text:00401473 mov [ebp-14h], eax .text:00401476 jz short loc_4014B1 .text:00401478 mov eax, NtstatusSave // а вот и наш NtStatus, о котором я говорила в самом начале .text:0040147D mov ecx, dword_40CCBC .text:00401483 add ecx, eax .text:00401485 add [ebp-14h], ecx .text:00401488 pusha .text:00401489 mov eax, [ebp-14h] // eax - серийник .text:0040148C mov edi, 'last' // окей, снова в прерывание .text:00401491 call int_call2 .text:00401496 ; --------------------------------------------------------------------------- .text:00401496 sub esi, 0 // серийник верный, в esi адрес перехода? .text:00401499 jz short loc_4014A5 .text:0040149B push esi .text:0040149C pop edi .text:0040149D add edi, dword_40CCEC .text:004014A3 call edi .text:004014A5 .text:004014A5 loc_4014A5: ; CODE XREF: .text:00401499 j Обратите внимание, что серийник, введенный юзером, считается с учетом наличия отладчика. И если вы решили пойти по пути отладки и были невнимательны, даже в случае нахождения верного ключа - аплодесментов не получите ). Ага, снова отправляемся в наше прерывание и смотрим что будет, если edi == ‘last’. Code: .text:000113E0 InterruptHandler proc near ; DATA XREF: DeviceControlRoutine+CD o .text:000113E0 ; DeviceControlRoutine+D6 o .text:000113E0 .text:000113E0 arg_4 = dword ptr 8 .text:000113E0 .text:000113E0 cmp edi, 'bran' .text:000113E6 jz short msrset .text:000113E8 cmp edi, 'sele' .text:000113EE jz short secondcheck .text:000113F0 cmp edi, 'last' .text:000113F6 jnz short check_tf .text:000113F8 mov edi, serialnumb // вот серийник, сохраненный в дрове .text:000113FE xor eax, edi // ксорим что в eax [значение введенное юзером в поле серийника + учет отладки] .text:00011400 mov ebx, eax .text:00011402 mov edi, 'sele' // прерывание готовится вернутся снова на нструкцию int xx и вызвать прерывание снова .text:00011407 mov eax, [esp+0] .text:0001140A sub eax, 2 // eip_ret - 2 .text:0001140D mov [esp+0], eax .text:00011410 jmp short intreturn .text:00011412 ; --------------------------------------------------------------------------- .text:00011412 .text:00011412 check_tf: ; CODE XREF: InterruptHandler+16 j .text:00011412 mov eax, [esp+arg_4] .text:00011416 bt eax, 8 .text:0001141A jnb short not_tf .text:0001141C mov eax, 0FFFFFFFCh .text:00011421 jmp short intreturn .text:00011423 ; --------------------------------------------------------------------------- .text:00011423 .text:00011423 secondcheck: ; CODE XREF: InterruptHandler+E j .text:00011423 cmp ebx, 0 // результат ксора .text:00011426 jnz short not_valid_serial .text:00011428 mov esi, 4010F1h // если валид сериал. .text:0001142D mov eax, [esp+0] .text:00011430 sub eax, 2 .text:00011433 mov [esp+0], eax .text:00011436 sub edi, edi .text:00011438 jmp short intreturn .text:0001143A ; --------------------------------------------------------------------------- .text:0001143A .text:0001143A not_valid_serial: ; CODE XREF: InterruptHandler+46 j .text:0001143A mov esi, 0 .text:0001143F mov eax, [esp+0] .text:00011442 sub eax, 2 .text:00011445 mov [esp+0], eax .text:00011448 sub edi, edi .text:0001144A jmp short intreturn .text:0001144C ; --------------------------------------------------------------------------- .text:0001144C .text:0001144C not_tf: ; CODE XREF: InterruptHandler+3A j .text:0001144C mov eax, 4 .text:00011451 jmp short intreturn .text:00011453 ; --------------------------------------------------------------------------- .text:00011453 .text:00011453 msrset: ; CODE XREF: InterruptHandler+6 j .text:00011453 mov ecx, 1D9h .text:00011458 rdmsr .text:0001145A or eax, 3 .text:0001145D wrmsr .text:0001145F .text:0001145F intreturn: ; CODE XREF: InterruptHandler+30 j .text:0001145F ; InterruptHandler+41 j ... .text:0001145F iret .text:0001145F InterruptHandler endp А вот тут уже надо быть еще внимательней. Хендлер прерывания, получая 'last' в edi, ксорит серийник из юзермода с вычисленным значением в дрове, затем изменяет eip (то место, куда по идее надо бы вернуться после iret) и кладет в edi 'sele' . И возврат снова на инструкцию прерывания. Но в edi будет уже 'sele' и если разница между верным серийником и тем, что введено пользователем 0, то в esi кладется адрес 4010F1h. Usermode part Code: .text:00401496 sub esi, 0 .text:00401499 jz short loc_4014A5 .text:0040149B push esi .text:0040149C pop edi .text:0040149D add edi, dword_40CCEC .text:004014A3 call edi Если серийник верный, то мы перейдем по адресу 4010F1h. Что там? Code: .text:004010F1 loc_4010F1: ; DATA XREF: sub_401630+8 o .text:004010F1 int 3 ; Trap to Debugger .text:004010F2 nop .text:004010F3 retn И все =) Однако, вспомним, что у нас до сих пор установлен обработчик исключений SEH2Handler. И там была проверка на эксепшн EXCEPTION_BREAKPOINT. Code: ] text:004012E5 cmp eax, EXCEPTION_BREAKPOINT .text:004012EA jz short loc_401329 … … … .text:00401329 loc_401329: ; CODE XREF: SEH2Handler+D j .text:00401329 push 40004h ; fdwSound .text:0040132E push 0 ; lpModuleName .text:00401330 call ds:GetModuleHandleW .text:00401336 push eax ; hmod .text:00401337 push 83h ; pszSound .text:0040133C call ds:PlaySoundW .text:00401342 .text:00401342 return: ; CODE XREF: SEH2Handler+14 j И))))) Если серийник верный, хендлер прерывания возвращает в esi адрес перехода, мы переходим по нему в юзермоде, по этому адресу int 3 == EXCEPTION_BREAKPOINT и мы слышим аплодисменты. Вот таки дела. Я не объяснила ВСЕ. Но тем, кто пробовал, пускай даже не отписался, этого будет достаточно, чтобы понять что к чему. Надеюсь, эта моя работа будет для кого-то познавательной. Тогда я не зря потратила время. На этом пока прощаюсь, удачи!
Пусть модеры удалят этот пост, но тут слов нету. Апплодисменты!!! Я не супер-реверсер, и даже по рангу - ниже новичка, но изложенное выше меня поразило оО! Кстати, ветка Реверсинга на ачате дохлая =\ Может не мое дело, но имхо - надо как-то оживлять... Заполнять чтоли или хз =\
протеус походу сначало не видел твою темку, так бы наверняка поковырял... taha в армии, Грейт редко бывает, neprovad хз, ну а остальные наврядли бы стали браться
У тех, кого ты перечислил объективно времени нет на реверсми. У всех работа. Это, кстати не полный список, правда остальные тоже здесь нечасто бывают. 2winterfrost если бы было обсуждение нормальное, я бы не стала писать солюшн. В следующий раз активней будут ) Привыкли, что задачи нерешенными месяцами лежат. Ждала 5 суток, что кто-то отпишет про прерывание хотя бы. Тогда и разговора бы не было. 2Unregistered да можно, icq 2ex3me да, работы здесь много. я пока в реале свои проблемы решала, почти ничего тут не писала. Сейчас хоть более менее в ритм вошла, смогу разделом по-нормальному заниматься. Хотелось бы отметить еще некоторые особенности. Как вы помните в этом фрагменте заюзана трассировка ветвлений Code: 00401388 $ CD 20 INT 20 0040138A . 9C PUSHFD 0040138B . 5F POP EDI 0040138C . 0FBAEF 08 BTS EDI,8 00401390 . 57 PUSH EDI 00401391 . 9D POPFD 00401392 . 90 NOP 00401393 . 90 NOP 00401394 . 90 NOP 00401395 . 90 NOP 00401396 . 90 NOP 00401397 . 90 NOP 00401398 . 90 NOP 00401399 . 33C0 XOR EAX,EAX 0040139B >-EB FE JMP SHORT reversem.0040139B устанавливается флаг tf, при установленном btf в msr регистре. При этом уже установлен SEH-обработчик, который должен сработать когда управление дойдет до jmp. Ну так вот. Если мы попытаемся пройтись по прерыванию по f7, то ольга понятное дело установит tf и установит раньше меня (раньше пары инструкций pushfd/popfd). При этом ее трассировка будет учитывать btf. Оля перебросит нас на jmp после прерывания, и после этого если мы нажмем shift+f9 SEH управления не получит, а программа зациклиться на джампе. Если вообще эту процедуру не трогать, то при отсутствии игнора эксепшнов мы остановимся как раз на джампе [single-step event] По крайней мере это так в имунити и обычной ольге. Поэтому, как я говрила, лучше ковырять в иде.