- Las secciones no existen en el contexto de un proceso en ejecución, solo segmentos.
mprotect
se puede usar para cambiar los permisos de las páginas eltext
el segmento está asignado. Aquí hay un tutorial sobre cómo lograr esto:escribir un programa en C x86_64 automutante- de las notas sobre el
mprotect
página de manual:En Linux, siempre está permitido llamar a mprotect() en cualquier dirección en el espacio de direcciones de un proceso (excepto en el área vsyscall del núcleo). En particular, se puede usar para cambiar las asignaciones de código existentes para que se puedan escribir .
La información de la sección se almacena en la tabla de encabezado de la sección. La tabla de encabezados de sección es una matriz de encabezados de sección. La tabla de encabezado de sección no está asignada a ningún segmento y el cargador de programas no la analiza. El cargador usa información de segmento solo cuando asigna un programa a la memoria virtual.
Los segmentos, no las secciones, tienen permisos, y estos se almacenan en el encabezado del programa del segmento en el p_flags
campo. Los encabezados de programa residen en la tabla de encabezados de programa del binario.
Todo esto está documentado en los capítulos 4 y 5 en System V ABI (genérico).
En el resultado a continuación, podemos ver los permisos asociados con cada segmento bajo el flags
columna:
$ readelf -l /bin/ls
Elf file type is EXEC (Executable file)
Entry point 0x404890
There are 9 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000019d44 0x0000000000019d44 R E 200000
LOAD 0x0000000000019df0 0x0000000000619df0 0x0000000000619df0
0x0000000000000804 0x0000000000001570 RW 200000
DYNAMIC 0x0000000000019e08 0x0000000000619e08 0x0000000000619e08
0x00000000000001f0 0x00000000000001f0 RW 8
NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x000000000001701c 0x000000000041701c 0x000000000041701c
0x000000000000072c 0x000000000000072c R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 10
GNU_RELRO 0x0000000000019df0 0x0000000000619df0 0x0000000000619df0
0x0000000000000210 0x0000000000000210 R 1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
El .rodata
La sección en los archivos ELF contiene partes del segmento de texto que no deben cambiarse.
esto es falso Todo el text
el segmento es Leer/Ejecutar.
De manera predeterminada, todas las páginas de esta sección son de solo lectura y cualquier intento de modificación generará una falla de protección general.
esto es falso Los segmentos, no las secciones, se asignan a las páginas (de ahí el Align
valores) y tener permisos (de ahí el Flags
valores).
Puede encontrar más información aquí:
- http://duartes.org/gustavo/blog/post/how-the-kernel-manages-your-memory/
- https://lwn.net/Articles/631631/
- http://nairobi-embedded.org/040_elf_sec_seg_vma_mappings.html#section-segment-vma-mappings
Del manual:
En Linux, siempre está permitido llamar a mprotect() en cualquier dirección del espacio de direcciones de un proceso (excepto en el área vsyscall del kernel). En particular, se puede usar para cambiar las asignaciones de código existentes para que se puedan escribir.
Aquí hay un programa de muestra para demostrarlo.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#define PAGE_SIZE 4096
const unsigned char rodata[3*PAGE_SIZE] = {1,2,3};
int main(void)
{
printf("rodata = %p\n", rodata);
uintptr_t page_base = ((uintptr_t)rodata / PAGE_SIZE + 1) * PAGE_SIZE;
unsigned char *p = (unsigned char *)rodata + PAGE_SIZE;
//*p = '!'; // this would cause a segfault
puts("Before mprotect:");
system("cat /proc/$PPID/maps");
if (mprotect((void*)page_base, 1, PROT_READ | PROT_WRITE) < 0) {
perror("mprotect");
return 1;
}
puts("After mprotect:");
system("cat /proc/$PPID/maps");
*p = '!';
return 0;
}
Por supuesto, cualquier dato que escriba en la página permanecerá en la memoria. Linux ve que el proceso está escribiendo en una página que actualmente está asignada como de solo lectura y hace una copia. En el momento de la escritura, el kernel no distingue esto de la copia en escritura después de que un proceso se haya bifurcado. Puede observar esto bifurcando, escribiendo en un proceso y leyendo en el otro:el otro proceso no verá la escritura ya que es una escritura en la memoria del proceso de escritura, no en la memoria del proceso de lectura.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#define PAGE_SIZE 4096
const unsigned char rodata[3*PAGE_SIZE] = {0};
void writer(char *p)
{
if (mprotect(p, 1, PROT_READ | PROT_WRITE) < 0) {
perror("mprotect");
return 1;
}
puts("After mprotect:");
system("cat /proc/$PPID/maps");
*p = 1;
printf("wrote %d\n", *p);
}
void reader(char *p)
{
printf("read %d\n", *p);
}
int main(void)
{
printf("rodata = %p\n", rodata);
uintptr_t page_base = (((uintptr_t)rodata / PAGE_SIZE + 1) * PAGE_SIZE);
volatile char *p = (volatile char *)page_base;
//*p = '!'; // this would cause a segfault
puts("Before mprotect:");
system("cat /proc/$PPID/maps");
if (fork() == 0) {
writer(p);
} else {
sleep(1);
reader(p);
}
return 0;
}
Sospecho que hay parches de refuerzo que evitan que un proceso cambie sus propias asignaciones de memoria, pero no tengo ninguno para ofrecer.