sirven que punteros puntero para los lenguaje funciones con cadenas arreglos aritmetica c linux pointers exploit

que - punteros void lenguaje c



Explicación de un puntero en el código de explotación (2)

El código toma la dirección de la variable local i para obtener un puntero en el marco de pila actual. Luego, alinea la dirección a la página de 8K (eso es lo que haces con x & ~8191 : 8191 es 2 ^ 13 - 1 lo que significa que ~8191 son todos menos los 13 bits bajos, entonces ANDing con un número borrará el 13 bits bajos, es decir, alinee el número al múltiplo inferior más cercano de 2 ^ 13, en otras palabras, alinee al límite de 8K).

A continuación, toma esta dirección y la interpreta como un puntero a un puntero y carga la dirección apuntada desde allí. Consulte Comprender cómo obtener el puntero task_struct de la pila de kernel de proceso para obtener más información.

Después de eso, intenta localizar una estructura específica almacenada en algún lugar después de esa dirección: Mira a través de los siguientes 1024-13 unsigned s, tratando de encontrar un lugar en la memoria donde se almacena la información del proceso actual (probablemente): Cuando encuentra una pieza de memoria que contiene varias copias del UID y el GID actuales, supone que lo ha encontrado. En ese caso, lo modifica para que el proceso actual obtenga UID y GID 0, haciendo que el proceso se ejecute en la raíz (además, almacena todos los elementos en los siguientes indicadores de capacidad).

Cf. struct cred .

En algunos exploits para obtener el shell raíz, a menudo veo un puntero:

int i; unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191);

¿Alguien puede explicar este puntero un poco? Creo que 8191 es el tamaño de la pila de kernel. p apunta a la parte inferior de la pila del kernel ? Aquí es cómo se usa el puntero p :

int i; unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191); for (i = 0; i < 1024-13; i++) { if (p[0] == uid && p[1] == uid && p[2] == uid && p[3] == uid && p[4] == gid && p[5] == gid && p[6] == gid && p[7] == gid) { p[0] = p[1] = p[2] = p[3] = 0; p[4] = p[5] = p[6] = p[7] = 0; p = (unsigned *) ((char *)(p + 8) + sizeof(void *)); p[0] = p[1] = p[2] = ~0; break; } p++; }


Voy a publicar otra respuesta porque realmente hay algo que agregar aquí.

unsigned *p = *(unsigned**)(((unsigned long)&i) & ~8191);

resulta en que p es el puntero al inicio del bloque de memoria de 8192 bytes de tamaño. Sin embargo, el código es incorrecto. Si p está por encima de INT_MAX (que puede ser o se convertiría en unsigned, no unsigned long), los bits altos se cortan con la máscara. El código correcto es el siguiente:

unsigned *p = *(unsigned**)(((ptrdiff_t)&i) & ~(ptrdiff_t)8191);

o usando uintptr_t:

unsigned *p = *(unsigned**)(((uintptr_t)&i) & ~(uintptr_t)8191U);

Es necesario convertir a entero y volver al puntero para que el código funcione; sin embargo, para garantizar un puntero de tamaño int, se requiere el uso de ptrdiff_t (recordamos que el comportamiento firmado y el no firmado se comportan exactamente igual para las operaciones bit a bit). En cuanto a por qué no los escriben con constantes hexadecimales, a quién le importa. Los chicos que hacen este tipo de cosas conocen sus poderes de 2 de memoria. Puede ser más rápido leer 8191 que 0x1FFF.