2013-07-03 2 views
1

Я пытаюсь закончить этот файл PE, сделанный только с помощью сборки, который должен отображать сообщение в консоли. Я хочу организовать его таким образом, что я могу добавить больше вещей позже (знаю, где добавить код, данные, импортированные функции).
я создал 4 разделов на данный момент, для кода, данных, непосвященных данных и импортированных элементы. Мои главные проблемы на данном этапе являются:Создание и использование разделов для PE-файла в сборке (NASM)

  1. Некоторые значения в заголовке раздела сделать исполняемый недействительным (не действует win32)
  2. Указатели на элементы из секции данных являются неправильными
  3. Некоторые из расчетов, которые включают предпочтительный абсолютный адрес, выравнивание раздела и выравнивание файлов могут быть неправильными

Во-первых, я покажу весь код ниже. Некоторые вещи, которые на самом деле не имеет значения, не будут добавлены для того, чтобы сэкономить время и сделать его более удобным для чтения Это NASM код

; Constants (use '$' as prefix) 
$SECTION_ALIGNMENT equ 4096  ; Each section is aligned to 4096 in memory 
$FILE_ALIGNMENT equ 512  ; Each section is aligned to 512 on disk 
$PREFERRED_ADDRESS equ 4194304 ; Preffered address for EXE is 4 MB 
$TOTAL_PE_SECTIONS equ 4  ; Code, Data, Bss and IData 
; Image size = headers aligned to section alignment + sections size aligned 
; to next multiple of section alignment, everything aligned, too 
$IMAGE_SIZE  equ $SECTION_ALIGNMENT + (HEADERS_SIZE/$SECTION_ALIGNMENT) + \ 
         $TOTAL_PE_SECTIONS * $SECTION_ALIGNMENT 


; Will help us align some of the values to the next specified multiple 
%define Round(Number, Multiple) Multiple+(Number/Multiple) 

section .header progbits vstart=0 

    ; This is the MZ header 
    DOS_HEADER: 
     db    "MZ"   ; MZ signature 

     ; ... 
     ; Here we have all the members of the DOS header, in 4 paragraphs 
     ; ... 

     db    PE_HEADER ; The last one is pointing to the PE header 


    DOS_STUB: 
     ; ... 
     ; A small DOS program to display a simple message in DOS (64 bytes) 
     ; ... 

    ; This is the PE header 
    PE_HEADER: 
     db    "PE", 0, 0 ; PE signature 
     dw    0x014C  ; Platform Intel I386 
     dw    $TOTAL_PE_SECTIONS 
     dd    1371668450 ; Creation timestamp 
     dd    0   ; No symbols table 
     dd    0   ; No symbols 
     dw    SECTIONS_TABLE - OPT_HEADER ; Optional header size 
     dw    0x0002|0x0004|0x0008|0x0100|0x0200 ; Characteristics 

    ; Optional header 
    OPT_HEADER: 
     dw    0x010B  ; Signature 
     db    0   ; Linker version 
     db    0   ; Minor linker version 
     dd    CODE_SIZE 
     dd    DATA_SIZE ; Initialized data size 
     dd    BSS_SIZE ; Uninitiated data size 
     dd    CODE  ; Entry point 
     dd    CODE  ; Code RVA 
     dd    DATA  ; Data RVA 
     dd    $PREFERRED_ADDRESS ; Preferred address in memory 
     dd    $SECTION_ALIGNMENT 
     dd    $FILE_ALIGNMENT 
     dw    4   ; OS version 
     dw    0   ; Minor OS version 
     dw    0   ; Image version 
     dw    0   ; Minor image version 
     dw    3   ; Subsystem version 
     dw    10   ; Minor subsystem version 
     dd    0   ; WIN32 version 
     dd    $IMAGE_SIZE ; Image size calculated above 
     dd    Round(HEADERS_SIZE, $SECTION_ALIGNMENT) ; Headers size 
     dd    0   ; Checksum 
     dw    3   ; System interface CUI 
     dw    0   ; DLL characteristics 
     dd    4096  ; Reserved stack 
     dd    4096  ; Still not  ?? 
     dd    65536  ; sure about  ?? 
     dd    0   ; these   ?? 
     dd    0 
     dd    2   ; Data directory entries 
     dd    0   ; Export table pointer 
     dd    0   ; Export table size 
     dd    I_TABLE  ; Import table pointer 
     dd    I_TABLE_S ; Size of import table 
     dq    0   ; Reserved 

    SECTIONS_TABLE: 
     CODE_SECTION_HEADER: 
      db   ".code", 0, 0, 0 
      dd   Round(CODE_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
      dd   CODE 
      dd   Round(CODE_SIZE, $FILE_ALIGNMENT) ; Size on disk 
      dd   Round(0, $FILE_ALIGNMENT) ; Real start address 
      dd   0 
      dd   0 
      dw   0 
      dw   0 
      dd   0x00000020|0x20000000|0x40000000|0x80000000 

     DATA_SECTION_HEADER: 
      db   ".data", 0, 0, 0 
      dd   Round(DATA_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
      dd   $SECTION_ALIGNMENT * 2 
      dd   Round(DATA_SIZE, $FILE_ALIGNMENT) ; Size on disk 
      dd   Round(0, $FILE_ALIGNMENT) ; Real start address 
      dd   0 
      dd   0 
      dw   0 
      dw   0 
      dd   0x00000040|0x40000000|0x80000000 

     BSS_SECTION_HEADER: 
      db   ".bss", 0, 0, 0, 0 
      dd   Round(BSS_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
      dd   $SECTION_ALIGNMENT * 3 
      dd   0 
      dd   0 
      dd   0 
      dd   0 
      dw   0 
      dw   0 
      dd   0x00000080|0x40000000|0x80000000 


     IDATA_SECTION_HEADER: 
      db   ".idata", 0, 0 
      dd   Round(IDATA_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
      dd   $SECTION_ALIGNMENT * 4 
      dd   0 
      dd   Round(0, $FILE_ALIGNMENT) ; Real start address 
      dd   0 
      dd   0 
      dw   0 
      dw   0 
      dd   0x00000040|0x40000000|0x80000000 

    HEADERS_SIZE equ $$ - DOS_HEADER 

    align 512 ; Align to 512 bytes in memory 

section .scode vstart=$SECTION_ALIGNMENT align=16 

    use32 

    CODE: 
     push -11 
     call dword [$PREFERRED_ADDRESS + F_GetStdHandle] 
     push 0 
     push 0x402000 
     push 6 
     push $PREFERRED_ADDRESS + hello 
     push eax 
     call dword [$PREFERRED_ADDRESS + F_WriteConsole] 
     push -1 
     call dword [$PREFERRED_ADDRESS + F_Sleep] 
     ret 

    CODE_SIZE equ $$ - CODE 

section .sdata vstart=$SECTION_ALIGNMENT*2 progbits align=4 
     DATA: 
      hello: db 'Hello!' 
     DATA_SIZE equ $$ - DATA 

section .sbss vstart=$SECTION_ALIGNMENT*3 align=4 
    BSS: 
     dd 5 
    BSS_SIZE equ $$ - BSS 

section .sidata vstart=$SECTION_ALIGNMENT*4 align=4 
    IDATA: 
     F_Sleep:   dd I_Sleep 
     F_WriteConsole: dd I_WriteConsole 
     F_GetStdHandle: dd I_GetStdHandle 
     dd    0 

     I_TABLE: 
      .originalfthk  dd 0 
      .timedate   dd 0 
      .forwarder  dd 0 
      .name    dd kernel32 
      .firstthunk  dd IDATA 

     I_TABLE_S equ $$ - I_TABLE 

     times 20 db 0 

     kernel32:    db 'kernel32.dll', 0 
     I_Sleep:   
      dw    0 
      db    'Sleep', 0 
      align    2 
     I_WriteConsole:  
      dw    0 
      db    'WriteConsoleA', 0 
      align    2 
     I_GetStdHandle: 
      dw    0 
      db    'GetStdHandle', 0 

    IDATA_SIZE equ $$ - IDATA 

Основная проблема здесь состоит в том, что исполняемые аварии, потому что указатели из код неправильный. Я говорю о указателе на приветственное сообщение от .sdata и указатели на импортированные функции, из раздела .sidata. Если я скопирую как переменную hello, так и весь контент .sidata в .scode (ниже ret), он работает, но как только я копирую каждую вещь в том, что должно быть ее соответствующим разделом, exe ломается.
Итак, похоже, что адрес просчитан. Начиная с этого места, в заголовках разделов или в другом месте могут быть неправильные значения. Как вы думаете?

UPDATE:
Там проблема у меня сейчас, после реализации изменений ниже. Все работает нормально, пока раздел .data меньше 512 байт. Как только он превышает это, я получаю «странное недопустимое приложение win32».

Итак, здесь у меня есть 2 файла HTML, экспортированных PEInfo. Это первый из них содержит информацию о рабочем файле (где секция .data меньше 512 байт): Working EXE PEInfo
Второй содержит информацию о коррумпированном EXE, когда секция .data содержит более 512 байт: Corrupt EXE PEInfo

Возможно, кто-то может заметить разницу и причину аварии.

+1

Как насчет загрузки вашего файла в PE Explorer или какой-либо другой подобный инструмент? Это должно дать вам подсказку о том, какие поля в заголовке могут быть неправильными. – Michael

+0

Я долго не делал Windows! Одна вещь, которую я вижу ... в конце вашего заголовка dos: 'db PE_HEADER'. Наверняка это должно быть 'dd', нет? Я сомневаюсь, что это ваша проблема (?), Я согласен, что адреса разделов могут быть неправильно рассчитаны. Я думаю, что у Майкла есть хорошая идея! (FWIW, проще использовать компоновщик!) –

ответ

4

Теперь у меня появилась возможность подробно изучить код и фактически запустить его. Итак, вот все проблемы, которые я нашел.

Во-первых, ни один из ваших расчетов по размеру не работал. Вы делаете что-то вроде этого:

CODE_SIZE equ $$ - CODE 

Но вы пытаетесь и ссылки, которые CODE_SIZE до строки, в которой она была определена, поэтому она просто вычисляется как ноль.

Моим решением было добавить концевые метки, например. CODE_END:, где бы вы, как правило, не выполнили один из этих вычислений. Затем в самом начале кода, прежде чем эти значения будут использованы, вычислите размер как разницу между меткой конца и меткой начала для каждого блока.

HEADERS_SIZE equ HEADERS_END - DOS_HEADER 
CODE_SIZE  equ CODE_END - CODE 
DATA_SIZE  equ DATA_END - DATA 
IDATA_SIZE equ IDATA_END - IDATA 
I_TABLE_SIZE equ I_TABLE_END - I_TABLE 

Следующая большая проблема была ваша Round макрос, который выглядит следующим образом:

%define Round(Number, Multiple) Multiple+(Number/Multiple) 

Я не уверен, что вы думали, что вы делаете, но это больше похоже на то, что вам нужно:

%define Round(Number, Multiple) (Number+Multiple-1)/Multiple*Multiple 

Вы хотите, чтобы убедиться, что Количество кратно Multiple , следовательно, последовательность размножения. Вы также должны добавить Multiple-1 к исходному номеру, чтобы заставить его округлить.

Следующая большая проблема - расчеты RVA или их отсутствие. В файловой структуре есть много мест, в которых вам нужно указать смещение как относительный виртуальный адрес (RVA), который является относительным смещением в памяти. Когда вы просто берете значение метки как есть, это дает вам смещение на диске.

Для смещения раздела вам необходимо разделить это смещение на выравнивание файла, а затем умножить его на выравнивание раздела. Кроме того, блок кода будет загружаться с одним выравниванием выравнивания, поэтому все должно быть рассчитано относительно блока кода, а затем добавить одно выравнивание раздела к результату.

%define RVA(BaseAddress) (BaseAddress - CODE)/$FILE_ALIGNMENT*$SECTION_ALIGNMENT+$SECTION_ALIGNMENT 

Теперь это работает для адресов на границах раздела. Для чего-то еще вам нужно рассчитать их внутреннее смещение относительно базового адреса раздела, а затем добавить это в RVA этого раздела.

%define RVA(Address,BaseAddress) RVA(BaseAddress)+(Address-BaseAddress) 

Эти расчеты предполагают, что различные секции уже выровнены со значением $FILE_ALIGNMENT, но это на самом деле не так. Вы имели align перед кодом раздел, который выглядит следующим образом:

align 512 ; Align to 512 bytes in memory 

Но вам нужно, что перед каждой секции, а также один на конце файла. Я бы также рекомендовал использовать константу $FILE_ALIGNMENT, иначе нет смысла это делать.

align $FILE_ALIGNMENT ; Align to 512 bytes in memory 

В дополнение к этому, вы должны избавиться от всех ваших section деклараций. Например, все эти строки необходимо удалить.

section .header progbits vstart=0 
section .scode vstart=$SECTION_ALIGNMENT align=16 
section .sdata vstart=$SECTION_ALIGNMENT*2 progbits align=4 
section .sbss vstart=$SECTION_ALIGNMENT*3 align=4 
section .sidata vstart=$SECTION_ALIGNMENT*4 align=4 

Поскольку вы вручную строить весь формат файла, они не служат никакой цели, и они мешают вам делать смещенные расчеты с этикетками сечений границ (то, что нам нужно очень много везде).

Теперь, когда все мы правильно выровнены, и у нас есть два наших макроса RVA, мы можем приступить к исправлению различных частей кода, которые должны использовать RVAs.

Сначала в необязательном заголовке мы имеем код RVA, данные RVA и точку входа. Кроме того, пока мы там, я считаю, что различные значения размера должны быть указаны как кратность выравнивания раздела.

dd Round(CODE_SIZE, $SECTION_ALIGNMENT) 
dd Round(DATA_SIZE, $SECTION_ALIGNMENT) ; Initialized data size 
dd Round(BSS_SIZE, $SECTION_ALIGNMENT) ; Uninitiated data size 
dd RVA(CODE)       ; Entry point 
dd RVA(CODE)       ; Code RVA 
dd RVA(DATA)       ; Data RVA 

Также в дополнительном заголовке, у вас есть размер заголовка округленное до выравнивания секции, когда я считаю, что должна быть округлена до выравнивания файла.

dd Round(HEADERS_SIZE, $FILE_ALIGNMENT) ; Headers size 

Это одна из тех вещей, которые на самом деле не делает никакой разницы - код будет работать так или иначе - но я до сих пор считаю, что это неправильно и должно быть исправлено.

Аналогичным образом, как я указал в своем первом ответе, размер таблицы каталога данных всегда должен быть установлен на 16, даже если вы не используете все 16 записей. Кажется, он работает, если вы этого не сделаете, но я бы рекомендовал вам сделать это правильно.

dd 16     ; Data directory entries 
dd 0     ; Export table pointer 
dd 0     ; Export table size 
dd RVA(I_TABLE,IDATA) ; Import table pointer 
dd I_TABLE_SIZE  ; Size of import table 
times 14 dq 0   ; Space the other 14 entries 

Кроме того, обратите внимание на I_TABLE смещение было обновлено, чтобы использовать RVA относительно секции IDATA.

Следующая таблица ваших разделов неверна. Например, начало заголовка раздела кода должен выглядеть следующим образом:

db ".code", 0, 0, 0 
dd Round(CODE_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
dd RVA(CODE)       ; Start address in memory 
dd Round(CODE_SIZE, $FILE_ALIGNMENT) ; Size on disk 
dd CODE         ; Start address on disk 

Аналогично для секции данных:

db ".data", 0, 0, 0 
dd Round(DATA_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
dd RVA(DATA)       ; Start address in memory 
dd Round(DATA_SIZE, $FILE_ALIGNMENT) ; Size on disk 
dd DATA         ; Start address on disk 

И секции IDATA:

db ".idata", 0, 0 
dd Round(IDATA_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
dd RVA(IDATA)       ; Start address in memory 
dd Round(IDATA_SIZE, $FILE_ALIGNMENT) ; Size on disk 
dd IDATA         ; Start address on disk 

Раздел ОНБ немного отличается. Весь смысл раздела bss заключается в том, что он не занимает места на диске, но занимает память в памяти. Это означает, что вы не можете включать какие-либо определения данных для ваших данных bss. Поэтому этот код должен быть отправлен:

BSS: 
    dd 5 

Но это означает, что разделы на диске не совпадут с разделами в памяти. Чтобы упростить расчеты RVA, моим рекомендуемым решением является включение секции bss в качестве последней вещи в файле. Когда его размер расширяется от 0 на диске, к тому, что в памяти не будет влиять на другие смещения.

Так что я хотел бы добавить ярлык на самом конце файла с именем IMAGE_END:, а затем определить раздел ОНБА как это:

db ".bss", 0, 0, 0, 0 
dd Round(BSS_SIZE, $SECTION_ALIGNMENT) ; Size in memory 
dd RVA(IMAGE_END)      ; Start address in memory 
dd 0         ; Size on disk 
dd 0         ; Start address on disk 

Обратите внимание, что этот раздел должен прийти после IDATA раздела в таблице разделов, так как адреса должны быть в порядке возрастания.

Возможно, вам интересно, откуда берется значение BSS_SIZE, если у вас больше нет секции bss в коде. Я боюсь, что вам придется определять это значение вручную. Вам также придется вручную определять константы для смещений любых переменных в этом разделе. Как я уже говорил, вы не можете использовать определения данных, потому что мы не хотим, чтобы он занимал какое-либо место на диске.

Далее мы переходим к таблице импорта. Макет, который вы используете для этого, несколько странный, но это, похоже, не проблема, поэтому я собираюсь оставить это как есть. Однако вам нужно обновить все адреса, чтобы использовать RVAs.

Сначала IAT:

F_Sleep:   dd RVA(I_Sleep,IDATA) 
F_WriteConsole: dd RVA(I_WriteConsole,IDATA) 
F_GetStdHandle: dd RVA(I_GetStdHandle,IDATA) 

Затем дескриптор импорта:

.originalfthk dd 0 
.timedate  dd 0 
.forwarder  dd 0 
.name   dd RVA(kernel32,IDATA) 
.firstthunk  dd RVA(IDATA,IDATA) 

Я должен также упомянуть, что вы сразу же установив переменную I_TABLE_S после этого дескриптора, и если вы помните, я сказал вы должны заменить эти вычисления размерами на концевые метки. Однако в этом случае размер дескрипторной таблицы также должен включать конечную нулевую запись. Таким образом, правильное место для размещения этой метки не здесь, а после заполнения times 20 db 0.

times 20 db 0  
I_TABLE_END: 

Это еще одна из тех вещей, которые, как я думаю, не имеет большого значения, но я по-прежнему рекомендую исправление.

Кроме того, этот макет отлично подходит при импорте из одной DLL, но когда вам нужно больше, вам понадобятся больше дескрипторов и больше разделов IAT. Поэтому я рекомендую добавлять ярлык перед каждым IAT, например. что-то вроде kernel32_iat в этом случае. Затем вы инициализируете свой первый удар.

.firstthunk  dd RVA(kernel32_iat,IDATA) 

Наконец, я хочу иметь дело с расчетом $IMAGE_SIZE. Расчет, который вы используете, предполагает фиксированный размер для каждого раздела. Но если у нас есть метка IMAGE_END в конце файла и макрос RVA, мы можем легко вычислить точный размер изображения как RVA(IMAGE_END).

Однако это не учитывает раздел bss, который делает изображение более крупным после его загрузки в память. Таким образом, правильное определение размера изображения должно быть:

$IMAGE_SIZE equ RVA(IMAGE_END) + Round(BSS_SIZE,$SECTION_ALIGNMENT) 

Обратите внимание, что это должно быть определено вблизи начала файла - до его использования в любом месте, но после RVA макро и BSS_SIZE были определены.

+0

Спасибо за потраченное время, чтобы сделать его таким подробным. Я, наконец, знаю, как создать файл PE «вручную» – ali

+0

На данный момент PEExplorer все еще показывает мне следующее: 05.07.2013 22:10:22: Внимание! Раздел <.data> (1) выходит за рамки смещения исходного файла в разделе <.bss> (2). 05.07.2013 22:10:22: Внимание! Раздел <.bss> (2) выходит за пределы виртуального адреса раздела <.idata> (3). – ali

+0

Вы добавили 'align $ FILE_ALIGNMENT' между каждым разделом? Вы переместили раздел bss после раздела idata в таблицу разделов? –

Смежные вопросы