Hola a todos.

Cuando se habla de movimientos laterales y herramientas legítimas, nos viene a la cabeza PSEXEC (otras posibles técnicas para conseguir lo mismo serían WMI y/o WinRM).

Según podemos leer en la página de Microsoft:

PsExec is a light-weight telnet-replacement that lets you execute processes on other systems, complete with full interactivity for console applications, without having to manually install client software. PsExec's most powerful uses include launching interactive command-prompts on remote systems and remote-enabling tools like IpConfig that otherwise do not have the ability to show information about remote systems.

La forma de utilizar la herramienta es la siguiente:

psexec [\\computer[,computer2[,...] | @file\]][-u user [-p psswd][-n s][-r servicename][-h][-l][-s|-e][-x][-i [session]][-c executable [-f|-v]][-w directory][-d][-<priority>][-a n,n,...] cmd [arguments]

Más información sobre su funcionamiento la podéis leer en la propia página de la herramienta.

Lo interesante de ella es ¿cómo lo hace? Ejecuta cualquier comando o aplicación de forma remota introduciéndole unas credenciales. Sin duda, es interesante y vamos a ver porqué.

Lo primero que hay que decir es que la herramienta funciona siempre y cuando seas administrador del equipo remoto. Necesitas copiar algo en un directorio en los que se necesitan permisos.

¿Pero que tenemos que copiar ahí? Ni más ni menos que un recurso que se encuentra dentro de PSEXEC.exe.

Si abrimos el ejecutable con un Editor de Recursos encontramos el fichero que será copiado al directorio de Windows (API GetSystemDirectoryWo “\\ip_o_hostname\admin$\psexesvc.exe”).

Ese es el fichero que se ejecutará en la máquina remota como un servicio. Hablamos ya de la máquina remota, cuando se ejecuta la aplicación con el parámetro “-install” se crea el servicio correspondiente ejecutado con la cuenta del sistema y apuntando al fichero que se encuentra en c:\windows (recuerda que se copia en “\\ip_o_hostname\admin$\psexesvc.exe”). Si copias a mano ese fichero en otra ruta y lo ejecutas con el parámetro mencionado, se crea el servicio apuntando a esa otra ruta (siempre en donde se encuentre ubicado).

Las opciones de este ejecutable son:

>psexesvc.exe PsInfSvc -install to install the service PsInfSvc -remove to remove the service PsInfSvc -debug <params> to run as a console app for debugging

Si se instala como servicio se creará la siguiente clave de registro:

HKLM\SYSTEM\CurrentControlSet\Services PSINFSVC

Ahora ya solamente queda esperar los comandos que quieran ejecutar en dicha máquina.

Realmente, si encontráis un “psexec.exe” en una máquina, no es un problema, eso significa que desde ahí se puede establecer una conexión contra otra/s máquina/s. Sin embargo, si lo que encontráis es un “psexesvc.exe”, ya no sólo dentro de Windows sino en cualquier otro lugar (en este caso tiene que estar en ejecución), eso significaría que ya han accedido a esa máquina, podría estar comprometida.

Hay muchas versiones de la suite de Sysinternals, en cada una puede que ese fichero sea distinto y los queremos todos. Vamos a conseguirlos ;-) Queremos detectarlas todas.

Vamos a la web oficial de Microsoft:

Y copiamos el enlace, suponiendo que siempre es el mismo en archive.org, tiene este aspecto:

En azul, vemos los días en los que se hizo una captura del contenido. En 2017, por ejemplo hay muchos más.

Todos estos, son diferentes versiones (o no, ya lo veremos después). Son las que están disponibles para descargar. El siguiente paso sería ver como son los enlaces.

Toca parsear (nos interesan los "ts":[fecha]):

Confeccionamos la URL de descarga, teniendo en cuenta que hay versiones desde 2006 a 2018:

for i in {2006..2018}; do curl -s -A "Mozilla 4.0" -k "https://web.archive.org/__wb/calendarcaptures?url=https%3A%2F%2Fdownload.sysinternals.com%2Ffiles%2FSysinternalsSuite.zip&selected_year="$i | grep "^\[" | sed 's/\"ts\"/\n\"ts\"/g' | grep "^\"ts\"" | awk -F"[" '{print $2}' | awk -F"]" '{print $1}' | sed 's/,/\n/g' | awk '{system ("wget https://web.archive.org/web/"$1"if_/https://download.sysinternals.com/files/SysinternalsSuite.zip -O SysinternalsSuite-"$1".zip")}'; done

Y a esperar un rato a que todo se descargue, puedes tomarte un café, coca-cola o lo que quieras, tienes un rato.

Ya las tenemos todas descargadas.

De esta forma tendríamos todas las Suite de Sysinternals para sacar los hashes md5 de sus ejecutables o lo que nos interesa en nuestro caso concreto (he usado la suite en lugar del ejecutable, que también está la URL directa disponible porque salen muchas más versiones en archive.org).

Una vez descomprimidas todas ellas nos queda extraer el binario que hace de servidor (por así decirlo).

Para ello he programado este script (hacerlo a mano es una locura):

import sys
import os
import re
import pefile

if len(sys.argv) > 1:
path = sys.argv[1]
else:
print "You need a file"
print "PSEXESVC extractor - by Rafa"
exit(1)

def find_pe(data):
while len(data):
mz_start = data.find('MZ')
if mz_start == -1:
return None
data = data[mz_start:]
pe = data.find('PE')
if pe != -1:
return data
return None

def dump_to_file(filename, data):
with open(filename, 'wb') as f:
f.write(data)

def get_rsc(pe,pred):
def walk_rsc(d):
if pred(pe,d):
x = d.data.struct
return pe.get_data(x.OffsetToData,x.Size)

if not hasattr(d,'directory'): return None

for d in d.directory.entries:
r = walk_rsc(d)
if r: return r

for d in pe.DIRECTORY_ENTRY_RESOURCE.entries:
r=walk_rsc(d)
if r:return r

def rsc_pred(pe,d):
return hasattr(d,'data')

def main():
if os.path.exists(path):
pe = pefile.PE(sys.argv[1]
inner_exe = open(sys.argv[1]).read()
print '[+] Extracting resource
inner_res =get_rsc(pe,rsc_pred

fname = 'PSEXESVC.exe'
print '[+] Save resourse like ', fname
with open( fname,'w') as f
f.write(inner_res)

data = bytearray(open(fname, 'rb').read()

dump_to_file(fname, data)
print "[+] File dumped: %s" % (fname

pe_data = find_pe(data)
if pe_data is None
print "[+] It's not a PE file!"
retur

if os.path.exists(fname):
print "[+] Finished: " + fname + " extracted."
else:
print "[+] Finished with errors."

pe_data = find_pe(data)
if pe_data is None:
return

else:
print "File \"" + path + "\" not exist."
exit(1)

if __name__ == '__main__':
main()

Su uso es bien sencillo:

$ python extract-psexesvc.py psexec.exe [+] Extracting resource [+] Save resourse like PSEXESVC.exe [+] File dumped: PSEXESVC.exe [+] Finished: PSEXESVC.exe extracted.

Tenemos un montón de versiones:

$ find . -iname "psexec.exe"
./sysinternals/20171121052032/PsExec.exe
./sysinternals/20070123230553/psexec.exe
./sysinternals/20160610232411/PsExec.exe
./sysinternals/20070104200744/psexec.exe
./sysinternals/20161220065806/PsExec.exe
./sysinternals/20120112064343/PsExec.exe
./sysinternals/20160331150947/PsExec.exe
./sysinternals/20160303071325/PsExec.exe
./sysinternals/20161129052051/PsExec.exe
./sysinternals/20160903091120/PsExec.exe
./sysinternals/20100201154325/psexec.exe
./sysinternals/20110815082858/PsExec.exe
./sysinternals/20170308221855/PsExec.exe
./sysinternals/20170117061351/PsExec.exe
./sysinternals/20120303053843/PsExec.exe
./sysinternals/20180412141809/PsExec.exe
./sysinternals/20170103065049/PsExec.exe
…..

Vamos a dejarlo todo accesible para el script:

$ find . -iname "psexec.exe" | awk -F"/" '{system("cp "$0" "$3"-"$4)}'

$ ls
20061216013739-psexec.exe 20130921111433-PsExec.exe 20160812133548-PsExec.exe
20061220154430-psexec.exe 20131026140207-PsExec.exe 20160820092348-PsExec.exe20061220154430-psexec.exe 20131026140207-PsExec.exe 20160820092348-PsExec.exe
20061231003824-psexec.exe 20131227135802-PsExec.exe 20160827093432-PsExec.exe
20070104015111-psexec.exe 20140208010814-PsExec.exe 20160903091120-PsExec.exe
20070104200744-psexec.exe 20140214130348-PsExec.exe 20160913015021-PsExec.exe20070104200744-psexec.exe 20140214130348-PsExec.exe 20160913015021-PsExec.exe
20070109121140-psexec.exe 20140221133728-PsExec.exe 20160920041002-PsExec.exe
…...

Ahora ya podemos usar el script que está pensado para un único fichero.

Al ejecutarlo se va extrayendo el recurso y se obtiene su hash md5:

$ for i in 'ls *-*s*xec.exe'; do python extract-psexesvc.py $i;md5sum PSEXESVC.exe | sort -u;
done ….
[+] Extracting resource
[+] Save resourse like PSEXESVC.exe
[+] File dumped: PSEXESVC.exe
[+] Finished: PSEXESVC.exe extracted. 75b55bb34dac9d02740b9ad6b6820360 PSEXESVC.exe
[+] Extracting resource
[+] Save resourse like PSEXESVC.exe
[+] File dumped: PSEXESVC.exe
[+] Finished: PSEXESVC.exe
extracted. 75b55bb34dac9d02740b9ad6b6820360 PSEXESVC.ex

De PSEXEC tenemos varias versiones:

$ grep -i psexec.exe salida-psexec.txt | awk '{print toupper($1)}' | sort -
0C5BB01EA544AD94A0D73EC26F194771
27304B246C7D5B4E149124D5F93C5B01
36A95D7DCC1943F78AC370DAFE79D5AE
66470E0780A6C890A656EAE305045484
A7F7A0F74C8B48F1699858B3B6C11EDA
AEEE996FD3484F28E5CD85FE26B6BDCD
D0DF366711C8B296680002840336B6FD
E13DBDE36CE556B634310399C1EFA257

Y de la aplicación que se utiliza como servicio:

$ grep -v "^\[" salida2-psexec.txt | awk '{print toupper($1)}' | sort -u
4CC62CAE08D73BBAF197D755D1D07993
5C708DF7BC8349EC19FE0AE9D01C90EA
5CB94F11459DA45D647D888EF4438B5B
5F2541AEACCA3BB141A9062F38C68C7D
75B55BB34DAC9D02740B9AD6B6820360
87DFAC39F577E5F52F0724455E8832A8
A283E768FA12EF33087F07B01F82D6DD
F19C18DB99D231F63935FCD7C6347219

Todas estas, por lo que tenemos los hashes de las aplicaciones para buscarlo en vuestros sistemas de detección que tengais.

Con la siguiente regla yara puede detectarse por hash:

import "hash

rule detected_psexec {

meta:
description = "Detecting psexec - by Rafa"

condition: uint16(0) == 0x5A4D an
hash.md5(0, filesize) == "0c5bb01ea544ad94a0d73ec26f194771" or
hash.md5(0, filesize) == "27304b246c7d5b4e149124d5f93c5b01" or
hash.md5(0, filesize) == "36a95d7dcc1943f78ac370dafe79d5ae" or
hash.md5(0, filesize) == "66470e0780a6c890a656eae305045484" or
hash.md5(0, filesize) == "a7f7a0f74c8b48f1699858b3b6c11eda" or
hash.md5(0, filesize) == "aeee996fd3484f28e5cd85fe26b6bdcd" or
hash.md5(0, filesize) == "d0df366711c8b296680002840336b6fd" or
hash.md5(0, filesize) == "e13dbde36ce556b634310399c1efa257"
}

rule detected_psexesvc {

meta
description = "Detecting psexesvc - by Rafa"

condition: uint16(0) == 0x5A4D an
hash.md5(0, filesize) == "4cc62cae08d73bbaf197d755d1d07991"
or hash.md5(0, filesize) == "5c708df7bc8349ec19fe0ae9d01c90ea"
or hash.md5(0, filesize) == "5cb94f11459da45d647d888ef4438b5b"
or hash.md5(0, filesize) == "5f2541aeacca3bb141a9062f38c68c7d"
or hash.md5(0, filesize) == "75b55bb34dac9d02740b9ad6b6820360"
or hash.md5(0, filesize) == "87dfac39f577e5f52f0724455e8832a8"
or hash.md5(0, filesize) == "a283e768fa12ef33087f07b01f82d6dd"
or hash.md5(0, filesize) == "f19c18db99d231f63935fcd7c6347219"
}

Vemos su funcionamiento.

$ find . -iname "psexec.exe" | awk '{system("yara psexec.yar "$1)}' | head -20
detected_psexec ./sysinternals/20171121052032/PsExec.exe
detected_psexec ./sysinternals/20070123230553/psexec.exe
detected_psexec ./sysinternals/20160610232411/PsExec.exe
detected_psexec ./sysinternals/20070104200744/psexec.exe
detected_psexec ./sysinternals/20161220065806/PsExec.exe
detected_psexec ./sysinternals/20120112064343/PsExec.exe
detected_psexec ./sysinternals/20160331150947/PsExec.exe
detected_psexec ./sysinternals/20160303071325/PsExec.exe
detected_psexec ./sysinternals/20161129052051/PsExec.exe
detected_psexec ./sysinternals/20160903091120/PsExec.exe
detected_psexec ./sysinternals/20100201154325/psexec.exe
detected_psexec ./sysinternals/20110815082858/PsExec.exe
detected_psexec ./sysinternals/20170308221855/PsExec.exe
detected_psexec ./sysinternals/20170117061351/PsExec.exe
detected_psexec ./sysinternals/20120303053843/PsExec.exe
detected_psexec ./sysinternals/20180412141809/PsExec.exe
detected_psexec ./sysinternals/20170103065049/PsExec.exe
detected_psexec ./sysinternals/20070128181721/psexec.exe
detected_psexec ./sysinternals/20061231003824/psexec.exe
detected_psexec ./sysinternals/20150213140441/PsExec.exe

Lo mismo ocurre para el servicio.

$ yara psexec.yar PSEXESVC.exe
detected_psexesvc PSEXESVC.ex

Espero que os haya gustado. Hasta otra!!