Руководство по ассемблеру MASM для архитектуры Intel x86-64

Тема в разделе "MASM", создана пользователем Mikl___, 28 окт 2026.

  1. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.406

    Взаимодействие с WinAPI

    Чтение текстового файла

    Для чтения данных мы можем использовать функцию ReadFile, которая рассматривалась на примерах консольного ввода. Только в данном случае ввод будет идти из файла, для чего функции ReadFile передается дескриптор открытого файла. Допустим, нам надо считать файл test.txt, который имеет следующее содержимое:
    Код (Text):
    1. Hello METANIT.COM1
    2. Hello METANIT.COM2
    3. Hello METANIT.COM3
    4. Hello METANIT.COM4
    5. Hello METANIT.COM5
    Для считывания этого файла и вывода считанных данных на консоль определим следующую программу:
    Код (ASM):
    1. includelib kernel32.lib
    2. extrn GetStdHandle: proc
    3. extrn ReadFile: proc
    4. extrn WriteFile: proc
    5. extrn CreateFileA: proc
    6. extrn CloseHandle: proc
    7. .data
    8. buffer byte 32 dup (?)  ; буфер для чтения
    9. bufferLen = $ - buffer  ; длина буфера
    10. filename byte "test.txt"  ; имя файла
    11. fHandle qword 0  ; дескриптор файла
    12. stdout qword 0  ; дескриптор консольного вывода
    13. stdoutSet byte 0  ; установлен ли дескриптор консольного вывода
    14. .code
    15. ; Параметры:
    16. ; RSI - адрес строки
    17. ; RCX - длина строки
    18. ; RAX - дескриптор файла
    19. ; Результат - через RAX возвращаем количество записанных байтов
    20. write proc
    21.  sub  rsp, 56
    22.  mov rdx, rsi  ; Второй параметр - строка
    23.  mov r8, rcx  ; Третий параметр - длина строки
    24.  mov  rcx, rax  ; Первый параметр WriteFile - в регистр RCX помещаем дескриптор файла - консольного вывода
    25.  lea  r9, bytesWritten  ; Четвертый параметр WriteFile - адрес для получения записанных байтов
    26.  mov qword ptr [rsp + 32], 0  ; Пятый параметр WriteFile
    27.  call WriteFile
    28.  
    29.  test rax, rax ; проверяем на наличие ошибки
    30.  mov eax, bytesWritten ; если все нормально, помещаем в RAX количество записанных байтов
    31.  jnz exit
    32.  mov rax, -1 ; Возвращаем через RAX код ошибки
    33. exit:
    34.  add  rsp, 56
    35.  ret
    36. bytesWritten equ [rsp+40]
    37. write endp
    38. ; Процедура вывода произвольной строки на консоль
    39. ; Параметры
    40. ; RSI - адрес строки
    41. ; RCX - количество символов
    42. ; Результат - в RAX количество записанных байтов или -1, если произошла ошибка
    43. writeToConsole proc
    44.  cmp stdoutSet, 1
    45.  jz writeData  ; если дескриптор консольного вывода установлен
    46.  sub rsp, 32
    47.  push rcx  ; сохраняем количество символов
    48.  mov rcx, -11  ; Аргумент для GetStdHandle - STD_OUTPUT
    49.  call GetStdHandle  ; вызываем функцию GetStdHandle
    50.  pop rcx  ; восстанавливаем RCX - количество символов
    51.  add rsp, 32
    52.  mov stdout, rax  ; помещаем в stdout дескриптор консольного вывода
    53.  mov stdoutSet, 1  ; дескриптор консольного вывода установлен
    54. writeData:
    55.  mov rax, stdout
    56.  call write
    57.  ret
    58. writeToConsole endp
    59. ; Процедура для считывания данных
    60. ; Параметры:
    61. ; RAX - дескриптор файла,
    62. ; RDI - буфер для считывания
    63. ; RCX - длина буфера
    64. ; Результат - в RAX количетсво записанных байтов или -1, если произошла ошибка
    65. read proc
    66.  sub rsp, 56
    67.  mov r8, rcx  ;Третий параметр ReadFile - размер буфера
    68.  mov rcx, rax ; Первый параметр ReadFile - дескриптор файла
    69.  mov rdx, rdi ; Второй параметр ReadFile - адрес буфера
    70.  lea r9, bytesRead  ; Четвертый параметр ReadFile - количество считанных байтов
    71.  mov qword ptr [rsp + 32], 0  ; Пятый параметр ReadFile - 0
    72.  call ReadFile  ; вызов функции ReadFile
    73.  test rax, rax  ; проверяем на ошибку
    74.  mov eax, bytesRead  ; если ошибки нет, в RAX количество считанных байтов
    75.  jnz exit  ; если в RAX ненулевое значение
    76.  mov rax, -1  ; помещаем -1
    77. exit:
    78.  add rsp, 56
    79.  ret
    80. bytesRead equ [rsp+40] ; область в стеке для хранения количества считанных байтов
    81. read endp
    82. ; Процедура открытия существующего файла
    83. ; Параметр - в RSI - имя файла
    84. ; Результат - в RAX - дескриптор открытого файла
    85. openFile proc
    86.  sub rsp, 56
    87.  mov rcx, rsi ; имя файла
    88.  mov rdx, 0C0000000h ; доступ на чтение
    89.  xor r8, r8 ; Эксклюзивный доступ
    90.  xor r9, r9 ; атрибуты безопасности отсутствуют
    91.  mov r10, 4 ; открыть существующий файл, если он не существует - создать
    92.  mov [rsp + 32], r10
    93.  mov r10, 128  ; обычный файл без атрибутов
    94.  mov [rsp + 40], r10
    95.  mov [rsp + 48], r9  ; NULL - шаблон не используется
    96.  call CreateFileA
    97.  add rsp, 56
    98.  ret
    99. openFile endp
    100. ; Процедура закрытия существующего файла
    101. ; Параметр - в RAX - дескриптор открытого файла
    102. ; Результат - в RAX - состояние операции (ошибка или нет)
    103. closeFile proc
    104.  sub rsp, 40
    105.  mov rcx, rax  ; В RCX помещаем дескриптор файла
    106.  call CloseHandle
    107.  add rsp, 40
    108.  ret
    109. closeFile endp
    110. main proc
    111.  lea rsi, filename
    112.  call openFile  ; открываем файл
    113.  mov fHandle, rax  ; сохраняем дескриптор файла
    114.  
    115.  mov rax, fHandle  ; в RAX дескриптор файла
    116.  lea rdi, buffer
    117.  mov rcx, bufferLen
    118.  call read  ; считываем за раз bufferLen байт в массив buffer
    119.  mov rcx, rax  ; далее передаем считанное количество байт для вывода на консоль
    120.  lea rsi, buffer
    121.  call writeToConsole ; выводим прочитанные данные на консоль
    122.  mov rax, fHandle  ; в RAX дескриптор файла
    123.  call closeFile  ; закрываем файл
    124.  ret
    125. main endp
    126. end
    В процедуре main вызываем процедуру openFile, которая в свою очередь использует функцию CreateFileA для открытия файла и получения его дескриптора. Полученный дескриптор файла сохраняется в переменную fHandle.
    Далее считываем в процедуре read bufferLen байт в массив buffer. Процедура read получает через регистр RAX дескриптор файла и вызывает функцию ReadFile для считывания данных.
    После этого данные считаны в buffer, а в регистре RAX содержится количество считанных байт. Для вывода считанных символов на консоль вызываем процедуру writeToConsole, которая в свою очередь получает дескриптор консольного вывода с помощью функции GetStdHandle для собственно вывода данных вызывает процедуру write. Процедура write для вывода данных получает дескриптор консольного вывода и вызывает функцию WriteFile.
    После завершения считывания вызываем процедуру closeFile для закрытия файла.
    Но в данном случае мы сталкиваемся с проблемой. Если мы скомпилируем и запустим приложение, то увидим, что файл считывается не полностью:
    c:\asm>hello
    Hello METANIT.COM1
    Hello WASM.IN
    c:\asm>

    Мы имеем ограниченный размер буфера (в примере выше - 32 байта), однако мы не знаем точный размер файла. Вполне возможно он будет гораздо больше размера буфера. В этом случае мы можем считывать часть файла в буфер в цикле, пока не считаем весь файл. Для этого изменим процедуру main следующим образом:
    Код (ASM):
    1. main proc
    2.  lea rsi, filename
    3.  call openFile  ; открываем файл
    4.  mov fHandle, rax  ; сохраняем дескриптор файла
    5.  ; считываем в цикле за раз bufferLen байт
    6. readLoop:
    7.  mov rax, fHandle  ; в RAX дескриптор файла
    8.  lea rdi, buffer
    9.  mov rcx, bufferLen
    10.  call read  ; считываем bufferLen байт в buffer
    11.  test eax, eax  ; проверяем количество считанных байт
    12.  jz done  ; если считано 0 байт, завершаем считывание
    13.  mov rcx, rax  ; иначе передаем количество байт для вывода на консоль
    14.  lea rsi, buffer
    15.  call writeToConsole ; выводим прочитанные данные на консоль
    16.  jmp readLoop
    17.  
    18. done:
    19.  mov rax, fHandle  ; в RAX дескриптор файла
    20.  call closeFile  ; закрываем файл
    21.  ret
    22. main endp
    Теперь считывание и вывод символов проецируется на метку readLoop. За раз считываем bufferLen байт (32 байта) и сохраняем в буфер и сразу выводим их на консоль. Если количестве считанных байт равно 0, значит, весь файл считан, поэтому выходим из цикла и переходим к закрытию файла. Таким образом, мы считаем весь файл. Пример работы приложения:
    c:\asm>hello.exe
    Hello METANIT.COM1
    Hello METANIT.COM2
    Hello METANIT.COM3
    Hello METANIT.COM4
    Hello METANIT.COM5
     
    Последнее редактирование: 1 ноя 2026 в 09:02
  2. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.406

    Взаимодействие с WinAPI

    Графическое приложение. Окно сообщения


    Для создания графического приложения в Windows применяется библиотека user32.lib. Рассмотрим создание простейшего графического приложения на примере диалогового окна. Для создания диалогового окна в WinAPI можно использовать функцию MessageBoxA, которая имеет следующее определение на языке C++:
    Код (C):
    1. int MessageBoxA(
    2.  [in, optional] HWND  hWnd,
    3.  [in, optional] LPCSTR lpText,
    4.  [in, optional] LPCSTR lpCaption,
    5.  [in]  UINT  uType
    6. );
    Первый параметр - hWnd представляет дескриптор родительского окна. Если родительское окно не применяется, то этот параметр имеет значение NULL. Второй параметр представляет текст отображаемого в окне сообщения, а третий параметр - текст заголовка окна. Четвертый параметр - числовое значение, которое указывает, какие кнопки будут использоваться на окне.
    Рассмотрим создание графического окна на примере. Пусть у нас есть файл hello.asm со следующим кодом:
    Код (ASM):
    1. includelib user32.lib  ; подключаем библиотеку user32.lib
    2. externdef MessageBoxA:PROC  ; подключаем функцию MessageBoxA
    3. .data
    4. message byte "Hello WASM.IN!",0  ; сообщение в окне
    5. win_title byte "Asm Window", 0  ; заголовок окна
    6. .code
    7. main proc
    8.  sub  rsp, 40  ; 32 (shadows storage) + 8 (выравнивание)
    9.  xor rcx, rcx  ; HWin = NULL
    10.  lea rdx, message  ; отображаемое сообщение
    11.  lea r8, win_title  ; Заголовок диалогового окна
    12.  mov r9d, 0  ; 0 - кнопка "OK"
    13.  call MessageBoxA  ; вызываем функцию MessageBoxA
    14.  add  rsp, 40
    15.  ret
    16. main endp
    17. end
    Вначале подключаем библиотеку user32.lib
    Код (ASM):
    1. includelib user32.lib
    Далее подключаем внешнюю функцию MessageBoxA:
    Код (ASM):
    1. externdef MessageBoxA:PROC
    Поскольку родительское окно не используется, то для первого параметра в регистр RCX передаем значение 0:
    Код (ASM):
    1. xor rcx, rcx
    Второй и третий параметры, которые передаются соответственно через регистры RDX и R8, принимают в качестве теста сообщения и заголовка адреса переменных message и win_title:
    Код (ASM):
    1. lea rdx, message
    2. lea r8, win_title
    В качестве кнопки последнему параметру через регистр R9 передаем число 0. Это значит, что окно будет иметь одну кнопку "OK":
    Код (ASM):
    1. mov r9, 0
    Можно передать и другие значения. Например, число 1 устанавливает кнопки OK и Cancel, а число 4 - кнопки "Yes" и "No". Полный список значений можно посмотреть в документации.
    Для компиляции программу компоновщику надо передать параметр /subsystem:windows. Данный параметр указывает, что будет создаваться графическое приложение. Так, скомпилируем приложение с помощью следующей команды:
    ml64 hello.asm /link /subsystem:windows /entry:main

    В итоге после запуска скомпилированного приложения мы увидим следующее окно:
     
    Последнее редактирование: 1 ноя 2026 в 09:11
  3. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.406

    Исследование и дизассемблирование файлов

    Формат COFF-файла

    Объектный файл представляет наиболее популярный формат, в который компилятор компилирует исходный код на некотором языке программирования. Объектный файл обычно начинается с заголовка. Заголовок содержит определенную информацию о подписи, которая идентифицирует файл как действительный объектный файл, а также несколько других значений, которые определяют расположение различных структур данных внутри файлы. Кроме заголовка объектный файл обычно делится на несколько разделов, каждый из которых содержит данные приложения, машинные инструкции, записи таблицы символов, данные релокаций/перемещений и другие метаданные. В некоторых случаях фактический код и данные программы представляют собой лишь небольшую часть всего файла объектного кода.
    Существуют различные форматы объектных файлов, но один из наиболее распространенных и популярных - формат COFF (Common Object File Format)
    [​IMG]
    Однако конечным файлом программы, как правило, является не объектный файл, а исполняемый файл в специальном формате. Большинство операционных систем используют специальный формат исполняемых файлов. Часто формат исполняемого файла подобен формату объектного файла, принципиальное отличие состоит в том, что в исполняемом файле обычно нет неразрешенных внешних ссылок.
    В дополнение к машинному коду и двоичным данным исполняемые файлы содержат другие метаданные, в том числе информацию об отладке, информацию о компоновке динамически подключаемых библиотек и сведения о том, как операционная система должна загружать различные разделы файла в память. В зависимости от процессора и ОС исполняемые файлы могут также содержать информацию о перемещениях/релокациях, чтобы ОС могла исправлять абсолютные адреса при загрузке файла в память. Обычно объектные и исполнимые файлы содержат одинаковую информацию и поэтому их форматы часто во многом совпадают. Например, Linux использует формат ELF (Executable and Linkable Format), Windows - формат PE, который во многом аналогичен формату COFF.
    Заголовок файла COFF
    Вначале каждого файла COFF идет заголовок файла COFF. На ОС Windows и Linux он имеет следующую структуру:
    Код (C):
    1. // версия для Windows в winnt.h
    2. typedef struct _IMAGE_FILE_HEADER {
    3.  WORD Machine;
    4.  WORD NumberOfSections;
    5.  DWORD TimeDateStamp;
    6.  DWORD PointerToSymbolTable;
    7.  DWORD NumberOfSymbols;
    8.  WORD SizeOfOptionalHeader;
    9.  WORD Characteristics;
    10. } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
    11. // версия для Linux в coff.h
    12. struct COFF_filehdr {
    13.  char f_magic[2]; /* число magic number */
    14.  char f_nscns[2]; /* количество разделов */
    15.  char f_timdat[4]; /* время и дата */
    16.  char f_symptr[4]; /* указатель на таблицу символов symtab */
    17.  char f_nsyms[4]; /* число записей в таблице символов symtab*/
    18.  char f_opthdr[2]; /* размер дополнительного заголовка - sizeof(optional hdr) */
    19.  char f_flags[2]; /* флаги */
    20. };
    Основные элементы заголовка файла:
    • f_magic/Machine
      Идентифицирует систему, для которой был создан этот файл COFF. Это значение является сигнатурой, которая указывает, содержит ли COFF-файл данные или машинные инструкции, подходящие для текущей операционной системы и процессора. Возможные значения:
      Значе-
      ние
      ОписаниеЗначе-
      ние
      Описание
      14ChIntel 3861D3hMatsushita AM33
      8664hx86-64 / AMD AMD641F0hPowerPC little endian
      162hMIPS R30001F1hPowerPC with floating-point support
      168hMIPS R10000200hIntel IA64
      169hMIPS little endian WCI v2266hMIPS16
      183hold Alpha AXP268hMotorola 68000 series
      184hAlpha AXP284hAlpha AXP 64-bit
      1A2hHitachi SH3366hMIPS with FPU
      1A3hHitachi SH3 DSP466hMIPS16 with FPU
      1A6hHitachi SH40EBChEFI bytecode
      1A8hHitachi SH59041hMitsubishi M32R little endian
      1C0hARM little endian0AA64hARM64 little endian
      1C2hThumb0C0EEhCLR pure MSIL
      1C4hARMv7

    • f_nscns/NumberOfSections
      Количество разделов в файле
    • f_timdat/TimeDateStamp
      Содержит метку времени в стиле Unix (количество секунд с 1 января 1970 г.), которая указывает на дату и время создания файла.
    • f_symptr/PointerToSymbolTable
      Содержит смещение в файле (то есть количество байтов от начала файла), которое указывает, где в файле начинается таблица символов. Таблица символов — это структура данных, в которой указаны имена и другая информация обо всех внешних, глобальных и других символах, используемых кодом в файле COFF. Линкеры используют таблицу символов для разрешения внешних ссылок.
    • f_nsyms/NumberOfSymbols
      Количество записей в таблице символов.
    • f_opthdr/SizeOfOptionalHeader
      Указывает на размер необязательного заголовка, который следует сразу за заголовком файла (то есть первый байт необязательного заголовка следует сразу за полем f_flags/Characteristics в структуре заголовка файла). Компоновщик или другая программа может использовать это значение, чтобы определить, где в файле заканчивается необязательный заголовок и начинаются заголовки разделов. Заголовки разделов следуют сразу за необязательным заголовком, но размер необязательного заголовка не является фиксированным. Различные реализации файла COFF могут иметь разные необязательные структуры заголовков. Если необязательный заголовок отсутствует в файле COFF, поле f_opthdr/SizeOfOptionalHeader будет содержать ноль, а заголовок первого раздела будет следовать сразу за заголовком файла.
    • f_flags/Characteristics
      Битовая маска, которая содержит определенные логические флаги, например, является ли файл исполняемым, содержит ли он информацию о символах и содержит ли он информацию о номере строки (для использования отладчиками).
    Понятный разбор темы «Системный подход»: важные детали и полезные примеры.
    Дополнительный заголовок
    Необязательный заголовок COFF содержит информацию, относящуюся к исполняемым файлам. Этот заголовок может отсутствовать, если файл содержит объектный код, который не является исполняемым. Данный необязательный заголовок всегда присутствует в файлах Linux COFF и Microsoft PE/COFF, даже если файл не является исполняемым. Структуры Windows и Linux для этого необязательного заголовка файла принимают следующие формы в C:
    Код (C):
    1. // Microsoft PE/COFF Optional Header (winnt.h)
    2. typedef struct _IMAGE_OPTIONAL_HEADER {
    3.  //
    4.  // Стандартные поля
    5.  //
    6.  WORD Magic;
    7.  BYTE MajorLinkerVersion;
    8.  BYTE MinorLinkerVersion;
    9.  DWORD SizeOfCode;
    10.  DWORD SizeOfInitializedData;
    11.  DWORD SizeOfUninitializedData;
    12.  DWORD AddressOfEntryPoint;
    13.  DWORD BaseOfCode;
    14.  DWORD BaseOfData;
    15.  //
    16.  // Дополнительные поля для NT
    17.  //
    18.  DWORD ImageBase;
    19.  DWORD SectionAlignment;
    20.  DWORD FileAlignment;
    21.  WORD MajorOperatingSystemVersion;
    22.  WORD MinorOperatingSystemVersion;
    23.  WORD MajorImageVersion;
    24.  WORD MinorImageVersion;
    25.  WORD MajorSubsystemVersion;
    26.  WORD MinorSubsystemVersion;
    27.  DWORD Win32VersionValue;
    28.  DWORD SizeOfImage;
    29.  DWORD SizeOfHeaders;
    30.  DWORD CheckSum;
    31.  WORD Subsystem;
    32.  WORD DllCharacteristics;
    33.  DWORD SizeOfStackReserve;
    34.  DWORD SizeOfStackCommit;
    35.  DWORD SizeOfHeapReserve;
    36.  DWORD SizeOfHeapCommit;
    37.  DWORD LoaderFlags;
    38.  DWORD NumberOfRvaAndSizes;
    39.  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
    40. } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
    41. // Linux/COFF Optional Header format (from coff.h)
    42. typedef struct
    43. {
    44.  char magic[2]; /* тип файла */
    45.  char vstamp[2]; /* версия */
    46.  char tsize[4]; /* размер раздела .text/.code */
    47.  char dsize[4]; /* размер раздела .data с инициализированными данными */
    48.  char bsize[4]; /* размер раздела .bss с неинициализированными данными */
    49.  char entry[4]; /* точка вхожа */
    50.  char text_start[4]; /* начало раздела .text */
    51.  char data_start[4]; /* начало раздела .data */
    52. } COFF_AOUTHDR;
    Первое, на что следует обратить внимание, что эти структуры не идентичны. Версия для Microsoft содержит значительно больше информации, чем версия для Linux. Поле f_opthdr/SizeOfOptionalHeader существует в заголовке файла для определения фактического размера необязательного заголовка. Основные компоненты заголовка:
    • magic/Magic
      Предоставляет еще одно значение подписи для файла COFF. Это значение подписи определяет тип файла (то есть COFF), а не систему, в которой он был создан. Линкеры используют значение этого поля, чтобы определить, действительно ли они работают с COFF-файлом (а не с каким-то произвольным файлом, который может запутать компоновщика).
    • vstamp/MajorLinkerVersion/MinorLinkerVersion
      Указывает номер версии формата COFF, чтобы компоновщик, написанный для более старой версии формата файла, не пытался обрабатывать файлы, предназначенные для более новых компоновщиков.
    • tsize/SizeOfCode
      Содержит размер раздела кода (.code/.text). Если файл COFF содержит более одного раздела кода, значение этого поля не определено, хотя обычно оно определяет размер первого раздела кода
    • dsize/SizeOfInitializedData
      Содержит размер раздела данных (раздел .data). Это поле не определено, если в файле есть два или более разделов данных. Обычно в этом поле указывается размер первого раздела данных, если разделов данных несколько.
    • bsize/SizeOfUninitializedData
      Содержит размер раздела .BSS — раздела неинициализированных данных. Это поле может быть не определено, если разделов .BSS два и более, в таких случаях в этом поле обычно указывается размер первого раздела BSS в файле.
    • entry/AddressOfEntryPoint
      Содержит начальный адрес исполняемой программы (точку входа). Это не фактический адрес памяти, а смещение относительно начала файла.
    • text_start/BaseOfCode
      Содержит начальный адрес, где начинается раздел кода. При наличии двух или более разделов кода это поле не определено, но обычно указывает смещение к первому разделу кода.
    • data_start/BaseOfData
      Содержит начальный адрес, где начинается раздел данных. При наличии двух или более разделов данных это поле не определено, но обычно оно указывает смещение к первому разделу данных.
    Для хранения адреса начала раздела .bss с неинициализированными данными отдельного поля не требуется. Формат файла COFF предполагает, что загрузчик операционной системы автоматически выделяет память для раздела BSS, когда программа загружается в память. А некоторые компиляторы фактически объединяют разделы BSS и DATA вместе из соображений производительности.
    Заголовки разделов
    Заголовки разделов следуют за необязательным заголовком в файле COFF. В отличие от файла и дополнительных заголовков, файл COFF может содержать несколько заголовков разделов. Поле f_nscns/NumberOfSections в заголовке файла указывает точное количество заголовков разделов (и, следовательно, разделов) в COFF-файле.
    Поскольку размер необязательного заголовка является переменным (и, по сути, может быть даже равен 0, если он отсутствует), вам нужно добавить поле f_opthdr/SizeOfOptionalHeader в заголовок файла к размеру заголовка файла, чтобы получить начальное смещение заголовка первого раздела.
    Заголовки разделов имеют фиксированный размер, поэтому, получив адрес первого заголовка раздела, можно легко вычислить адрес любого другого, умножив желаемый номер заголовка раздела на размер заголовка раздела и добавив результат к базовому смещению заголовка первого раздела.
    Определения структур на языке C для заголовков разделов Windows и Linux:
    Код (C):
    1. // определение для Windows  (из файла winnt.h)
    2. typedef struct _IMAGE_SECTION_HEADER {
    3.  BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
    4.  union {
    5.  DWORD PhysicalAddress;
    6.  DWORD VirtualSize;
    7.  } Misc;
    8.  DWORD VirtualAddress;
    9.  DWORD SizeOfRawData;
    10.  DWORD PointerToRawData;
    11.  DWORD PointerToRelocations;
    12.  DWORD PointerToLinenumbers;
    13.  WORD NumberOfRelocations;
    14.  WORD NumberOfLinenumbers;
    15.  DWORD Characteristics;
    16. } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
    17. // Определение для Linux (из файла coff.h)
    18. struct COFF_scnhdr
    19. {
    20.  char s_name[8]; /* название раздела */
    21.  char s_paddr[4]; /* физический адрес */
    22.  char s_vaddr[4]; /* виртуальный адрес */
    23.  char s_size[4]; /* размер раздела */
    24.  char s_scnptr[4]; /* указатель файла на непосредственные данные раздела */
    25.  char s_relptr[4]; /* указатель файла на релокацию */
    26.  char s_lnnoptr[4]; /* указатель файла на номера строк */
    27.  char s_nreloc[2]; /* количество записей о релокации */
    28.  char s_nlnno[2]; /* количество записей номеров строк */
    29.  char s_flags[4]; /* флаги */
    30. };
    Компоненты заголовка раздела
    • s_name/Name
      хранит имя раздела. Как видно из определения Linux, это поле ограничено восемью символами, и, соответственно, имена разделов будут иметь длину не более восьми символов. (Обычно, если в исходном файле указано более длинное имя, компилятор/ассемблер усекает его до 8 символов при создании COFF-файла.) Если длина имени раздела составляет ровно восемь символов, эти восемь символов займут все 8 байтов этого поля, нулевой байт, который обычно служит в качестве завершения строки, не будет добавляться. Если имя раздела короче восьми символов, за именем будет следовать концевой нулевой байт.
      Значение этого поля часто имеет вид .text, CODE, .data или DATA. Но стоит учитывать, что имя не определяет тип раздела. Так, можно создать раздел .code/.text и назвать его "DATA", либо создать раздел данных (.data) и назвать его ".text" или "CODE". Поле s_flags/Characteristics определяет фактический тип этого раздела.
    • s_paddr/PhysicalAddress/VirtualSize
      Редко используется большинством инструментов. В Unix-подобных операционных системах (таких как Linux) для этого поля обычно устанавливается то же значение, что и для поля VirtualAddress. Различные инструменты Windows устанавливают для этого поля разные значения (включая ноль).
    • s_vaddr/VirtualAddress
      Содержит адрес загрузки секции в память (то есть ее адрес виртуальной памяти). Обратите внимание, что это адрес памяти времени выполнения, а не смещение в файле. Загрузчик программы использует это значение, чтобы определить, куда загрузить раздел в память.
    • s_size/SizeOfRawData
      Хранит размер раздела в байтах.
    • s_scnptr/PointerToRawData
      Хранит смещение файла до начала данных раздела
    • s_reptr/PointerToRelocations
      Хранит смещение файла до списка релокаций для этого раздела.
    • s_lnnoptr/PointerToLinenumbers
      Хранит смещение файла к записям номеров строк для текущего раздела.
    • s_nreloc/NumberOfRelocations
      Хранит количество записей перемещения для этого раздела.
    • s_nlnno/NumberOfLinenumbers
      Хранит количество записей номеров строк для раздела. Информация о номере строки используется отладчиками
    • s_flags/Characteristics
      Битовая маска, которая хранит характеристики этого раздела. В частности, в этом поле будет указано, требует ли раздел перемещения/релокаций, содержит ли он код, доступен ли он только для чтения и т. д.
    Дополнительный заголовок
    Необязательный заголовок COFF содержит информацию, относящуюся к исполняемым файлам. Этот заголовок может отсутствовать, если файл содержит объектный код, который не является исполняемым. Данный необязательный заголовок всегда присутствует в файлах Linux COFF и Microsoft PE/COFF, даже если файл не является исполняемым. Структуры Windows и Linux для этого необязательного заголовка файла принимают следующие формы в C:
    Код (C):
    1. // Microsoft PE/COFF Optional Header (winnt.h)
    2. typedef struct _IMAGE_OPTIONAL_HEADER {
    3.  //
    4.  // Стандартные поля
    5.  //
    6.  WORD Magic;
    7.  BYTE MajorLinkerVersion;
    8.  BYTE MinorLinkerVersion;
    9.  DWORD SizeOfCode;
    10.  DWORD SizeOfInitializedData;
    11.  DWORD SizeOfUninitializedData;
    12.  DWORD AddressOfEntryPoint;
    13.  DWORD BaseOfCode;
    14.  DWORD BaseOfData;
    15.  //
    16.  // Дополнительные поля для NT
    17.  //
    18.  DWORD ImageBase;
    19.  DWORD SectionAlignment;
    20.  DWORD FileAlignment;
    21.  WORD MajorOperatingSystemVersion;
    22.  WORD MinorOperatingSystemVersion;
    23.  WORD MajorImageVersion;
    24.  WORD MinorImageVersion;
    25.  WORD MajorSubsystemVersion;
    26.  WORD MinorSubsystemVersion;
    27.  DWORD Win32VersionValue;
    28.  DWORD SizeOfImage;
    29.  DWORD SizeOfHeaders;
    30.  DWORD CheckSum;
    31.  WORD Subsystem;
    32.  WORD DllCharacteristics;
    33.  DWORD SizeOfStackReserve;
    34.  DWORD SizeOfStackCommit;
    35.  DWORD SizeOfHeapReserve;
    36.  DWORD SizeOfHeapCommit;
    37.  DWORD LoaderFlags;
    38.  DWORD NumberOfRvaAndSizes;
    39.  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
    40. } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
    41. // Linux/COFF Optional Header format (from coff.h)
    42. typedef struct
    43. {
    44.  char magic[2]; /* тип файла */
    45.  char vstamp[2]; /* версия */
    46.  char tsize[4]; /* размер раздела .text/.code */
    47.  char dsize[4]; /* размер раздела .data с инициализированными данными */
    48.  char bsize[4]; /* размер раздела .bss с неинициализированными данными */
    49.  char entry[4]; /* точка вхожа */
    50.  char text_start[4]; /* начало раздела .text */
    51.  char data_start[4]; /* начало раздела .data */
    52. } COFF_AOUTHDR;
    Первое, на что следует обратить внимание, что эти структуры не идентичны. Версия для Microsoft содержит значительно больше информации, чем версия для Linux. Поле f_opthdr/SizeOfOptionalHeader существует в заголовке файла для определения фактического размера необязательного заголовка. Основные компоненты заголовка:
    • magic/Magic
      Предоставляет еще одно значение подписи для файла COFF. Это значение подписи определяет тип файла (то есть COFF), а не систему, в которой он был создан. Линкеры используют значение этого поля, чтобы определить, действительно ли они работают с COFF-файлом (а не с каким-то произвольным файлом, который может запутать компоновщика).
    • vstamp/MajorLinkerVersion/MinorLinkerVersion
      Указывает номер версии формата COFF, чтобы компоновщик, написанный для более старой версии формата файла, не пытался обрабатывать файлы, предназначенные для более новых компоновщиков.
    • tsize/SizeOfCode
      Содержит размер раздела кода (.code/.text). Если файл COFF содержит более одного раздела кода, значение этого поля не определено, хотя обычно оно определяет размер первого раздела кода
    • dsize/SizeOfInitializedData
      Содержит размер раздела данных (раздел .data). Это поле не определено, если в файле есть два или более разделов данных. Обычно в этом поле указывается размер первого раздела данных, если разделов данных несколько.
    • bsize/SizeOfUninitializedData
      Содержит размер раздела .BSS — раздела неинициализированных данных. Это поле может быть не определено, если разделов .BSS два и более, в таких случаях в этом поле обычно указывается размер первого раздела BSS в файле.
    • entry/AddressOfEntryPoint
      Содержит начальный адрес исполняемой программы (точку входа). Это не фактический адрес памяти, а смещение относительно начала файла.
    • text_start/BaseOfCode
      Содержит начальный адрес, где начинается раздел кода. При наличии двух или более разделов кода это поле не определено, но обычно указывает смещение к первому разделу кода.
    • data_start/BaseOfData
      Содержит начальный адрес, где начинается раздел данных. При наличии двух или более разделов данных это поле не определено, но обычно оно указывает смещение к первому разделу данных.
    Для хранения адреса начала раздела .bss с неинициализированными данными отдельного поля не требуется. Формат файла COFF предполагает, что загрузчик операционной системы автоматически выделяет память для раздела BSS, когда программа загружается в память. А некоторые компиляторы фактически объединяют разделы BSS и DATA вместе из соображений производительности.
    Заголовки разделов
    Заголовки разделов следуют за необязательным заголовком в файле COFF. В отличие от файла и дополнительных заголовков, файл COFF может содержать несколько заголовков разделов. Поле f_nscns/NumberOfSections в заголовке файла указывает точное количество заголовков разделов (и, следовательно, разделов) в COFF-файле.
    Поскольку размер необязательного заголовка является переменным (и, по сути, может быть даже равен 0, если он отсутствует), вам нужно добавить поле f_opthdr/SizeOfOptionalHeader в заголовок файла к размеру заголовка файла, чтобы получить начальное смещение заголовка первого раздела.
    Заголовки разделов имеют фиксированный размер, поэтому, получив адрес первого заголовка раздела, можно легко вычислить адрес любого другого, умножив желаемый номер заголовка раздела на размер заголовка раздела и добавив результат к базовому смещению заголовка первого раздела.
    Определения структур на языке C для заголовков разделов Windows и Linux:
    Код (C):
    1. // определение для Windows  (из файла winnt.h)
    2. typedef struct _IMAGE_SECTION_HEADER {
    3.  BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
    4.  union {
    5.  DWORD PhysicalAddress;
    6.  DWORD VirtualSize;
    7.  } Misc;
    8.  DWORD VirtualAddress;
    9.  DWORD SizeOfRawData;
    10.  DWORD PointerToRawData;
    11.  DWORD PointerToRelocations;
    12.  DWORD PointerToLinenumbers;
    13.  WORD NumberOfRelocations;
    14.  WORD NumberOfLinenumbers;
    15.  DWORD Characteristics;
    16. } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
    17. // Определение для Linux (из файла coff.h)
    18. struct COFF_scnhdr
    19. {
    20.  char s_name[8]; /* название раздела */
    21.  char s_paddr[4]; /* физический адрес */
    22.  char s_vaddr[4]; /* виртуальный адрес */
    23.  char s_size[4]; /* размер раздела */
    24.  char s_scnptr[4]; /* указатель файла на непосредственные данные раздела */
    25.  char s_relptr[4]; /* указатель файла на релокацию */
    26.  char s_lnnoptr[4]; /* указатель файла на номера строк */
    27.  char s_nreloc[2]; /* количество записей о релокации */
    28.  char s_nlnno[2]; /* количество записей номеров строк */
    29.  char s_flags[4]; /* флаги */
    30. };
    Компоненты заголовка раздела
    • s_name/Name
      хранит имя раздела. Как видно из определения Linux, это поле ограничено восемью символами, и, соответственно, имена разделов будут иметь длину не более восьми символов. (Обычно, если в исходном файле указано более длинное имя, компилятор/ассемблер усекает его до 8 символов при создании COFF-файла.) Если длина имени раздела составляет ровно восемь символов, эти восемь символов займут все 8 байтов этого поля, нулевой байт, который обычно служит в качестве завершения строки, не будет добавляться. Если имя раздела короче восьми символов, за именем будет следовать концевой нулевой байт.
      Значение этого поля часто имеет вид .text, CODE, .data или DATA. Но стоит учитывать, что имя не определяет тип раздела. Так, можно создать раздел .code/.text и назвать его "DATA", либо создать раздел данных (.data) и назвать его ".text" или "CODE". Поле s_flags/Characteristics определяет фактический тип этого раздела.
    • s_paddr/PhysicalAddress/VirtualSize
      Редко используется большинством инструментов. В Unix-подобных операционных системах (таких как Linux) для этого поля обычно устанавливается то же значение, что и для поля VirtualAddress. Различные инструменты Windows устанавливают для этого поля разные значения (включая ноль).
    • s_vaddr/VirtualAddress
      Содержит адрес загрузки секции в память (то есть ее адрес виртуальной памяти). Обратите внимание, что это адрес памяти времени выполнения, а не смещение в файле. Загрузчик программы использует это значение, чтобы определить, куда загрузить раздел в память.
    • s_size/SizeOfRawData
      Хранит размер раздела в байтах.
    • s_scnptr/PointerToRawData
      Хранит смещение файла до начала данных раздела
    • s_reptr/PointerToRelocations
      Хранит смещение файла до списка релокаций для этого раздела.
    • s_lnnoptr/PointerToLinenumbers
      Хранит смещение файла к записям номеров строк для текущего раздела.
    • s_nreloc/NumberOfRelocations
      Хранит количество записей перемещения для этого раздела.
    • s_nlnno/NumberOfLinenumbers
      Хранит количество записей номеров строк для раздела. Информация о номере строки используется отладчиками
    • s_flags/Characteristics
      Битовая маска, которая хранит характеристики этого раздела. В частности, в этом поле будет указано, требует ли раздел перемещения/релокаций, содержит ли он код, доступен ли он только для чтения и т. д.
     
    Последнее редактирование: 1 ноя 2026 в 11:05
  4. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.406

    Исследование и дизассемблирование файлов

    Исследование файла с помощью dumpbin

    Для исследования сгенерированного объектного/исполняемого файла можно использовать ряд утилит. Так, 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

    В качестве примера возьмем простейшую программу на ассемблере:
    Код (ASM):
    1. .data
    2. num1 dword 22
    3. num2 dword 32
    4. num3 dword ?
    5. .code
    6. main proc
    7.  mov rax, 20
    8.  mov rbx, 10
    9.  add rax, rbx
    10.  ret
    11. main endp
    12. end
    Пусть программа располагается в файле hello.asm, и при компиляции создается объектный файл hello.obj.
    Проект бизнес-класса рядом с набережной. Успевайте забронировать с выгодой
    /all
    Параметр /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
    Опция /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
    Параметр /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
    Параметр /imports выводит список всех внешних символов, которые операционная система должна предоставлять при загрузке программы в память.
    /relocations
    Параметр /relocations выводит список всех релокаций/перемещений в файле. Грубо говоря, релокации представляют замену в программе некоторых адресов по умолчанию на реальные адреса. Часто релокации предполагают перемещение в программу адресов внешних функций. Рассмотрим релокации на другом примере. Пусть у нас будет программа, которая использует внешние функции для вывода сообщения на консоль:
    Код (ASM):
    1. includelib kernel32.lib  ; подключаем библиотеку kernel32.lib
    2. ; подключаем функции WriteFile и GetStdHandle
    3. extrn WriteFile: PROC
    4. extrn GetStdHandle: PROC
    5. .code
    6. text byte "Hello METANIT.COM!"  ; выводимая строка
    7. main proc
    8.  sub  rsp, 48
    9.  mov  rcx, -11  ; Аргумент для GetStdHandle - STD_OUTPUT
    10.  call GetStdHandle ; вызываем функцию GetStdHandle
    11.  mov  rcx, rax  ; Первый параметр WriteFile - в регистр RCX помещаем дескриптор файла - консоли
    12.  lea  rdx, text  ; Второй параметр WriteFile - загружаем указатель на строку в регистр RDX
    13.  mov  r8d, 18  ; Третий параметр WriteFile - длина строки для записи в регистре R8D
    14.  xor  r9, r9  ; Четвертый параметр WriteFile - адрес для получения записанных байтов
    15.  mov  qword ptr [rsp + 32], 0  ; Пятый параметр WriteFile
    16.  call WriteFile ; вызываем функцию WriteFile
    17.  add  rsp, 48
    18.  ret
    19. main endp
    20. 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
     
    Последнее редактирование: 1 ноя 2026 в 11:29
  5. Mikl___

    Mikl___ Супермодератор Команда форума

    Публикаций:
    14
    Регистрация:
    25 июн 2008
    Сообщения:
    4.406

    Исследование и дизассемблирование файлов

    Исследование файла с помощью objdump

    Еще одной полезной программой для исследования скомпилированных файлов является консольная утилита objdump, которая поставляется в рамках пакета компиляторов GNU GCC. Про установку компиляторов GNU GCC (и вместе с ними утилиты objdump) на Windows можно прочитать в статье Компилятор GCC. Первая программа на Windows, а про установку на Linux - в статье GCC. Первая программа на Linux
    Общий формат вызова objdump:
    objdump options file
    где file - ранее скомпилированный файл, а options - опции, которые указывают, какую именно информацию о файле надо вывести на консоль. Доступных опций, довольно много, все их можно посмотреть либо в документации, либо вывести на консоль, применив опцию -H (или --help)
    objdump -H
    Перечислю только основные опции
    • -f (--file-headers): отобразить содержимое общего заголовка файла
    • -p (--private-headers): отобразить содержимое заголовка файла, специфичное для формата объекта
    • -h (--[section-]headers): отобразить содержимое заголовков разделов
    • -x (--all-headers): отобразить содержимое всех заголовков
    • -d (--disassemble): отобразить содержимое исполняемых секций на ассемблере
    • -D (--disassemble-all): отобразить ассемблерное содержимое всех секций
    • -S (--source): отобразить исходный код Intermix с дизассемблированием
    • -s (--full-contents): отобразить полное содержимое всех запрошенных разделов
    • -g (--debugging): отобразить отладочную информацию в объектном файле
    • -e (--debugging-tags): отобразить отладочную информацию в стиле ctags
    • -G (--stabs): отобразить (в необработанном виде) любую информацию STABS в файле
    • -t (--syms): отобразить содержимое таблицы (таблиц) символов
    • -T (--dynamic-syms): отобразить содержимое таблицы динамических символов
    • -r (--reloc): отобразить записи о перемещении в файле
    • -R (--dynamic-reloc): отобразить записи о динамических релокациях/перемещениях
    • -v (--version): отобразить номер версии этой программы
    • -i (--info): отобразить список поддерживаемых форматов и архитектур объектов
    В качестве примера возьмем простейшую программу, которая применяет синтаксис ассемблера MASM:
    Код (ASM):
    1. .data
    2. num1 dword 22
    3. num2 dword 32
    4. num3 dword ?
    5. .code
    6. main proc
    7.  mov rax, 20
    8.  mov rbx, 10
    9.  add rax, rbx
    10.  ret
    11. main endp
    12. end
    Пусть программа располагается в файле hello.asm, и при компиляции создается объектный файл hello.obj.
    Используем опцию -S для вывода ассемблерного кода:
    c:\asm>objdump hello.obj -S

    hello.obj: file format pe-x86-64
    Disassembly of section .text$mn:

    0000000000000000 <main>:
    0: 48 c7 c0 14 00 00 00 mov $0x14,%rax
    7: 48 c7 c3 0a 00 00 00 mov $0xa,%rbx
    e: 48 03 c3 add %rbx,%rax
    11: c3 ret

    c:\asm>

    Здесь мы видим ассемблерные инструкции, их код и по каким смещениям в файле они расположены. Единственное, что в отличие от исходного файла, который применяет синтаксис ассемблера MASM, для вывода objdump используется синтаксис ассемблера GAS.
    Для вывода ассемблерного кода всех секций применяется опция -D. Например, в случае с выше скомпилированным файлов hello.obj я получу следующий консольный вывод:
    c:\asm>objdump hello.obj -D

    hello.obj: file format pe-x86-64
    Disassembly of section .text$mn:

    0000000000000000 <main>:
    0: 48 c7 c0 14 00 00 00 mov $0x14,%rax
    7: 48 c7 c3 0a 00 00 00 mov $0xa,%rbx
    e: 48 03 c3 add %rbx,%rax
    11: c3 ret

    Disassembly of section .data:

    0000000000000000 <.data>:
    0: 16 (bad)
    1: 00 00 add %al,(%rax)
    3: 00 20 add %ah,(%rax)
    5: 00 00 add %al,(%rax)
    7: 00 00 add %al,(%rax)
    9: 00 00 add %al,(%rax)
    ...

    Disassembly of section .debug$S:

    0000000000000000 <.debug$S>:
    0: 04 00 add $0x0,%al
    2: 00 00 add %al,(%rax)
    4: f1 int1
    5: 00 00 add %al,(%rax)
    7: 00 52 00 add %dl,0x0(%rdx)
    a: 00 00 add %al,(%rax)
    c: 17 (bad)
    d: 00 01 add %al,(%rcx)
    f: 11 00 adc %eax,(%rax)
    11: 00 00 add %al,(%rax)
    13: 00 63 3a add %ah,0x3a(%rbx)
    16: 5c pop %rsp
    17: 61 (bad)
    18: 73 6d jae 87 <@feat.00+0x77>
    1a: 5c pop %rsp
    1b: 68 65 6c 6c 6f push $0x6f6c6c65
    20: 2e 6f outsl %ds:dntknw:%rsi),(%dx)
    22: 62 (bad)
    23: 6a 00 push $0x0
    25: 37 (bad)
    26: 00 3c 11 add %bh,(%rcx,%rdx,1)
    29: 03 02 add (%rdx),%eax
    2b: 00 00 add %al,(%rax)
    2d: d0 00 rolb (%rax)
    ...
    37: 0e (bad)
    38: 00 24 00 add %ah,(%rax,%rax,1)
    3b: 14 7f adc $0x7f,%al
    3d: 00 00 add %al,(%rax)
    3f: 4d 69 63 72 6f 73 6f imul $0x666f736f,0x72(%r11),%r12
    46: 66
    47: 74 20 je 69 <@feat.00+0x59>
    49: 28 52 29 sub %dl,0x29(%rdx)
    4c: 20 4d 61 and %cl,0x61(%rbp)
    4f: 63 72 6f movsxd 0x6f(%rdx),%esi
    52: 20 41 73 and %al,0x73(%rcx)
    55: 73 65 jae bc <@feat.00+0xac>
    57: 6d insl (%dx),%es:dntknw:%rdi)
    58: 62 (bad)
    59: 6c insb (%dx),%es:dntknw:%rdi)
    5a: 65 72 00 gs jb 5d <.debug$S+0x5d>
    5d: 00 00 add %al,(%rax)
    ...

    c:\asm>
     
    Последнее редактирование: 1 ноя 2026 в 11:12