Manipulación de la stack en C sin utilizar ensamblaje en línea

Me he estado preparando para un concurso de encoding y encontré esta pregunta en Internet:

#include  void a(); void b(); void c(); int main() { a(); printf("\n"); return 0; } void a() { b(); printf("hi "); } void b() { c(); printf("there "); } void c() { int x; // code here and nowhere else } 

La solución es escribir código que se imprima “hola” en lugar de “hola hola” (no se pueden usar funciones de impresión adicionales y el código solo va en el bloque de comentarios).

Dado que he hecho algunos códigos de ensamblaje básicos, me di cuenta de que esto podría hacerse con la manipulación de la stack utilizando el entero x como la base.

Intenté usar gdb para encontrar la dirección de retorno de las funciones y luego cambié las direcciones de retorno de a y b . El comstackdor genera un error de segmentación y, por lo tanto, asumo que no he usado la dirección de retorno adecuada.

¿Cómo calculo correctamente el desplazamiento para encontrar la dirección de retorno? El comando de marco de información en gdb no fue útil, ya que usar el valor de dirección de stack dado no funcionó.

Estoy ejecutando esto en Linux usando gcc.

No estoy seguro si lo siguiente contará. Es portátil en POSIX. Básicamente, cambia el búfer de printf antes de su primera llamada y lo manipula antes de que se descargue al terminal

 void c() { static int first = 1; if (first) { first = 0; char buf0[BUFSIZ]; char buf1[BUFSIZ]; setvbuf(stdout, buf0, _IOFBF, BUFSIZ); a(); memcpy(buf1, buf0 + 6, 3); memcpy(buf1 + 3, buf0, 6); memcpy(buf0, buf1, 9); buf0[8] = '\n'; fflush(stdout); exit(0); } } 

Recibirá advertencias sobre la statement implícita de las funciones de biblioteca memcpy y exit . Es legal en C89 aunque desanimado. Pero en tu caso, ningún truco está demasiado sucio, supongo. Puedes evitar el memcpy copiando los caracteres manualmente. Puede evitar la exit redireccionando stdout través de freopen . Puede cambiar BUFSIZ a constantes grandes si el sistema tiene un tamaño de búfer extrañamente pequeño (más pequeño que 9). Hay variantes de esta solución que no requieren que usted inserte manualmente ese \n cambio, permita que el progtwig salga normalmente de main y tenga el printf("\n") para poner ese final de línea

Este problema no se puede resolver a menos que rompas la stack de la misma manera que un atacante rompe la stack de algún proceso.

Y a smesh la stack se puede hacer solo si conoce cada detalle de la implementación del comstackdor, el problema no se puede resolver de otra manera.

Si conoce los detalles de la comstackción (la estructura de la stack en particular) puede usar la dirección de la variable x local para obtener la dirección del marco actual de la stack (de FRAME_C); En cada cuadro se encuentra el puntero base del cuadro anterior y se modifica.

La stack se ve así:

  FRAME_MAIN = RET_OS some-data FRAME_A = RET_MAIN some-data FRAME_B = RET_A some-data FRAME_C = RET_B some-data(including the variable `x`) 

Usando el &x podemos detectar la posición del FRAME_C.

Una solucion es

  1. para imprimir “Hola” en la función c ()
  2. Modifique FRAME_B para que RET_A se convierta en RET_MAIN
  3. retorno de la función c () con return

La operación complicada es 2. pero si cada fotogtwig tiene un tamaño conocido, podemos modificar el puntero de retorno RET_A del fotogtwig B y detectar RET_MAIN algo así:

 *(&x+FRAME_C_SIZE+some-small-offset1) = /* *&RET_A = */ *(&x+(FRAME_C_SIZE+FRAME_B_SIZE)+some-small-offset2). /* *&RET_MAIN */ 

Como puede ver, necesita conocer muchos detalles sobre la implementación del comstackdor, por lo que esta no es en absoluto una solución portátil.


Otra solución sería imprimir “hola, allí” y redirigir la stdout a /dev/null . Supongo que exit () u otros trucos dependientes del comstackdor no están permitidos, de lo contrario el problema no tiene sentido para un concurso.

Mi solución es para x86 / x64 y para el comstackdor de CL , pero creo que para gcc también existe.

sólo pregunta – existen equivalentes para la función:

void ** _AddressOfReturnAddress();

y son equivalentes para __declspec(noinline) – para decirle al comstackdor que nunca incorpore una función en particular

let void* pb – es la dirección en void b() justo después de c(); y void* pa es la dirección en void a() justo después de b();

porque b casi iguales, podemos asumir que

(ULONG_PTR)pa - (ULONG_PTR)&a == (ULONG_PTR)pb - (ULONG_PTR)&b;

y, por supuesto, el diseño de la stack en a y b debe ser el mismo. Basado en esto y solución. el siguiente código probado / trabajado con el comstackdor CL – en x86 / x64 (windows) y con /Ox (Full Optimization) y con /Od (Disable (Debug)) – todo funcionó.

 extern "C" void ** _AddressOfReturnAddress(); void a(); void b(); void c(); int main() { a(); printf("\n"); return 0; } __declspec(noinline) void a() { b(); printf("hi "); } __declspec(noinline) void b() { c(); printf("there "); } __declspec(noinline) void c() { void** pp = _AddressOfReturnAddress(); void* pb = *pp; void* pa = (void*)((ULONG_PTR)&a + ((ULONG_PTR)pb - (ULONG_PTR)&b)); for (;;) { if (*++pp == pa) { *pp = pb; *_AddressOfReturnAddress() = pa; return; } } }