◄— Предыдущая | Следующая —► |
Проблема заключается в том, что использование стандартных lib-файлов от Майкрософт иногда приводит к ошибкам компиляции ассемблерного кода (например, сгенерированного дизассемблером IdaPro), связанных с отсутствием адекватных определений функций в файлах библиотек. Также нередко бывает ситуация, когда нужных либов просто нет, ни в MASM32, ни в MS Visual Studio. Скажем, файлы regapi.lib или utildll.lib и другие, для системных библиотек Windows, вы не найдете в стандартной поставке lib-файлов.
Кроме того, иногда возникает необходимость вызова функций по ординалам, что фактически сводится к вопросу вызова функции по ее псевдоимени или адресу.
Заметим, что конструкция MASM32
call FuncName
фактически является псевдокодом. В некоторых версиях ассемблера псевдокодом является также явный вызов по ординалу функции (ее номеру в таблице экспорта). Например, отладчик OllyDbg может для условного выражения
call mfc42u.#561
написать такой псевдокод
01001A03 call <jmp.&mfc42u.#561> ; call 01003112 . . . 01003112 jmp dword ptr ds:[<&mfc42u.#561>] ; jmp dword ptr ds:[1001090] ; jmp 7F0EB6C0
что означает вызов функции из модуля mfc42u.dll по ординалу 561.
Реальный код, эквивалентный этой конструкции, будет
call 7F0EB6C0
Аналогичный пример, конструкция MASM32
call MessageBoxA
или, в версии «Оли Дебаговой»,
0040100E call <jmp.&user32.MessageBoxA> ; call 00401022 . . . 00401022 jmp dword ptr ds:[402008] ; jmp 773С425F
эквивалентна реальному выражению
call 773С425F
что уже непосредственно переводится в шестнадцатеричные команды процессора.
Итак, мы видим, что имена и ординалы не являются родными для процессора, однако компилятор MASM32 может автоматически перевести имена функций в их адреса, хотя не может сделать это для ординалов функций. Впрочем, это не большая потеря. Если мы не хотим пользоваться явной адресацией вместо вызова по ординалу, то у нас еще остается в запасе назначение псевдоимени функции без имени . Только сделать это можно в def-файле, из которого затем компилируется lib-файл. Соответственно, упоминавшиеся уже неадекватные описания функций, можно заменить в нашем def-файле на более корректные определения. Таким образом, мы со всей очевидностью приходим к необходимости иметь собственные def-файлы для нужных нам dll и возможности генерировать такие файлы автоматически.
Еще один вопрос, связанный с универсальностью вызова функций, заключается в желании написать такой def-файл, чтобы можно было бы вызывать любое из выражений (без использования дополнительных определений, связанных с директивой equ) вида:
call MessageBoxA call MessageBoxA@16 call _imp__MessageBoxA call _imp__MessageBoxA@16
так как на практике встречаются все эти варианты.
Оказывается, подобное решение существует.
Действительно, создадим следующие файлы определений:
user32.def (где находится функция MessageBoxA)
LIBRARY "user32.dll" EXPORTS MessageBoxA _MessageBoxA@16 = MessageBoxA
и kernel32.def (где находится функция ExitProcess)
LIBRARY "kernel32.dll" EXPORTS ExitProcess _ExitProcess@4 = ExitProcess
На их основе построим lib-файлы с помощью командного файла def2lib.bat
SET DEF=Def SET LIB=Lib SET FILENAME=kernel32 :: Создание lib файла из def файла :: Bin\lib /DEF:%DEF%\%FILENAME%.def /OUT:%LIB%\%FILENAME%.lib /MACHINE:X86 > %FILENAME%. Bin\polib /DEF:%DEF%\%FILENAME%.def /OUT:%LIB%\%FILENAME%.lib /MACHINE:X86 > %FILENAME%. SET FILENAME=user32 :: Создание lib файла из def файла :: Bin\lib /DEF:%DEF%\%FILENAME%.def /OUT:%LIB%\%FILENAME%.lib /MACHINE:X86 > %FILENAME%. Bin\polib /DEF:%DEF%\%FILENAME%.def /OUT:%LIB%\%FILENAME%.lib /MACHINE:X86 > %FILENAME%.
Этот командный файл берет файлы kernel32.def и user32.def из каталога Def и создает файлы kernel32.lib и user32.lib в каталоге Lib. Попутно выводятся сообщения компиляции с теми же именами без расширений. Если эти файлы пустые, то ошибок не обнаружено.
Файлы lib.exe и polib.exe можно взять из бесплатного пакета MASM32. Теперь, на основе этих библиотек напишем небольшую ассемблерную программу mb.asm.
; =========================================================================== .686p .mmx .model flat option casemap:none ; extrn MessageBoxA : proc ; extrn MessageBoxA@16 : proc ; extrn _imp__MessageBoxA : dword extrn _imp__MessageBoxA@16 : dword includelib Lib\user32.lib ; extrn ExitProcess : proc ; extrn ExitProcess@4 : proc ; extrn _imp__ExitProcess : dword extrn _imp__ExitProcess@4 : dword includelib Lib\kernel32.lib ; =========================================================================== .data Header db "Question", 0 MsgText db "Do you want quit?", 0 ; =========================================================================== .code _Start: push 4 ; MB_YESNO push offset Header push offset MsgText push 0 ; call MessageBoxA ; call MessageBoxA@16 ; call _imp__MessageBoxA call _imp__MessageBoxA@16 cmp eax, 6 ; IDYES : 6 - Yes, 7 - No. je Quit jmp _Start Quit: push 0 ; call ExitProcess ; call ExitProcess@4 ; call _imp__ExitProcess call _imp__ExitProcess@4 end _Start ; ===========================================================================
В качестве lib-файлов, используем только что полученные библиотеки. На основе этого кода можно получить четыре варианта исполняемого файла (раскомментируя соответствующие строки). Обратим внимание, что первые два определения extrn содержат ключевое слово proc, а последние два – dword. Компилируем эту программу. Все четыре ее варианта дадут один и тот же результат (рис. 1).
Нам интересно в ней то, что вызов функции мы можем осуществлять четырьмя различными способами, которые покрывают как стиль программирования Iczelion’a, так и стиль Ильфака Гильфанова (в его программе IdaPro). Только, в отличие от Iczelion’a, нам нет необходимости создавать или использовать чужие громоздкие inc-файлы.
Интересно, что пары файлов генерят практически идентичный бинарный код. Первая пара дает (в OllyDbg v. 1.10) код
00401000 >/$ 6A 04 /PUSH 4 ; /Style = MB_YESNO|MB_APPLMODAL 00401002 |. 68 00304000 |PUSH mb1.00403000 ; |Title = "Question" 00401007 |. 68 09304000 |PUSH mb1.00403009 ; |Text = "Do you want quit?" 0040100C |. 6A 00 |PUSH 0 ; |hOwner = NULL 0040100E |. E8 0F000000 |CALL <JMP.&user32.MessageBoxA> ; \MessageBoxA 00401013 |. 83F8 06 |CMP EAX,6 00401016 |. 74 02 |JE SHORT mb1.0040101A 00401018 |.^EB E6 \JMP SHORT mb1.<ModuleEntryPoint> 0040101A |> 6A 00 PUSH 0 ; /ExitCode = 0 0040101C \. E8 07000000 CALL <JMP.&kernel32.ExitProcess> ; \ExitProcess 00401021 CC INT3 00401022 $-FF25 08204000 JMP DWORD PTR DS:[<&user32.MessageBoxA>] ; user32.MessageBoxA 00401028 .-FF25 00204000 JMP DWORD PTR DS:[<&kernel32.ExitProcess>] ; kernel32.ExitProcess
а вторая, код
00401000 >/$ 6A 04 /PUSH 4 ; /Style = MB_YESNO|MB_APPLMODAL 00401002 |. 68 00304000 |PUSH mb4.00403000 ; |Title = "Question" 00401007 |. 68 09304000 |PUSH mb4.00403009 ; |Text = "Do you want quit?" 0040100C |. 6A 00 |PUSH 0 ; |hOwner = NULL 0040100E |. FF15 08204000 |CALL DWORD PTR DS:[<&user32.MessageBoxA>] ; \MessageBoxA 00401014 |. 83F8 06 |CMP EAX,6 00401017 |. 74 02 |JE SHORT mb4.0040101B 00401019 |.^EB E5 \JMP SHORT mb4.<ModuleEntryPoint> 0040101B |> 6A 00 PUSH 0 ; /ExitCode = 0 0040101D \. FF15 00204000 CALL DWORD PTR DS:[<&kernel32.ExitProcess>] ; \ExitProcess
Мы видим, что extrn . . . proc порождает переходники для системных функций, а extrn . . . dword – нет.
Все это, конечно, замечательно, однако для больших проектов нам нужны будут средства автоматизации или хотя бы механизации построения полных def-файлов для системных dll-ек и не только системных.
Если бы дело касалось только родных экспортируемых имен некоторой dll-ки, то мы могли бы взять за основу текстовый файл, генерируемой утилитой dumpbin из MS VS C++ или аналогичную программу. Однако нам нужны будут еще отладочные символы, а это значит, что придется работать с pdb-файлами от Майкрософт (для системных dll-ек). В идеале было бы хорошо написать свою утилиту, типа pdb2def.exe, преобразующей отладочные символы непосредственно в определения библиотечных файлов. Но для этого нужно знать формат pdb-файлов или API для работы с ними. Но до тех пор, пока это не будет сделано, можно воспользоваться более простым вариантом, хотя и немного громоздким. Хотя, как показывает опыт, без ручной корректировки созданных def-файлов не обойтись в любом случае. Не обязательно, делать все изменения сразу, да это практически и невозможно ибо Майкрософт не обеспечивает полного соответствия отладочных символов реально используемым. Однако имея под рукой полный def-файл, для данного системного dll-файла, всегда можно внести необходимые корректировки по ходу дела, компилируя затем, на его основе, lib-файл.
Мы будем использовать IdaPro v. 5.7 demo. Для других версий «Иды» строки сигнатур (Line1 и Line2) могут отличаться (например, за счет использования пробелов вместо табуляторов или наоборот). Скрипт мы будем писать на Visual FoxPro. Вы можете использовать другой, более удобный вам язык, применяя похожий алгоритм.
Приведем сразу окончательный вариант нашего скрипта lst2def.prg, изучать который удобней всего в процессе работы. Если у вас будут какие-то замечания или предложения по его функционированию, то можете высказаться, используя контактную информацию. Конструктивная критика приветствуется .
****************************************************************************** * lst2def.prg ****************************************************************************** CLEAR SET TALK OFF SET SAFETY OFF SET TEXTMERGE ON filename = "mfc42u" infile = filename + ".lst" outfile = filename + ".txt" dbffile = "temp.dbf" dbffile2 = filename + ".dbf" deffile = filename + ".def" Line1 = " ; Exported entry" Len1 = LEN(Line1) Line2 = " public" Len2 = LEN(Line2) MaxFuncNameLen = 125 && Выделенная длина для имени функции OrdinalOffset = 75 && Отступ для записи ординала ToExportOrdinals = .T. crlf = CHR(13) + CHR(10) && Символы завершения строки tabulator = CHR(9) * tabulator2 = tabulator + tabulator tabulator2 = " " file1 = FOPEN(infile) IF file1 < 0 WAIT "Не могу открыть файл: " + infile QUIT ENDIF file2 = FCREATE(outfile) IF file2 < 0 WAIT "Не могу создать файл: " + outfile QUIT ENDIF End1 = FSEEK(file1, 0, 2) && Определяем размер файла Top1 = FSEEK(file1, 0) && Идем в начало. Сама переменная не нужна IF End1 <= 0 && Если файл пуст MESSAGEBOX("Пустой файл: " + infile) QUIT ENDIF pos = 0 IsLine1 = .F. && Максимально возможное количество строк для общих функций и до строки public (д.б. >= 6) KolStr = 50 DIMENSION aSnum[KolStr] && Строковые ординалы функций DIMENSION aFunc[KolStr] && Их базовые имена && Обнуляем массивы FOR i = 1 TO KolStr aSnum[i] = "" aFunc[i] = "" ENDFOR DO WHILE NOT EOF(file1) && На самом деле мы не можем доверять этому условию strline = FGETS(file1) Curpos1 = FSEEK(file1, 0, 1) IF Curpos1 >= End1 && Достигнут конец файла EXIT ENDIF pos = AT(Line1, strline) && Ищем первую строку IF pos > 0 && Нашли первую строку Line1 IsLine1 = .T. ELSE LOOP ENDIF && Первая строка Line1 найдена! Однако этих строк может быть несколько. && Т.е. разные функции могут иметь общий код. strline = SUBSTR(strline, pos + Len1 + 1) && Выделяем число (номер / ординал функции) pos = AT(".", strline) && Ищем признак завершения числа IF pos > 0 && Нашли конец числа aSnum[1] = RIGHT(" " + LTRIM(LEFT(strline, pos - 1)), 5) + " " aFunc[1] = LEFT(SUBSTR(strline, pos + 2) + REPLICATE(" ", MaxFuncNameLen), MaxFuncNameLen) ELSE MESSAGEBOX("Нет признака для номера функции!") QUIT ENDIF KolLine1 = 1 && Количество найденных подряд строк Line1 по умолчанию && В течении следующих KolStr - 1 пытаемся искать другие строки Line1 FOR i = 2 TO KolStr strline = FGETS(file1) pos = AT(Line1, strline) && Снова ищем первую строку IF pos > 0 && Нашли новую копию первой строки Line1 KolLine1 = i strline = SUBSTR(strline, pos + Len1 + 1) && Выделяем число (номер / ординал функции) pos = AT(".", strline) && Ищем признак завершения числа pos = IF pos > 0 && Нашли конец числа aSnum[i] = pos = RIGHT(" " + pos = LTRIM(LEFT(strline, pos - 1)), 5) + " " aFunc[i] = pos = LEFT(SUBSTR(strline, pos + 2) + pos = REPLICATE(" ", MaxFuncNameLen), MaxFuncNameLen) pos = ELSE MESSAGEBOX("Нет признака для номера функции!") pos = QUIT ENDIF pos = ELSE && Нет других копий строк Line1 pos = EXIT ENDIF ENDFOR && Ищем вторую строку Line2, которой могут соответствовать несколько первых строк Line1 IsLine2 = .F. && В течении следующих KolStr должна быть найдена вторая строка Line2 FOR i = 1 TO KolStr pos = AT(Line2, strline) && Ищем вторую строку Line2 IF pos > 0 && Нашли вторую строку Line2 IsLine2 = .T. EXIT ENDIF strline = FGETS(file1) ENDFOR IF NOT IsLine2 MESSAGEBOX("Нет пары для первой строки!") QUIT ENDIF && Вторая строка Line2 найдена! && Выделяем полное имя экспортируемой функции FuncName = TRIM(SUBSTR(strline, pos + Len2 + 1)) FuncName = LEFT(FuncName + REPLICATE(" ", MaxFuncNameLen), MaxFuncNameLen) i1 = 0 OrdList = "" && Следующим KolLine1 строкам Line1 соответствует одна строка Line2 FOR i = 1 TO KolLine1 IF NOT EMPTY(aFunc[i]) FWRITE(file2, FuncName + aSnum[i] + aFunc[i] + crlf) ELSE i1 = IIF(EMPTY(i1), i, i1) OrdList = OrdList + ALLTRIM(aSnum[i]) + ", " ENDIF ENDFOR && Включаем список ординалов с одинаковым телом функции IF NOT EMPTY(OrdList) FWRITE(file2, FuncName + aSnum[i1] + aFunc[i1] + LEFT(OrdList, LEN(OrdList) - 2) + crlf) ENDIF ENDDO IF NOT IsLine1 MESSAGEBOX("Искомая строка не найдена!") QUIT ENDIF CLOSE ALL && Dbf файл для экспортируемых функций CREATE TABLE (dbffile) (FuncName c((MaxFuncNameLen)), Ord n(6,0), Comments c((MaxFuncNameLen)), OrdList c(254)) APPEND FROM (outfile) TYPE SDF CLOSE ALL && Сортруем dbf файл по ординалу SELECT * FROM (dbffile) ORDER BY Ord INTO TABLE (dbffile2) SELECT (dbffile2) SET TEXTMERGE TO (deffile) \\LIBRARY "<<filename + '.dll'>>" \EXPORTS ExpLine = "" ExpLine2 = "" FuncLine = "" && Генерим def файл FOR i = 1 TO RECCOUNT() GO i SCATTER MEMVAR IF NOT EMPTY(m.Comments) AND TRIM(m.FuncName) <> TRIM(m.Comments) \<<TRIM(m.Comments)>> FuncLine = TRIM(m.FuncName) + " = " + TRIM(m.Comments) ELSE IF ToExportOrdinals OrdList2 = ALLTRIM(OrdList) pos = AT(", ", OrdList2) && Ищем первую запятую в списке ординалов IF pos > 0 && Нашли первую запятую в списке ординалов OrdList1 = "@" + LEFT(OrdList2, pos - 1) OrdList3 = SUBSTR(OrdList2, pos + 2) FuncLine = TRIM(m.FuncName) + tabulator2 + OrdList1 + IIF(EMPTY(OrdList3), "", " ; @") + OrdList3 ELSE FuncLine = TRIM(m.FuncName) + tabulator2 + IIF(EMPTY(OrdList), "", "@") + OrdList2 ENDIF ELSE && NOT ToExportOrdinals FuncLine = TRIM(m.FuncName) + tabulator2 + IIF(EMPTY(OrdList), "", "; @") + ALLTRIM(OrdList) ENDIF ENDIF FuncLineLen = LEN(TRIM(FuncLine)) IF FuncLineLen < OrdinalOffset ExpLine = LEFT(TRIM(FuncLine) + REPLICATE(" ", OrdinalOffset), OrdinalOffset) ELSE ExpLine = LEFT(TRIM(FuncLine) + REPLICATE(" ", FuncLineLen + 1), FuncLineLen + 1) ENDIF \<<TRIM(FuncLine)>> ENDFOR CLOSE ALL QUIT ****************************************************************************** ******************************************************************************
Исходный листинг (lst-файл) для данного dll-файла с отладочными символами (pdb-файл) просто создаем, копируя в него содержимое буфера обмена полученного в IdaPro v. 5.7 demo, на который затем направляем наш скрипт. В итоге получим def-файл (другие временные файлы можно удалить) из которого уже можно делать соответствующий lib-файл. Если возникнут шероховатости, то можно подправить либо скрипт, либо сгенерированный def-файл.
Код скрипта lst2def.prg можно найти также в папке Prg прилагаемого файла с результатами исследований. Там же в качестве примера приложен листинг version.lst соответствующей dll-ки. Запуская на выполнение скрипт, получим в результате файл version.def (ненужные файлы удаляем). В папке Def находятся уже отредактированные вручную созданные, таким образом, файлы определений. Получая свои дефы, вы можете сравнить их с нашими.
Наверное, пару слов надо сказать относительно Visual FoxPro. Кому-то нравится Perl для обработки текстовых файлов, а мне VFP. Для его работы (на уровне ядра) достаточно двух файлов, например, для 9-й версии VFP (годится и меньшая версия) это будут vfp9.exe (5.6 Мб) и vfp9enu.dll (1.5 Мб), которые можно взять непосредственно из любой инсталляции Visual FoxPro. Для исполнения скрипта достаточно записать в командной строке
vfp9.exe lst2def.prg
в каталоге, где находится исходный файл листинга. Однако более удобно запускать программу Visual FoxPro в его собственной оболочке, назначив ассоциацию на файл *.prg (клавиша F10 в Total Commander) вида
Microsoft Visual FoxPro Program ( "C:\Program Files\Microsoft Visual FoxPro 9\vfp9.exe" -SHELLOPEN "%1")
Нажав Enter на файл lst2def.prg, мы загружаемся в IDE VFP, где мы можем отредактировать скрипт перед его исполнением (Ctrl+E). Есть еще варианты работы с runtime версиями, но, думаю, при желании, вы разберетесь с ними сами.
Вот пример сгенерированного нашим скриптом файла определений mfc42u.def для mfc42u.dll, взятого из папки C:\WINDOWS\system32\ .
LIBRARY "mfc42u.dll" EXPORTS ?classCCachedDataPathProperty@CCachedDataPathProperty@@2UCRuntimeClass@@B ?classCDataPathProperty@CDataPathProperty@@2UCRuntimeClass@@B DllCanUnloadNow _DllCanUnloadNow@0 = DllCanUnloadNow DllGetClassObject _DllGetClassObject@12 = DllGetClassObject DllRegisterServer _DllRegisterServer@0 = DllRegisterServer DllUnregisterServer _DllUnregisterServer@0 = DllUnregisterServer ??0_AFX_CHECKLIST_STATE@@QAE@XZ @256 ??0_AFX_COLOR_STATE@@QAE@XZ @257 ?_AfxBinaryCompatibleStubFunction@@YAXXZ @258 ; @590, 598, 944, 946, 947, 948, 949, 951, 952, ; 953, 954, 1178, 1187, 1188, 2186, 2761 ??0_AFX_DAO_STATE@@QAE@XZ @259 ??0_AFX_EDIT_STATE@@QAE@XZ @260 . . . ?GetMenuStringW@CMenu@@QBEHIAAVCString@@I@Z @6923 ??0CInvalidArgException@@QAE@HI@Z @6924 ??1CInvalidArgException@@UAE@XZ @6925 ?GetRuntimeClass@CInvalidArgException@@UBEPAUCRuntimeClass@@XZ @6926 ?classCInvalidArgException@CInvalidArgException@@2UCRuntimeClass@@B @6927 ?AfxThrowInvalidArgException@@YGXXZ @6928 ?AtlA2WHelper@@YGPAGPAGPBDH@Z @6929 ?AtlW2AHelper@@YGPADPADPBGH@Z @6930 ?GetMonth@CTime@@QBEHXZ @6931 ?GetThreadValue@CThreadSlotData@@QAEPAXH@Z @6932 ?GetYear@CTime@@QBEHXZ @6933 ?HashKey@CMapStringToPtr@@QBEIPBG@Z @6934 ; @6935, 6936
Скажем пару слов по структуре нашего def-файла. Как показывает запуск dumpbin.bat (dumpbin.exe взят из MS Visual Studio C++)
SET WINSYS32=C:\WINDOWS\SYSTEM32 SET FILENAME=mfc42u :: Экспортируемые функции данной dll Bin\dumpbin /EXPORTS %WINSYS32%\%FILENAME%.dll /OUT:%FILENAME%.txt > %FILENAME%.
в файле mfc42u.dll практически все функции определены по ординалам
Ord Hint RVA Name 5 0 000117E0 ?classCCachedDataPathProperty@CCachedDataPathProperty@@2UCRuntimeClass@@B 6 1 00011810 ?classCDataPathProperty@CDataPathProperty@@2UCRuntimeClass@@B 7 2 000DE1D0 DllCanUnloadNow 8 3 000DE190 DllGetClassObject 9 4 000DE210 DllRegisterServer 10 5 000DE260 DllUnregisterServer 256 00042FD0 [NONAME] 257 0004A4A0 [NONAME] 258 000D3820 [NONAME] 259 000718B0 [NONAME] 260 0005A4E0 [NONAME] . . . 6936 00024190 [NONAME] 6937 00080A30 [NONAME] 6938 00080A30 [NONAME] 6939 00080A30 [NONAME] 6940 00080A30 [NONAME]
Поскольку экспортируемых имен функций в mfc42u.dll практически нет (за исключением первых шести), то наши имена определенные в mfc42u.def по сути являются псевдоименами, для определенности связанные явно с экспортируемыми ординалами. Когда же в dll-ке экспортируются имена, то нам уже нет необходимости и смысла указывать ординалы (которые могут быть скорее подвержены изменениям в будущих версиях библиотек, чем сами имена). Поэтому первые шесть функций (с их алиасами) указаны без ординалов.
В данном случае, для экспорта по ординалам, мы объявляем переменную
ToExportOrdinals = .T.
в результате чего ординалы функций указаны явно. Когда же нет необходимости экспортировать ординалы, то в этом случай мы пишем
ToExportOrdinals = .F.
в скрипте lst2def.prg. В смешанном же случае ситуация будет сложнее. Пока мы можем меньшую часть отредактировать вручную (до сих пор это не превышало несколько случаев на один файл). В перспективе, можно отслеживать экспорт по ординалу в исследуемом dll-файле (как это делает dumpbin.exe) для более корректной генерации def-файла. Тем не менее, если имя функции имеет алиас в отладочных символах, то этот случай подразумевает экспорт имен и наша переменная ToExportOrdinals игнорируется. В любом случае, вы всегда может подрегулировать данный скрипт под свои нужды.
В качестве теста для созданных lib-файлов используем имеющиеся уже у нас asm-файлы из первых двух статей. Они также продублированы в каталоге Results. Содержимое соответствующей подпапки копируем на два уровня выше (туда, куда скопировано содержимое каталога write). Все подкаталоги с ассемблерным кодом содержат следующие командные файлы:
def2lib.cmd (для генерации 11-ти, используемых в нашем проекте системных lib-файлов) def2lib.bat (для создания только одного, явно указанного, lib-файла) asm.bat (один или несколько, для генерации соответствующего exe-шника, на базе созданных lib-файлов).
Пакетный файл def2lib.cmd достаточно запустить один раз, который из 11-ти def-файлов из папки Def сгенерирует такое же количество lib-файлов в папку Lib (изначально пустой). Если надо пересоздать какой-нибудь один lib-файл, то запускаем def2lib.bat (указав в нем нужное имя). asm.bat генерит непосредственно приложение, а если их несколько, то различные варианты этого приложения.
В результате работы пакетных файлов, создаются разнообразные файлы сообщений. Пустые файлы означают нормальную работу соответствующей команды. Все их можно спокойно удалять.
Для тестирования нам понадобятся наши собственные файлы:
advapi32.lib
В качестве исходных dll-файлов можно взять, в принципе, любые версии Windows NT помня только о различиях в составе функций разных версий NT и их сервис паков. В нашем случае, дефы, построенные по длл-кам Windows 2003 Server, sp. 2, работали под Windows XP, sp. 3 и наоборот.
Проблемы, возникающие при использование дефов / либов, полученных данным способом, возникают из-за неполноты информации в отладочных символах. Например, в листинге mfc42u.lst для mfc42u.dll можно встретить такой код
.text:7F05FE80 ; Exported entry 1016. .text:7F05FE80 ; Exported entry 1055. .text:7F05FE80 ; Exported entry 1747. .text:7F05FE80 ; Exported entry 2015. .text:7F05FE80 ; Exported entry 2016. .text:7F05FE80 ; Exported entry 2117. .text:7F05FE80 ; Exported entry 2474. .text:7F05FE80 ; Exported entry 2977. .text:7F05FE80 ; Exported entry 3074. .text:7F05FE80 ; Exported entry 3101. .text:7F05FE80 ; Exported entry 3142. .text:7F05FE80 ; Exported entry 3254. .text:7F05FE80 ; Exported entry 3744. .text:7F05FE80 ; Exported entry 4043. .text:7F05FE80 ; Exported entry 4103. .text:7F05FE80 ; Exported entry 4112. .text:7F05FE80 ; Exported entry 4383. .text:7F05FE80 ; Exported entry 4564. .text:7F05FE80 ; Exported entry 4650. .text:7F05FE80 ; Exported entry 4666. .text:7F05FE80 ; Exported entry 4721. .text:7F05FE80 ; Exported entry 4876. .text:7F05FE80 ; Exported entry 4974. .text:7F05FE80 ; Exported entry 5010. .text:7F05FE80 ; Exported entry 5501. .text:7F05FE80 ; Exported entry 5508. .text:7F05FE80 ; Exported entry 5523. .text:7F05FE80 ; Exported entry 5524. .text:7F05FE80 ; Exported entry 5526. .text:7F05FE80 ; Exported entry 5549. .text:7F05FE80 ; Exported entry 5559. .text:7F05FE80 ; Exported entry 5561. .text:7F05FE80 ; Exported entry 6051. .text:7F05FE80 ; Exported entry 6320. .text:7F05FE80 .text:7F05FE80 ; =============== S U B R O U T I N E ======================================= .text:7F05FE80 .text:7F05FE80 ; public: virtual unsigned long __stdcall .text:7F05FE80 ; COlePropertiesDialog::XOleUIObjInfo::Release(void) .text:7F05FE80 public ?Release@XOleUIObjInfo@COlePropertiesDialog@@UAGKXZ .text:7F05FE80 ?Release@XOleUIObjInfo@COlePropertiesDialog@@UAGKXZ proc near .text:7F05FE80 ; CODE XREF: COleServerDoc::GetInterfaceHook(void const *)+55 p .text:7F05FE80 ; CMonikerFile::Open(ushort const *,CFileException *)+40 p ... .text:7F05FE80 xor eax, eax ; MFC42u_1016 and others .text:7F05FE82 retn 4 .text:7F05FE82 ?Release@XOleUIObjInfo@COlePropertiesDialog@@UAGKXZ endp .text:7F05FE82 .text:7F05FE82 ; ---------------------------------------------------------------------------
Это означает, что определена только одна функция
virtual unsigned long __stdcall COlePropertiesDialog::XOleUIObjInfo::Release(void)
или, в записи понятной компилятору,
public ?Release@XOleUIObjInfo@COlePropertiesDialog@@UAGKXZ proc near
которая является телом следующих функций, определенных только по ординалам:
1016, 1055, 1747, 2015, 2016, 2017, 2474, 2977, 3074, 3101, 3142, 3254, 3744, 4043, 4103, 4112, 4383, 4564, 4650, 4666, 4721, 4876, 4974, 5010, 5501, 5508, 5523, 5524, 5526, 5549, 5559, 5561, 6051, 6320.
Всего 34 функции. Однако для этих ординалов, в exe-шниках (от Майкрософт), где они используются, могут быть определены их реальные имена. Скажем, в winmsd.exe, рассмотренном в прошлой статье, встречается импорт из mfc42u.dll этих функций не по ординалам, а по их реальным именам (известных только Майкрософту)
Address Ord FuncName 01001054 2977 ?GetConnectionHook@CCmdTarget@@MAEPAUIConnectionPoint@@ABU_GUID@@@Z 01001058 3142 ?GetExtraConnectionPoints@CCmdTarget@@MAEHPAVCPtrArray@@@Z 0100105C 3254 ?GetInterfaceHook@CCmdTarget@@UAEPAUIUnknown@@PBX@Z 01001088 3074 ?GetDispatchIID@CCmdTarget@@UAEHPAU_GUID@@@Z(другие функции просто не применяются в winmsd.exe). Получается, что в одних источниках Майкрософт секретит информацию, а в других рассекречивает («правая рука не знает, что делает левая» ). Подобные случаи трудно обработать автоматически, поэтому, чтобы использовать данный файл mfc42u.def для winmsd.asm его нужно немного подправить напильником . В этом и состоит смысл построения def-файлов, чтобы всегда иметь возможность внести в них нужные исправления.
В нашем случае компилятор ругается на 12 функций, используемых в winmsd.asm, но определения которых отсутствуют в нашем mfc42u.def. Модифицируем этот def-файл. Вместо строки
?Release@XOleUIObjInfo@COlePropertiesDialog@@UAGKXZ @1016 ; @1055, 1747, 2015, 2016, 2117, ...
созданной нашим скриптом, напишем
?Release@XOleUIObjInfo@COlePropertiesDialog@@UAGKXZ @1016 ; @1055, 1747, 2015, 2016, 2117, ... ?GetConnectionHook@CCmdTarget@@MAEPAUIConnectionPoint@@ABU_GUID@@@Z @2977 ?GetDispatchIID@CCmdTarget@@UAEHPAU_GUID@@@Z @3074 ?GetExtraConnectionPoints@CCmdTarget@@MAEHPAVCPtrArray@@@Z @3142 ?GetInterfaceHook@CCmdTarget@@UAEPAUIUnknown@@PBX@Z @3254
По-хорошему, надо бы описать и оставшиеся 30 функций. Но в данном тесте в winmsd.asm их нет, а мы народ ленивый , привыкли «решать проблемы по мере их поступления» . Короче, перекомпилируем модифицированный mfc42u.def в mfc42u.lib и затем снова компилируем winmsd.asm. Как и ожидалось, число ошибок уменьшилось на четыре. Остальные ошибки устраняются аналогично. Для этого берем имя (возможно неполное) из файла сообщения линковщика (у нас это файл c.a). На вкладке импорта IdaPro (или в другом походящем месте «Иды») для загруженного файла winmsd.exe смотрим какой ординал соответствует этому имени. В нашем, уже созданном, def-файле ищем этот ординал и рядышком (для порядка ) делаем соответствующую запись вида
ImportFuncName @Ord
как в примере выше.
Проделав эту работу для остальных неопознанных линковщиком функций, мы получаем конечный, для данного теста, вариант файла mfc42u.def, который уже можно преобразовать в lib-файл.
Для других библиотек ошибки линковщика устраняются аналогично. Отметим только некоторые дополнительные нюансы.
В файле kernel32.def имя функции
HeapSize
заменили, с учетом информации «Иды», на
NTDLL.RtlSizeHeap _HeapSize@12 = NTDLL.RtlSizeHeap
Просто оставить HeapSize не удается, так как функция «forwarded to NTDLL.RtlSizeHeap».
Некоторые функции имеют несколько алиасов. Так, например, в user32.def пришлось к записям
DestroyWindow _NtUserDestroyWindow@4 = DestroyWindow
добавить еще одну
_DestroyWindow@4 = DestroyWindow
В shlwapi.def функцию _IsOS@4 пришлось явно определить по ординалу
_IsOS@4 @437
(Смотрите по этому поводу предыдущую статью.)
Скомпилированный для обновленных lib-файлов winmsd.exe выполнился успешно. Все используемые def-файлы (кроме mfc42u.def) создавались с отключенным экспортом по ординалам (ToExportOrdinals = .F.). Исправлений в def-файлах было относительно немного для компилируемых нами asm-файлов, Все exe-шники работали без явных проблем. Впрочем, и внешних функций они использовали не очень много.
Есть такая утилита PEview. Очень хороша для просмотра структуры lib-файлов (рис.2). При желании, вы можете пользоваться ею также.
Понятно, что представленное решение не является идеальным, И для новых проектов может потребоваться дополнительная правка def-файлов. Однако думается, что это будет вполне обозримым по трудоемкости. В любом случае, встречные предложения по совершенствованию техники создания собственных def и lib-файлов для различных dll только приветствуются.
◄— Предыдущая | Следующая —► |