Saltar a contenido

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:

  1. cin.get(): Lectura de un carácter desde teclado
  2. cin.ignore(): Vaciado del buffer de entrada
  3. Control de flujo condicional en assembly
  4. Casting de tipos (char → int para hex/decimal)
  5. 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()

cin.ignore(10, '\n');

Parámetros: - 10: Ignorar máximo 10 caracteres - '\n': O hasta encontrar salto de línea (0x0A)

Comportamiento:

Buffer antes:     | i p o \n
Buffer después:   | [vacío]
                   (ignoró hasta '\n' inclusive)

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++:

int istream::get();

Comportamiento: - Lee un carácter del buffer de entrada - Retorna el carácter como int (0-255) - No consume el newline (\n)

En assembly:

call    cin::get()      ; Retorna en RAX/EAX
mov     [rsp+20], al    ; Guardar solo byte bajo

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:

istream& ignore(streamsize n = 1, int delim = EOF);

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++:

if (c == 'Q')  // Comparar char con char literal

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:

Options > General > Stack pointer

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

  1. Ejecutar en x64dbg
  2. Poner breakpoint en cin.get()
  3. Escribir diferentes caracteres
  4. 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