Creador del Ejercicio: Ricardo Narvaja

Análisis General.
El objetivo fue entender el flujo de ejecución del programa, identificar cómo se realiza la verificación de una key (clave) de entrada y manipular los valores internos para lograr decifrar la key.
Análisis Inicial.
Comienzo analizando la instrucción mov rax, cs:__security_cookie
Esta línea carga en el registro rax el valor de una cookie de seguridad, una medida de protección contra ataques de desbordamiento de pila. Luego realiza una operación XOR entre esta cookie y el puntero de pila rsp.
xor rax, rsp
El resultado se almacena en la pila con mov [rsp+68h+var_20], rax
Este valor es una forma ofuscada de la cookie original, se utiliza más adelante para verificar la integridad de la ejecución. Modifico var_20 para mayor claridad como cookie.


Análisis de Punteros y funciones.
Siguiendo el flujo, sobre los punteros,
off_140009010 apunta a off_140009008 Y este finalmente lleva a la función sub_140001070.

El puntero 0ff_140009010 sigue a la variable (puntero) 0ff_140009008, finalizando a la dirección (función) sub_140001070.

sub_140001070 apunta a esta función a resolver.

Modifico las direcciones de punteros y funciones
Resolver1 = R1 = “sub_140001070”
Puntero_Resolver1 = P_R1 = “0ff_140009008”
Puntero1_Puntero_Resovler1 = P1_P_R1 = “0ff_140009010”


Identificación del Bug.
El programa carga `off_140009010` (valor inicial: `off_140009008`) y le suma 8:
mov rax, cs:off_140009010 ; rax = 0x140009008
add rax, 8 ; rax = 0x140009010 (ahora apunta a sí mismo!)
mov cs:off_140009010, rax ; Se autoreferencia
Esto causa un crash al intentar ejecutar call qword ptr [rax], ya que 0x140009010 no es código válido.


Teniendo en cuenta que crashea intencionalmente, El programa utiliza constantes como 2 y 3 en variables locales, pero no son códigos de retorno. La constante 3 se emplea en el bloque __finally para ajustar el offset del puntero (0x5060 + 3 = 0x5063), clave para la verificación de la key.

Modifico var_40 por Const_Entero

Luego levanta un string ascii lo copia a var_34, asique lo modifico por Str_Asc_Good.


Acá pasa lo mismo, pero en este caso Bad, Asique modifico var_2c en Str_Asc_Bad.

var_40 vale 0, luego le suma 4, asique lo modifico a constante de 4 “Const_4“

“cout” imprime “Ingrese la Key”, por medio de la función sub_140001550.

Es la variable que es el operador —(menor, menor) que imprime en cout

Es el flujo de salida estándar de C++ (objeto global de la clase ostream), _cout (su dirección/referencia).
Devuelve una referencia a std::cout (para encadenar operaciones, cout << "A" << "B"; "por ejemplo").

“v9” Está determinado como un entero, y se le va a signar un valor por medio de std::cin, &v9, puedo verificar que se le asignara la “Key:“, asique modifico v9 por “key“

Imprime la “Key: “, vemos que v5 también es cout asique le asigno _cout1

Luego imprime la key y aplico ^ para obtener el valor con 0x4142u;
la key es el resultado de una operación ^ con ese valor.
Al final salta a buscar el contenido de P1_P_R1 que termina apuntando sobre sí mismo.


Como indique anteriormente, la idea es que la dirección que termina en off_140009010 debería pasar por el puntero origen off_140009008 y terminar con la función a resolver que seria sub_140001070, esto no sucede porque se le suma 8 cuando la variable origen es off_140009008, por ende termina buscándose a sí mismo off_140009010, por eso crashea.

Breve demostración. Ejecuto el puntero 9010 sobre el origen 9008, sumando 8 unidades dando con 9010 de origen.


El offset 9010 se sigue a sí mismo.

Me indica que no hay código.

Salto a la misma dirección, genera el mismo código tratando de buscar alguna dirección con función y por eso se rompe.

Entonces se va a producir una excepción en este punto.

Indica que si hay una excepción va al __finally y si no hay excepción ejecuta la copia del __finally que por lo general esta debajo del código, teniendo en cuenta que el compilador la repite 2 veces.


La instrucción retn en main+109 marca el fin lógico del programa. El código posterior main+10A es inalcanzable debido al crash previo, asique lo marco como indefinido ya que no se va a ejecutar.


En este punto se va a ejecutar el verdadero “__finally”, cuando se produzca la excepción.

Si busco una referencia del main, sobre RUNTIME_FUNCTION.

Puedo verificar donde comienza el main.
Tambien donde termina que sería el +1


Veo un puntero a la estructura.

UNWIND_INFO tiene el Handler general, que maneja las funciones.


Cálculo de la Key.
Termina redirigiendo al __finally, sigo el flujo levanta el main (rax, main), sigue con el contenido de P1_P_R1 que crasheaba, pero levanta un dword, luego resta ese dword con el dword final de main.
P1_P_R1 – main =
0000000140009010 – 0000000140003FB0 = 0x5060




El último Cons_entero tenía el valor de 3, asique se le suma 0x5060+3 y lo guarda en Const_Entero, luego la key xoreada con 0x4142



key ^ 0x4142 = 0x5060 + 3
key ^ 0x4142 = 0x5063
key = 0x5063 ^ 0x4142
key = 0x1121 (hex)
key = 4385 (dec)
0x5063 en binario: 0101 0000 0110 0011
0x4142 en binario: 0100 0001 0100 0010
Aplico XOR bit a bit:
0101 0000 0110 0011 (0x5063)
^
0100 0001 0100 0010 (0x4142)
--------------------
0001 0001 0010 0001 (0x1121)
Resultado: 0x1121 (hexadecimal) = 4385 en decimal.

Funciono correctamente.
Output ("Good Boy"/"Bad Boy").
Sigo con las strings, offset Good y Bad.


Lo que hace es reemplazar Good o Bad dependiendo de la key si es válida o no, seguido por Boy, sub_1400055B0 este es el constructor de la string.

Entonces std::string::replace obtiene el contenido en [rax] de P1_P_String y le reemplaza la parte ascii, comienza los argumentos por edz y termina en r8d, va a hacer un replace sobre los 4 caracteres para meter el Good dentro de “GOOD Boy”.

Ocurre exactamente lo mismo para Bad, con la diferencia que dentro de los 4 caracteres deja +1 espacio sobre “Bad Boy“.

Completado!.
You must be logged in to post a comment.