Esto me tomó alrededor de 4 días (+2 días para actualizar), pero lo hice funcionar… núcleo de óxido + distribución para Cobalt Strike BOF.
Esto es en gran medida una prueba de concepto, pero me encantaría ver que otros lo usen y contribuyan.
Tabla de Contenidos
edificio
- Instalar mingw
- Instale Nightly Rust usando las cadenas de herramientas x86_64-pc-windows-gnu e i686-pc-windows-gnu
- correr
cargo install cargo-make
- correr
cargo make
- ????
- Beaufit
Hazlo tu mismo
Edite la función de entrada en rustbof/src/lib.rs. Puede utilizar el siguiente comando para agregar parámetros bof_pack
Las funciones están en el script del atacante, simplemente no cambie las dos primeras ya que son reubicaciones.
¿Qué tal?
Creo que me gustaría escribir una entrada de blog sobre esto en algún momento, pero por ahora, este es el proceso:
- ¿Cómo compilar un archivo objeto desde Rust?
- Rustc tiene uno
--emit=obj
El indicador solo enviará el archivo de destino a la carpeta deps del destino.
- Rustc tiene uno
- BOF es un soltero Archivos objeto, no muchos. Rust compila cada componente en su propio archivo .o. ¿Cómo los combino?
- ld tiene una característica llamada «reubicable» (-i) para enlaces incrementales
- El cargador Cobalt Strike lanza NullPointerException
beacon_inline_execute
. ¿Por qué?- Un poco de descompilación y depuración de códigos de bytes me llevaron a descubrir que el analizador CS COFF estaba bloqueado por ciertos símbolos en el objeto. Resulta que estos símbolos sólo se incluyen como información de depuración (documentación).
- En mi investigación encontré
OBJExecutable
yOBJParser
¡Las clases en cobaltstrike.jar tienen la función principal de obtener la ruta al archivo de destino e imprimir un montón de información útil!
- ¿Cómo eliminar símbolos no deseados en el archivo de destino?
- Excelentemente,
strip
¡trabajar!strip
en realidad hay uno--strip-uneeded
Quite las banderas de todo lo que no sea necesario para la reubicación, como la información de depuración.
- Excelentemente,
- El cargador Cobalt Strike BOF ahora se queja de algunos símbolos indefinidos, p.
rust_oom
y__rust_alloc
.- En este punto no estoy usando alloc en absoluto, pero todavía se está compilando y luego agregando al BOF a través de:
ld -i
. ¿Cómo puedo deshacerme de estos símbolos?- Al principio, simplemente eliminé el archivo de destino de asignación porque no lo estaba usando. Simple.
- Creo que en algún momento descubrí
--gc-sections
Bandera para ld que le permite definir símbolos raíz mediante-u
bandera, luego elimina cualquier símbolo al que nunca se haya hecho referencia. Esto también lo resolvió.
- En este punto no estoy usando alloc en absoluto, pero todavía se está compilando y luego agregando al BOF a través de:
- DE ACUERDO En este punto podemos cargar sin fallar. Intentemos importar una función de KERNEL32. Cómo utilizamos el óxido
__imp_
¿símbolo?- Podemos definir el nombre del enlace por
#[link_name = "__imp_KERNEL32$OutputDebugStringA]
- Podemos definir el nombre del enlace por
- El problema con la pregunta anterior es que
__imp_
El símbolo debe ser un puntero a la tabla de importación, no a la función en sí, por lo que Rust considera que el símbolo es un puntero único a una función, no un puntero doble a una función.- ¿Cómo diablos podemos convencer a Rust de una manera limpia de que las métricas de funciones son en realidad métricas de funciones?
- Bueno, resulta que podemos usar óxido peligroso para convertir un puntero en un puntero doble, pero eso es sólo la mitad de la solución porque no quiero decir
unsafe { make my function pointer a double pointer}(args)
Cada vez que quiero llamar. - De hecho, puedes hacer que Rust importe símbolos de la siguiente manera
__imp_
método, pero sólo puedo hacer que funcione en variables, no en funciones, por lo que es algo inútil.
- Bueno, resulta que podemos usar óxido peligroso para convertir un puntero en un puntero doble, pero eso es sólo la mitad de la solución porque no quiero decir
- ¿Cómo diablos podemos convencer a Rust de una manera limpia de que las métricas de funciones son en realidad métricas de funciones?
- ¿Cómo lanzar automáticamente el puntero durante una llamada?
- Si no lo sabías, el óxido está llamando
deref
Al llamar al tipo. Entonces puedes envolver el indicador de función en un tipo y luego implementarcore::ops::Deref
Para este tipo, el indicador se puede convertir dinámicamente en un indicador doble.- El resultado es que la llamada a la función tiene éxito.
- Si no lo sabías, el óxido está llamando
- Ahora podemos importar la función, pero ¿qué pasa con el asignador? Quiero poder usar cadenas y vectores.
- Esta es la ventaja de Rust Core: ¡alloc es fácil de implementar! El asignador solo crea y administra el montón a través de la función Rtl*Heap de NTDLL.
- Definí un asignador global que debe inicializarse antes de poder usarse. Ahora obtengo esos símbolos indefinidos de alloc nuevamente, como
__rust_alloc
yrust_oom
. ¿Por qué?- Resulta que cuando Rust vincula un archivo binario, crea estos símbolos y los señala al asignador del sistema o a un asignador personalizado (si hay uno definido). Necesito definirlos manualmente.
- Definí un asignador global que debe inicializarse antes de poder usarse. Ahora obtengo esos símbolos indefinidos de alloc nuevamente, como
- Esta es la ventaja de Rust Core: ¡alloc es fácil de implementar! El asignador solo crea y administra el montón a través de la función Rtl*Heap de NTDLL.
- Ahora aparece un símbolo indefinido que se parece al nombre de mi asignador global. ¿Qué ocurre?
- BOF no utiliza BSS, el asignador se coloca en BSS. Simplemente le dije explícitamente a Rust que pusiera ALLOCATOR en la sección .data
#[link_section = ".data"]
. Fijado.
- BOF no utiliza BSS, el asignador se coloca en BSS. Simplemente le dije explícitamente a Rust que pusiera ALLOCATOR en la sección .data
- Ahora que puedo asignar memoria, intentemos usar
format!
Hong Zaialloc
asignarString
. ¡Se estrelló! ¿Qué regalar?- Esto me llevó un tiempo encontrarlo. El cargador BOF no reubica el contenido en .data y .rdata, solo .text.
- El analizador COFF tiene una función
pe.OBJExecutable.getRelocations
Crea estructuras de reubicación para símbolos en .text, pero nada más.
- El analizador COFF tiene una función
- El bloqueo ocurrió en una tabla de funciones virtuales que no se actualizó en .rdata.
- Sin embargo, el cargador BOF no admite el tipo 1 (IMAGE_REL_AMD64_ADDR64), que es el tipo que genera óxido para .rdata.
- Esto me llevó un tiempo encontrarlo. El cargador BOF no reubica el contenido en .data y .rdata, solo .text.
- ¿Cómo puedo solicitar la reubicación?
- Antes de hacer algo loco (como referencias de vtable), escriba mi propio programa de arranque para aplicar las reubicaciones manualmente
- No es tan difícil, pero requiere cierta coordinación con el cliente Cobalt Strike.
- Yo uso Cobalt Strike
OBJExecutable
categorías para analizar COFF yParser
Categoría para empaquetar movimientos adicionales.- Esto es más o menos lo que hace CS
getRelocations
. El lado de Rust obtiene la información a través del parámetro BOF y luego aplica la reubicación.
- Esto es más o menos lo que hace CS
- ¿Cómo sé dónde colocar las otras partes de la baliza?
- El cargador BOF en la baliza elegirá un lugar para colocar
.data
y.rdata
partes, pero no sabemos de dónde vienen esas partes en nuestro código. - Terminé agregando símbolos importables al comienzo de las secciones .text, .rdata y .data modificando el script del vinculador. Si alguien tiene una idea mejor, me encantaría escucharla.
- El cargador BOF en la baliza elegirá un lugar para colocar
- Cuando estos símbolos se importan como estáticos externos usando variables, uno de ellos produce valores indefinidos.
refptr
símbolo. ¿Cómo puedo evitar que haga esto?- Terminé importándolos como funciones y luego convirtiéndolos para su uso. Un truco, pero hace el trabajo.
- Una vez solucionadas, las llamadas a vtable funcionan.
- a 32 bits. Cuando intentas cargarlo, un montón de símbolos no están definidos, incluido un montón que comienza con
__
. ¿Cómo podemos solucionar este problema?- 32 bits agrega un guión bajo a un montón de cosas que por alguna razón puedo buscar pero que no me importan.
- Cambié el símbolo de importación a
_imp_
en lugar de__imp_
- Al hacer esto se rompieron los 64 bits, así que agregué un
cfg_attr
Hacer que los nombres de importación tengan la cantidad correcta de guiones bajos según el objetivo
- Al hacer esto se rompieron los 64 bits, así que agregué un
- Agregué un
cfg_attr
llegar__section_start__
La notación en el vinculador es solo para x86 y utiliza el nombre del vínculo menos un guión bajo. - El símbolo final es terrible.
chkstk
…He discutido esto en detalle
- ¿Cómo podemos obtener chkstk?
- Está definido en NTDLL por lo que podemos simplemente importarlo.
NTDLL!_chkstk
y definir un__chkstk
lo llamamos nosotros mismos
- Está definido en NTDLL por lo que podemos simplemente importarlo.
Sí, ahora funcionan BOF de 32 y 64 bits.
Todavía no he probado nada demasiado sofisticado, pero avíseme si tiene alguna pregunta.
Para obtener más información, haga clic aquí.