Ejercicio 13: cin.get() e cin.ignore() - Reversing de Entrada/Salida¶
Introducción¶
Este ejercicio combina reversing de C++ con conceptos de I/O stream y buffer de entrada. El objetivo es comprender:
- cin.get(): Lectura de un carácter desde teclado
- cin.ignore(): Vaciado del buffer de entrada
- Control de flujo condicional en assembly
- Casting de tipos (char → int para hex/decimal)
- Llamadas a funciones STL (operator<<, hex, endl)
Código Fuente Original¶
#include <iostream>
using namespace std;
bool final();
int main()
{
bool flag = 0;
while (true)
{
flag = final();
if (flag) {
break;
}
}
return 0;
}
bool final()
{
int a;
char c;
cout << "Pulse una tecla o Q para terminar" << endl;
c = cin.get();
cin.ignore(10, '\n');
if (c == 'Q') {
return 1;
}
else {
cout << "Tipeaste 0x" << hex << (int)c << " entonces seguimos" << endl << endl;
return 0;
}
}
Análisis del Problema¶
Problema: Buffer de Entrada¶
Cuando el usuario escribe "Pipo\n" (5 caracteres + enter):
Buffer inicial: | P i p o \n
^
cin.get() toma solo 'P'
Buffer después: | i p o \n
^
Siguiente cin.get() toma 'i' (no espera teclado)
Consecuencia: El programa no espera entrada del usuario, toma automáticamente del buffer.
Solución: cin.ignore()¶
Parámetros:
- 10: Ignorar máximo 10 caracteres
- '\n': O hasta encontrar salto de línea (0x0A)
Comportamiento:
Ahora el siguiente cin.get() espera entrada de teclado.
Análisis del Assembly¶
Función main()¶
main proc near
sub rsp, 38h ; Prólogo: reservar stack (56 bytes)
mov [rsp+38h+var_18], 0; flag = 0 (booleano, 1 byte en [rsp+20])
loc_7FF68DEA1329: ; Etiqueta del while (true)
xor eax, eax ; EAX = 0 (comparación con 1)
cmp eax, 1 ; Comparar: ¿EAX == 1? (siempre false)
jz loc_7FF68DEA1346 ; Si igual → salir (nunca se ejecuta)
call sub_7FF68DEA1250 ; Llamar a final()
mov [rsp+20], al ; Guardar retorno en flag
movzx eax, [rsp+20] ; EAX = (int)flag (extender a 32 bits)
test eax, eax ; Testear: ¿flag == 0?
jz loc_7FF68DEA1344 ; Si cero → ir al inicio del loop
jmp short loc_7FF68DEA1346; Si no cero → salir
loc_7FF68DEA1344: ; Vuelta al while
jmp short loc_7FF68DEA1329
loc_7FF68DEA1346: ; Salida del while
xor eax, eax ; EAX = 0 (return value)
add rsp, 38h ; Epílogo: liberar stack
retn
main endp
Observación: El cmp eax, 1 es redundante porque xor eax, eax siempre pone EAX en 0. La comparación nunca sale por esa rama.
Función final()¶
sub_7FF68DEA1250 proc near ; bool final()
sub rsp, 38h ; Prólogo
; === PARTE 1: PRINT "Pulse una tecla o Q para terminar" ===
lea rdx, aPulseUnaTeclaO ; RDX = puntero a string
lea rcx, std::cout ; RCX = &cout (this)
call operator<< ; cout << string
; === PARTE 2: cin.get() ===
lea rcx, std::cin ; RCX = &cin (this)
call cin::get() ; cin.get() → EAX = carácter
mov [rsp+20], al ; Guardar en variable c (byte local)
; === PARTE 3: cin.ignore(10, '\n') ===
mov r8b, 0Ah ; R8B = '\n' (carácter terminador)
mov edx, 0Ah ; EDX = 10 (cantidad máxima)
lea rcx, std::cin ; RCX = &cin (this)
call cin::ignore() ; cin.ignore(10, '\n')
; === PARTE 4: Comparación if (c == 'Q') ===
movsx eax, [rsp+20] ; EAX = (int)c (sign-extend byte→int)
cmp eax, 51h ; Comparar: ¿c == 'Q' (0x51)?
jnz loc_7FF68DEA12AB ; Si no igual → rama else
; === PARTE 5A: if (c == 'Q') → return 1 ===
mov al, 1 ; AL = 1 (true)
jmp short loc_7FF68DEA1313; Ir a retorno
; === PARTE 5B: else → print y return 0 ===
loc_7FF68DEA12AB:
movsx eax, [rsp+20] ; EAX = (int)c
mov [rsp+24], eax ; Guardar temporalmente en [rsp+24]
; cout << "Tipeaste 0x"
lea rdx, aTipeaste0x ; RDX = string pointer
lea rcx, std::cout ; RCX = &cout
call operator<< ; cout << "Tipeaste 0x"
; << hex
lea rdx, sub_7FF68DEA8910 ; RDX = manipulador hex
mov rcx, rax ; RCX = cout retornado
call operator<< ; cout << hex
; << (int)c
mov ecx, [rsp+24] ; ECX = valor de c
mov edx, ecx ; EDX = ECX (preparar argumento)
mov rcx, rax ; RCX = cout retornado
call operator<< ; cout << (int)c
; << " entonces seguimos"
lea rdx, aEntoncesSeguim ; RDX = string
mov rcx, rax ; RCX = cout
call operator<< ; cout << string
; << endl
lea rdx, sub_7FF68DEA2F10 ; RDX = manipulador endl
mov rcx, rax ; RCX = cout
call operator<< ; cout << endl
; << endl (de nuevo)
lea rdx, sub_7FF68DEA2F10 ; RDX = endl
mov rcx, rax ; RCX = cout
call operator<< ; cout << endl
xor al, al ; AL = 0 (false)
loc_7FF68DEA1313: ; Retorno
add rsp, 38h ; Epílogo
retn
sub_7FF68DEA1250 endp
Stack Frame (Representación Estática)¶
main()¶
Dirección Contenido Propósito
─────────────────────────────────────────────────
RSP+30h (48) [?] Shadow space arg3
RSP+28h (40) [?] Shadow space arg2
RSP+20h (32) [flag] Variable booleana (1 byte)
RSP+18h (24) [padding] Relleno
RSP+8h (8) [return address] Dirección de retorno (pusheada por call)
RSP+0h (0) [?] Tope de pila (antes de call)
Total: 56 bytes (0x38)
Variables:
- flag en [RSP+20] (tamaño: 1 byte, tipo: bool/unsigned char)
final()¶
Dirección Contenido Propósito
─────────────────────────────────────────────────
RSP+30h (48) [?] Shadow space arg3
RSP+28h (40) [?] Shadow space arg2
RSP+24h (36) [temp int] Almacenamiento temporal para (int)c
RSP+20h (32) [c] Variable char (1 byte)
RSP+1Fh (31) [padding] Alineamiento (3 bytes)
RSP+8h (8) [return address] Dirección de retorno
RSP+0h (0) [?] Tope de pila
Total: 56 bytes (0x38)
Variables:
- c en [RSP+20] (tamaño: 1 byte, tipo: char)
- int a en [RSP+24] (tamaño: 4 bytes, declarado pero no usado)
Conceptos Clave¶
1. cin.get()¶
Prototipo en C++:
Comportamiento:
- Lee un carácter del buffer de entrada
- Retorna el carácter como int (0-255)
- No consume el newline (\n)
En assembly:
Problema sin cin.ignore():
Si usuario escribe "PipoQ\n":
Llamada 1: cin.get() → 'P'
Llamada 2: cin.get() → 'i' (sin esperar teclado)
Llamada 3: cin.get() → 'p'
Llamada 4: cin.get() → 'o'
Llamada 5: cin.get() → 'Q'
Llamada 6: cin.get() → '\n'
Llamada 7: cin.get() → [espera teclado por primera vez]
2. cin.ignore()¶
Prototipo:
Parámetros:
- n: Cantidad máxima de caracteres a ignorar
- delim: Carácter delimitador
Comportamiento: Ignora hasta n caracteres O hasta encontrar delim (inclusive).
En assembly:
mov r8b, 0Ah ; R8B = '\n' (delimitador)
mov edx, 0Ah ; EDX = 10 (máximo)
lea rcx, std::cin ; RCX = &cin (this)
call cin::ignore() ; cin.ignore(10, '\n')
Ejemplo: Después de cin.get(), si buffer = "ipoQ\n":
cin.ignore(10, '\n')ignora: "ipoQ\n" (5 caracteres, encuentra '\n')- Buffer queda vacío
- Próximo
cin.get()espera entrada de teclado
3. char vs. int Casting¶
Problema: char es 1 byte, int es 4 bytes.
En assembly:
; Lectura de char en c [rsp+20]
movsx eax, [rsp+20] ; EAX = sign-extend c a int (rellenar con 0 o F)
; Impresión como hexadecimal
call operator<< hex ; Manipulador: imprimir siguiente int como hex
call operator<< eax ; Imprimir EAX
Ejemplos:
c = 'A' (0x41): EAX = 0x00000041 → imprime "41"
c = 'Q' (0x51): EAX = 0x00000051 → imprime "51"
c = '\n' (0x0A): EAX = 0x0000000A → imprime "a" (minúscula por defecto)
4. Comparación con constante carácter¶
En C++:
En assembly:
movsx eax, [rsp+20] ; EAX = (int)c
cmp eax, 51h ; Comparar: 0x51 es ASCII de 'Q'
jnz loc_... ; Si no igual, ir al else
Flujo de Ejecución Paso a Paso¶
Escenario 1: Usuario escribe "o\n"¶
1. main(): flag = 0
2. while (true):
- xor eax, eax → EAX = 0
- cmp eax, 1 → ZF = 0 (nunca salta)
- call final()
3. final():
- cout << "Pulse una tecla..."
- [Usuario escribe: o + Enter]
- cin.get() → c = 0x6F ('o')
- cin.ignore(10, '\n') → vacía buffer
- cmp 0x6F, 0x51 → ZF = 0 (no es 'Q')
- cout << "Tipeaste 0x" << hex << 111 << " entonces seguimos" << endl << endl
- return 0 (AL = 0)
4. main():
- [rsp+20] = 0 (flag)
- test 0, 0 → ZF = 1
- jz loc_1329 → vuelve al inicio del while
5. Vuelve a paso 2
Escenario 2: Usuario escribe "Q\n"¶
1-3. [Igual hasta final()]
- cin.get() → c = 0x51 ('Q')
- cin.ignore(10, '\n')
- cmp 0x51, 0x51 → ZF = 1 (¡es 'Q'!)
- mov al, 1 → AL = 1 (true)
- return 1
4. main():
- [rsp+20] = 1 (flag)
- test 1, 1 → ZF = 0
- jz loc_1344 → [no salta, continúa]
- jmp loc_1346 → [salta al épílogo]
5. Epilogo:
- add rsp, 38h
- retn
- Programa termina
Puntos Clave de Reversing¶
1. Redundancia en el Código Compilado¶
xor eax, eax ; EAX siempre = 0
cmp eax, 1 ; ¿Cuándo es EAX == 1? NUNCA
jz ... ; Esta rama nunca se ejecuta
IDA muestra esto como código roto, pero es una optimización fallida del compilador.
2. Calling Convention Windows x64 (FastCall)¶
Parámetros en registros:
- RCX: Primer parámetro (o this para métodos)
- RDX: Segundo parámetro
- R8: Tercer parámetro
- R9: Cuarto parámetro
- [RSP+...]: Parámetros 5+
Shadow Space: Reservar 32 bytes (0x20) después del return address para los 4 primeros parámetros.
3. Manipuladores de iostream¶
En C++:
cout << hex << value // Imprimir en hexadecimal
cout << dec << value // Imprimir en decimal (default)
cout << endl // Salto de línea + flush
En assembly:
lea rdx, sub_xxx ; RDX = dirección del manipulador (hex, dec, endl, etc.)
mov rcx, rax ; RCX = objeto cout retornado
call operator<< ; cout << manipulador
4. Sign Extension vs. Zero Extension¶
movsx eax, [rsp] ; Sign-extend (rellenar bit de signo)
movzx eax, [rsp] ; Zero-extend (rellenar con ceros)
Para caracteres ASCII (0-127), ambos dan el mismo resultado. Pero:
c = 0xFF (char negativo en little-endian):
movsx → EAX = 0xFFFFFFFF (-1 en dos complementos)
movzx → EAX = 0x000000FF (255 sin signo)
Stack Pointer Tracking¶
Después del prólogo (sub rsp, 0x38):
RBP = dirección anterior de RBP
RSP = RBP - 0x38
Alineamiento: RSP debe terminar en 0x0 (múltiplo de 16) después del prólogo
para que los CALL internos estén alineados.
Verificación en IDA:
Marca [SP+offset] en cada instrucción.
Ejercicio de Reversing¶
Tarea 1: Identificar el Control de Flujo¶
Dibuja un diagrama de flujo (flowchart) que muestre:
- Inicio en main
- Loop infinito while (true)
- Llamada a final()
- Decisión en if (flag)
- Salida
Tarea 2: Stack Frame Completo¶
Para final(), crea una tabla con:
| Offset | Tamaño | Tipo | Nombre | Valor Típico |
|---|---|---|---|---|
| [RSP+20] | 1 byte | char | c | 0x51 ('Q') |
| [RSP+24] | 4 bytes | int | a | [no usado] |
| ... | ... | ... | ... | ... |
Tarea 3: Rastreo Dinámico¶
- Ejecutar en x64dbg
- Poner breakpoint en
cin.get() - Escribir diferentes caracteres
- Observar cómo cambia el buffer de entrada
Conceptos aplicados:
✅ Lectura de entrada con cin.get()
✅ Vaciado de buffer con cin.ignore()
✅ Casting de tipos (char → int)
✅ Impresión en hexadecimal con manipulador hex
✅ Control de flujo condicional (if/else)
✅ Loops infinitos (while (true))
✅ Calling convention Windows x64
✅ Shadow space en stack