Operadores y sentencias (parte 2) – apuntes de reversing¶
Repaso clave: idiv (con signo) arma un operando de 64 bits usando EDX:EAX y deja cociente en EAX y resto en EDX. Necesita cdq para extender el signo (EDX = 0 si positivo, EDX = 0xFFFFFFFF si negativo). div (sin signo) no necesita cdq, pero sí EDX en cero; aun así cdq sirve porque deja EDX=0 en positivos.
Si omites cdq con números negativos, el divisor “ve” un valor positivo (EDX=0) y el cociente sale con signo incorrecto; el resto puede seguir coincidiendo, por eso a veces el bug pasa desapercibido.
Representación con signo: en 32 bits el rango va de 0x00000000 (0) a 0x7FFFFFFF (máx. positivo) y de 0x80000000 (primer negativo) a 0xFFFFFFFF (-1). En 64 bits lo mismo con prefijos de 8 bytes; los negativos decrecen desde 0x8000...000 hasta 0xFFFF...FFF.
for(inti=-20;i<=0;++i){cout<<i;if(i%3==0)cout<<" es multiplo de 3";elsecout<<" no es multiplo de 3";cout<<endl;}
- Aquí se muestra el porqué de cdq: al dividir negativos por 3, idiv usa EDX:EAX; cdq corrige el signo en EDX. Sin él, el cociente da mal (se interpreta como positivo).
- Reversing: en IDA se ve el ajuste de stack sub rsp, 38h (shadow space + padding de alineación 16B) y el resto en EDX usado para decidir el mensaje.
- En asm: dos bucles con comparaciones y saltos; j se reinicia a 1 cada línea. El primer cout de cada línea usa RCX = std::cout, RDX = j. Stack frame: return address, shadow space (4 qwords), locales i y j, padding de alineación.
- Nota: no todas las funciones usan shadow space para todos los argumentos; a veces solo guardan RCX.
Ejemplo 3: secuencia 1, 5, 3, 7, 5, 9… hasta 23 (do while)¶
- Alterna +4 y -2 invirtiendo el flag sumar. El chequeo de fin se hace tras imprimir y el último 23 termina con punto.
Ejemplo 4: factorización prima con entrada repetida¶
intnumero,factor=2;charresp[12];do{cout<<"Introduce un numero entero: ";cin>>numero;factor=2;while(numero>=factor*factor){if(numero%factor==0){cout<<factor<<" * ";numero/=factor;continue;}factor=(factor==2)?3:factor+2;// 2,3,5,7,...}cout<<numero<<endl;// ultimo factor (primo)cout<<"Descomponer otro numero? (s/n): ";cin>>resp;}while(resp[0]=='s'||resp[0]=='S');
- Estrategia: divide por 2 hasta que no sea divisible; luego prueba 3 y avanza solo por impares (factor+=2). Se detiene cuando factor*factor supera al número restante.
- Reversing: se observa el uso de cdq+idiv para % y /, el continue salta la actualización del factor, y las comparaciones del carácter de respuesta ('s'/'S').
- Seguridad: el compilador inserta stack cookie al haber un buffer (resp[12]); se guarda en el prólogo y se verifica en el epílogo. El layout del frame queda: return address, shadow space, factor, numero, resp[12], padding de 4 bytes para alinear a 16, cookie, padding final para mantener rsp alineado antes de llamadas.
Activar nombres demanglados y “setup names” para abreviar std::. Ajustar el grafo para limpiar flechas y leer mejor el control flow.
El decompilado (F5) sincronizado ayuda a mapear líneas de cout/cin a varias instrucciones (RCX = std::cout, RDX = argumento), pero entender el ensamblador sigue siendo necesario cuando falten símbolos.