Здравствуйте. Цель: Надоело каждый раз зашивать номера системных серверов драйверы или использовать ненадежные методы их поиска. Поэтому необходимо создать парсер таблиц экспорта PE файлов. Затем распарсить ntdll.dll и ntoskernel.dll. И далее с помощью дизассемблера или поиска по сигнатурам(на основе алгоритма Пратта-Кнута-Морриса) вытаскивать смещения, адреса функций, либо ещё какие-нибудь константы. (Возможно есть более простое решение?) Задачи: 1)Весь функционал нужно реализовать в ядре. Для тестирования начал делать в юзер моде вот что получилось вначале: Код (Text): [...] HANDLE p=GetModuleHandle("ntdll.dll"); IMAGE_DOS_HEADER* idh=(PIMAGE_DOS_HEADER)p; IMAGE_NT_HEADERS* inth=(PIMAGE_NT_HEADERS)((DWORD)p+idh->e_lfanew); IMAGE_EXPORT_DIRECTORY* ied=(PIMAGE_EXPORT_DIRECTORY)((DWORD)p+inth->OptionalHeader.DataDirectory[0].VirtualAddress); DWORD num=ied->NumberOfNames; PCHAR* name = (PCHAR*)((DWORD)p+ied->AddressOfNames); PDWORD func = (PDWORD)((DWORD)p+ied->AddressOfFunctions); PWORD ordinal = (PWORD)((DWORD)p+ied->AddressOfNameOrdinals); for(int i=0;i<num;i++) [...] Сверился с PEiD вроде все результаты совпадают. Но потом я вручную слодил базу и RVA полученную прогой и сравнил с GetProcAddress. Результаты разошлись. Почему? Далее хотел передавать информацию в драйвер ну и т.д. Но если похукать хотя бы GetModuleHandle метод можно легко обломать. Поэтому решил парсить файл на диске. Обратил внимание, что в интернете все делают как я в 1 случае и почему то никто не парсил с диска. Попробовал: Код (Text): [...] FILE* f=fopen(Edit1->Text.c_str(),"rb"); fseek(f,0,SEEK_END); DWORD fs=ftell(f); fseek(f,0,SEEK_SET); IMAGE_DOS_HEADER idh; GetDosHeader(f,&idh); IMAGE_NT_HEADERS inth; GetNtHeader(f,idh.e_lfanew,&inth); fseek(f,inth.OptionalHeader.DataDirectory[0].VirtualAddress,SEEK_SET); IMAGE_EXPORT_DIRECTORY ied; fread(&ied,sizeof(ied),1,f); //здесь уже начинается неправильно. Правильно ли я понял что RVA(для файла, а не для памяти) это просто смещение от начала файла? Если да, то в чём ошибка? [...] В чём я ошибаюсь? Возможно пошёл не по тому пути? Может для ядра существуют более лёгкие пути решения проблемы. В общем хотелось бы услышать мнения кто как решал данную проблему... Вариант, например с MmGetSystemRoutineAddress не подойдет, т.к. нужно именно распарсить файл с диска(так получается более защищенный от хуков код, или я ошибаюсь?), ну и естественно в ядре.
haxorart Код (Text): GET_CURRENT_GRAPH_ENTRY macro Call _$_GetCallbackReference endm SEH_Prolog proc C pop ecx push ebp push eax Call SEH_GetRef push eax assume fs:nothing push dword ptr fs:[TEB.Tib.ExceptionList] mov dword ptr fs:[TEB.Tib.ExceptionList],esp jmp ecx SEH_Prolog endp ; o Не восстанавливаются Ebx, Esi и Edi. ; SEH_Epilog proc C pop ecx pop dword ptr fs:[TEB.Tib.ExceptionList] lea esp,[esp + 3*4] jmp ecx SEH_Epilog endp SEH_GetRef proc C GET_CURRENT_GRAPH_ENTRY mov eax,dword ptr [esp + 4] mov esp,dword ptr [esp + 2*4] ; (esp) -> ExceptionList mov eax,EXCEPTION_RECORD.ExceptionCode[eax] mov ebp,dword ptr [esp + 3*4] jmp dword ptr [esp + 2*4] SEH_GetRef endp _$_GetCallbackReference:: pop eax ret ; + ;Проверяет валидность заголовка модуля. ; LdrImageNtHeader proc ImageBase:PVOID, ImageHeader:PIMAGE_NT_HEADERS mov edx,ImageBase mov eax,STATUS_INVALID_IMAGE_FORMAT assume edx:PIMAGE_DOS_HEADER cmp [edx].e_magic,'ZM' jne @f add edx,[edx].e_lfanew assume edx:PIMAGE_NT_HEADERS cmp [edx].Signature,'EP' jne @f cmp [edx].FileHeader.SizeOfOptionalHeader,sizeof(IMAGE_OPTIONAL_HEADER32) jne @f cmp [edx].FileHeader.Machine,IMAGE_FILE_MACHINE_I386 jne @f test [edx].FileHeader.Characteristics,IMAGE_FILE_32BIT_MACHINE je @f mov ecx,ImageHeader xor eax,eax mov dword ptr [ecx],edx @@: ret LdrImageNtHeader endp ; + ; CompareAsciizString proc uses ebx String1:PSTR, String2:PSTR mov ecx,String1 mov edx,String2 xor ebx,ebx @@: mov al,byte ptr [ecx + ebx] cmp byte ptr [edx + ebx],al jne @f inc ebx test al,al jne @b @@: ret CompareAsciizString endp ; + ; Поиск функции по имени/хэшу в экспорте. ; LdrImageQueryEntryFromCrc32 proc uses ebx esi edi ImageBase:PVOID, Crc32OrFunctionName:DWORD, pRtlComputeCrc32:PVOID, PartialCrc:ULONG, Function:PVOID Local ExportDirectory:PIMAGE_EXPORT_DIRECTORY Local ImageHeader:PIMAGE_NT_HEADERS Local NumberOfNames:ULONG Call SEH_Epilog_Reference Call SEH_Prolog mov eax,fs:[TEB.Peb] mov ebx,ImageBase mov eax,PEB.Ldr[eax] test ebx,ebx mov eax,PEB_LDR_DATA.InLoadOrderModuleList.Flink[eax] .if Zero? mov eax,LDR_DATA_TABLE_ENTRY.InLoadOrderModuleList.Flink[eax] mov ebx,LDR_DATA_TABLE_ENTRY.DllBase[eax] ; ntdll.dll .endif invoke LdrImageNtHeader, Ebx, addr ImageHeader test eax,eax mov edx,ImageHeader jnz Exit assume edx:PIMAGE_NT_HEADERS mov eax,[edx].OptionalHeader.DataDirectory.VirtualAddress test eax,eax jz ErrImage add eax,ebx assume eax:PIMAGE_EXPORT_DIRECTORY mov ExportDirectory,eax mov esi,[eax].AddressOfNames test esi,esi jz ErrTable mov eax,[eax].NumberOfNames test eax,eax jz ErrTable mov NumberOfNames,eax add esi,ebx xor edi,edi cld Next: mov eax,dword ptr [esi] add eax,ebx .if pRtlComputeCrc32 != NULL push edi mov edi,eax mov ecx,MAX_PATH mov edx,edi xor eax,eax repne scasb not ecx pop edi add ecx,MAX_PATH push ecx push edx push PartialCrc Call pRtlComputeCrc32 cmp Crc32OrFunctionName,eax .else invoke CompareAsciizString, Crc32OrFunctionName, Eax .endif jnz @f mov ecx,ExportDirectory assume ecx:PIMAGE_EXPORT_DIRECTORY mov eax,[ecx].AddressOfNameOrdinals add eax,ebx movzx edi,word ptr [2*edi+eax] .if edi .if edi >= [ecx]._Base sub edi,[ecx]._Base .endif inc edi .endif mov esi,[ecx].AddressOfFunctions add esi,ebx mov ecx,dword ptr [4*edi + esi] test ecx,ecx mov edx,Function jz ErrImage add ecx,ebx xor eax,eax mov dword ptr [edx],ecx jmp Exit @@: add esi,4 inc edi dec NumberOfNames jnz Next mov eax,STATUS_PROCEDURE_NOT_FOUND jmp Exit SEH_Epilog_Reference: GET_CURRENT_GRAPH_ENTRY Exit: Call SEH_Epilog ret ErrImage: mov eax,STATUS_INVALID_IMAGE_FORMAT jmp Exit ErrTable: mov eax,STATUS_BAD_FUNCTION_TABLE jmp Exit LdrImageQueryEntryFromCrc32 endp Далее мапите с диска ядро, либо нтдлл, в случае если нужно ID-сервисов находить и извлекаете их из стабов. Для этого есть универсальный двиг, создаётся граф и далее парсится и разбирается вашими калбэками. Это позволяет писать минимум кода для поиска какихто функций/переменных лежащих глубоко в недрах кода. Также иногда инфа извлекается из релоков, в загруженном ядре секция с ними выгружена, поэтому также нужно мапить с диска.
Забыл, тот код юзермодный, вот ядерный: Код (Text): ImageQueryEntryFromName proc uses ebx esi edi ImageBase:PVOID, FunctionName:PCHAR, RvaOrAddress:BOOLEAN, Function:PULONG Local ExportDirectory:PIMAGE_EXPORT_DIRECTORY, ImageHeader:PIMAGE_NT_HEADERS Local NumberOfNames:ULONG mov ebx,ImageBase invoke LdrImageNtHeader, Ebx, addr ImageHeader test eax,eax mov edx,ImageHeader jnz err_image_ assume edx:PIMAGE_NT_HEADERS mov eax,[edx].OptionalHeader.DataDirectory.VirtualAddress test eax,eax jz err_image_ add eax,ebx assume eax:PIMAGE_EXPORT_DIRECTORY mov ExportDirectory,eax mov esi,[eax].AddressOfNames test esi,esi jz err_table_ mov eax,[eax].NumberOfNames test eax,eax jz err_table_ mov NumberOfNames,eax add esi,ebx xor edi,edi loop_: mov eax,dword ptr [esi] add eax,ebx invoke CompareAsciizString, FunctionName, Eax .if Eax mov ecx,ExportDirectory assume ecx:PIMAGE_EXPORT_DIRECTORY mov eax,[ecx].AddressOfNameOrdinals add eax,ebx movzx edi,word ptr [2*edi+eax] .if edi .if edi >= [ecx]._Base sub edi,[ecx]._Base .endif inc edi .endif mov esi,[ecx].AddressOfFunctions add esi,ebx mov eax,dword ptr [4*edi + esi] test eax,eax mov ebx,Function jz err_image_ .if RvaOrAddress == TRUE add eax,ImageBase .endif mov dword ptr [ebx],eax xor eax,eax .else add esi,4 inc edi dec NumberOfNames jnz loop_ mov eax,STATUS_PROCEDURE_NOT_FOUND .endif exit_: ret err_image_: mov eax,STATUS_INVALID_IMAGE_FORMAT jmp exit_ err_table_: mov eax,STATUS_BAD_FUNCTION_TABLE jmp exit_ ImageQueryEntryFromName endp
Да и ещё основной вопрос остаётся открытым: почему возникают различия при парсинге файла на диске и загруженного(к примеру с помощью LoadLibrary\GetModuleHandle)? Как это исправить?
грузишь файл в память, с обработкой всех секций. Это не сложно. Затем вот тебе код. Ему даешь имя апишки и ImageBase загруженного файла и она возвращает адрес её. Только обрати внимание на код: if ((ULONG)AddressTable < 0x80000000 && AddressTable->Name[0]) тут я проверял что импорт идет не по имени а по ординалу. В ядре с этим могут быть проблемы из-за адреса. Код (Text): typedef struct _IMPORT_TABLE { ULONG LookUp; ULONG TimeStamp; ULONG ForwardChain; ULONG NameRVA; ULONG AddresTableRVA; } IMPORT_TABLE, *PIMPORT_TABLE; #pragma pack(1) typedef struct _ADDRESS_TABLE { USHORT Hint; char Name[]; } ADDRESS_TABLE, *PADDRESS_TABLE; #pragma pack() ULONG FindImportAddrByApiNameInModule(char * name, ULONG filebase) { ULONG PE; PIMPORT_TABLE ImportTable; PADDRESS_TABLE AddressTable; ULONG IAT_Index; ULONG RVA; ULONG ret = 0; PE = *(ULONG*)(filebase + 0x3C) + filebase; if (*(ULONG*)(PE + 0x80)) { ImportTable = (PIMPORT_TABLE)(*(ULONG*)(PE + 0x80) + filebase); while (ImportTable->NameRVA) { if (ImportTable->LookUp) { RVA = ImportTable->LookUp + filebase; } else { RVA = ImportTable->AddresTableRVA + filebase; } IAT_Index = 0; while (*(ULONG*)RVA) { AddressTable = (PADDRESS_TABLE)(*(ULONG*)RVA + filebase); if ((ULONG)AddressTable < 0x80000000 && AddressTable->Name[0]) { if (!strcmp(name, AddressTable->Name)) { if (ImportTable->AddresTableRVA) { ret = ImportTable->AddresTableRVA + filebase + IAT_Index; } else { ret = RVA; } return ret; } } RVA += 4; IAT_Index += 4; } ImportTable = (PIMPORT_TABLE)((ULONG)ImportTable+sizeof(IMPORT_TABLE)); } } return ret; }
haxorart Разумеется, он паблик, хотя там немного кривой(дизасм не полноценный), тут лежит аттач http://wasm.ru/forum/viewtopic.php?id=34151&p=1 В нём собственно двиг есть, один момент - там юзермодный менеджер памяти. Удалите его и перелинкуйте.
Возник такой очень странный вопрос(в документации ответа не нашел): Обязательно ли таблицы с именами упорядочены по имени или это произошло в тех 2х длл, которые я разбирал? Обязательно ли адреса функций упорядочены?
Для ntoskrnl.exe и HAL.DLL - да (см. сорцы WRK: реализация MmGetSystemRoutineAddress(), MiFindExportedRoutineByName()), за остальное точно сказать не могу
Я смотрел kernel32.dll ntdll.dll ntoskrnl.exe там так + ещё пару PE. За это компилятор отвечает или стандарт есть?
Потому что есть FileAlignment, а есть и SectionAlignment. Научиться читать доку. Упомянутая MiFindExportedRoutineByName и аналог в юзере рассчитывают на это и используют binary search. Нет.
J0E Это аналогично как распространённый среди пишущих на дельфе код kernel32!GetProcAddress(kernel32!"GetGetProcAddress"), или типо того.
Поясни, какое отношение дельфе и kernel32!GetProcAddress(kernel32!"GetGetProcAddress") имеют к ядру? Если не понято про MiFindExportedRoutineByName, попробую по другому: реализация гарантирует, что линкер Майкрософт должен упорядочить импорт по алфавиту (написали же: см. исходник).
Ссылку в студию, где в доках написанно, что линкер обязан упорядочивать имена. +Учесть тот факт, что пост 0x6b65 был посде моего вопроса.
haxorart Во-первых, этот комментарий относился к различиям образа в памяти и на диске. Во-вторых, страница 51: "Name pointer table | An array of pointers to the public export names, sorted in ascending order".