Может быть, кто-то из присутствующих решал такую задачу. Есть exe-шник, у него - секция (директория?) EXPORT, там - функция с определенным именем. Необходимо найти в exe-шнике, где начинается код этой функции, и вставить туда xor eax, eax ret Т.е. сделать вместо функции "заглушку". Весь вопрос в том, как найти адрес кода, зная имя функции. Собственно, я тут полчасика почитал разные документы про PE-заголовки, и понял, что вручную это все реализовывать - замучаешься. Там смешения на смещениях сидят и смещением погоняют. Несколько дней на отладку точно обеспечено. По сему - если у кого-то случайно завалялся код, выполняющий примерно описанные действия, очень прошу им поделиться.
Для начала нужно загрузить этот экзешник с помощью LoadLibraryEx и с помощью GetProcAddress определить адрес интересующей функци. Затем вычисляем ее RVA который будет равен полученому адресу - адрес который вернула LoadLibraryEx. Затем нужно преобразовать RVA в RAW offset, для этого сначала получаем указатель на IMAGE_NT_HEADERS с помощью следующего макроса: Код (Text): #define NT_HEADERS(Image) ((PIMAGE_NT_HEADERS)(RVATOVA(Image, ((PIMAGE_DOS_HEADER)Image)->e_lfanew))) Затем получаем указатель на заголовок первой секции PE файла: Код (Text): #define IMAGE_FIRST_SECTION( ntheader ) ((PIMAGE_SECTION_HEADER) \ ((ULONG_PTR)ntheader + \ FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) + \ ((PIMAGE_NT_HEADERS)(ntheader))->FileHeader.SizeOfOptionalHeader \ )) Затем делаем цикл по секциям и проверяем на принадлежность полученого RVA каждой из них. Размер секции равен Section->VirtualSize (если он = 0, то использовать SizeOfRawData), RVA секции - Section->VirtualAddress. Если нужная секция найдена, то вычисляем смещение в секции Offset = MyRVA - Section->VirtualAddress и получаем смещение в файле. FileOffset = Section->PointerToRawData + Offset. Вот собственно и все, по полученому смещению можно патчить сам файл.
Dmitry_Koteroff > "вручную это все реализовывать - замучаешься" Зачем же вручную, c помощью OllyDbg, например, это делается легко и просто за пару минут Открываешь свой exe или dll, переключаешь на него View, делаешь Search for Name (label) in current module и находишь интересующее имя (+адрес,секция,тип export\import), жмешь Follow in Disassembler и попадаешь на точку входа. Правишь код (Binary \ Edit) и копируешь изменения в экзешник Copy to executable - Оля сама посчитает все смещения и запишет куда надо
Мало того, что нужно именно "вручную", это еще и нужно реализовать на Perl. Специфика задачи. Сразу скажу, что это не вирус и не троян, и вообще - цели преследуются исключительно добрые (а именно - нужно "доработать" одну бесплатную и OpenSource-программу под MinGW, но не перекомпилируя ее каждый раз, т.к. новые версии выходят очень часто, и компилировать снова и снова в будущем - замучаешься). Вот как раз этого-то делать и не хотелось бы. В принципе, прочитав 10 раз Ваше сообщения, я его наконец-то понял (это моя вина, Вы как раз очень хорошо все описали). Нельзя ли получить RVA непосредственно из секции, не загружая exe-шник в память?
Замечательно! Только, догадываюсь, до этой "директории экспорта" добраться ничуть не проще, чем описанные выше инструкции.
до PE header знаешь как добратся ? далее + 78h DWord Export Table RVA + 7Ch DWord Export Data Size идешь туда. Там будет typedef struct _IMAGE_EXPORT_DIRECTORY { ULONG Characteristics; ULONG TimeDateStamp; USHORT MajorVersion; USHORT MinorVersion; ULONG Name; ULONG Base; ULONG NumberOfFunctions; ULONG NumberOfNames; PULONG *AddressOfFunctions; PULONG *AddressOfNames; PUSHORT *AddressOfNameOrdinals; } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; ищещь свое имя и получаешь rva функции.
Dmitry_Koteroff Вот приблизительный код GetProcAddress на сях, правда там много лишнего (поддержка экспортов по ордилалам, форвардинга и.т.д.). Я понамаю, что этот код далеко не лучший для обучения пример, но возможно он будет чем-нибудь полезен. Код (Text): PVOID GetProcAddressEx( IN PVOID Image, IN PCSTR lpProcName, IN GETMODULEHANDLE CsGetModuleHandle ) { PIMAGE_DOS_HEADER dHeader = Image; PVOID Result = NULL; if (dHeader->e_magic == IMAGE_DOS_SIGNATURE) { PIMAGE_NT_HEADERS ntHeaders = RVATOVA(Image, dHeader->e_lfanew); if (ntHeaders->Signature == IMAGE_NT_SIGNATURE) { ULONG ExpRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]. VirtualAddress; ULONG ExpSize = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]. Size; if (ExpRVA && ExpRVA < ntHeaders->OptionalHeader.SizeOfImage) { PIMAGE_EXPORT_DIRECTORY Export = RVATOVA(Image, ExpRVA); ULONG NamesAddr = (ULONG)RVATOVA(Image, Export->AddressOfNameOrdinals); ULONG NameAddr = (ULONG)RVATOVA(Image, Export->Name); PULONG pName = (PULONG)RVATOVA(Image, Export->AddressOfNames); ULONG r; for (r = 0; r < Export->NumberOfFunctions; r++) { BOOLEAN bFindByOrdinal = ((ULONG)lpProcName == (USHORT)lpProcName); ULONG nOrdinal, dwFuncRva; if (r * 2 + NamesAddr == NameAddr) break; nOrdinal = ((PUSHORT)NamesAddr)[r]; dwFuncRva = ((PULONG)RVATOVA(Image, Export->AddressOfFunctions))[nOrdinal]; nOrdinal += (USHORT)Export->Base; if (!bFindByOrdinal && pName && r < Export->NumberOfNames) { if (!_stricmp(lpProcName,(PCHAR)RVATOVA(Image, *pName))) { if (IN_REGION(dwFuncRva, ExpRVA, ExpSize)) { CHAR DllName[0x100]; PCHAR fwdName = RVATOVA(Image, dwFuncRva); PCHAR fnName = strchr(fwdName, '.'); PVOID pLibrary; if (fnName) { memset(DllName, 0, sizeof(DllName)); strncpy(DllName, fwdName, fnName - fwdName); strcat(DllName, ".dll"); fnName++; pLibrary = CsGetModuleHandle(DllName); if (pLibrary) { Result = GetProcAddressEx(pLibrary, fnName, CsGetModuleHandle); } } } else Result = RVATOVA(Image, dwFuncRva); break; } } else if (bFindByOrdinal && (nOrdinal == (USHORT)lpProcName)) { Result = RVATOVA(Image, dwFuncRva); break; } pName++; } } } } return Result; }
Иду туда, но там, похоже, нету. Причем Data Size верно вычисляется, похоже (брал 1 exe-шник и 1 dll с потолка, смотрел, что выдает скрипт, потом - что выдает PE Editor из Far, размеры совпали). Но вот Export Table RVA показывает куда-то в пустоту. Вот скрипт: Код (Text): #!perl -w open(F, "u1230_32.dll"); binmode(F); seek(F, 60, 0); my ($e_lfanew) = unpack("L", fread(*F, 4)); print "e_lfanew = $e_lfanew\n"; seek(F, $e_lfanew+0x7C, 0); # DWord Export Table RVA my ($exportTableRVA) = unpack("L", fread(*F, 4)); print "exportTableRVA = $exportTableRVA\n"; my ($exportDataSize) = unpack("L", fread(*F, 4)); print "exportDataSize = $exportDataSize\n"; seek(F, $exportTableRVA+18, 0); # IMAGE_EXPORT_DIRECTORY.Name my ($numberOfFunctions) = unpack("L", fread(*F, 4)); print "numberOfFunctions = $numberOfFunctions\n"; my ($numberOfNames) = unpack("L", fread(*F, 4)); print "numberOfNames = $numberOfNames\n"; my ($addressOfFunctions) = unpack("L", fread(*F, 4)); print "addressOfFunctions = $addressOfFunctions\n"; my ($addressOfNames) = unpack("L", fread(*F, 4)); print "addressOfNames = $addressOfNames\n"; sub fread { my ($h, $sz) = @_; read($h, my $data, $sz) or die "Cannot read\n"; return $data; }
Там только комментарий неправильный, не IMAGE_EXPORT_DIRECTORY.Name, а NumberOfFunctions, но это дела не меняет. Собственно, получаются неправдоподобные numberOfFunctions. В коде выше, где эмуляция GetProcAddressEx, делается то же самое, насколько я понимаю.
Функция GetProcAddressEx работает с уже отображенным образом PE файла а не с его RAW данными. Нужно сначала отмапить файл по секциям, либо переводить все RVA в RAW offset.
Ну вот, после многочасового сеанса отладки все, наконец, получилось. Решение на Perl тут: http://forum.dklab.ru/perl/advises/PoluchenieAdresaNachalaFunktsiiSOpr edelennimImenemVTeleExe-fayla.html Огромное спасибо Ms Rem! Без его подсказок вообще бы ничего не получилось, это уж точно - ибо документации на данную тему практически нет (все патчат EXE-шники в памяти, мало кто - в файлах).