Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Binary Hooking Problemas
Binary Hooking Problemas
La mayoría de los hooking engines binarios escriben un desvío en el punto de entrada de la función de
destino. Otros hooking engines parchean la tabla IAT, y así sucesivamente. Uno de los problemas al sobrescribir
el punto de entrada con una instrucción JMP es que necesita suficiente espacio para esa instrucción, por lo
general, solo bastarán 5 bytes.
Bastante fácil, usan un desensamblador para consultar el tamaño de cada instrucción que escanean, por lo que si
el tamaño total de las instrucciones que se escanearon es superior a 5 bytes, ya están listas.
Como ejemplo, generalmente, las funciones comienzan con estas dos instrucciones:
PUSH EBP
MOV EBP, ESP
que toman solo 3 bytes. Y ya dijimos que necesitamos 5 bytes en total, para reemplazar las primeras
instrucciones con nuestra instrucción JMP. Por lo tanto, la exploración tendrá que continuar con la siguiente
instrucción más o menos, hasta que tengamos al menos 5 bytes.
Entonces, 5 bytes en x86 podrían contener de una instrucción a 5 instrucciones (donde cada una toma un solo
byte, obviamente). O incluso una sola instrucción cuyo tamaño es mayor a 5 bytes. (En x64, es posible que
necesite 12-14 bytes para un JMP completo, y esto solo empeora las cosas).
Está claro por qué necesitamos saber el tamaño de las instrucciones, puesto que sobrescribimos los primeros 5
bytes, debemos reubicarlos en otra ubicación, el trampolín. Allí queremos continuar la ejecución de la función
original que enganchamos y, por lo tanto, debemos continuar desde la siguiente instrucción que no hemos
anulado. Y no es necesariamente la instrucción en el desplazamiento 5 ... de lo contrario, podríamos continuar la
ejecución en medio de una instrucción, lo cual es bastante malo.
Los hooking engines cojos no usan desensambladores, solo tienen una tabla predefinida de instrucciones
prólogo populares. Si viene un código compilado diferente, no podrán enganchar una función. De todos modos,
también necesitamos un desensamblador por otra razón, para saber si alcanzamos una instrucción de punto
muerto, como: RET, INT 3, JMP, etc. Estos son hooking spoilers, porque si la primera instrucción de la función
de destino es simple RET (por lo tanto, la función no hace nada, deja de lado los efectos secundarios de la
memoria caché por ahora), o incluso una función de "return 0", que generalmente se traduce en "xor
eax,eax;ret", todavía toma solo 3 bytes y no podemos plantar un desvío. Así que nos encontramos tratando
de anular 5 bytes donde la función completa toma varios bytes (<5 bytes), y no podemos anular más allá de esa
instrucción ya que no sabemos qué hay allí. Puede ser el punto de entrada de otra función, los datos, un tubogan
NOP de no. El punto es que no se nos permite hacer eso y eventualmente no podemos enganchar la función,
falla.
Otro problema son las instrucciones de desplazamiento relativo. Supongamos que cualquiera de los primeros 5
bytes es una instrucción de bifurcación condicional, tendremos que reubicar esa instrucción. Por lo general, la
instrucción de bifurcación condicional es de solo 2 bytes. Y si los copiamos al trampolín, tendremos que
convertirlos en la variación más larga que es de 6 bytes y corregir el desplazamiento. Y eso funcionaría bien. En
x64, las instrucciones relativas RIP también son dolorosas, así como cualquier otra instrucción de
desplazamiento relativo que requiera una solución. Por lo tanto, hay una larga lista de esas y un buen motor de
hooking tiene que soportarlas todas, especialmente en x64, donde no hay un prólogo estándar para una función
Entonces podría decir, ok, obtuve una solución genérica para eso, sigamos la rama incondicional y conectemos
ese punto. Entonces engancharé WaitForSingleObjectEx en su lugar, ¿verdad? Pero ahora llegaste al temido
problema de los entry points. Es posible que llamen por un punto de entrada diferente que nunca quisiste
conectar. Querías enganchar WaitForSingleObject y ahora terminas enganchando WaitForSingleObjectEx,
por lo que todas que llaman a WaitForSingleObject llegan a ti, eso es cierto. Además, ahora todas las llamadas
a WaitForSingleObjectEx llega también . Ese es un gran problema. Y nunca puedes darte cuenta del nombre de
quién fue llamada (con una solución rápida y legítima).
¿Qué hacer entonces en tales casos? Porque obtuve algunas soluciones, aunque nada es perfecto.