Home Hacking y Seguridad Rootkits / Kernel Como ser invisible en Windows NT

Ultimos Mensajes del Foro

Manual Aleatorio

borrado seguro de ficheros en distintos sistemas

En este articulo voy a explicar el borrado de datos de forma segura desde diversas plataformas. Es importante tener conocimiento de esto ya que nos puede salvar de algun contratiempo no deseado.

Existen varias formas para borrar un fichero de manera que no se pueda recuperar, y no es borrandolo de tu papelera de reciclaje.

Leer más...
Como ser invisible en Windows NT Imprimir E-mail
Hacking y Seguridad - Rootkits / Kernel
Este documento trata sobre tecnicas de esconder objetos, ficheros, servicios, procesos, etc. en sistemas Windows NT. Estos metodos estan basados en los ganchos a funciones de la Windows API que estan descritos en el documentos "Hooking Windows API".

Texto Completo:
========================[ Invisibilidad en sistemas NT ]========================

                        Como ser invisible en Windows NT
                        --------------------------------

                       Autor:  Holy_Father <
 Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
 >
		       Traduccion: Kintaro <
 Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
 >	
                       Version: 1.2 spanish
                       Fecha:   01.01.2005
	

=====[ 1. Contenidos ]==========================================================

 1. Contenidos
 2. Introduccion
 3. Ficheros
	3.1 NtQueryDirectoryFile
	3.2 NtVdmControl
 4. Procesos
 5. Registro
	5.1 NtEnumerateKey
	5.2 NtEnumerateValueKey
 6. Servicios de sistema y drivers
 7. Expandiendo los ganchos
	7.1 Derechos
	7.2 Gancho global
	7.3 Nuevos procesos
	7.4 DLL
 8. Memoria
 9. Handle (Manejadores)
	9.1 Nombrando el handle y obteniendo el tipo
10. Puertos
	10.1 Netstat, OpPorts en WinXP, FPort en WinXP
	10.2 OpPorts en Win2k y NT4, FPort en Win2k
11. Terminando



=====[ 2. Introduccion ]========================================================

	Este documento trata sobre tecnicas de esconder objetos, ficheros,
servicios, procesos, etc. en sistemas Windows NT. Estos metodos estan basados
en lso ganchos a funciones de la Windows API que estan descritos en mi
documentos "Hooking Windows API".
	Todo esto lo aprendi por mis investigaciones durante el desarrollo
del codigo del rootkit, asique es posible que pueda ser escrito mejor y mas
facilmente de lo que yo lo hago. Esto incluye mi implementacion del rootkit.

	Ocultacion de objeto arbitrario, en este documento, significa cambiar
algunas funciones que muestran estos objetos de manera que evitemos que 
muestren el objeto. En el caso de que ese objeto fuese el valor de retorno
de esa funcion, nosotros devolveriamos el valor como si el objeto no 
existiese.

	El metodo basico (excluyendo casos en que indique diferente) es que
llamaremos la funcion original con los argumentos originales y lo que 
cambiariamos seria su salida.
	
	En esta de este texto se describen metodos de ocultar ficheros, 
procesos, claves y valores de registros, servicios de sistema y drivers, 
reservas de memoria y handles.



=====[ 3. Ficheros ]============================================================

	Hay varias posibilidades de ocultar ficheros de manera que no los vea
el SO. Nosotros nos dedicaremos solo a modificar la APi, olvidandonos de 
aquellas tecnicas que realizan cambios en el sistema de ficheros. Asi es mucho 
mas facil ya que no necesitmos conocer el sistema de ficheros en particular.


=====[ 3.1 NtQueryDirectoryFile ]===============================================

	La busqueda de ficheros en WNT en algun directorio se basa en buscar 
todos sus ficheros y todos sus subdirectorios con ficheros. Para enumerar 
ficheros se usa la funcion NtQueryDirectoryFile.

	NTSTATUS NtQueryDirectoryFile(
		IN HANDLE FileHandle,
		IN HANDLE Event OPTIONAL,
		IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
		IN PVOID ApcContext OPTIONAL,
		OUT PIO_STATUS_BLOCK IoStatusBlock,
		OUT PVOID FileInformation,
		IN ULONG FileInformationLength,
		IN FILE_INFORMATION_CLASS FileInformationClass,
		IN BOOLEAN ReturnSingleEntry,
		IN PUNICODE_STRING FileName OPTIONAL,
		IN BOOLEAN RestartScan
	);


	Los parametros importantes para nosotros son FileHandle, 
FileInformation y FileInformationClass.
	FileHandle es el handle del directorio, el cual se obtiene con 
NtOpenFile.
	FileInformation es un puntero a una zona de memoria donde se escribira
la informacion pedida.
	FileInformationClass determina el tipo de registro que se escribira
en FileInformation. FileInformationClass es un tipo enumerado, y son cuatro
los valores que necesitamos para la enumeracion de un directorio.
 
	#define FileDirectoryInformation 1
	#define FileFullDirectoryInformation 2
	#define FileBothDirectoryInformation 3
	#define FileNamesInformation 12


la estructura escrita es FileInformation con FileDirectoryInformation es:

	typedef struct _FILE_DIRECTORY_INFORMATION { 
		ULONG NextEntryOffset;
		ULONG Unknown;
		LARGE_INTEGER CreationTime;
		LARGE_INTEGER LastAccessTime;
		LARGE_INTEGER LastWriteTime;
		LARGE_INTEGER ChangeTime;
		LARGE_INTEGER EndOfFile;
		LARGE_INTEGER AllocationSize; 
		ULONG FileAttributes;
		ULONG FileNameLength;
		WCHAR FileName[1];
	} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;


con FileFullDirectoryInformation:

	typedef struct _FILE_FULL_DIRECTORY_INFORMATION {
		ULONG NextEntryOffset;
		ULONG Unknown;
		LARGE_INTEGER CreationTime;
		LARGE_INTEGER LastAccessTime;
		LARGE_INTEGER LastWriteTime;
		LARGE_INTEGER ChangeTime;
		LARGE_INTEGER EndOfFile;
		LARGE_INTEGER AllocationSize;
		ULONG FileAttributes;
		ULONG FileNameLength;
		ULONG EaInformationLength;
		WCHAR FileName[1];
	} FILE_FULL_DIRECTORY_INFORMATION, *PFILE_FULL_DIRECTORY_INFORMATION;


con FileBothDirectoryInformation:

	typedef struct _FILE_BOTH_DIRECTORY_INFORMATION { 
		ULONG NextEntryOffset;
		ULONG Unknown;
		LARGE_INTEGER CreationTime;
		LARGE_INTEGER LastAccessTime;
		LARGE_INTEGER LastWriteTime;
		LARGE_INTEGER ChangeTime;
		LARGE_INTEGER EndOfFile;
		LARGE_INTEGER AllocationSize;
		ULONG FileAttributes;
		ULONG FileNameLength;
		ULONG EaInformationLength;
		UCHAR AlternateNameLength;
		WCHAR AlternateName[12];
		WCHAR FileName[1];
	} FILE_BOTH_DIRECTORY_INFORMATION, *PFILE_BOTH_DIRECTORY_INFORMATION; 


y con FileNamesInformation:

	typedef struct _FILE_NAMES_INFORMATION {
		ULONG NextEntryOffset;
		ULONG Unknown;
		ULONG FileNameLength;
		WCHAR FileName[1];
	} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;


	Esta funcion escribe una lista de estas estructuras en FileInformation.
	Solo tres varibles nos importan en cualquiera de estos tipos 
de estructuras.
	NextEntryOffset es la longitud en bytes del nodo estructura actual.
El primer nodo empieza en la direccion FileInformation + 0. El segundo
en FileInformation + NextEntryOffset (NextEntryOffset del primer nodo).
El ultimo nodo tiene su campo NextEntryOffset puesto a cero.

        FileName es el nombre completo del fichero.
        FileNameLength es la longitud del nombre.

	Si queremos ocultar un fichero, debemos tener en cuenta estos
cuatro tipos y para cada registro devuelto comparamos su nombre
con el que queremos ocultar. Si queremos ocultar el primer registro tenemos
que mover las estructuras siguientes al lugar de la primera. Esto
causara la sobreescritura del primer registro. Si queremos ocultar
otro registro, cambiamos el valor de NextEntryOffset del registro que
le precede. 

(Nota del traductor:
 Aqui hay 2 posibilidades:
 	1)Mover solamente el segundo registro
 	2)Mover todos los registros siguientes
 La 1º es algo mas rapida y tambien mas facil, la 2º es mejor :). 
 En la 1º moveriamos el segundo registro sobre el primero, sobreescribiendolo,
 y cambiamos el NextEntryOffset de este registro que acabamos de escribr
 al valor 
 NextEntryOffset nuevo=NextEntryOffset del 1º registro + NextEntryOffset del 2º
 pero esto tiene un problema: si a alguien le da por leer esta zona de memoria
 vera que hay gato encerrado, detras del primer registro encontraria una
 copia del mismo registro,y este no estaria apuntado por el anterior. Un poco
 de buena vista bastara para detectarnos! En vez de eso podemos recurrir a la
 forma 2. Esta forma consiste en sobreescribir ese registro fantasma con los 
 regitros siguientes, es decir desplazar todos los registros siguientes hacia 
 el primera registro, de manera que todos los registros estan encadenados de
 forma contigua, no hay en memoria una zona desapuntada ni una copia de ningun
 otro registro. bueno, ahora si estas entendiendo esto seguramente no estaras
 de acuerdo en todo, porque en realidad no es del todo cierto: si que hay una
 copia de algun registro. ¿De cual? Pues del ultimo no? En este momento todo
 funcionaria bien, pero podria seguir siendo detectable de manera parecida a 
 la anterior, aunque mas difícilmente, pero se puede mejorar,
 qué se os ocurre hacer?  lo logico seria sobreescribirlo con los datos que 
 hubiese antes tras el ultimo registro, pues vamos alla. ponemos el tamaño
 del ultimo registro fantasma a NULL. Ahora la detección esta mucho mas
 difícil, pero de nuevo no es perfecta! Existe una variable de salida de la
 función con el numero de bytes escritos, entonces un avispado forense podria
 comprobar esto y detectarnos de nuevo :/ . La solución aqui consiste en hacer
 una nueva llamada a la función pasandole como puntero del buffer la dirección
 justo despues del último registro. Ahora la variable con el número de bytes
 escritos contendrá el numero de bytes escritos en esta ocación. Pero 
 seguiremos haciendo esta llamada hasta que en una de ella el resultado sea
 "espacio insuficiente", entonces la variable de bytes escritos ya no puede
 comprobarse ,jeje :P  
 Para los curiosos, los posibles valores de retorno de la funcion, de tipo 
 NTSYSAPI NTSTATUS NTAPI estan listados en el ntstatus.h )
 
Si queremos ocultar el ultimo registro, el nuevo valor de NextEntryOffset
seria cero, en otro caso el valor seria la suma del NextEntryOffset del 
registro precedente y del NextEntryOffset del registro a esconder.
En este caso deberiamos cambiar el valor de Unknown del registro
previo, que es probablemente un indice para la busqueda siguiente.
El valor de Unknown del registro previo debe contener el valor del
Unknown del registro a ocultar.
	Si no se encuentran mas ficheros que sí queramos mostrar, devolveremos
STATUS_NO_SUCH_FILE.

	#define STATUS_NO_SUCH_FILE 0xC000000F


=====[ 3.2 NtVdmControl ]=======================================================

	Por razones desconocidas, la emulacion de DOS NTVDM puede obtener 
tambien una lista de ficheros con NtVdmContol.

	NTSTATUS NtVdmControl(        
		IN ULONG ControlCode,
		IN PVOID ControlData
	);

	ControlCode especifica la subfuncion que se aplica a los datos
del buffer ControlData. Si ControlCode es VdmDirectoryFile, esta funcion
hace lo mismo que NtQueryDirectoryFile con FileInformationClass puesto
a FileBothDirectoryInformation.

	#define VdmDirectoryFile 6

	Entonces ControlData es usado como FileInformation. La unica diferencia
aqui es que no sabemos la longitud de este buffer. Asique tenemos que
contarlo manualmente. Tenemos que sumar NextEntryOffset de todos los registros
y FileNameLength del ultimo registro y 0x5E como longitud del ultimo registro
excluyendo el nombre del fichero. Los metodos de ocultacion son los mismos
que en el caso de NtQueryDirectoryFile.



=====[ 4. Procesos ]============================================================

	Cierta informacion esta disponible usando NtQuerySystemInformation.

	NTSTATUS NtQuerySystemInformation(
		IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
		IN OUT PVOID SystemInformation,
		IN ULONG SystemInformationLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	
	SystemInformationClass especifica el tipo de informacion que queremos 
		obtener.
	SystemInformation es un puntero al buffer de salida.
	SystemInformationLength es la longitud de este buffer.
	ReturnLength es el numero de bytes escritos.
	
	Para la enumeracion de procesos en ejecucion pasamos
como SystemInformationClass la constante SystemProcessesAndThreadsInformation


	#define SystemInformationClass 5


	Estructura devuelta en el buffer SystemInformation:

	typedef struct _SYSTEM_PROCESSES { 
		ULONG NextEntryDelta;
		ULONG ThreadCount;
		ULONG Reserved1[6];
		LARGE_INTEGER CreateTime;
		LARGE_INTEGER UserTime;
		LARGE_INTEGER KernelTime;
		UNICODE_STRING ProcessName; 
		KPRIORITY BasePriority;
		ULONG ProcessId;
		ULONG InheritedFromProcessId;
		ULONG HandleCount;
		ULONG Reserved2[2];
		VM_COUNTERS VmCounters;
		IO_COUNTERS IoCounters;  // Windows 2000 solo
		SYSTEM_THREADS Threads[1];
	} SYSTEM_PROCESSES, *PSYSTEM_PROCESSES;


	Ocultar procesos es igual que en el caso de ocultar ficheros.
Tenemos que cambiar NextEntryDelta del registro previo a aquel que queremos
ocultar. Usualmente no querremos cambiar nunca el primero registro de todos,
pues este es el proceso Idle (Proceso inactivo de sistema)



=====[ 5. Registro ]============================================================

	El registro de windows es bastante grande en cuanto a la estructura
de arbol, conteniendo dos tipos importantes de registros para nosotros que 
podriamos querer ocultar. El primer tipo son las claves de registro, y el 
segundo son los valores. La ocultacion del registro no es tan trivial como
la ocultacion de ficheros o procesos.


=====[ 5.1 NtEnumerateKey ]=====================================================

	Debido a su estructura no nos es posible obtener una lista de todas
las claves en una parte determinada del registro. Tan solo podemos obtener
informacion sobre una clave especificada por su indice en algun lugar del 
registro. La funcion que provee de esta informacion es NtEnumerateKey.

	NTSTATUS NtEnumerateKey(
		IN HANDLE KeyHandle,
		IN ULONG Index,
		IN KEY_INFORMATION_CLASS KeyInformationClass, 
		OUT PVOID KeyInformation,
		IN ULONG KeyInformationLength,
		OUT PULONG ResultLength
	);


	KeyHandle es un handle a una clave, y queremos solicitar la informacion
de una subclave especificada por Index. El tipo de la informacion de salida 
se especifica mediante KeyInformationClass. Los datos se escriben en el 
buffer de salida KeyInformation, donde KeyInformationLength es el tamaño. 
ResultLength es el numero de bytes escritos.

	Lo mas importante a tener en cuenta es que si escondemos una clave,
los indices de las claves siguientes serian todos desplazados. Entonces para
obtener informacion de una clave con un indice mayor pediremos informacion
sobre la clave con un indice menor, por tanto tenemos que contar cuantos
registros hay antes de ocultar alguno y entonces nosotros devolvemos el
correcto.
	
	Echad un vistazo a este ejemplo. Pongamos que tenemos tres claves:
A, B, C, D, E, y F en alguna parte del registro. EL indice empieza en cero, por
lo que el indice 4 corresponde a la clave E. Ahora si queremos esconder B y 
la aplicacion enganchada llama a NtEnumerateKey con Index a 4, nosotros
deberiamos devolver informacion sobre F, porque el indice se ha desplazado.
El problema es que no sabemos que desplazamiento ha habido. Y si no nos
preocupamos de los desplazamientos y devolvemos E en lugar de F cuando se pida
la clave con Index 4, entonces no deberiamos devolver nada cuando se pida
la clave con Index 1, o bien devolveriamos C. Ambos casos son erroneos. Esto
es por lo que tenemos que que tener cuidado con los desplazamientos.

	Ahora si contamos el desplazamiento mediante rellamadas a la
funcion por cada indice de 0 a Index, entonces habria veces que deberiamos
esperar años ( en un procesador de 1GHz tardaria 10 segundos con el registro
estandar, lo cual es demasiado). Asique tenemos que resolverlo por otro metodo
mas sofisticado.

	Sabemos que las claves (a excepcion de las referencias) estan ordenadas
alfabeticamente. Si pasamos de las referencias (que no las queremos esconder)
podemos contar el desplazamiento por el siguiente metodo. Ordenaremos
alfabeticamente nuestra lista de nombres de claves que queremos ocultar
(con RtlCompareUnicodeString por ejemplo), entonces cuando la aplicacion
llame a NtEnumerateKey, no la rellamaremos con los argumentos inalterados
sino que descubriremos el nombre del registro especificado por Index.

	NTSTATUS RtlCompareUnicodeString(       
		IN PUNICODE_STRING String1, 
		IN PUNICODE_STRING String2, 
		IN BOOLEAN  CaseInSensitive  
	);

	String1 y String2 son las cadenas a comparar, CaseInSensitive 
es True queremos comparar con distincion de mayuculas y minusculas. 
	El resultado de la funcion describe la relacion entre String1 
y String2:

		resultado > 0:	String1 > String2
		resultado = 0:	String1 = String2
		resultado < 0:	String1 < String2

Ahora tenemos que encontrar un punto fronterizo en la lista. 
Compararemos alfabeticamente el nombre de la clave especificada por 
Index con los nombres de nuestra lista.
El punto fronterizo seria el ultimo nombre menor de nuestra lista (el siguiente
seria el mismo nombre). Sabemos que el desplazamiento es como mucho el numero
de nombres que hay antes del punto fronterizo en nuestra lista. Pero no todos
los items de nuestra lista tiene que ser una clave existente en la parte
del registro en la que estamos. Asique pedimos para todos los items de nuestra
lista hasta el punto frontera si estos estan en esta parte del registro.
Esto se hace con NtOpenKey.


	NTSTATUS NtOpenKey(
		OUT PHANDLE KeyHandle,
		IN ACCESS_MASK DesiredAccess,
		IN POBJECT_ATTRIBUTES ObjectAttributes
	);

	KeyHandle es un handle de una superclave. Usaremos el valor de
NtEnumerateKey aqui. DesiredAccess son los derechos de acceso. El valor
correcto es KEY_ENUMERATE_SUB_KEYS. ObjectAttributes describe la subclave
que queremos abrir (incluyendo su nombre).


	#define KEY_ENUMERATE_SUB_KEYS 8

	si el resultado de NtOpenKey es 0 la apertura de clave se realizo con
exito, lo que significa que esta clave de nuestra lista existe. La clave
abierta se cierra con NtClose.

	NTSTATUS NtClose(
		IN HANDLE Handle
	);

	
	Para cada llamada a NtEnumerateKey contaremos el desplazamiento
como el numero de claves de nuestra lista que existen en la parte dada
del registro. Entonces añadiremos este desplazamiento al argumento Index
y finalmente llamaremos a la NtEnumerateKey original.
	Para obtener del nombre de la clave especificada por Index usaremos
KeyBasicInformation como KeyInformationClass.

	#define KeyBasicInformation 0

	NtEnumerateKey devuelve esta estructura en KeyInformation:

	typedef struct _KEY_BASIC_INFORMATION {
		LARGE_INTEGER LastWriteTime;
		ULONG TitleIndex;
		ULONG NameLength;
		WCHAR Name[1];            
	} KEY_BASIC_INFORMATION, *PKEY_BASIC_INFORMATION;

	Lo único que necesitamos aqui es Name y NameLength. Si no hay
entrada para el Index desplazado devolveremos error 
STATUS_EA_LIST_INCONSISTENT.

	#define STATUS_EA_LIST_INCONSISTENT 0x80000014


=====[ 5.2 NtEnumerateValueKey ]================================================

	Los valores de registro no estan ordenados alfabeticamente.
Afortunadamente el numero de valores en una clave es bastante pequeño y
podemos usar el metodo de rellamada para los desplazamientos

	NTSTATUS NtEnumerateValueKey(
		IN HANDLE KeyHandle,
		IN ULONG Index,
		IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
		OUT PVOID KeyValueInformation,
		IN ULONG KeyValueInformationLength,
		OUT PULONG ResultLength
	);

	KeyHandle es de nuevo un handle de una superclave. Index es un
indice de la lista de valores en la clave dada. KeyValueInformationClass 
describe un tipo de informacion que sera almacenado en el buffer 
KeyValueInformation, que es ocupa KeyValueInformationLength bytes. El numero
de bytes escritos se develve en ResultLength.

	De nuevo podemos contar el desplazamiento pero segun el numero de
valores en una clave podemos rellamar esta funcion para todos los indices
desde 0 a Index. El numero del valor puede obtenerse cuando 
KeyValueInformationClass es puesto a KeyValueBasicInformation.

	
	#define KeyValueBasicInformation 0


	Entonces obtendremos la siguiente estructura en el buffer
KeyValueInformation : 

	typedef struct _KEY_VALUE_BASIC_INFORMATION {
		ULONG TitleIndex;
		ULONG Type;
		ULONG NameLength;
		WCHAR Name[1];
	} KEY_VALUE_BASIC_INFORMATION, *PKEY_VALUE_BASIC_INFORMATION;

	De nuevo lo que nos interesa solo es Name y Namelength.


	Si no hay entrada para el Index desplazado devolveremos
STATUS_NO_MORE_ENTRIES.

	#define STATUS_NO_MORE_ENTRIES 0x8000001A



=====[ 6. Servicios de sistema y drivers ]======================================

	Los servicios de sistema y drivers se enumeran mediante
cuatro funciones API, independientes unas de otras. La conexion entre ellas
es diferente en cada version de Windows. Por eso es por lo que tenemos
que usar ganchos con las cuatro funciones.

	BOOL EnumServicesStatusA(
		SC_HANDLE hSCManager,
		DWORD dwServiceType,
		DWORD dwServiceState,
		LPENUM_SERVICE_STATUS lpServices,
		DWORD cbBufSize,
		LPDWORD pcbBytesNeeded,
		LPDWORD lpServicesReturned,
		LPDWORD lpResumeHandle
	);

	BOOL EnumServiceGroupW(
		SC_HANDLE hSCManager,
		DWORD dwServiceType,
		DWORD dwServiceState,
		LPBYTE lpServices,
		DWORD cbBufSize,
		LPDWORD pcbBytesNeeded,
		LPDWORD lpServicesReturned,
		LPDWORD lpResumeHandle,
		DWORD dwUnknown
	);

	BOOL EnumServicesStatusExA(
		SC_HANDLE hSCManager,
		SC_ENUM_TYPE InfoLevel,
		DWORD dwServiceType,
		DWORD dwServiceState,
		LPBYTE lpServices,
		DWORD cbBufSize,
		LPDWORD pcbBytesNeeded,
		LPDWORD lpServicesReturned,
		LPDWORD lpResumeHandle,
		LPCTSTR pszGroupName
	);

	BOOL EnumServicesStatusExW(
		SC_HANDLE hSCManager,
		SC_ENUM_TYPE InfoLevel,
		DWORD dwServiceType,
		DWORD dwServiceState,
		LPBYTE lpServices,
		DWORD cbBufSize,
		LPDWORD pcbBytesNeeded,
		LPDWORD lpServicesReturned,
		LPDWORD lpResumeHandle,
		LPCTSTR pszGroupName
	);


	Aqui lo importante es lpServices, que apunta al buffer donde se va a
almacenar la lista de servicios. Y tambien nos importa lpServicesReturned
que indica el numero de registros que se han creado en el buffer.
La estructura de datos que sera grabada al buffer de salida depende de las
funciones. Las funciones EnumServicesStatusA y EnumServicesGroupW devuelven
una estructura del tipo:

	typedef struct _ENUM_SERVICE_STATUS {
		LPTSTR lpServiceName;
		LPTSTR lpDisplayName;
		SERVICE_STATUS ServiceStatus;
	} ENUM_SERVICE_STATUS, *LPENUM_SERVICE_STATUS;

	typedef struct _SERVICE_STATUS {
		DWORD dwServiceType;
		DWORD dwCurrentState;
		DWORD dwControlsAccepted;
		DWORD dwWin32ExitCode;
		DWORD dwServiceSpecificExitCode;
		DWORD dwCheckPoint;
		DWORD dwWaitHint;
	} SERVICE_STATUS, *LPSERVICE_STATUS;

y EnumServicesStatusExA y EnumServicesStatusExW otras como estas:

	typedef struct _ENUM_SERVICE_STATUS_PROCESS {
		LPTSTR lpServiceName;
		LPTSTR lpDisplayName;
		SERVICE_STATUS_PROCESS ServiceStatusProcess;
	} ENUM_SERVICE_STATUS_PROCESS, *LPENUM_SERVICE_STATUS_PROCESS;

	typedef struct _SERVICE_STATUS_PROCESS {
		DWORD dwServiceType;
		DWORD dwCurrentState;
		DWORD dwControlsAccepted;
		DWORD dwWin32ExitCode;
		DWORD dwServiceSpecificExitCode;
		DWORD dwCheckPoint;
		DWORD dwWaitHint;
		DWORD dwProcessId;
		DWORD dwServiceFlags;
	} SERVICE_STATUS_PROCESS, *LPSERVICE_STATUS_PROCESS;


	Nada mas que nos interesa lpServiceName, que es el nombre del servicio
de sistema. Los registros tienen tamaño estatico, asique ocultar uno es facil,
solo hay que mover los registros siguientes hacia el que queremos ocultar,
sobreescribiendolo. Tenemos que diferenciar entre el tamaño de 
SERVICE_STATUS y el de SERVICE_STATUS_PROCESS.



=====[ 7. Expandiendo los ganchos ]=============================================

	Para conseguir el efecto deseado tenemos que meter los ganchos
a todos los procesos en ejecucion, y tambien a los procesos nuevos que 
se creen despues. Los procesos nuevos deberian ser enganchados antes
de que se ejecute su primera instruccion, de otra forma podria ver
los objetos que hemos ocultado en ese lapso de tiempo antes de 
engancharlo.
	

=====[ 7.1 Privilegios ]========================================================

	Debes saber que lo primero que necesitamos es, como minimo, privilegios
de administrador, para acceder a todos los procesos en ejecucion. La mejor
posibilidad es ejecutar nuestro proceso como servicio de sistema, que es
ejecutado como usuario SYSTEM en la maquina. Para instalar el servicio
tambien necesitamos privilegios especiales.	

	Tambien SeDebugPrivilege es muy util. Puede hacerse usando 
OpenProcessToken, LookupPrivilegeValue y AdjustTokenPrivileges.

	BOOL OpenProcessToken(
		HANDLE ProcessHandle,
		DWORD DesiredAccess,
		PHANDLE TokenHandle
	);

	BOOL LookupPrivilegeValue(
		LPCTSTR lpSystemName,
		LPCTSTR lpName,
		PLUID lpLuid
	);

	BOOL AdjustTokenPrivileges(
		HANDLE TokenHandle,
		BOOL DisableAllPrivileges,
		PTOKEN_PRIVILEGES NewState,
		DWORD BufferLength,
		PTOKEN_PRIVILEGES PreviousState,
		PDWORD ReturnLength
	);


	El codigo sera algo parecido a esto:

	#define SE_PRIVILEGE_ENABLED	0x0002
	#define TOKEN_QUERY		0x0008
	#define TOKEN_ADJUST_PRIVILEGES	0x0020

	HANDLE hToken;
	LUID DebugNameValue;
	TOKEN_PRIVILEGES Privileges;
	DWORD dwRet;

	OpenProcessToken(GetCurrentProcess(),
			 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,hToken);
	LookupPrivilegeValue(NULL,"SeDebugPrivilege",&DebugNameValue);
	Privileges.PrivilegeCount=1;
	Privileges.Privileges[0].Luid=DebugNameValue;
	Privileges.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
	AdjustTokenPrivileges(hToken,FALSE,&Privileges,sizeof(Privileges),
			      NULL,&dwRet);
	CloseHandle(hToken);


=====[ 7.2 Gancho global ]======================================================

	La enumeracion de procesos es hecha por los metodos ya mencionados
de la funcion NtQuerySystemInformation. Hay unos pocos procesos nativos
en el sistema, con los que usaremos el metodo de gancheo de reescribir las 
primeras instrucciones de la funcion. Haremos lo mismo para para todos los
procesos en ejecucion. Reservaremos una parte de memoria en el proceso
victima donde escribiremos nuestro nuevo codigo para las funciones que queramos 
enganchar. Entonces cambiaremos los cinco primeros bytes de estas funciones 
con la instruccion jmp. El salto redirigira el flujo de ejecucion a nuestro 
codigo. Asique el jmp sera ejecutado inmediatamente despues de llamar a la 
funcion. EL como salvar las instrucciones ya fue descrito en el capitulo
3.2.3. del documento "Hooking Windows API".
	Lo primero que tenemos que abrir es el proceso victima con 
NtOpenProcess y obtener su handle. Si no tenemos suficientes privilegios esto 
fallara.

	NTSTATUS NtOpenProcess(
		OUT PHANDLE ProcessHandle,
		IN ACCESS_MASK DesiredAccess,
		IN POBJECT_ATTRIBUTES ObjectAttributes,
		IN PCLIENT_ID ClientId OPTIONAL
	);

	ProcessHandle es un puntero a un handle donde se va a almacenar el
resultado. DesiredAccess debe ponerse al valor PROCESS_ALL_ACCESS. Pondremos
el PID del proceso victima a UniqueProcess, en la estructura ClientId, y
UniqueThread lo debemos poner a 0. El handle abierto siempre hay que
cerrarlo con NtClose.

	#define PROCESS_ALL_ACCESS 0x001F0FFF

	Ahora vamos a reservar la parte de memoria que necesitamos para nuestro
codigo. Para ello utilizamos NtAllocateVirtualMemory.

	NTSTATUS NtAllocateVirtualMemory(
		IN HANDLE ProcessHandle,
		IN OUT PVOID *BaseAddress,
		IN ULONG ZeroBits,
		IN OUT PULONG AllocationSize,
		IN ULONG AllocationType,
		IN ULONG Protect
	);

(***Nota del traductor: En la version original de este documento, holy_father
 		escribe el segundo argumento como IN OUT PVOID BaseAddress,
		sin embargo en la documentacion de la funcion aparece
		IN OUT PVOID *BaseAddress, donde el asterisco indica
		puntero doble, yo lo he puesto como aparece en la 
		documentacion)
		

	ProcessHandle es el obtenido con NtOpenProcess. BaseAddress es un
puntero a otro puntero al comienzo de la zona que queremos reservar. Aqui
se almacenara la direccion de la memoria reservada. El de entrada tambien
puede ser cero ( en ese caso la direccion sera elegida por el sistema).
AllocationSize es un puntero al numero de bytes que queremos reservar. Y de
nuevo tambien es usado como valor de salida para el numero de bytes
reservados. Es bueno poner AllocationType a MEM_TOP_DOWN junto con MEM_COMMIT
porque asi la memoria sera reservada en la posicion mas alta posible.

	#define MEM_COMMIT	0x00001000
	#define MEM_TOP_DOWN	0x00100000	


	Ahora ya podemos escribir nuestro codigo con NtWriteVirtualMemory.

	NTSTATUS NtWriteVirtualMemory(
		IN HANDLE ProcessHandle,
		IN PVOID BaseAddress,
		IN PVOID Buffer,
		IN ULONG BufferLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	BaseAddress sera la direccion devuelta por NtAllocateVirtualMemory. 
El buffer apunta a los bytes que queremos escribir, y BufferLength es el
numero de bytes que queremos escribir.

	Ahora tenemos que poner ganchos a funciones sueltas. La unica libreria
que es cargada independientemente por todos los procesos es ntdll.dll. Asi
pues tendremos que comprobar si la funcion que queremos ganchear es importada
o no desde ntdll.dll. Pero la mempria donde estaria esta funcion podria
estar reservada , asique reescribir bytes en su direccion podria provocar un
error en el proceso. Es por esto por lo que tenemos que comprobar si la 
libreria (donde esta la funcion que queremos ganchear) esta cargada en 
el proceso 
victima o no.

	Necesitamos obtener el PEB (Process Environment Block, Bloque 
de entorno de proceso) mediante NtQueryInformationProcess.


	NTSTATUS NtQueryInformationProcess(
		IN HANDLE ProcessHandle,
		IN PROCESSINFOCLASS ProcessInformationClass,
		OUT PVOID ProcessInformation,
		IN ULONG ProcessInformationLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	Pondremos ProcessInfromationClass a ProcessBasicInformation. Entonces
la estructura PROCESS_BASIC_INFORMATION seria devuelta en el buffer
ProcessInformation, cuyo tamaño es dado por ProcessInformationLength.


	#define ProcessBasicInformation 0

	typedef struct _PROCESS_BASIC_INFORMATION {
		NTSTATUS ExitStatus;
		PPEB PebBaseAddress;
		KAFFINITY AffinityMask;
		KPRIORITY BasePriority;
		ULONG UniqueProcessId;
		ULONG InheritedFromUniqueProcessId;
	} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

	PebBaseAddress es lo que estamos buscando. En PebBaseAddress+0x0C esta
la direccion PPEB_LDR_DATA. Esto se obtiene llamando a NtReadVirtualMemory.

	NTSTATUS NtReadVirtualMemory(
		IN HANDLE ProcessHandle,
		IN PVOID BaseAddress,
		OUT PVOID Buffer,
		IN ULONG BufferLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	Los parametros son igual que en NtWriteVirtualMemory. 
	En PPEB_LDR_DATA+0x1C esta la direccion de 
InInitializationOrderModuleList. Es la lista de librerias cargadas al proceso.
Nos interesa solo una parte de esta estructura.

	typedef struct _IN_INITIALIZATION_ORDER_MODULE_LIST {
		PVOID Next,
		PVOID Prev,
		DWORD ImageBase,
		DWORD ImageEntry,
		DWORD ImageSize,
		...
	);

	Next es un puntero al siguiente registro, Prev al anterior, el ultimo
registro apunta al primero. ImageBase es la direccion del modulo en la memoria,
ImageEntry es el EntryPoint del modulo, ImageSize el tamaño.
	
	Para todas las librerias que queremos enganchar tenemos que obtener
su ImageBase (por ejemplo usando GetModuleHandle or LoadLibrary). Esta
ImageBase la comparamos con ImagaBase de cada entrada en 
InInitializationOrderModuleList.
	Ahora estamos listos para enganchar. Como estamos enganchando procesos
en ejecucion cabe la posibilidad de que el codido que queremos reescribir
estuviera en ejecucion en ese momento. Esto causariaa un error, asique primero
detendremos todos los hilos del proceso victima. La lista de sus hilos se
obtiene via NtQuerySystemInformation con SystemProcessesAndThreadsInformation
como SystemInformationClass. El resultado de esta funcion esta descrito en
el capitulo 4. Pero tenemos que añadir la descripcion de la estructura
SYSTEM_THREADS donde esta la informacion sobre el hilo.

	typedef struct _SYSTEM_THREADS {
		LARGE_INTEGER KernelTime;
		LARGE_INTEGER UserTime;
		LARGE_INTEGER CreateTime;
		ULONG WaitTime;
		PVOID StartAddress;
		CLIENT_ID ClientId;
		KPRIORITY Priority;
		KPRIORITY BasePriority;
		ULONG ContextSwitchCount;
		THREAD_STATE State;
		KWAIT_REASON WaitReason;
	} SYSTEM_THREADS, *PSYSTEM_THREADS; 

	Para cada hilo tenemos que conseguir su handle usando NtOpenThread.
Usaremos ClientId para ello.

	NTSTATUS NtOpenThread(
		OUT PHANDLE ThreadHandle,
		IN ACCESS_MASK DesiredAccess,
		IN POBJECT_ATTRIBUTES ObjectAttributes,
		IN PCLIENT_ID ClientId
	);

El handle que queremos sera almacenado en ThreadHandle. Ponemos
DesiredAccess a THREAD_SUSPEND_RESUME.

	#define THREAD_SUSPEND_RESUME 2

	ThreadHandle will be used for calling NtSuspendThread.
	ThreadHandle sera usado al llamar a NtSuspendThread.

	NTSTATUS NtSuspendThread(
		IN HANDLE ThreadHandle,
		OUT PULONG PreviousSuspendCount OPTIONAL
	);


	El proceso suspendido esta listo para reescribir. Procederemos
como esta explicado en el capitulo 3.2.2 en Hooking Windows API". La
unica diferencia es que usaremos funciones para otros procesos.

	Despues de un gancho reviviremos todos los hilos del proceso
mediante NtResumeThread.

	NTSTATUS NtResumeThread(
		IN HANDLE ThreadHandle,
		OUT PULONG PreviousSuspendCount OPTIONAL
	);


=====[ 7.3 Nuevos procesos ]====================================================

	La infeccion de todos los procesos en ejecucion no afecta a los
procesos que se ejecuten despues. Podriamos obtener la lista de procesos
y al cabo de un rato volver a obtenerla e infectar aquellos que no estuvieran
en la primera lista pues serian los nuevos. Pero este metodo no parece muy
fiable. 
	Es mucho mejor enganchar la funcion que se llama para crear un nuevo
proceso. Con este metodo no perdemos ningun nuevo proceso. Podemos enganchar
NtCreateThread, pero no es la mejor forma. Engancharemos NtResumeThread que
tambien es llamado cada vez que un proceso se crea. Se le llama despues de
NtCreateThread.
	El unico problema con NtResumeThread es que no solo se llama cuando
comienza un proceso nuevo. Pero esto se puede superar con facilidad. 
NtQueryInformationThread nos dara informacion sobre que procesos posee
el hilo especifico. La ultima cosa que nos queda por hacer es comprobar si
el proceso ya esta enganchado o no. Esto lo hacemos leyendo el primer byte
de cualquier funcion que enganchemos.


	NTSTATUS NtQueryInformationThread(
		IN HANDLE ThreadHandle,
		IN THREADINFOCLASS ThreadInformationClass,
		OUT PVOID ThreadInformation,
		IN ULONG ThreadInformationLength, 
		OUT PULONG ReturnLength OPTIONAL
	);

	ThreadInformationClass es el tipo de informacion y en nuestro caso
deberia ser puesta a ThreadBasicInformation. ThreadInformation es el buffer
para el resultado cuyo tamaño es ThreadInformationLength bytes.


	#define ThreadBasicInformation 0

	Para el tipo ThreadBasicInformation se devuelve esta estructura:

	typedef struct _THREAD_BASIC_INFORMATION {
		NTSTATUS ExitStatus;
		PNT_TIB TebBaseAddress;
		CLIENT_ID ClientId;
		KAFFINITY AffinityMask;
		KPRIORITY Priority;
		KPRIORITY BasePriority;
	} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;

	En ClientId esta el PID del proceso que posee el hilo.

	Ahora tenemos que infectar el nuevo proceso. El problema es que
el nuevo proceso solo tiene en memoria ntdll.dll. Todos los otros modulos
son cargados inmediatamente despues de llamar NtResumeThread. Hay varias
formas de solventar este problema. Por ejemplo, podemos enganchar la API 
LdrInitializeThunk que es llamada durante el inicio del proceso.


	NTSTATUS LdrInitializeThunk(
		DWORD Unknown1,
		DWORD Unknown2,
		DWORD Unknown3
	);

	Al principio ejecutaremos el codigo original y entonces engancharemos
las funciones que queramos en este nuevo proceso. Pero es mejor que 
desenganchemos LdrInitializeThunk porque es llamado muchas veces despues y no 
queremos reenganchar todas las funciones otra vez. Aqui todo se hace antes de 
la ejecucion de la primera instruccion de la aplicacion enganchada. Por eso es
por lo que no tiene posibilidad de llamar ninguna funcion enganchada antes de 
que nosotros la enganchemos.

	Los ganchos en si son igual que cuando enganchamos procesos 
en ejecucion solo que aqui no nos preocupamos de hilos en ejecucion.


=====[ 7.4 DLL ]================================================================

	En cada proceso del sistema hay una copia de ntdll.dll. Esto significa
que podemos enganchar cualquier funcion de este modulo en el proceso desde
un primer momento. ¿Pero que pasa con funciones de otros modulos como
la kernel32.dll o advapi32.dll? Y hay varios procesos que solo tienen la 
ntdll.dll. Todos los demas modulos podrian ser cargados dinamicamente en mitad
del codigo despues del gancho. Lo que tenemos que hacer entonces es enganchar
LdrLoadDll que carga nuevos modulos.
	
	NTSTATUS LdrLoadDll( 
		PWSTR szcwPath,
		PDWORD pdwLdrErr,      
		PUNICODE_STRING pUniModuleName,
		PHINSTANCE pResultInstance
	);

	Lo mas importante para nosotros aqui es pUniModuleName que es el nombre
del modulo. pResultInstance contendra su direccion si la llamada tiene
exito.
	Llamaremos al LdrLoadDll original y entonces engancharemos todas las
funciones del modulo cargado.



=====[ 8. Memoria ]=============================================================

	Cuando enganchamos una funciones modificamos sus 5 primeros bytes.
Llamando a NtReadVirtualMemory cualquiera puede detectar si una funcion 
ha sufrido un gancho. Asique tenemos que enganchar NtReadVirtualMemory 
para prevenir la deteccion.

	NTSTATUS NtReadVirtualMemory(
		IN HANDLE ProcessHandle,
		IN PVOID BaseAddress,
		OUT PVOID Buffer,
		IN ULONG BufferLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	Hemos cambiado bytes en el comienzo de todas las funciones que
enganchamos y tenemos tambien memoria reservada para nuestro nuevo
codigo. Deberiamos comprobar si la llamada lee alguno de estos bytes. Si
tenemos nuestros bytes en el rango de BaseAddress a BaseAddress + BufferLength
tenemos que camviar algunos bytes en Buffer.
	Si alguien pide bytes de nuestra zona reservada deberiamos retornar
Buffer vacio y un error STATUS_PARTIAL_COPY. Este valor dice que no se copiaron
todos los bytes pedidos en el buffer. Tambien se usa cuando pedimos memoria
no reservada. ReturnLength deberia ser puesto a 0 en este caso.

	#define STATUS_PARTIAL_COPY 0x8000000D

Si alguien pide los primeros bytes de una funcion enganchada tenemos que
llamar al codigo original y copiar lo bytes originales al Buffer (los hemos
salvado papra llamadas originales).
	
	Ahora el proceso no es capaz de detectar un gancho leyendo su memoria.
Tambien si trazas el proceso enganchado con un debugger este tendria problemas.
Mostrara los bytes originales pero ejecutara nuestro codigo :D 

	Para conseguir un aocultacion perfecta tambien podemos enganchar
NtQueryVirtualMemory. Esta funcion es usada para obtener informacion
sobre memoria virtual. Podemos engancharla para prevenir detectar nuestra
memoria reservada.

	NTSTATUS NtQueryVirtualMemory(
		IN HANDLE ProcessHandle,
		IN PVOID BaseAddress,
		IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
		OUT PVOID MemoryInformation,
		IN ULONG MemoryInformationLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	MemoryInformationClass especifica la clase de datos devueltos.
Los primeros dos tipos nos interesan.

	#define MemoryBasicInformation 0
	#define MemoryWorkingSetList 1

	Para la clase MemoryBasicInformation se devuelve esta estructura:

	typedef struct _MEMORY_BASIC_INFORMATION {
		PVOID BaseAddress;
		PVOID AllocationBase;
		ULONG AllocationProtect;
		ULONG RegionSize;
		ULONG State;
		ULONG Protect;
		ULONG Type;
	} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;

	Cada seccion de memoria tiene su tamaño RegionSize y su tipo Type.
La memoria libre es del tipo MEM_FREE.

	#define MEM_FREE 0x10000

	Si una seccion antes de la nuestra tiene tipo MEM_FREE deberiamos 
añadir el tamaño de nuestra seccion a su RegionSize. Si la siguiente seccion es
tambien MEM_FREE deberiamos añadir al tamaño de la siguiente seccion otra
vez la RegionSize.
	Si una seccion antes de la nuestra tiene otro tipo devolvemos MEM_FREE
para nuestra seccion. Su tamaño es contado otra vez de acuerdo a la seccion
consiguiente.

	Para la clase MemoryWorkingSetList se devuelve la estructura:

	typedef struct _MEMORY_WORKING_SET_LIST { 
		ULONG NumberOfPages;
		ULONG WorkingSetList[1];
	} MEMORY_WORKING_SET_LIST, *PMEMORY_WORKING_SET_LIST;

	NumberOfPages es el numero de elementos de WorkingSetList. Este
numero deberia ser decrementado. Deberiamos encontrar nuestra seccion en 
WorkingSetList y mover los registros siguientes sobre los nuestros.
WorkingSetList es un array de DWORDs donde los 20 bits de mayor peso
especifican los 20 bits mas altos de la direccion de la seccion y los 12
de menor peso son flags.



=====[ 9. Handle ]==============================================================

	Llamar NtQuerySystemInformation con SystemHandleInformation como clase
nos devuelve un vector de todos los handles abiertos en la estructura
_SYSTEM_HANDLE_INFORMATION_EX.

	#define SystemHandleInformation 0x10

	typedef struct _SYSTEM_HANDLE_INFORMATION {
		ULONG ProcessId;
		UCHAR ObjectTypeNumber;
		UCHAR Flags;
		USHORT Handle;
		PVOID Object;
		ACCESS_MASK GrantedAccess;
	} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;

	typedef struct _SYSTEM_HANDLE_INFORMATION_EX {
		ULONG NumberOfHandles;
		SYSTEM_HANDLE_INFORMATION Information[1];
	} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX;

	ProcessId especifica el proceso que posee al handle. ObjectTypeNumber 
es el tipo del handle. NumberOfHandles es el numero de registros en el vector
Information. Esconder un elemento es trivial. Tenemos que remover todos los
registros siguientes uno por uno y decrementar NumberOfHandles. Borrar
todos los siguientes es necesario porque los handles en el array estan 
agrupados por ProcessId. Esto significa que todos los handles de un mismo
proceso estan juntos. Y para un proceso el numero de Handle es creciente.
	Ahora recuerda la estructura _SYSTEM_PROCESSES  que es devuelta
por esta funcion con clase SystemProcessesAndThreadsInformation. Aqui podemos
ver que cada proceso tiene informacion sobre su numero de handles en 
HandleCount.
Si queremos perfeccion deberiamos modificar HandleCount con cuantos handles 
ocultemos cuando se llame a esta funcion con clase 
SystemProcessesAndThreadsInformation. Pero esta correcion consumiria mucho 
tiempo. Normalmente hay muchos handles abriendose y cerrandose en espacios de 
tiempo muy cortos durante la ejecucion del sistema. Asique puede ocurrir 
facilmente que el numero de handles cambie entre dos llamadas de esta funcion
y no necesitemos cambiar HandleCount.


=====[ 9.1 Nombrando handles y obteniendo su tipo ]=============================

	Ocultar un handle es trivial pero encontrar que handle hay que esconder
es mucho mas complicado. Si tenemos por ejemplo un proceso oculto deberiamos
esconder todos sus handles y todos los handles con los que esta conectado.
Esconder handles de este proceso es tambien trivial. Solo estamos comparando
el processId del handle y el PID de nuestros procesos y cuando son iguales los
ocultamos. pero los handles de otros procesos tienen que ser nombrados antes
de que podamos comparar algo. El numero de handles en el sistema es usualmente
muy grande, asi que lo mejor que podemos hacer es comparar el tiipo de handle
primero anttes de intentar nombrarlo. Nombrar tipos llevar mucho tiempo para
handles que no nos interesarian.
	Nombrar un handle y tipo de handle se hace via NtQueryObject.

	NTSTATUS ZwQueryObject(
		IN HANDLE ObjectHandle,
		IN OBJECT_INFORMATION_CLASS ObjectInformationClass,
		OUT PVOID ObjectInformation,
		IN ULONG ObjectInformationLength,
		OUT PULONG ReturnLength OPTIONAL
	);

	ObjectHandle el handle del que queremos obtener informacion.
	ObjectInformationClass es el tipo de informacion que sera almacenada
en el buffer ObjectInformation que ocupa ObjectInformation bytes.
	Usamos la clase ObjectNameInformation y ObjectAllTypesInformation.
	We will use class ObjectNameInformation and ObjectAllTypesInformation.
ObjectNameInfromation llenara el buffer con la estructura 
OBJECT_NAME_INFORMATION y ObjectAllTypesInformation con la estructura 
OBJECT_ALL_TYPES_INFORMATION.

	#define ObjectNameInformation 1
	#define ObjectAllTypesInformation 3

	typedef struct _OBJECT_NAME_INFORMATION {
		UNICODE_STRING Name;
	} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;

	Name determina el nombre del handle.


	typedef struct _OBJECT_TYPE_INFORMATION {
		UNICODE_STRING Name;
		ULONG ObjectCount;
		ULONG HandleCount;
		ULONG Reserved1[4];
		ULONG PeakObjectCount;
		ULONG PeakHandleCount;
		ULONG Reserved2[4];
		ULONG InvalidAttributes;
		GENERIC_MAPPING GenericMapping;
		ULONG ValidAccess;
		UCHAR Unknown;
		BOOLEAN MaintainHandleDatabase;
		POOL_TYPE PoolType;
		ULONG PagedPoolUsage;
		ULONG NonPagedPoolUsage;
	} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;

	typedef struct _OBJECT_ALL_TYPES_INFORMATION {
		ULONG NumberOfTypes;
		OBJECT_TYPE_INFORMATION TypeInformation;
	} OBJECT_ALL_TYPES_INFORMATION, *POBJECT_ALL_TYPES_INFORMATION;


	Name determina el nombre del tipo del objeto que inmediatamente
sigue cada estructura OBJECT_TYPE_INFORMATION. La siguiente estructura
OBJECT_TYPE_INFORMATION sigue a este Name, comenzando en el limite de los
cuatro primeros bytes.

	ObjectTypeNumber de la estructura SYSTEM_HANDLE_INFORMATION es un
indice del array TypeInformation.

	Mas complicado es obtener el nombre del handle de otro proceso. Hay
dos posibilidades de como nombrarlas. La primera es copiar el handle via
NtDuplicateObject a nuestro proceso y entonces nombrarlas. Este metodo
fallara para unos tipos especificos de handles. Pero fallara solo para unos 
pocos asique podemos usar tranquilos este metodo.

	NtDuplicateObject(
		IN HANDLE SourceProcessHandle,
		IN HANDLE SourceHandle,
		IN HANDLE TargetProcessHandle,
		OUT PHANDLE TargetHandle OPTIONAL,
		IN ACCESS_MASK DesiredAccess,
		IN ULONG Attributes,
		IN ULONG Options
	);

	SourceProcessHandle es un handle del proceso que posee a SourceHandle
que es el handle que queremos copiar. TargetProcessHandle es el handle del
proceso a copiar. Este sera el handle de nuestro proceso en nuestro caso.
TargetHandle es el puntero al handle donde queremos guardar una copia del
handle original. DesiredAccess deberia ser puesto a PROCESS_QUERY_INFORMATION,
Attributes y Options a 0.

	El segundo metodo que funciona con cualquier handle es usar un driver
de sistema. El codigo fuente para esto esta disponible en el proyecto OpHandle
en http://rootkit.host.sk.



=====[ 10. Puertos ]============================================================

	La manera mas facil de enumerar los puertos abiertos es usar 
AllocateAndGetTcpTableFromStack y AllocateAndGetUdpTableFromStack, y/o 
AllocateAndGetTcpExTableFromStack y AllocateAndGetUdpExTableFromStack de
iphlpapi.dll. Las funciones Ex estan disponibles desde Windows XP.


	typedef struct _MIB_TCPROW {
		DWORD dwState;
		DWORD dwLocalAddr;
		DWORD dwLocalPort;
		DWORD dwRemoteAddr;
		DWORD dwRemotePort;
	} MIB_TCPROW, *PMIB_TCPROW;

	typedef struct _MIB_TCPTABLE {
		DWORD dwNumEntries;
		MIB_TCPROW table[ANY_SIZE];
	} MIB_TCPTABLE, *PMIB_TCPTABLE;

	typedef struct _MIB_UDPROW {
		DWORD dwLocalAddr;
		DWORD dwLocalPort;
	} MIB_UDPROW, *PMIB_UDPROW;

	typedef struct _MIB_UDPTABLE {
		DWORD dwNumEntries;
		MIB_UDPROW table[ANY_SIZE];
	} MIB_UDPTABLE, *PMIB_UDPTABLE;

	typedef struct _MIB_TCPROW_EX
	{
		DWORD dwState;
		DWORD dwLocalAddr;
		DWORD dwLocalPort;
		DWORD dwRemoteAddr;
		DWORD dwRemotePort;
		DWORD dwProcessId;
	} MIB_TCPROW_EX, *PMIB_TCPROW_EX;

	typedef struct _MIB_TCPTABLE_EX
	{
		DWORD dwNumEntries;
		MIB_TCPROW_EX table[ANY_SIZE];
	} MIB_TCPTABLE_EX, *PMIB_TCPTABLE_EX;

	typedef struct _MIB_UDPROW_EX
	{
		DWORD dwLocalAddr;
		DWORD dwLocalPort;
		DWORD dwProcessId;
	} MIB_UDPROW_EX, *PMIB_UDPROW_EX;

	typedef struct _MIB_UDPTABLE_EX
	{
		DWORD dwNumEntries;
		MIB_UDPROW_EX table[ANY_SIZE];
	} MIB_UDPTABLE_EX, *PMIB_UDPTABLE_EX;

	DWORD WINAPI AllocateAndGetTcpTableFromStack(
		OUT PMIB_TCPTABLE *pTcpTable,
		IN BOOL bOrder,
		IN HANDLE hAllocHeap,
		IN DWORD dwAllocFlags,
		IN DWORD dwProtocolVersion;
	);

	DWORD WINAPI AllocateAndGetUdpTableFromStack(
		OUT PMIB_UDPTABLE *pUdpTable,
		IN BOOL bOrder,
		IN HANDLE hAllocHeap,
		IN DWORD dwAllocFlags,
		IN DWORD dwProtocolVersion;
	);

	DWORD WINAPI AllocateAndGetTcpExTableFromStack(
		OUT PMIB_TCPTABLE_EX *pTcpTableEx,
		IN BOOL bOrder,
		IN HANDLE hAllocHeap,
		IN DWORD dwAllocFlags,
		IN DWORD dwProtocolVersion;
	);

	DWORD WINAPI AllocateAndGetUdpExTableFromStack(
		OUT PMIB_UDPTABLE_EX *pUdpTableEx,
		IN BOOL bOrder,
		IN HANDLE hAllocHeap,
		IN DWORD dwAllocFlags,
		IN DWORD dwProtocolVersion;
	);

	
	Hay otra forma de hacer esto. Cuando un programa crea un socket y
se pone a escuchar seguramente tiene un handle abierto para el y para el
puerto abierto. Podemos enumerar todos los handles abiertos en el sistema y
enviarles un buffer especial mediante NtDeviceIoControlFile para descubrir
si el handle es para un puerto abierto o no. Esta dara tambien informacion
sobre el puerto. Como hay muchos handles abiertos solo probaremos handles
cuyo tipo sea File y nombre sea \Device\Tcp o \Device\Udp. Los puertos
abiertos solo tienen handles de este tipo y nombre.

	Echando un vistazo al codigo de las funciones iphlpapi.dll que estan
ahi arriba descubrimos que esas funciones tambien llaman a 
NtDeviceIoControlFiley envian buffers especiales para obtener una lista de
todos los puertos abiertos en el sistema. Eso significa que la unica funcion
que necesitamos para esconder puertos es NtDeviceIoControlFile.


	NTSTATUS NtDeviceIoControlFile(
		IN HANDLE FileHandle
		IN HANDLE Event OPTIONAL,
		IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
		IN PVOID ApcContext OPTIONAL,
		OUT PIO_STATUS_BLOCK IoStatusBlock,
		IN ULONG IoControlCode,
		IN PVOID InputBuffer OPTIONAL,
		IN ULONG InputBufferLength,
		OUT PVOID OutputBuffer OPTIONAL,
		IN ULONG OutputBufferLength
	);	

	Los argumentos interesantes para nosotros son FileHandle que especifica
un handle o dispositivo con el que comunicar, IoStatusBlock que apunta a una
variable que recibe el estado de completacion final e informacion sobre la
operacion solicitada, IoControlCode que es un numero especificando el tipo del
dispositivo, metodo, acceso de fichero y una funcion. InputBuffer contiene 
datos de entrada que son InputBufferLength bytes y similarmente OutputBuffer
y OutputbufferLength.


=====[ 10.1 Netstat, OpPorts en WinXP, FPort en WinXP ]=========================

	Obtener una lista de todos los puertos abiertos es laa primera forma
usada por ejemplo por OpPorts y FPort en Windows Xp y tambien Netstat.
	Estos programas llaman a NtDeviceIoControlFile dos veces con 
IoControlCode 0x000120003. OutputBuffer es escrito despues de una segunda
llamada. El nombre de FileHandle es  siempre \Device\Tcp. InputBuffer difiere
para distintos tipos de llamada:

	1) Para obtener vector de MIB_TCPROW InputBuffer seria:

primera llamada:
0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 

segunda llamada:
0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 
0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 


	2) Para obtener un vector de MIB_UDPROW:

primera llamada:
0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 

segunda llamada:
0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 
0x01 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 


	3) Para obtener un vector de MIB_TCPROW_EX:

primera llamada:
0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 

segunda llamada:
0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 
0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 


	4) Para obtener un vector de MIB_UDPROW_EX:

primera llamada:
0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 
0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 

segunda llamada:
0x01 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00 0x00 
0x02 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 


	Puedes ver que los buffer son distintos en unos cuantos bytes solo.
Podemos claramente agrupar estos:

	Las llamadas en que estamos interesados tienen InputBuffer[1] 
puesto a 0x04 y principalmente InputBuffer[17] on 0x01. Solo despues de estos
datos de entrada se llenara el OutputBuffer con las tablas deseadas. Si 
queremos obtener info sobre puertos TCP ponemos InputBuffer[0] a 0x00, 
y para UDP a 0x01. Si queremos tablas extendidas de salida 
(MIB_TCPROW_EX o MIB_UDPROW_EX) usamos Inputbuffer[16] en la segunda llamada
puesta a 0x02.
	
	Si descubrimos la llamada con estos parametros podemos cambiar el 
buffer de salida. Para obtener numeros de filas en el buffer de salida
simplemente dividimos Information de IoStatusBlock por el tamaño de la fila.
Esconder una fila es facil ahora. Solo reescribelo con las filas siguientes
y borra la ultima. No olvides cambiar OutputBufferLength y IoStatusBlock.


=====[ 10.2 OpPorts en Win2k y NT4, FPort en Win2k ]============================

	Usamos NtDeviceIoControlFile con IoControlCode 0x00210012 para
determinar si el handle de tipo File y nombre \Device\Tcp o \Device\Udp
es el handle del puerto abierto.

	Asique lo primero comparamos IoControlCode y entonces el tipo y nombre
del hanlde. Si es todavia interesante entonces comparamos la longitud del
buffer de entrada que deberia ser igual a la longitud de la estructura
TDI_CONNECTION_IN. Esta longitud es 0x18. OutputBuffer es TDI_CONNECTION_OUT.


	typedef struct _TDI_CONNECTION_IN
	{
		ULONG UserDataLength,
		PVOID UserData,
		ULONG OptionsLength,
		PVOID Options,
		ULONG RemoteAddressLength,
		PVOID RemoteAddress
	} TDI_CONNECTION_IN, *PTDI_CONNECTION_IN;

	typedef struct _TDI_CONNECTION_OUT
	{
		ULONG State,
		ULONG Event,
		ULONG TransmittedTsdus,
		ULONG ReceivedTsdus,
		ULONG TransmissionErrors,
		ULONG ReceiveErrors,
		LARGE_INTEGER Throughput
		LARGE_INTEGER Delay,
		ULONG SendBufferSize,
		ULONG ReceiveBufferSize,
		ULONG Unreliable,
		ULONG Unknown1[5],
		USHORT Unknown2
	} TDI_CONNECTION_OUT, *PTDI_CONNECTION_OUT;


	La implementacion concreta de como determinar si el handle es
puerto abierto esta disponible en el codigo fuente de OpPorts en 
http://hxdef.czweb.org (http://rootkit.host.sk).
	Estamos interesados en esconder un puerto especifico ahora. Ya
comparamos InputBufferLength y IoControlCode. Ahora tenemos que comparar
RemoteAddressLength. Esta es siempre 3 o 4 para un puerto abierto. La ultima
cosa que tenemos que hcer es comparar ReceivedTsdus de OutputBuffer que 
contiene el puerto en formato network y nuestra lista de puertos que queremos 
esconder. La diferencia entre TCP y UDP esta en ell nombre del handle. Borrando 
OutputBuffer, cambiando IoStatusBlock y devolviendo el valor de 
STATUS_INVALID_ADDRESS esconderemos este puerto.


       
=====[ 11. Finalizando ]========================================================

	La implementacion las tecnicas aqui descritas estaran disponibles
con las fuentes del Hacker Defender 1.0.0 en la pagina http://hxdef.czweb.org
(http://rootkit.host.sk) o en http://www.rootkit.com.
	Es posible que añada mas información sobre invisibilidad en Windows NT
en el futuro. Nuevas versiones de este documento podrian contener mejoras de
las tecnicas comentadas. 
	Agradecimientos especiales a Ratter que me enseño todo lo necesario
para escribir este documento y escribir el Hacker Defender.
	Enviadme comentarios a 
 Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
  o al foro de 
http://hxdef.czweb.org (http://rootkit.host.sk.)

(Saludos del traductor: 
 holy_father-> he aprendido muchas cosas te lo agradezco :D
 salu2 a Ale(spiderxd en hotmail.com)
 muxos besos Elenikkita :*** te kero & TF !!! :)

===================================[ Fin ]======================================