¿Cómo hacer un archivo ELF ejecutable en Linux usando un editor hexadecimal?
assembly (2)
Descompile un mundo hola NASM y comprenda cada byte en él
Versión de esta respuesta con un buen TOC y más contenido: http://www.cirosantilli.com/elf-hello-world (llegando al límite de 30k char aquí)
Normas
ELF es especificado por el LSB:
- genérico principal: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/elf-generic.html
- núcleo AMD64: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/book1.html
El LSB básicamente se vincula a otros estándares con extensiones menores, en particular:
-
genérico (ambos por SCO):
- System V ABI 4.1 (1997) http://www.sco.com/developers/devspecs/gabi41.pdf , no 64 bit, aunque tiene reservado un número mágico. Lo mismo para los archivos principales.
- System V ABI Update DRAFT 17 (2003) http://www.sco.com/developers/gabi/2003-12-17/contents.html , agrega 64 bits. Solo actualiza los capítulos 4 y 5 del documento anterior: los otros siguen siendo válidos y todavía se mencionan.
-
arquitectura específica:
- IA-32: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-IA32/LSB-Core-IA32/elf-ia32.html , apunta principalmente a http://www.sco.com/developers/devspecs/abi386-4.pdf
- AMD64: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/elf-amd64.html , apunta principalmente a http://www.x86-64.org/documentation/abi.pdf
Puede encontrar un resumen útil en:
man elf
Su estructura puede ser examinada de forma legible por humanos a través de utilidades como
readelf
y
objdump
.
Genera el ejemplo
Analicemos un ejemplo de Linux x86-64 ejecutable mínimo:
section .data
hello_world db "Hello world!", 10
hello_world_len equ $ - hello_world
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, hello_world_len
syscall
mov rax, 60
mov rdi, 0
syscall
Compilado con:
nasm -w+all -f elf64 -o ''hello_world.o'' ''hello_world.asm''
ld -o ''hello_world.out'' ''hello_world.o''
Versiones
- NASM 2.10.09
-
Binutils versión 2.24 (contiene
ld
) - Ubuntu 14.04
No usamos un programa en C ya que eso complicaría el análisis, será el nivel 2 :-)
Hexdumps
hd hello_world.o
hd hello_world.out
Salida en: https://gist.github.com/cirosantilli/7b03f6df2d404c0862c6
Estructura global de archivos
Un archivo ELF contiene las siguientes partes:
-
Encabezado ELF. Señala la posición de la tabla de encabezado de sección y la tabla de encabezado del programa.
-
Tabla de encabezado de sección (opcional en ejecutable). Cada uno tiene
e_shnum
seccióne_shnum
, cada uno apuntando a la posición de una sección. -
N secciones, con
N <= e_shnum
(opcional en ejecutable) -
Tabla de encabezado del programa (solo en ejecutable). Cada uno tiene
e_phnum
programae_phnum
, cada uno apuntando a la posición de un segmento. -
N segmentos, con
N <= e_phnum
(opcional en ejecutable)
El orden de esas partes no es fijo: lo único fijo es el encabezado ELF que debe ser lo primero en el archivo: los documentos genéricos dicen:
Encabezado ELF
La forma más fácil de observar el encabezado es:
readelf -h hello_world.o
readelf -h hello_world.out
Salida en: https://gist.github.com/cirosantilli/7b03f6df2d404c0862c6
Bytes en el archivo objeto:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 |..>.............|
00000020 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |....@.....@.....|
Ejecutable:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 b0 00 40 00 00 00 00 00 |..>.......@.....|
00000020 40 00 00 00 00 00 00 00 10 01 00 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 02 00 40 00 06 00 03 00 |[email protected]...@.....|
Estructura representada:
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
Desglose manual:
-
0 0:
EI_MAG
=7f 45 4c 46
=0x7f ''E'', ''L'', ''F''
: número mágico ELF -
0 4:
EI_CLASS
=02
=ELFCLASS64
: elfo de 64 bits -
0 5:
EI_DATA
=01
=ELFDATA2LSB
: datos big endian -
0 6:
EI_VERSION
=01
: versión de formato -
0 7:
EI_OSABI
(solo en la actualización de 2003) =00
=ELFOSABI_NONE
: sin extensiones. -
0 8:
EI_PAD
= 8x00
: bytes reservados. Debe establecerse en 0. -
1 0:
e_type
=01 00
= 1 (big endian) =ET_REl
: formato reubicableEn el ejecutable es
02 00
paraET_EXEC
. -
1 2:
e_machine
=3e 00
=62
=EM_X86_64
: arquitectura AMD64 -
1 4:
e_version
=01 00 00 00
: debe ser 1 -
1 8:
e_entry
= 8x00
: punto de entrada de la dirección de ejecución, o 0 si no corresponde, como para el archivo de objeto, ya que no hay un punto de entrada.En el ejecutable, es
b0 00 40 00 00 00 00 00
. TODO: ¿a qué más podemos configurar esto? El núcleo parece poner la IP directamente en ese valor, no está codificado. -
2 0:
e_phoff
= 8x00
: desplazamiento de la tabla de encabezado del programa, 0 si no está presente.40 00 00 00
en el ejecutable, es decir, comienza inmediatamente después del encabezado ELF. -
2 8:
e_shoff
=40
7x00
=0x40
: desplazamiento de archivo de tabla de encabezado de sección, 0 si no está presente. -
3 0:
e_flags
=00 00 00 00
TODO. Arco específico. -
3 4:
e_ehsize
=40 00
: tamaño de este encabezado elfo. TODO por qué este campo? ¿Cómo puede variar? -
3 6:
e_phentsize
=00 00
: tamaño de cada encabezado de programa, 0 si no está presente.38 00
en el ejecutable: tiene 56 bytes de longitud -
3 8:
e_phnum
=00 00
: número de entradas de encabezado de programa, 0 si no está presente.02 00
en ejecutable: hay 2 entradas. -
3 A:
e_shentsize
ye_shnum
=40 00 07 00
: tamaño del encabezado de sección y número de entradas -
3 E:
e_shstrndx
(Section Header STRing iNDeX
) =03 00
: índice de la sección.shstrtab
.
Tabla de encabezado de sección
Matriz de estructuras
Elf64_Shdr
.
Cada entrada contiene metadatos sobre una sección determinada.
e_shoff
del encabezado ELF da la posición inicial, 0x40 aquí.
e_shentsize
y
e_shnum
del encabezado ELF dicen que tenemos 7 entradas, cada una de
0x40
bytes de longitud.
Entonces la tabla toma bytes de 0x40 a
0x40 + 7 + 0x40 - 1
= 0x1FF.
Algunos nombres de sección están reservados para ciertos tipos de sección:
http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections
por ejemplo
.text
requiere un tipo
SHF_ALLOC
y
SHF_ALLOC
+
SHF_EXECINSTR
readelf -S hello_world.o
:
There are 7 section headers, starting at offset 0x40:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .data PROGBITS 0000000000000000 00000200
000000000000000d 0000000000000000 WA 0 0 4
[ 2] .text PROGBITS 0000000000000000 00000210
0000000000000027 0000000000000000 AX 0 0 16
[ 3] .shstrtab STRTAB 0000000000000000 00000240
0000000000000032 0000000000000000 0 0 1
[ 4] .symtab SYMTAB 0000000000000000 00000280
00000000000000a8 0000000000000018 5 6 4
[ 5] .strtab STRTAB 0000000000000000 00000330
0000000000000034 0000000000000000 0 0 1
[ 6] .rela.text RELA 0000000000000000 00000370
0000000000000018 0000000000000018 4 2 4
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
struct
representada por cada entrada:
typedef struct {
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
Secciones
Índice 0 sección
Contenido en bytes 0x40 a 0x7F.
La primera sección siempre es mágica: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html dice:
Si el número de secciones es mayor o igual que SHN_LORESERVE (0xff00), e_shnum tiene el valor SHN_UNDEF (0) y el número real de entradas de la tabla de encabezado de sección está contenido en el campo sh_size del encabezado de sección en el índice 0 (de lo contrario, el El miembro sh_size de la entrada inicial contiene 0).
También hay otras secciones mágicas detalladas en la
Figure 4-7: Special Section Indexes
.
En el índice 0,
SHT_NULL
es obligatorio.
¿Tiene otros usos?
¿Cuál es el uso de la sección SHT_NULL en ELF?
?
sección de datos
.data
es la sección 1:
00000080 01 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00 |................|
00000090 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 |................|
000000a0 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000b0 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
-
80 0:
sh_name
=01 00 00 00
: índice 1 en la tabla de cadenas.shstrtab
Aquí,
1
dice que el nombre de esta sección comienza en el primer carácter de esa sección y termina en el primer carácter NUL, formando la cadena.data
..data
es uno de los nombres de sección que tiene un significado predefinido http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.htmlEstas secciones contienen datos inicializados que contribuyen a la imagen de memoria del programa.
-
80 4:
sh_type
=01 00 00 00
:SHT_PROGBITS
: el contenido de la sección no está especificado por ELF, solo por cómo lo interpreta el programa. Normal desde una sección.data
. -
80 8:
sh_flags
=03
7x00
:SHF_ALLOC
ySHF_EXECINSTR
: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#sh_flags , según sea necesario en una sección.data
-
90 0:
sh_addr
= 8x00
: en qué dirección virtual se colocará la sección durante la ejecución,0
si no se coloca -
90 8:
sh_offset
=00 02 00 00 00 00 00 00
=0x200
: número de bytes desde el inicio del programa hasta el primer byte en esta sección -
a0 0:
sh_size
=0d 00 00 00 00 00 00 00
Si tomamos 0xD bytes comenzando en
sh_offset
200, vemos:00000200 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a 00 |Hello world!.. |
AHA! Entonces nuestro
"Hello world!"
la cadena está en la sección de datos como le dijimos que estuviera en el NASM.Una vez que nos graduemos de
hd
, buscaremos esto como:readelf -x .data hello_world.o
que salidas:
Hex dump of section ''.data'': 0x00000000 48656c6c 6f20776f 726c6421 0a Hello world!.
NASM establece propiedades decentes para esa sección porque trata
.data
mágicamente: http://www.nasm.us/doc/nasmdoc7.html#section-7.9.2También tenga en cuenta que esta fue una mala elección de sección: un buen compilador de C pondría la cadena en
.rodata
en.rodata
lugar, porque es de solo lectura y permitiría más optimizaciones del sistema operativo. -
a0 8:
sh_link
ysh_info
= 8x 0: no se aplican a este tipo de sección. http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections -
b0 0:
sh_addralign
=04
= TODO: ¿por qué es necesaria esta alineación? ¿Es solo parash_addr
, o también para símbolos dentro desh_addr
? -
b0 8:
sh_entsize
=00
= la sección no contiene una tabla. Si! = 0, significa que la sección contiene una tabla de entradas de tamaño fijo. En este archivo, vemos en la salida dereadelf
que este es el caso de las secciones.symtab
y.rela.text
.
sección de texto
Ahora que hemos hecho una sección manualmente,
readelf -S
y usemos el
readelf -S
de las otras secciones.
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 2] .text PROGBITS 0000000000000000 00000210
0000000000000027 0000000000000000 AX 0 0 16
.text
es ejecutable pero no editable: si intentamos escribir en él, Linux segfaults.
Veamos si realmente tenemos algún código allí:
objdump -d hello_world.o
da:
hello_world.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_start>:
0: b8 01 00 00 00 mov $0x1,%eax
5: bf 01 00 00 00 mov $0x1,%edi
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
14: ba 0d 00 00 00 mov $0xd,%edx
19: 0f 05 syscall
1b: b8 3c 00 00 00 mov $0x3c,%eax
20: bf 00 00 00 00 mov $0x0,%edi
25: 0f 05 syscall
Si grep
b8 01 00 00
en el
hd
, vemos que esto solo ocurre en
00000210
, que es lo que dice la sección.
Y el tamaño es 27, que también coincide.
Entonces debemos estar hablando de la sección correcta.
Parece el código correcto: una
write
seguida de una
exit
.
La parte más interesante es la línea
a
que hace:
movabs $0x0,%rsi
para pasar la dirección de la cadena a la llamada del sistema.
Actualmente, el
0x0
es solo un marcador de posición.
Después de que ocurra la vinculación, se modificará para contener:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
Esta modificación es posible debido a los datos de la sección
.rela.text
.
SHT_STRTAB
Las secciones con
sh_type == SHT_STRTAB
se llaman
tablas de cadenas
.
Contienen una serie de cadenas separadas por un valor nulo.
Dichas secciones son utilizadas por otras secciones cuando se van a utilizar nombres de cadenas. La sección de uso dice:
- qué tabla de cadenas están usando
- ¿Cuál es el índice en la tabla de cadenas de destino donde comienza la cadena?
Entonces, por ejemplo, podríamos tener una tabla de cadenas que contenga: TODO: ¿tiene que comenzar con
/0
?
Data: /0 a b c /0 d e f /0
Index: 0 1 2 3 4 5 6 7 8
Y si otra sección quiere usar la cadena
def
, tienen que apuntar al índice
5
de esta sección (letra
d
).
Secciones notables de la tabla de cadenas:
-
.shstrtab
-
.strtab
.shstrtab
Tipo de sección:
sh_type == SHT_STRTAB
.
Nombre común: tabla de cadena de encabezado de sección .
El nombre de la sección
.shstrtab
está reservado.
El estándar dice:
Esta sección contiene los nombres de las secciones.
El campo
e_shstrnd
del encabezado ELF mismo señala esta sección.
Los índices de cadena de esta sección se señalan mediante el campo
sh_name
de los encabezados de sección, que denota cadenas.
Esta sección no tiene
SHF_ALLOC
marcado, por lo que no aparecerá en el programa en ejecución.
readelf -x .shstrtab hello_world.o
Da:
Hex dump of section ''.shstrtab'':
0x00000000 002e6461 7461002e 74657874 002e7368 ..data..text..sh
0x00000010 73747274 6162002e 73796d74 6162002e strtab..symtab..
0x00000020 73747274 6162002e 72656c61 2e746578 strtab..rela.tex
0x00000030 7400 t.
Los datos en esta sección tienen un formato fijo: http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html
Si observamos los nombres de otras secciones, vemos que todas contienen números, por ejemplo, la sección
.text
es el número
7
.
Luego, cada cadena termina cuando se encuentra el primer carácter NUL, por ejemplo, el carácter
12
es
/0
justo después de
.text/0
.
.symtab
Tipo de sección:
sh_type == SHT_SYMTAB
.
Nombre común: tabla de símbolos .
Primero notamos que:
-
sh_link
=5
-
sh_info
=6
Para
SHT_SYMTAB
secciones
SHT_SYMTAB
, esos números significan que:
-
las cadenas que dan nombres de símbolos se encuentran en la sección 5,
.strtab
-
los datos de reubicación se encuentran en la sección 6,
.rela.text
Una buena herramienta de alto nivel para desmontar esa sección es:
nm hello_world.o
lo que da:
0000000000000000 T _start
0000000000000000 d hello_world
000000000000000d a hello_world_len
Sin embargo, esta es una vista de alto nivel que omite algunos tipos de símbolos y en la que los tipos de símbolos. Se puede obtener un desmontaje más detallado con:
readelf -s hello_world.o
lo que da:
Symbol table ''.symtab'' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello_world.asm
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world
5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len
6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start
El formato binario de la tabla está documentado en http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html
Los datos son:
readelf -x .symtab hello_world.o
Lo que da:
Hex dump of section ''.symtab'':
0x00000000 00000000 00000000 00000000 00000000 ................
0x00000010 00000000 00000000 01000000 0400f1ff ................
0x00000020 00000000 00000000 00000000 00000000 ................
0x00000030 00000000 03000100 00000000 00000000 ................
0x00000040 00000000 00000000 00000000 03000200 ................
0x00000050 00000000 00000000 00000000 00000000 ................
0x00000060 11000000 00000100 00000000 00000000 ................
0x00000070 00000000 00000000 1d000000 0000f1ff ................
0x00000080 0d000000 00000000 00000000 00000000 ................
0x00000090 2d000000 10000200 00000000 00000000 -...............
0x000000a0 00000000 00000000 ........
Las entradas son de tipo:
typedef struct {
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;
Al igual que en la tabla de sección, la primera entrada es mágica y se establece en valores sin sentido fijos.
STT_FILE
La entrada 1 tiene
ELF64_R_TYPE == STT_FILE
.
ELF64_R_TYPE
continúa dentro de
st_info
.
Análisis de bytes:
-
10 8:
st_name
=01000000
= carácter 1 en el.strtab
, que hasta el siguiente/0
hacehello_world.asm
El enlazador puede utilizar este archivo de información para decidir qué secciones de segmento van.
-
10 12:
st_info
=04
Bits 0-3 =
ELF64_R_TYPE
= Tipo =4
=STT_FILE
: el propósito principal de esta entrada es usarst_name
para indicar el nombre del archivo que generó este archivo objeto.Bits 4-7 =
ELF64_ST_BIND
=ELF64_ST_BIND
=0
=STB_LOCAL
. Valor requerido paraSTT_FILE
. -
10 13:
st_shndx
=st_shndx
sección de tabla de símbolos Índice =f1ff
=SHN_ABS
. Necesario paraSTT_FILE
. -
20 0:
st_value
= 8x00
: requerido para el valor deSTT_FILE
-
20 8:
st_size
= 8x00
: sin tamaño asignado
Ahora desde la
readelf
, interpretamos a los demás rápidamente.
Hay dos entradas de este tipo, una apuntando a
.data
y la otra a
.text
(índices de sección
1
y
2
).
Num: Value Size Type Bind Vis Ndx Name
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
TODO cuál es su propósito?
STT_NOTYPELuego vienen los símbolos más importantes:
Num: Value Size Type Bind Vis Ndx Name
4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world
5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len
6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start
hello_world
cadena
hello_world
está en la sección
.data
(índice 1).
Su valor es 0: apunta al primer byte de esa sección.
_start
está marcado con visibilidad
GLOBAL
desde que escribimos:
global _start
en NASM. Esto es necesario ya que debe verse como el punto de entrada. A diferencia de C, por defecto las etiquetas NASM son locales.
SHN_ABS
hello_world_len
apunta al
st_shndx == SHN_ABS == 0xF1FF
especial
st_shndx == SHN_ABS == 0xF1FF
.
Se elige
0xF1FF
para no entrar en conflicto con otras secciones.
st_value == 0xD == 13
que es el valor que hemos almacenado allí en el ensamblaje: la longitud de la cadena
Hello World!
.
Esto significa que la reubicación no afectará este valor: es una constante.
Esta es una pequeña optimización que nuestro ensamblador hace por nosotros y que tiene soporte ELF.
Si hubiéramos utilizado la dirección de
hello_world_len
cualquier lugar, el ensamblador no habría podido marcarla como
SHN_ABS
, y el enlazador tendría un trabajo de reubicación adicional más adelante.
Por defecto, NASM también coloca un
.symtab
en el ejecutable.
Esto solo se usa para la depuración. Sin los símbolos, estamos completamente ciegos y debemos aplicar ingeniería inversa a todo.
Puede
objcopy
con
objcopy
, y el ejecutable seguirá ejecutándose.
Dichos ejecutables se denominan
ejecutables eliminados
.
.strtab
Contiene cadenas para la tabla de símbolos.
Esta sección tiene
sh_type == SHT_STRTAB
.
Se señala mediante
sh_link == 5
de la sección
.symtab
.
readelf -x .strtab hello_world.o
Da:
Hex dump of section ''.strtab'':
0x00000000 0068656c 6c6f5f77 6f726c64 2e61736d .hello_world.asm
0x00000010 0068656c 6c6f5f77 6f726c64 0068656c .hello_world.hel
0x00000020 6c6f5f77 6f726c64 5f6c656e 005f7374 lo_world_len._st
0x00000030 61727400 art.
Esto implica que es una limitación de nivel ELF que las variables globales no pueden contener caracteres NUL.
.rela.text
Tipo de sección:
sh_type == SHT_RELA
.
Nombre común: sección de reubicación .
.rela.text
contiene datos de reubicación que indican cómo se debe modificar la dirección cuando se vincula el ejecutable final.
Esto apunta a bytes del área de texto que deben modificarse cuando sucede que la vinculación apunta a las ubicaciones de memoria correctas.
Básicamente, traduce el texto del objeto que contiene la dirección del marcador de posición 0x0:
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
al código ejecutable real que contiene el 0x6000d8 final:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
Fue señalado por
sh_info
=
6
de la sección
.symtab
.
readelf -r hello_world.o
da:
Relocation section ''.rela.text'' at offset 0x3b0 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0
La sección no existe en el ejecutable.
Los bytes reales son:
00000370 0c 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 |................|
00000380 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
La
struct
representada es:
typedef struct {
Elf64_Addr r_offset;
Elf64_Xword r_info;
Elf64_Sxword r_addend;
} Elf64_Rela;
Entonces:
-
370 0:
r_offset
= 0xC: dirección en el.text
cuya dirección modificará esta reubicación -
370 8:
r_info
= 0x200000001. Contiene 2 campos:-
ELF64_R_TYPE
= 0x1: el significado depende de la arquitectura exacta. -
ELF64_R_SYM
=ELF64_R_SYM
: índice de la sección a la que apunta la dirección, entonces.data
que está en el índice 2.
El
R_X86_64_64
AMD64 dice que el tipo1
se llamaR_X86_64_64
y que representa la operaciónS + A
donde:-
S
: el valor del símbolo en el archivo objeto, aquí0
porquemovabs $0x0,%rsi
a00 00 00 00 00 00 00 00
demovabs $0x0,%rsi
-
A
: el agregado, presente en el campor_added
Esta dirección se agrega a la sección en la que opera la reubicación.
Esta operación de reubicación actúa en un total de 8 bytes.
-
-
380 0:
r_addend
= 0
Entonces, en nuestro ejemplo, concluimos que la nueva dirección será:
S + A
=
.data + 0
y, por lo tanto, lo primero en la sección de datos.
Tabla de encabezado del programa
Solo aparece en el ejecutable.
Contiene información sobre cómo se debe colocar el ejecutable en la memoria virtual del proceso.
El ejecutable es generado a partir de archivos de objetos por el vinculador. Los principales trabajos que realiza el vinculador son:
-
determine qué secciones de los archivos de objeto irán a qué segmentos del ejecutable.
En Binutils, esto se reduce a analizar un script vinculador y a lidiar con un montón de valores predeterminados.
Puede obtener el script de enlazador utilizado con
ld --verbose
, y configurar uno personalizado conld -T
. -
hacer reubicación en secciones de texto. Esto depende de cómo se guardan las múltiples secciones en la memoria.
readelf -l hello_world.out
da:
Elf file type is EXEC (Executable file)
Entry point 0x4000b0
There are 2 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000d7 0x00000000000000d7 R E 200000
LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
0x000000000000000d 0x000000000000000d RW 200000
Section to Segment mapping:
Segment Sections...
00 .text
01 .data
En el encabezado ELF,
e_phoff
,
e_phnum
y
e_phentsize
nos dijeron que hay 2 encabezados de programa, que comienzan en
0x40
y tienen
0x38
longitud de
0x38
bytes cada uno, por lo que son:
00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....|
00000060 d7 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 |................|
00000070 00 00 20 00 00 00 00 00 |.. ..... |
y:
00000070 01 00 00 00 06 00 00 00 | ........|
00000080 d8 00 00 00 00 00 00 00 d8 00 60 00 00 00 00 00 |..........`.....|
00000090 d8 00 60 00 00 00 00 00 0d 00 00 00 00 00 00 00 |..`.............|
000000a0 0d 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 |.......... .....|
Estructura representada http://www.sco.com/developers/gabi/2003-12-17/ch5.pheader.html :
typedef struct {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
Elf64_Xword p_filesz;
Elf64_Xword p_memsz;
Elf64_Xword p_align;
} Elf64_Phdr;
Desglose del primero:
-
40 0:
p_type
=01 00 00 00
=PT_LOAD
: TODO. Creo que significa que en realidad se cargará en la memoria. Otros tipos pueden no ser necesariamente. -
40 4:
p_flags
=05 00 00 00
= ejecutar y leer permisos, no escribir TODO -
40 8:
p_offset
= 8x00
TODO: ¿qué es esto? Parecen desplazamientos desde el comienzo de los segmentos. ¿Pero esto significaría que algunos segmentos están entrelazados? Es posible jugar un poco con él:gcc -Wl,-Ttext-segment=0x400030 hello_world.c
-
50 0:
p_vaddr
=00 00 40 00 00 00 00 00
: dirección de memoria virtual inicial para cargar este segmento -
50 8:
p_paddr
=00 00 40 00 00 00 00 00
: dirección física inicial para cargar en la memoria. Solo es importante para los sistemas en los que el programa puede configurar su dirección física. De lo contrario, como en el Sistema V, como los sistemas, puede ser cualquier cosa. NASM parece simplemente copiarp_vaddrr
-
60 0:
p_filesz
=d7 00 00 00 00 00 00 00
: TODO vsp_memsz
-
60 8:
p_memsz
=d7 00 00 00 00 00 00 00
: TODO -
70 0:
p_align
=00 00 20 00 00 00 00 00
: 0 o 1 significa que no se requiere alineación TODO ¿qué significa eso? de lo contrario redundante con otros campos
El segundo es análogo.
Entonces la:
Section to Segment mapping:
La sección de
readelf
nos dice que:
-
0 es el segmento
.text
. Ajá, por eso es ejecutable y no escribible -
1 es el segmento
.data
.
Sólo curioso. Obviamente, esta no es una muy buena solución para la programación real, pero digamos que quería hacer un ejecutable en Bless (un editor hexadecimal).
Mi arquitectura es x86. ¿Qué es un programa muy simple que puedo hacer? Un hola mundo? ¿Un bucle infinito? Similar a this pregunta, pero en Linux.
Como mencioné en mi comentario, esencialmente escribirás tu propio encabezado elfo para el ejecutable eliminando las secciones innecesarias. Todavía hay varias secciones requeridas. La documentación en Muppetlabs-TinyPrograms hace un trabajo justo explicando este proceso. Por diversión, aquí hay un par de ejemplos:
El equivalente de / bin / true (45 bytes):
00000000 7F 45 4C 46 01 00 00 00 00 00 00 00 00 00 49 25 |.ELF..........I%|
00000010 02 00 03 00 1A 00 49 25 1A 00 49 25 04 00 00 00 |......I%..I%....|
00000020 5B 5F F2 AE 40 22 5F FB CD 80 20 00 01 |[_..@"_... ..|
0000002d
Tu clásico ''¡Hola Mundo!'' (160 bytes):
00000000 7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 03 00 01 00 00 00 74 80 04 08 34 00 00 00 |........t...4...|
00000020 00 00 00 00 00 00 00 00 34 00 20 00 02 00 28 00 |........4. ...(.|
00000030 00 00 00 00 01 00 00 00 74 00 00 00 74 80 04 08 |........t...t...|
00000040 74 80 04 08 1f 00 00 00 1f 00 00 00 05 00 00 00 |t...............|
00000050 00 10 00 00 01 00 00 00 93 00 00 00 93 90 04 08 |................|
00000060 93 90 04 08 0d 00 00 00 0d 00 00 00 06 00 00 00 |................|
00000070 00 10 00 00 b8 04 00 00 00 bb 01 00 00 00 b9 93 |................|
00000080 90 04 08 ba 0d 00 00 00 cd 80 b8 01 00 00 00 31 |...............1|
00000090 db cd 80 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a |...Hello world!.|
000000a0
No olvides hacerlos ejecutables ...