Introdução
Estou tentando aprender sobre ELF
Arquivos e também experimentar um pouco com eles. Atualmente, estou seguindo o tutorial aqui , que trata de criar um pequeno ELF
arquivo de 32 bits que sai apenas com o código 42. Este tutorial usa NASM
e ld
para criar os ELF
arquivos. No entanto, eu gostaria de usar o GNU Assembler (GAS)
para criar um arquivo de 64 bits ELF
(e não de 32 bits), pois estou familiarizado com ele (mas não sou um especialista em usá-lo!)
No momento, estou preso na parte do tutorial em que eles começam a criar ELF
arquivos do zero - o que envolve escrever ELF
o cabeçalho e os cabeçalhos do programa. Como os arquivos de 32 e 64 bits ELF
diferem ligeiramente (por exemplo, em termos de tamanho do cabeçalho ELF), minha versão do .s
arquivo está abaixo:
.intel_syntax noprefix
ehdr:
.byte 0x7F
.byte 0x45 # E
.byte 0x4c # L
.byte 0x46 # F
.byte 0x02 # 64 bit ; CLASS
.byte 0x01 # lsb ; DATA
.byte 0x01 # ; VERSION
.byte 0x00 # None/Sytem V ; OS ABI
.8byte 0x0 # ABI VERSION + PADDING
.2byte 0x02 # ET_EXEC ; E_TYPE
.2byte 0x3E # AMD64 ; E_MACHINE
.4byte 0x01 # 1 ; E_VERSION
.8byte _start # ; E_ENTRY
.8byte phdr - ehdr # offset into program header ; E_PHOFF
.8byte 0x00 # offset into section header ; E_SHOFF
.4byte 0x00 # flag ; E_FLAGS
.2byte ehdrsize # ELF header size ; E_EHSIZE
.2byte phdrsize # Program header size ; E_PHSIZE
.2byte 0x01 # Number of program headers ; E_PHNUM
.2byte 0x00 # Section header size ; E_SHENTSIZE
.2byte 0x00 # Number of section headers ; E_SHNUM
.2byte 0x00 # Section header string table index ; E_SHSTRNDX
DECLARE_ELF_HEADER_SIZE:
.set ehdrsize, DECLARE_ELF_HEADER_SIZE - ehdr
phdr:
.4byte 0x01 # PT_LOAD ; P_TYPE
.4byte 0x00 # located at offset 0??? ; P_OFFSET
.8byte ehdr # ; P_VADDR
.8byte ehdr # ; P_PADDR
.8byte filesize # ; P_FILESIZE
.8byte filesize # ; P_MEMSZ
.8byte 5 # R-X ; P_FLAGS
.8byte 0x1000 # P_ALIGN
DECLARE_PHEADER_SIZE:
.set phdrsize, DECLARE_PHEADER_SIZE - phdr
_start:
mov eax, 60
mov edi, 42
syscall
DECLARE_FILE_SIZE:
.set filesize, DECLARE_FILE_SIZE - ehdr
Mais informações sobre cabeçalhos de arquivos ELF podem ser encontradas observando o código-fonte correspondente aqui . Como ainda estou aprendendo sobre ELF
arquivos, o código acima pode estar incorreto (avise-me caso encontre alguma falha!)
Problema
Supondo que o código acima esteja correto, gostaria de converter o .s
arquivo acima em um ELF
arquivo. Para isso, o tutorial utiliza o comando:
$ nasm -f bin -o a.out tiny.asm
No entanto, não consigo descobrir qual comando correspondente devo usar com o GAS para criar o ELF
arquivo correspondente, e esse é o problema que estou enfrentando.
O que eu tentei...
Eu tentei várias abordagens. Vou listá-los abaixo:
- Há uma pergunta semelhante no SO, que pode ser encontrada aqui . Esta abordagem usa os 2 comandos a seguir para obter o arquivo ELF correspondente:
$ as --64 -o test.o test.s
$ ld -Ttext 200000 --oformat binary -o test.bin test.o
Executando os mesmos comandos para o .s
arquivo que escrevi, recebo um arquivo ELF, junto com o seguinte aviso :
ld: warning: cannot find entry symbol _start; defaulting to 0000000000200000
A execução do arquivo ELF gerado obtém uma falha de segmentação e sai com o código 139. O ELF
cabeçalho do arquivo gerado está abaixo (obtido executando readelf -h <output ELF file>
):
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x200078
Start of program headers: 64 (bytes into file)
Start of section headers: 0 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 1
Size of section headers: 0 (bytes)
Number of section headers: 0
Section header string table index: 0
readelf: Error: the segment's file size is larger than its memory size
Acredito que a falha de segmentação está acontecendo porque Entry Point address
provavelmente está incorreta (isso também foi sugerido ld
na mensagem de erro).
- Nesta abordagem, modifiquei os comandos:
$ as --64 -o test.o test.s
$ ld -Ttext 200000 --oformat binary -o test.bin test.o
para
$ as --64 -o test.o test.s (same as before)
$ ld -nmagic -s test.o (changed)
Isso também cria um arquivo ELF, mas novamente com o seguinte aviso :
ld: warning: cannot find entry symbol _start; defaulting to 0000000000400078
E isso novamente sugere que teremos uma falha de segmentação, o que é de fato o caso do novo ELF
arquivo. O cabeçalho ELF para este arquivo é:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400078
Start of program headers: 64 (bytes into file)
Start of section headers: 0 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 1
Size of section headers: 64 (bytes)
Number of section headers: 0
Section header string table index: 0
Sem sorte!
- A partir dos avisos dados por
ld
, parece que podemos querer adicionar.global _start
nosso.s
arquivo. Abaixo está o novo.s
arquivo que também contém esta linha:
.intel_syntax noprefix
.global _start # Added here
ehdr:
.byte 0x7F
.byte 0x45 # E
.byte 0x4c # L
.byte 0x46 # F
.byte 0x02 # 64 bit ; CLASS
.byte 0x01 # lsb ; DATA
.byte 0x01 # ; VERSION
.byte 0x00 # None/Sytem V ; OS ABI
.8byte 0x0 # ABI VERSION + PADDING
.2byte 0x02 # ET_EXEC ; E_TYPE
.2byte 0x3E # AMD64 ; E_MACHINE
.4byte 0x01 # 1 ; E_VERSION
.8byte _start # ; E_ENTRY
.8byte phdr - ehdr # offset into program header ; E_PHOFF
.8byte 0x00 # offset into section header ; E_SHOFF
.4byte 0x00 # flag ; E_FLAGS
.2byte ehdrsize # ELF header size ; E_EHSIZE
.2byte phdrsize # Program header size ; E_PHSIZE
.2byte 0x01 # Number of program headers ; E_PHNUM
.2byte 0x00 # Section header size ; E_SHENTSIZE
.2byte 0x00 # Number of section headers ; E_SHNUM
.2byte 0x00 # Section header string table index ; E_SHSTRNDX
DECLARE_ELF_HEADER_SIZE:
.set ehdrsize, DECLARE_ELF_HEADER_SIZE - ehdr
phdr:
.4byte 0x01 # PT_LOAD ; P_TYPE
.4byte 0x00 # located at offset 0??? ; P_OFFSET
.8byte ehdr # ; P_VADDR
.8byte ehdr # ; P_PADDR
.8byte filesize # ; P_FILESIZE
.8byte filesize # ; P_MEMSZ
.8byte 5 # R-X ; P_FLAGS
.8byte 0x1000 # P_ALIGN
DECLARE_PHEADER_SIZE:
.set phdrsize, DECLARE_PHEADER_SIZE - phdr
_start:
mov eax, 60
mov edi, 42
syscall
DECLARE_FILE_SIZE:
.set filesize, DECLARE_FILE_SIZE - ehdr
Novamente, criamos o arquivo ELF para o código acima usando:
$ as --64 -o test.o test.s
$ ld -nmagic -s test.o
Desta vez, não recebemos nenhum erro e o arquivo ELF faz o que deveria fazer (ou seja, sai com o código 42). O cabeçalho ELF para este arquivo é:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x4000f0
Start of program headers: 64 (bytes into file)
Start of section headers: 0 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 1
Size of section headers: 64 (bytes)
Number of section headers: 0
Section header string table index: 0
Problema resolvido, né?
Infelizmente, acho que não. Aprofundando-se um pouco mais no binário, revela-se que ele não usa o cabeçalho manuscrito ELF
que havíamos escrito no .s
arquivo:
gef➤ search-pattern 'ELF'
[+] Searching 'ELF' in memory
[+] In '/home/VM/Desktop/Experiments/ELF/a.out'(0x400000-0x401000), permission=r-x
0x400001 - 0x400004 → "ELF[...]"
0x400079 - 0x40007c → "ELF[...]"
Ops, parece que ld
(ou as
, não tenho certeza!) criou seu próprio ELF
cabeçalho (como é evidente pelo fato de que existem 2 locais contendo a string ELF
. Pelo que entendi, a ELF
string at 0x400001
é aquela que foi contribuída por ld
( ou as
), e aquele em 0x400079
era o que eu incluí no .s
arquivo.
No entanto, isso vai contra o propósito de escrever um arquivo ELF do zero, incluindo o ELF
cabeçalho e os cabeçalhos do programa! Também tenho certeza de que esse problema está presente nas abordagens 1 e 2 que listei antes, mas como elas saíram com uma falha de segmentação, não destaquei esse problema nessas abordagens.
Agora, estou sem ideias sobre o que fazer, então agradeceria muito qualquer ajuda para construir o ELF
arquivo corretamente!
Muito obrigado!
A mensagem sobre o
_start
é uma pista falsa. Isso normalmente é usado para preencher o ponto de entrada, mas como você faz isso manualmente e não está usando o vinculador para emitir o cabeçalho ELF, é irrelevante. Você errou o phdr. Em particular,p_offset
deve ter 8 bytes,p_flags
deve ter 4 bytes e ser colocado como o segundo campo. A versão fixa é: