Cómo determinar si un puntero está en rodata [duplicado]
¿Puedo saber si un puntero está en la sección rodata de un ejecutable? Al igual que en, editar los datos de ese puntero causaría una trampa del sistema en tiempo de ejecución.
Ejemplo (usando un puntero de carácter C):
void foo(char const * const string) {
if ( in_rodata( string ) ) {
puts("It's in rodata!");
} else {
puts("That ain't in rodata");
}
}
Ahora estaba pensando que, tal vez, simplemente podría comparar el puntero con la sección rodata.
Algo parecido a:
if ( string > start_of_rodata && string < end_of_rodata ) {
// it's in rodata!
}
¿Es este un plan / idea factible? ¿Alguien tiene una idea de cómo podría hacer esto?
(¿Existe alguna información del sistema que uno pueda necesitar para responder a esto?) Estoy ejecutando el programa en una plataforma Linux.
Respuestas
Dudo que pueda ser portátil
Si no desea meterse con los scripts del enlazador o usar API de consulta de mapas de memoria específicos de la plataforma, un enfoque de proxy es bastante portátil en plataformas con protección de memoria, si está dispuesto a saber si la ubicación es de solo lectura y escritura , o ninguno. La idea general es hacer una prueba de lectura y una prueba de escritura. Si el primero tiene éxito pero el segundo falla, es probable que .rodata
sea un segmento de código. Esto no le dice "es rodata seguro" - puede ser un segmento de código, o alguna otra página de solo lectura, como un mapeo de memoria de archivos de solo lectura que tiene la función de copia en escritura desactivada. Pero eso depende de lo que tenga en mente para esta prueba: cuál fue el propósito final.
Otra advertencia es: para que esto sea incluso remotamente seguro, debe suspender todos los demás subprocesos en el proceso cuando realice esta prueba, ya que existe la posibilidad de que pueda corromper algún estado al que pueda referirse el código que se ejecuta en otro subproceso. Hacer esto desde dentro de un proceso en ejecución puede tener casos de esquina difíciles de depurar que dejarán de acechar y se mostrarán durante una demostración para el cliente. Por lo tanto, en las plataformas que admiten esto, siempre es preferible generar otro proceso que suspenda el primer proceso en su totalidad (todos los subprocesos), lo pruebe, escriba el resultado en el espacio de direcciones del proceso (en alguna variable de resultado) y reanude el proceso. y terminarse a sí mismo. En algunas plataformas, no es posible modificar el espacio de direcciones de un proceso desde el exterior y, en su lugar, debe suspender el proceso en su mayor parte o por completo, inyectar un hilo de prueba, suspender los otros hilos restantes, dejar que la sonda haga su trabajo, escribir una respuesta a alguna variable acordada, termina, luego reanuda todo lo demás desde la seguridad de un proceso externo.
En aras de la simplicidad, a continuación se asumirá que todo se hace desde dentro del proceso. A pesar de que los ejemplos independientes "totalmente capaces" que funcionan en procesos cruzados no serían muy largos, escribir este material es un poco tedioso, especialmente si lo quieres corto, elegante y al menos en su mayoría correcto. Me imagino el valor de un día completo de trabaja. Entonces, en cambio, haré algunos bocetos y te dejaré completar los espacios en blanco (ja).
Ventanas
Las excepciones estructuradas se lanzan, por ejemplo, debido a fallas de protección o se dividen por cero. Para realizar la prueba, intente leer desde la dirección en cuestión. Si tiene éxito, sabrá que es al menos una página asignada (de lo contrario, arrojará una excepción que puede detectar). Luego intente escribir allí; si eso falla, entonces es de solo lectura. El código es casi aburrido:
static const int foo;
static int bar;
#if _WIN32
typedef struct ThreadState ThreadState;
ThreadState *suspend_other_threads(void) { ... }
void resume_other_threads(ThreadState *) { ... }
int check_if_maybe_rodata(void *p) {
__try {
(void) *(volatile char *)p;
} __finally {
return false;
}
volatile LONG result = 0;
ThreadState *state = suspend_other_threads();
__try {
InterlockedExchange(&result, 1);
LONG saved = *(volatile LONG*)p;
InterlockedExchange((volatile LONG *)p, saved);
InterlockedExchange(&result, 0); // we succeeded writing there
} __finally {}
resume_other_threads(state);
return result;
}
int main() {
assert(check_if_maybe_rodata(&foo));
assert(!check_if_maybe_rodata(&bar));
}
#endif
La suspensión de los subprocesos requiere recorrer la lista de subprocesos y suspender cada subproceso que no es el subproceso actual. La lista de todos los hilos suspendidos debe crearse y guardarse, de modo que luego se pueda recorrer la misma lista para reanudar todos los hilos.
Seguramente hay advertencias, y los subprocesos WoW64 tienen su propia API para suspensión y reanudación, pero probablemente sea algo que, en circunstancias controladas, funcionaría bien.
Unix
La idea es aprovechar el kernel para comprobar el puntero "con los brazos extendidos" para que no se emita ninguna señal. El manejo de las señales POSIX que resultan de fallas de protección de la memoria requiere parchear el código que causó la falla, lo que inevitablemente lo obliga a modificar el estado de protección de la memoria del código . No muy bien. En su lugar, pase un puntero a una llamada al sistema que sepa que debería tener éxito en todas las circunstancias normales para leer desde la dirección apuntada, por ejemplo /dev/zero
, abrir y escribir en ese archivo desde un búfer al que apunta el puntero. Si eso falla EFAULT, es debido a que buf [está] fuera de su espacio de direcciones accesible. Si ni siquiera puede leer desde esa dirección, no es .rodata
seguro.
Luego haga lo contrario: desde un abierto /dev/zero
, intente un read
a la dirección que está probando. Si la lectura se realiza correctamente, no se trata de datos de solo lectura. Si la lectura falla, lo EFAULTmás probable es que signifique que el área en cuestión era de solo lectura, ya que la lectura se realizó correctamente, pero la escritura no.
En todos los casos, sería preferible utilizar API de plataforma nativa para probar el estado de mapeo de la página en la que se encuentra la dirección a la que intenta acceder, o incluso mejor, para recorrer la lista de secciones del ejecutable mapeado (ELF en Linux , PE en Windows) y ver exactamente qué fue y dónde. No se garantiza de alguna manera que en todos los sistemas con protección de memoria la .rodata
sección o su equivalente se mapeará como de solo lectura, por lo que la imagen del ejecutable según se mapeó en el proceso en ejecución es la máxima autoridad. Eso todavía no garantiza que la sección esté asignada actualmente como de solo lectura. Una mprotect
llamada o una similar podría haberlo cambiado, o partes de él, para que se puedan escribir, incluso modificarlos, y luego quizás volverlos a cambiar a solo lectura. Entonces tendría que sumar la sección si el formato del ejecutable proporciona tales datos, o mmap
el mismo binario en algún otro lugar de la memoria y comparar las secciones.
Pero huelo un leve olor a problema XY: ¿qué es lo que realmente estás tratando de hacer? Quiero decir, seguramente no querrás comprobar si una dirección está disponible .rodata
por curiosidad. Debe tener algún uso para esa información, y es esta aplicación la que finalmente decidiría si incluso hacer esta .rodata
verificación debería estar en el radar. Puede ser, puede que no. Basándose solo en su pregunta, es un sólido "¿quién sabe?"