Для исследования сгенерированного объектного/исполняемого файла можно использовать ряд утилит. Так, Microsoft вместе с компилятором для C/C++ предоставляет утилиту dumpbin. Соответственно для использования dumpbin, надо установить компиляторы Visual C++ через программу установки Visual Studio. А для обращения к самой утилите dumpbin в консоли можно использовать программу x64 Native Tools Command Prompt for VS 2022, которая устанавливается вместе с Visual Studio.
Вызов утилиты dumpbin имеет следующую форму:
dumpbin options filename
Параметр filename
представляет имя объектного или исполняемого файла в формате PE/COFF, который надо исследовать, а параметр
options
представляет набор опций, которые указывают, какую именно информацию о файле надо отобразить. Применяются следующие опции:
/ALL /ARCHIVEMEMBERS /CLRHEADER /DEPENDENTS /DIRECTIVES /DISASM[:{BYTES|NOBYTES}] /ERRORREPORT:{NONE|PROMPT|QUEUE|SEND} /EXPORTS /FPO /HEADERS /IMPORTS[:filename] /LINENUMBERS /LINKERMEMBER[:{1|2}] /LOADCONFIG /NOLOGO /OUT:filename /PDATA /PDBPATH[:VERBOSE] /RANGE:vaMin[,vaMax] /RAWDATA[:{NONE|1|2|4|8}[,#]] /RELOCATIONS /SECTION:name /SUMMARY /SYMBOLS /TLS /UNWINDINFO
В качестве примера возьмем простейшую программу на ассемблере:
.data num1 dword 22 num2 dword 32 num3 dword ? .code main proc mov rax, 20 mov rbx, 10 add rax, rbx ret main endp end
Пусть программа располагается в файле hello.asm, и при компиляции создается объектный файл hello.obj.
Параметр /all позволяет отобразить всю возможную информацию, кроме дизассемблированного кода. Для использования dumpbin
запустим
x64 Native Tools Command Prompt for VS 2022 и в этой программе перейдем к расположению объектного файла hello.obj и затем выполним команду:
dumpbin hello.exe /all
Нам отобразится вывод типа следующего:
c:\asm>dumpbin hello.obj /all Microsoft (R) COFF/PE Dumper Version 14.36.32532.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file hello.obj File Type: COFF OBJECT FILE HEADER VALUES 8664 machine (x64) 3 number of sections 64C00B7D time date stamp Tue Jul 25 20:50:53 2023 10A file pointer to symbol table 9 number of symbols 0 size of optional header 0 characteristics SECTION HEADER #1 .text$mn name 0 physical address 0 virtual address 12 size of raw data 8C file pointer to raw data (0000008C to 0000009D) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 60500020 flags Code 16 byte align Execute Read RAW DATA #1 00000000: 48 C7 C0 14 00 00 00 48 C7 C3 0A 00 00 00 48 03 HЗА....HЗГ....H. 00000010: C3 C3 ГГ SECTION HEADER #2 .data name 0 physical address 0 virtual address C size of raw data 9E file pointer to raw data (0000009E to 000000A9) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers C0500040 flags Initialized Data 16 byte align Read Write RAW DATA #2 00000000: 16 00 00 00 20 00 00 00 00 00 00 00 .... ....... SECTION HEADER #3 .debug$S name 0 physical address 0 virtual address 60 size of raw data AA file pointer to raw data (000000AA to 00000109) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 42100040 flags Initialized Data Discardable 1 byte align Read Only RAW DATA #3 00000000: 04 00 00 00 F1 00 00 00 52 00 00 00 17 00 01 11 ....с...R....... 00000010: 00 00 00 00 63 3A 5C 61 73 6D 5C 68 65 6C 6C 6F ....c:\asm\hello 00000020: 2E 6F 62 6A 00 37 00 3C 11 03 02 00 00 D0 00 00 .obj.7.<.....Р.. 00000030: 00 00 00 00 00 00 00 0E 00 24 00 14 7F 00 00 4D .........$.....M 00000040: 69 63 72 6F 73 6F 66 74 20 28 52 29 20 4D 61 63 icrosoft (R) Mac 00000050: 72 6F 20 41 73 73 65 6D 62 6C 65 72 00 00 00 00 ro Assembler.... COFF SYMBOL TABLE 000 01037F14 ABS notype Static | @comp.id 001 00000010 ABS notype Static | @feat.00 002 00000000 SECT1 notype Static | .text$mn Section length 12, #relocs 0, #linenums 0, checksum 0 004 00000000 SECT2 notype Static | .data Section length C, #relocs 0, #linenums 0, checksum 0 006 00000000 SECT3 notype Static | .debug$S Section length 60, #relocs 0, #linenums 0, checksum 0 008 00000000 SECT1 notype () External | main String Table Size = 0x0 bytes Summary C .data 60 .debug$S 12 .text$mn c:\asm>
Как видно, здесь довольно обширная информация. Чем больше программа, чем больше кода она использует, особенно внешних функций, тем больше подобный вывод. Поэтому, возможно, в большинстве ситуаций целесообразнее применять другие опции для вывода какой-то отдельной информации.
Опция /disasm позволяет дизассемблировать файл. Например, дизассемблируем файл hello.obj:
c:\asm>dumpbin hello.obj /disasm Microsoft (R) COFF/PE Dumper Version 14.36.32532.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file hello.obj File Type: COFF OBJECT main: 0000000000000000: 48 C7 C0 14 00 00 mov rax,14h 00 0000000000000007: 48 C7 C3 0A 00 00 mov rbx,0Ah 00 000000000000000E: 48 03 C3 add rax,rbx 0000000000000011: C3 ret Summary C .data 60 .debug$S 12 .text$mn c:\asm>
Здесь мы видим ассемблерный код, аналогичный тому, который определен в исходном файле hello.asm.
Параметр /headers выводит содержимое заголовка файла и заголовков разделов. Например, заголовки файла hello.obj:
c:\asm>dumpbin hello.obj /headers Microsoft (R) COFF/PE Dumper Version 14.36.32532.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file hello.obj File Type: COFF OBJECT FILE HEADER VALUES 8664 machine (x64) 3 number of sections 64C00B7D time date stamp Tue Jul 25 20:50:53 2023 10A file pointer to symbol table 9 number of symbols 0 size of optional header 0 characteristics SECTION HEADER #1 .text$mn name 0 physical address 0 virtual address 12 size of raw data 8C file pointer to raw data (0000008C to 0000009D) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 60500020 flags Code 16 byte align Execute Read SECTION HEADER #2 .data name 0 physical address 0 virtual address C size of raw data 9E file pointer to raw data (0000009E to 000000A9) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers C0500040 flags Initialized Data 16 byte align Read Write SECTION HEADER #3 .debug$S name 0 physical address 0 virtual address 60 size of raw data AA file pointer to raw data (000000AA to 00000109) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 42100040 flags Initialized Data Discardable 1 byte align Read Only Summary C .data 60 .debug$S 12 .text$mn c:\asm>
Параметр /imports выводит список всех внешних символов, которые операционная система должна предоставлять при загрузке программы в память.
Параметр /relocations выводит список всех релокаций/перемещений в файле. Грубо говоря, релокации представляют замену в программе некоторых адресов по умолчанию на реальные адреса. Часто релокации предполагают перемещение в программу адресов внешних функций. Рассмотрим релокации на другом примере. Пусть у нас будет программа, которая использует внешние функции для вывода сообщения на консоль:
includelib kernel32.lib ; подключаем библиотеку kernel32.lib ; подключаем функции WriteFile и GetStdHandle extrn WriteFile: PROC extrn GetStdHandle: PROC .code text byte "Hello METANIT.COM!" ; выводимая строка main proc sub rsp, 48 mov rcx, -11 ; Аргумент для GetStdHandle - STD_OUTPUT call GetStdHandle ; вызываем функцию GetStdHandle mov rcx, rax ; Первый параметр WriteFile - в регистр RCX помещаем дескриптор файла - консоли lea rdx, text ; Второй параметр WriteFile - загружаем указатель на строку в регистр RDX mov r8d, 18 ; Третий параметр WriteFile - длина строки для записи в регистре R8D xor r9, r9 ; Четвертый параметр WriteFile - адрес для получения записанных байтов mov qword ptr [rsp + 32], 0 ; Пятый параметр WriteFile call WriteFile ; вызываем функцию WriteFile add rsp, 48 ret main endp end
Для получения дескриптора консольного вывода и последующего вывода на консоль сообщения применяются нативные функции Windows - WriteFile и GetStdHandle.
Пусть эта программа компилируется в объектный файл hello.obj
. Если мы дизассемблируем его, то мы получим следующий консольный вывод:
c:\asm>dumpbin hello.obj /disasm Microsoft (R) COFF/PE Dumper Version 14.36.32532.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file hello.obj File Type: COFF OBJECT text: 0000000000000000: 48 0000000000000001: 65 6C ins byte ptr [rdi],dx 0000000000000003: 6C ins byte ptr [rdi],dx 0000000000000004: 6F outs dx,dword ptr [rsi] 0000000000000005: 20 4D 45 and byte ptr [rbp+45h],cl 0000000000000008: 54 push rsp 0000000000000009: 41 000000000000000A: 4E 000000000000000B: 49 54 push r12 000000000000000D: 2E 000000000000000E: 43 000000000000000F: 4F 0000000000000010: 4D 21 48 83 and qword ptr [r8-7Dh],r9 0000000000000014: EC in al,dx 0000000000000015: 30 48 C7 xor byte ptr [rax-39h],cl 0000000000000018: C1 F5 FF sal ebp,0FFh 000000000000001B: FF 000000000000001C: FF E8 jmp rax 000000000000001E: 00 00 add byte ptr [rax],al 0000000000000020: 00 00 add byte ptr [rax],al 0000000000000022: 48 8B C8 mov rcx,rax 0000000000000025: 48 8D 15 00 00 00 lea rdx,[text] 00 000000000000002C: 41 B8 12 00 00 00 mov r8d,12h 0000000000000032: 4D 33 C9 xor r9,r9 0000000000000035: 48 C7 44 24 20 00 mov qword ptr [rsp+20h],0 00 00 00 000000000000003E: E8 00 00 00 00 call WriteFile 0000000000000043: 48 83 C4 30 add rsp,30h 0000000000000047: C3 ret Summary 0 .data 60 .debug$S 19 .drectve 48 .text$mn c:\asm>
Обратим внимание в дизассемблированном коде на вызов функции WriteFile:
000000000000003E: E8 00 00 00 00 call WriteFile
По адресу 0x3E
в файле располагается код инструкции call
, которая имеет опкод E8
и которая вызывает функцию WriteFile. Причем начиная с
адреса 0x3F
(адрес сразу после опкода инструкции call
) идет ряд нулей 00 00 00 00
. Это адрес релокации или перемещаемый адрес (relocatable address)
То же самое касается и инструкции lea
, которая загружает в регистр RCX адрес переменной text:
0000000000000025: 48 8D 15 00 00 00 00 lea rdx,[text]
Здесь после 3-х байтного опкода инструкции lea
- 48 8D 15
идут четыре байта 00 00 00 00
, которые должны указывать на
адрес переменной text. Но опять же адрес перемнной text совсем не 00 00 00 00
. И в данном случае это также перемещаемый адрес.
Теперь используем опцию /relocations
для просмотра релокаций:
c:\asm>dumpbin hello.obj /relocations RELOCATIONS #1 Symbol Symbol Offset Type Applied To Index Name -------- ---------------- ----------------- -------- ------ 0000001E REL32 00000000 B GetStdHandle 00000028 REL32 00000000 C text 0000003F REL32 00000000 A WriteFile Summary 0 .data 60 .debug$S 19 .drectve 48 .text$mn c:\asm>
Здесь мы видим 3 записи о релокации. В столбце Offset указано смещение в байтах в файле, где
должны быть применены перемещения/релокации. Так, инструкция lea
начинается со смещения 0x25
, поэтому фактическое смещение, куда будет помещаться адрес
переменной text, равно смещению 0x28
(адрес инструкции lea плюс размер ее опкода - 3 байта).
Точно так же инструкция call начинается со смещения 0x3E
, поэтому адрес фактической подпрограммы, которую необходимо изменить, находится на 1 байт дальше -
по адресу 0x3F
.
При компоновке исполняемого файла компоновщик исправляет эти адреса и вставляет вместо них реальные. Например, дизассемблируем файл hello.exe, в который скомпонована выше указанная программа:
c:\asm>dumpbin hello.exe /disasm Microsoft (R) COFF/PE Dumper Version 14.36.32532.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file hello.exe File Type: EXECUTABLE IMAGE 0000000140001000: 48 0000000140001001: 65 6C ins byte ptr [rdi],dx 0000000140001003: 6C ins byte ptr [rdi],dx 0000000140001004: 6F outs dx,dword ptr [rsi] 0000000140001005: 20 4D 45 and byte ptr [rbp+45h],cl 0000000140001008: 54 push rsp 0000000140001009: 41 000000014000100A: 4E 000000014000100B: 49 54 push r12 000000014000100D: 2E 000000014000100E: 43 000000014000100F: 4F 0000000140001010: 4D 21 48 83 and qword ptr [r8-7Dh],r9 0000000140001014: EC in al,dx 0000000140001015: 30 48 C7 xor byte ptr [rax-39h],cl 0000000140001018: C1 F5 FF sal ebp,0FFh 000000014000101B: FF 000000014000101C: FF E8 jmp rax 000000014000101E: 2C 00 sub al,0 0000000140001020: 00 00 add byte ptr [rax],al 0000000140001022: 48 8B C8 mov rcx,rax 0000000140001025: 48 8D 15 D4 FF FF lea rdx,[0000000140001000h] FF 000000014000102C: 41 B8 12 00 00 00 mov r8d,12h 0000000140001032: 4D 33 C9 xor r9,r9 0000000140001035: 48 C7 44 24 20 00 mov qword ptr [rsp+20h],0 00 00 00 000000014000103E: E8 05 00 00 00 call 0000000140001048 0000000140001043: 48 83 C4 30 add rsp,30h 0000000140001047: C3 ret 0000000140001048: FF 25 BA 0F 00 00 jmp qword ptr [0000000140002008h] 000000014000104E: FF 25 AC 0F 00 00 jmp qword ptr [0000000140002000h] Summary 1000 .rdata 1000 .text
Здесь уже в качестве адреса переменной text будет использоваться совсем другой - исправленный адрес, а не стандартные 00 00 00 00
:
0000000140001025: 48 8D 15 D4 FF FF lea rdx,[0000000140001000h]
То же самое касается вызова функции WriteFile:
000000014000103E: E8 05 00 00 00 call 0000000140001048