¿Cómo obtener código c para ejecutar código de máquina hexadecimal?
Quiero un método C simple para poder ejecutar código de bytes hexadecimal en una máquina Linux de 64 bits. Aquí está el programa en C que tengo:
char code[] = "\x48\x31\xc0";
#include <stdio.h>
int main(int argc, char **argv)
{
int (*func) ();
func = (int (*)()) code;
(int)(*func)();
printf("%s\n","DONE");
}
El código que estoy intentando ejecutar ( "\x48\x31\xc0"
) lo obtuve escribiendo este sencillo programa ensamblador (se supone que no debe hacer nada)
.text
.globl _start
_start:
xorq %rax, %rax
y luego compilarlo y volcarlo para obtener el código de bytes.
Sin embargo, cuando ejecuto mi programa C aparece un error de segmentación. ¿Algunas ideas?
El código de máquina debe estar en una página ejecutable. Estás char code[]
en la sección de datos de lectura+escritura, sin permiso ejecutivo, por lo que el código no se puede ejecutar desde allí.
A continuación se muestra un ejemplo sencillo de cómo asignar una página ejecutable con mmap
:
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main ()
{
char code[] = {
0x8D, 0x04, 0x37, // lea eax,[rdi+rsi]
0xC3 // ret
};
int (*sum) (int, int) = NULL;
// allocate executable buffer
sum = mmap (0, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
// copy code to buffer
memcpy (sum, code, sizeof(code));
// doesn't actually flush cache on x86, but ensure memcpy isn't
// optimized away as a dead store.
__builtin___clear_cache (sum, sum + sizeof(sum)); // GNU C
// run code
int a = 2;
int b = 3;
int c = sum (a, b);
printf ("%d + %d = %d\n", a, b, c);
}
Consulte otra respuesta a esta pregunta para obtener detalles sobre __builtin___clear_cache
.
Hasta las versiones recientes del kernel de Linux (en algún momento antes de la 5.4), simplemente se podía compilar gcc -z execstack
, lo que haría que todas las páginas fueran ejecutables, incluidos los datos de solo lectura ( .rodata
) y los datos de lectura y escritura ( .data
) char code[] = "..."
.
Ahora -z execstack
solo se aplica a la pila real, por lo que actualmente solo funciona para matrices locales no constantes. es decir, mudarse char code[] = ...
a main
. Los sistemas modernos hacen que la menor cantidad de páginas ejecutables sea posible para protegerse contra exploits.
Consulte Comportamiento predeterminado de Linux en la sección `.data` para conocer el cambio del kernel y Permiso ejecutivo inesperado de mmap cuando se incluyen archivos ensamblados en el proyecto para conocer el comportamiento anterior: habilitar READ_IMPLIES_EXEC
el proceso de Linux para ese programa. (En Linux 5.4, esas preguntas y respuestas muestran que solo obtendrías READ_IMPLIES_EXEC
un faltante PT_GNU_STACK
, como un binario muy antiguo; el GCC moderno -z execstack
establecería PT_GNU_STACK = RWX
metadatos en el ejecutable, lo que Linux 5.4 manejaría como si solo la pila fuera ejecutable. En algún momento antes de eso , PT_GNU_STACK = RWX
resultó en READ_IMPLIES_EXEC
.)
La otra opción es realizar llamadas al sistema en tiempo de ejecución para copiar en una página ejecutable, o cambiar los permisos en la página en la que se encuentra. Eso es aún más complicado que usar una matriz local para hacer que GCC copie el código en la memoria de pila ejecutable.
(No sé si hay una manera fácil de habilitarlo READ_IMPLIES_EXEC
en los kernels modernos. No tener ningún atributo de pila GNU en un binario ELF hace eso para el código de 32 bits, pero no para el de 64 bits).
Otra opción más es __attribute__((section(".text"))) const char code[] = ...;
el ejemplo práctico: https://godbolt.org/z/draGeh .
Si necesita que se pueda escribir en la matriz, por ejemplo, para el código shell que inserta algunos ceros en las cadenas, tal vez podría vincularlo con ld -N
. Pero probablemente sea mejor usar -z execstack y una matriz local.
Dos problemas en la pregunta:
- permiso exec en la página, porque usó una matriz que irá en la
.data
sección de lectura+escritura de noexec. - su código de máquina no termina con una
ret
instrucción , por lo que incluso si se ejecutara, la ejecución caería en lo que estuviera a continuación en la memoria en lugar de regresar.
Y por cierto, el prefijo REX es redundante. "\x31\xc0"
xor eax,eax
tiene exactamente el mismo efecto quexor rax,rax
.
Necesita que la página que contiene el código de máquina tenga permiso de ejecución . Las tablas de páginas x86-64 tienen un bit separado para ejecución independiente del permiso de lectura, a diferencia de las tablas heredadas de 386 páginas.
La forma más sencilla de conseguir que las matrices estáticas estuvieran en la memoria read+exec era compilar con gcc -z execstack
. (Se utiliza para hacer ejecutables la pila y otras secciones, ahora solo la pila).
typedef int (*intfunc_int)(int);
int main(void)
{
unsigned char execbuf[] = { // compile with -zexecstack
0x8d, 0x47, 0x01, // lea 0x1(%rdi),%eax
0xc3 // ret
};
// a string initializer like char execbuf[] = "\xc3"; also works
// Tell GCC we're about to run this data as code. x86 has coherent I-cache,
// but this also stops optimization from removing the initialization as dead stores.
__builtin___clear_cache (execbuf, execbuf+sizeof(execbuf)-1);
// Without this, the store disappears
intfunc_int fptr = (intfunc_int) execbuf; // cast to function pointer.
int res = fptr(2); // deref the function pointer
return res; // returns 3 on non-Windows ISAs where the first arg is in EDI
}
Se compila en un ensamblaje simple ( Godbolt ; también muestra que está roto sin él __builtin___clear_cache
; se saltará la tienda y simplemente saltará al espacio de pila no inicializado). Esto se ejecuta correctamente con -z execstack
, se producirá un error de segmentación sin él.
# GCC -O3 for x86-64
main:
sub rsp, 24 # GCC reserves 16 bytes more stack space than it needed
mov edi, 2 # function arg
mov DWORD PTR [rsp+12], -1023326323 # store 4 bytes of machine code
lea rax, [rsp+12] # pointer into a register
call rax # call through the function pointer
add rsp, 24
ret
Enlazador GNU más antiguo ld
usado para hacer .rodata
read+exec
Hasta hace poco (2018 o 2019), la cadena de herramientas estándar (binutils ld
) colocaba la sección .rodata
en el mismo segmento ELF que .text
, por lo que ambos tendrían permiso de lectura+ejecución. Por lo tanto, el uso const char code[] = "...";
fue suficiente para ejecutar bytes especificados manualmente como datos, sin execstack.
Pero en mi sistema Arch Linux con GNU ld (GNU Binutils) 2.31.1
, ese ya no es el caso. readelf -a
muestra que la .rodata
sección entró en un segmento ELF con .eh_frame_hdr
y .eh_frame
y solo tiene permiso de lectura. .text
va en un segmento con Read + Exec y .data
va en un segmento con Read + Write (junto con y .got
) .got.plt
. (¿ Cuál es la diferencia entre sección y segmento en formato de archivo ELF ?)
Supongo que este cambio es para dificultar los ataques ROP y Spectre al no tener datos de solo lectura en páginas ejecutables donde secuencias de bytes útiles podrían usarse como "dispositivos" que terminan con los bytes de una instrucción ret
o jmp reg
.
// See above for char code[] = {...} inside main with -z execstack, for current Linux
// This is broken on recent Linux, used to work without execstack.
#include <stdio.h>
// can be non-const if you use gcc -z execstack. static is also optional
static const char code[] = {
0x8D, 0x04, 0x37, // lea eax,[rdi+rsi] // retval = a+b;
0xC3 // ret
};
static const char ret0_code[] = "\x31\xc0\xc3"; // xor eax,eax ; ret
// the compiler will append a 0 byte to terminate the C string,
// but that's fine. It's after the ret.
int main () {
// void* cast is easier to type than a cast to function pointer,
// and in C can be assigned to any other pointer type. (not C++)
int (*sum) (int, int) = (void*)code;
int (*ret0)(void) = (void*)ret0_code;
// run code
int c = sum (2, 3);
return ret0();
}
En sistemas Linux más antiguos: gcc -O3 shellcode.c && ./a.out
(Funciona en const
matrices globales/estáticas)
En Linux anterior a 5.5 (más o menos)gcc -O3 -z execstack shellcode.c && ./a.out
(funciona independientemente -zexecstack
de dónde esté almacenado el código de máquina). Dato curioso: gcc permite -zexecstack
sin espacio, pero clang solo acepta clang -z execstack
.
Estos también funcionan en Windows, donde ingresan datos de solo lectura .rdata
en lugar de archivos .rodata
.
El compilador generado main
tiene este aspecto (de objdump -drwC -Mintel
). Puede ejecutarlo internamente gdb
y establecer puntos de interrupción code
yret0_code
(I actually used gcc -no-pie -O3 -zexecstack shellcode.c hence the addresses near 401000
0000000000401020 <main>:
401020: 48 83 ec 08 sub rsp,0x8 # stack aligned by 16 before a call
401024: be 03 00 00 00 mov esi,0x3
401029: bf 02 00 00 00 mov edi,0x2 # 2 args
40102e: e8 d5 0f 00 00 call 402008 <code> # note the target address in the next page; that's where .rodata goes
401033: 48 83 c4 08 add rsp,0x8
401037: e9 c8 0f 00 00 jmp 402004 <ret0_code> # optimized tailcall
O utilice llamadas al sistema para modificar los permisos de la página.
En lugar de compilar con gcc -zexecstack
, puede utilizar mmap(PROT_EXEC)
para asignar nuevas páginas ejecutables o mprotect(PROT_EXEC)
cambiar las páginas existentes a ejecutables. (Incluidas las páginas que contienen datos estáticos). Por lo general, también desea al menos PROT_READ
y a veces PROT_WRITE
, por supuesto.
Usarlo mprotect
en una matriz estática significa que todavía estás ejecutando el código desde una ubicación conocida, lo que tal vez facilite establecer un punto de interrupción en él.
En Windows puedes usar VirtualAlloc o VirtualProtect.
Decirle al compilador que los datos se ejecutan como código
Normalmente, los compiladores como GCC suponen que los datos y el código están separados. Esto es como un alias estricto basado en tipos, pero incluso usarlo char*
no lo deja bien definido para almacenarlo en un búfer y luego llamar a ese búfer como un puntero de función.
En GNU C, también es necesario usarlo __builtin___clear_cache(buf, buf + len)
después de escribir bytes de código de máquina en un búfer , porque el optimizador no trata la desreferenciación de un puntero de función como una lectura de bytes de esa dirección. La eliminación del almacén muerto puede eliminar los almacenes de bytes de código de máquina en un búfer, si el compilador demuestra que nada lee el almacén como datos. https://codegolf.stackexchange.com/questions/160100/the-repetitive-byte-counter/160236#160236 y https://godbolt.org/g/pGXn3B tienen un ejemplo en el que gcc realmente hace esta optimización, porque gcc "sabe sobre" malloc
. También el primer bloque de código en esta respuesta, donde usamos una matriz local en el espacio de pila ejecutable.
(Y en arquitecturas que no son x86 donde I-cache no es coherente con D-cache, en realidad realizará cualquier sincronización de caché necesaria. En x86 es puramente un bloqueador de optimización en tiempo de compilación y no se expande a ninguna instrucción en sí, porque un salto o una llamada es suficiente en papel para JIT o código automodificable, y en la práctica es completamente imposible observar código obsoleto después de un almacenamiento en CPU x86 reales).
Re: el nombre extraño con tres guiones bajos: es el __builtin_name
patrón habitual, pero name
es __clear_cache
.
Mi edición de la respuesta de @AntoineMathys agregó esto.
En la práctica, GCC/clang no "conoce" mmap(MAP_ANONYMOUS)
la forma en que lo sabe malloc
. Entonces, en la práctica, el optimizador asumirá que la memcpy en el búfer podría leerse como datos mediante la llamada a la función no en línea a través del puntero de función, incluso sin __builtin___clear_cache()
. (A menos que haya declarado el tipo de función como __attribute__((const))
.)
En x86, donde I-cache es coherente con los cachés de datos, que los almacenes se realicen en asm antes de la llamada es suficiente para que sea correcto. En otras ISA, __builtin___clear_cache()
en realidad emitirá instrucciones especiales y garantizará el orden correcto en tiempo de compilación.
Es una buena práctica incluirlo al copiar código en un búfer porque no afecta el rendimiento y evita que futuros compiladores hipotéticos rompan su código. (por ejemplo, si entienden que eso mmap(MAP_ANONYMOUS)
proporciona memoria anónima recién asignada a la que nada más tiene un puntero, como malloc).
Con GCC actual, pude provocar que GCC realmente hiciera una optimización que no queremos al usarla__attribute__((const))
para decirle que el optimizador sum()
es una función pura (que solo lee sus argumentos, no la memoria global). GCC entonces sabe sum()
que no puede leer el resultado de los memcpy
datos.
Con otro memcpy
en el mismo búfer después de la llamada, GCC realiza la eliminación del almacén inactivo solo en el segundo almacén después de la llamada. Esto da como resultado que no se almacene antes de la primera llamada, por lo que ejecuta los 00 00 add [rax], al
bytes, con un error de segmentación.
// demo of a problem on x86 when not using __builtin___clear_cache
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main ()
{
char code[] = {
0x8D, 0x04, 0x37, // lea eax,[rdi+rsi]
0xC3 // ret
};
__attribute__((const)) int (*sum) (int, int) = NULL;
// copy code to executable buffer
sum = mmap (0,sizeof(code),PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_ANON,-1,0);
memcpy (sum, code, sizeof(code));
//__builtin___clear_cache(sum, sum + sizeof(code));
int c = sum (2, 3);
//printf ("%d + %d = %d\n", a, b, c);
memcpy(sum, (char[]){0x31, 0xc0, 0xc3, 0}, 4); // xor-zero eax, ret, padding for a dword store
//__builtin___clear_cache(sum, sum + 4);
return sum(2,3);
}
Compilado en el explorador del compilador Godbolt con GCC9.2 -O3
main:
push rbx
xor r9d, r9d
mov r8d, -1
mov ecx, 34
mov edx, 7
mov esi, 4
xor edi, edi
sub rsp, 16
call mmap
mov esi, 3
mov edi, 2
mov rbx, rax
call rax # call before store
mov DWORD PTR [rbx], 12828721 # 0xC3C031 = xor-zero eax, ret
add rsp, 16
pop rbx
ret # no 2nd call, CSEd away because const and same args
Al pasar argumentos diferentes se habría obtenido otro call reg
, pero incluso con __builtin___clear_cache
las dos sum(2,3)
llamadas se puede hacer CSE . __attribute__((const))
no respeta los cambios en el código de máquina de una función. No lo hagas. Sin embargo, es seguro si va a ejecutar JIT la función una vez y luego llamarla muchas veces.
Descomentar los primeros __clear_cache
resultados en
mov DWORD PTR [rax], -1019804531 # lea; ret
call rax
mov DWORD PTR [rbx], 12828721 # xor-zero; ret
... still CSE and use the RAX return value
La primera tienda está ahí gracias a __clear_cache
la sum(2,3)
llamada. (Eliminar la primera sum(2,3)
llamada permite que se elimine el almacén muerto en todo el archivo __clear_cache
).
El segundo almacén está ahí porque se supone que el efecto secundario en el búfer devuelto mmap
es importante, y ese es el valor final main
que sale.
La opción de Godbolt ./a.out
para ejecutar el programa todavía parece fallar siempre (estado de salida de 255); ¿Quizás sea un espacio aislado para JITing? Funciona en mi escritorio __clear_cache
y falla sin él.
mprotect
en una página que contiene variables C existentes.
También puede otorgar permiso de lectura+escritura+ejecución a una única página existente. Esta es una alternativa a la compilación con-z execstack
No necesita __clear_cache
una página que contenga variables C de solo lectura porque no hay una tienda para optimizar. Aún lo necesitarías para inicializar un búfer local (en la pila). De lo contrario, GCC optimizará el inicializador para este búfer privado al que una llamada de función no en línea definitivamente no tiene un puntero. (Análisis de fuga). No considera la posibilidad de que el búfer contenga el código de máquina para la función a menos que se lo indique a través de __builtin___clear_cache
.
#include <stdio.h>
#include <sys/mman.h>
#include <stdint.h>
// can be non-const if you want, we're using mprotect
static const char code[] = {
0x8D, 0x04, 0x37, // lea eax,[rdi+rsi] // retval = a+b;
0xC3 // ret
};
static const char ret0_code[] = "\x31\xc0\xc3";
int main () {
// void* cast is easier to type than a cast to function pointer,
// and in C can be assigned to any other pointer type. (not C++)
int (*sum) (int, int) = (void*)code;
int (*ret0)(void) = (void*)ret0_code;
// hard-coding x86's 4k page size for simplicity.
// also assume that `code` doesn't span a page boundary and that ret0_code is in the same page.
uintptr_t page = (uintptr_t)code & -4095ULL; // round down
mprotect((void*)page, 4096, PROT_READ|PROT_EXEC|PROT_WRITE); // +write in case the page holds any writeable C vars that would crash later code.
// run code
int c = sum (2, 3);
return ret0();
}
Lo usé PROT_READ|PROT_EXEC|PROT_WRITE
en este ejemplo para que funcione independientemente de dónde esté su variable. Si fuera un local en la pila y lo omitiera PROT_WRITE
, call
fallaría después de hacer que la pila solo lea cuando intentara enviar una dirección de retorno.
Además, PROT_WRITE
le permite probar el código shell que se automodifica, por ejemplo, para editar ceros en su propio código de máquina u otros bytes que estaba evitando.
$ gcc -O3 shellcode.c # without -z execstack
$ ./a.out
$ echo $?
0
$ strace ./a.out
...
mprotect(0x55605aa3f000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
exit_group(0) = ?
+++ exited with 0 +++
Si comento mprotect
, se produce un error de segmentación con versiones recientes de GNU Binutils ld
que ya no colocan datos constantes de solo lectura en el mismo segmento ELF que la .text
sección.
Si hiciera algo como ret0_code[4] = 0xc3;
, después de eso necesitaría __builtin___clear_cache(ret0_code+2, ret0_code+2)
asegurarme de que la tienda no esté optimizada, pero si no modifico las matrices estáticas, entonces no es necesario después mprotect
. Es necesario después del almacenamiento mmap
+ memcpy
o manual, porque queremos ejecutar bytes que se han escrito en C (con memcpy
).
Debe incluir el ensamblado en línea a través de una directiva de compilación especial para que termine correctamente en un segmento de código. Consulte esta guía, por ejemplo: http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html
Su código de máquina puede estar bien, pero su CPU se opone.
Las CPU modernas administran la memoria en segmentos. En funcionamiento normal, el sistema operativo carga un nuevo programa en un segmento de texto del programa y configura una pila en un segmento de datos . El sistema operativo le dice a la CPU que nunca ejecute código en un segmento de datos. Su código está en code[]
, en un segmento de datos. De ahí la falla de segmentación.
Esto requerirá algo de esfuerzo.
Su code
variable se almacena en la .data
sección de su ejecutable:
$ readelf -p .data exploit
String dump of section '.data':
[ 10] H1À
H1À
es el valor de tu variable.
La .data
sección no es ejecutable:
$ readelf -S exploit
There are 30 section headers, starting at offset 0x1150:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[...]
[24] .data PROGBITS 0000000000601010 00001010
0000000000000014 0000000000000000 WA 0 0 8
Todos los procesadores de 64 bits con los que estoy familiarizado admiten páginas no ejecutables de forma nativa en las tablas de páginas. La mayoría de los procesadores de 32 bits más nuevos (los que admiten PAE) proporcionan suficiente espacio adicional en sus tablas de páginas para que el sistema operativo emule páginas de hardware no ejecutables. Necesitará ejecutar un sistema operativo antiguo o un procesador antiguo para obtener una .data
sección marcada como ejecutable.
Debido a que estos son solo indicadores en el ejecutable, debería poder configurarlos X
a través de algún otro mecanismo, pero no sé cómo hacerlo. Y es posible que su sistema operativo ni siquiera le permita tener páginas que se puedan escribir y ejecutar.