c++ - teclas - Borrando la memoria de forma segura y reasignaciones
porque no puedo expulsar mi usb windows 10 (5)
Después de la discusión here , si desea tener una clase segura para almacenar información confidencial (por ejemplo, contraseñas) en la memoria, debe:
- memset / borrar la memoria antes de liberarla
- las reasignaciones también deben seguir la misma regla: en lugar de utilizar realloc, use malloc para crear una nueva región de memoria, copie lo antiguo a lo nuevo y luego configure / borre la memoria anterior antes de liberarla finalmente
Así que esto suena bien, y creé una clase de prueba para ver si esto funciona. Así que hice un caso de prueba simple donde sigo agregando las palabras "LOL" y "WUT", seguidas de un número para esta clase de buffer seguro alrededor de mil veces, destruyendo ese objeto, antes de finalmente hacer algo que causa un volcado del núcleo.
Dado que se supone que la clase debe limpiar con seguridad la memoria antes de la destrucción, se supone que no puedo encontrar un "LOLWUT" en el núcleo. Sin embargo, logré encontrarlos todavía, y me pregunté si mi implementación es incorrecta. Sin embargo, probé lo mismo con el SecByteBlock de la biblioteca CryptoPP:
#include <cryptopp/osrng.h>
#include <cryptopp/dh.h>
#include <cryptopp/sha.h>
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include <cryptopp/filters.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;
int main(){
{
CryptoPP::SecByteBlock moo;
int i;
for(i = 0; i < 234; i++){
moo += (CryptoPP::SecByteBlock((byte*)"LOL", 3));
moo += (CryptoPP::SecByteBlock((byte*)"WUT", 3));
char buffer[33];
sprintf(buffer, "%d", i);
string thenumber (buffer);
moo += (CryptoPP::SecByteBlock((byte*)thenumber.c_str(), thenumber.size()));
}
moo.CleanNew(0);
}
sleep(1);
*((int*)NULL) = 1;
return 0;
}
Y luego compilar usando:
g++ clearer.cpp -lcryptopp -O0
Y luego habilitar el volcado del núcleo
ulimit -c 99999999
Pero luego, habilitar el volcado del núcleo y ejecutarlo
./a.out ; grep LOLWUT core ; echo hello
da la siguiente salida
Segmentation fault (core dumped)
Binary file core matches
hello
¿Qué está causando esto? ¿Toda la región de la memoria para la aplicación se reasignó a sí misma, debido a la reasignación causada por el apéndice SecByteBlock?
Además, esta es la documentación de SecByteBlock
editar : Después de revisar el volcado del núcleo usando vim, obtuve esto: http://imgur.com/owkaw
edit2 : código actualizado para que sea más fácil de compilar e instrucciones de compilación
edición final3 : parece que memcpy es el culpable. Vea la implementación de mymemcpy
Rasmus en su respuesta a continuación.
A pesar de aparecer en el núcleo, la contraseña ya no está en la memoria después de borrar los buffers. El problema es que al memcpy
una cadena lo suficientemente larga se filtra la contraseña a los registros SSE, y eso es lo que aparece en el núcleo.
Cuando el argumento de size
para memcpy
es mayor que cierto umbral, 80 bytes en el mac, entonces las instrucciones SSE se usan para hacer la copia de la memoria. Estas instrucciones son más rápidas porque pueden copiar 16 bytes a la vez en paralelo en lugar de ir carácter por carácter, byte por byte o palabra por palabra. Aquí está la parte clave del código fuente de Libc en mac :
LAlignedLoop: // loop over 64-byte chunks
movdqa (%rsi,%rcx),%xmm0
movdqa 16(%rsi,%rcx),%xmm1
movdqa 32(%rsi,%rcx),%xmm2
movdqa 48(%rsi,%rcx),%xmm3
movdqa %xmm0,(%rdi,%rcx)
movdqa %xmm1,16(%rdi,%rcx)
movdqa %xmm2,32(%rdi,%rcx)
movdqa %xmm3,48(%rdi,%rcx)
addq $64,%rcx
jnz LAlignedLoop
jmp LShort // copy remaining 0..63 bytes and done
%rcx
es el registro de índice de bucle, %rsi
es el registro de dirección de %rsi
, y %rdi
es el registro de dirección de estimación. Cada corrida alrededor del bucle, se copian 64 bytes del búfer fuente a los 4 registros SSE de 16 bytes xmm{0,1,2,3}
; a continuación, los valores en esos registros se copian en el búfer de destino.
Hay muchas más cosas en ese archivo de origen para asegurarse de que las copias se realicen solo en direcciones alineadas, para completar la parte de la copia que queda después de hacer fragmentos de 64 bytes y para manejar el caso donde la fuente y el destino se superponen.
Sin embargo, ¡ los registros SSE no se borran después de su uso! Eso significa que 64 bytes del búfer que se copió todavía están presentes en los xmm{0,1,2,3}
.
Aquí hay una modificación del programa de Rasmus que muestra esto:
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <emmintrin.h>
inline void SecureWipeBuffer(char* buf, size_t n){
volatile char* p = buf;
asm volatile("rep stosb" : "+c"(n), "+D"(p) : "a"(0) : "memory");
}
int main(){
const size_t size1 = 200;
const size_t size2 = 400;
char* b = new char[size1];
for(int j=0;j<size1-10;j+=10){
memcpy(b+j, "LOL", 3);
memcpy(b+j+3, "WUT", 3);
sprintf((char*) (b+j+6), "%d", j);
}
char* nb = new char[size2];
memcpy(nb, b, size1);
SecureWipeBuffer(b,size1);
SecureWipeBuffer(nb,size2);
/* Password is now in SSE registers used by memcpy() */
union {
__m128i a[4];
char c;
};
asm ("MOVDQA %%xmm0, %0": "=x"(a[0]));
asm ("MOVDQA %%xmm1, %0": "=x"(a[1]));
asm ("MOVDQA %%xmm2, %0": "=x"(a[2]));
asm ("MOVDQA %%xmm3, %0": "=x"(a[3]));
for (int i = 0; i < 64; i++) {
char p = *(&c + i);
if (isprint(p)) {
putchar(p);
} else {
printf("//%x", p);
}
}
putchar(''/n'');
return 0;
}
En mi mac, esto imprime:
0/0LOLWUT130/0LOLWUT140/0LOLWUT150/0LOLWUT160/0LOLWUT170/0LOLWUT180/0/0/0
Ahora, al examinar el volcado del núcleo, la contraseña solo se produce una sola vez, y como esa 0/0LOLWUT130/0...180/0/0/0
exacta 0/0LOLWUT130/0...180/0/0/0
. El volcado del núcleo debe contener una copia de todos los registros, por lo que esa cadena está allí, son los valores de los xmm{0,1,2,4}
.
Entonces, la contraseña ya no está en la memoria RAM después de llamar a SecureWipeBuffer
, solo parece ser porque en realidad está en algunos registros que solo aparecen en el núcleo. Si te preocupa que memcpy
tenga una vulnerabilidad que pueda ser explotada por la congelación de RAM, no te preocupes más. Si le molesta tener una copia de la contraseña en los registros, use una memcpy
modificada que no use los registros SSE2, o los borre cuando haya terminado. Y si realmente está paranoico acerca de esto, siga probando sus núcleos para asegurarse de que el compilador no esté optimizando su código de contraseña.
Aquí hay otro programa que reproduce el problema más directamente:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
inline void SecureWipeBuffer(char* buf, size_t n){
volatile char* p = buf;
asm volatile("rep stosb" : "+c"(n), "+D"(p) : "a"(0) : "memory");
}
void mymemcpy(char* b, const char* a, size_t n){
char* s1 = b;
const char* s2= a;
for(; 0<n; --n) *s1++ = *s2++;
}
int main(){
const size_t size1 = 200;
const size_t size2 = 400;
char* b = new char[size1];
for(int j=0;j<size1-10;j+=10){
memcpy(b+j, "LOL", 3);
memcpy(b+j+3, "WUT", 3);
sprintf((char*) (b+j+6), "%d", j);
}
char* nb = new char[size2];
memcpy(nb, b, size1);
//mymemcpy(nb, b, size1);
SecureWipeBuffer(b,size1);
SecureWipeBuffer(nb,size2);
*((int*)NULL) = 1;
return 0;
}
Si reemplaza memcpy
con mymemcpy
o usa tamaños más pequeños, el problema desaparece, por lo que mi mejor opción es que la memcpy incorporada haga algo que deje en la memoria parte de los datos copiados.
Supongo que esto solo muestra que eliminar datos confidenciales de la memoria es prácticamente imposible a menos que esté diseñado en todo el sistema desde cero.
Los literales de cadena se almacenarán en la memoria y no serán administrados por la clase SecByteBlock.
Esta otra pregunta SO hace un trabajo decente de explicarlo: ¿Es una cadena literal en C ++ creada en memoria estática?
Puedes probar y confirmar si las coincidencias de grep pueden ser contabilizadas por los literales de cadena al ver cuántas coincidencias obtienes. También puede imprimir las ubicaciones de memoria de los búferes SecByteBlock y tratar de ver si se corresponden con las ubicaciones en el volcado del núcleo que coinciden con su marcador.
Sin inspeccionar los detalles de memcpy_s
, sospecho que lo que está viendo es un búfer de pila temporal utilizado por memcpy_s
para copiar búferes de memoria pequeños. Puede verificar esto ejecutando un depurador y ver si aparece LOLWUT
al visualizar la memoria de la pila.
[La implementación de la reallocate
en Crypto ++ utiliza memcpy_s
al cambiar el tamaño de las asignaciones de memoria, por lo que podrá encontrar cierto número de cadenas LOLWUT
en la memoria. Además, el hecho de que muchas cadenas diferentes de LOLWUT
superpongan en ese vuelco sugiere que se trata de un búfer temporal que se está reutilizando].
La versión personalizada de memcpy
que es simplemente un bucle simple no requiere almacenamiento temporal más allá de los contadores, por lo que sin duda sería más seguro que cómo se implementa memcpy_s
.
Sugeriría que la forma de hacerlo es encriptar los datos en la memoria. De esa forma, los datos están siempre seguros ya sea que estén todavía en la memoria o no. El inconveniente, por supuesto, es una sobrecarga en términos de encriptación / descifrado de los datos cada vez que se accede a ellos.