jueves, 3 de julio de 2008

Articulo de Programacion #4 (Parte 2)

De modo usuario a modo kernel, SYSENTER

Vimos en la entrada anterior como OpenProcess llama a ntdll.NtOpenProcess con los parámetros arreglados para llamar a esta funcion, pero aun no hemos visto la verdadera funcionalidad de NtOpenProcess y es lo que vamos a analizar ahora. Hay algo que podemos notar, en la librería ntdll.dll podemos ver dos versiones de esta funcion NtOpenProcess y ZwOpenProcess, en ntdll.dll las dos funciones exportadas apuntan a la misma, ZwOpenProcess es solo un alias de NtOpenProcess, lo cual ocurre con todas las Apis nativas, teniendo sus versiones Zw y Nt, sin embargo en el kernel, exactamente en la imagen ntoskrnl.exe, asi no ocurre. Podemos ver entonces la funcion NtOpenProcess en la librería ntdll.dll:

ntdll!ZwOpenProcess:
7c91dd7b b87a000000 mov eax,7Ah
7c91dd80 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c91dd85 ff12 call dword ptr [edx]
7c91dd87 c21000 ret 10h

Podemos analizar el código anterior, como primeras instrucciones, vemos que mueve el valor 7Ah en eax, este valor viene siendo su índice en la KeServiceDescriptorTable en el kernel. Esta tabla contiene un arreglo de punteros, esta variable es llamada KiServiceTable, la cual es usada por el kernel para saber que funcion llamar en su transición a modo kernel. Despues vemos la siguiente instrucción, el cual mueve a edx la dirección de memoria del puntero que contiene la funcion SystemCallStub, el cual es llamado KiFastSystemCall contenido también en ntdll, el cual es la funcion que usa SYSENTER para hacer la transición a modo kernel, la cual podemos ver acontinuacion:

ntdll!KiFastSystemCall:
7c91eb8b 8bd4 mov edx,esp
7c91eb8d 0f34 sysenter
7c91eb8f 90 nop
7c91eb90 90 nop
7c91eb91 90 nop
7c91eb92 90 nop
7c91eb93 90 nop
7c91eb94 c3 ret

Podemos ver en la primera instrucción que ahora almacena en edx el valor de la pila actual, con esto tenemos lo siguiente:

EAX=ServiceDescriptorTableIndex, EDX=UserModeStack

Es decir, en eax esta el índice de la funcion que corresponde en KiServiceTable y en edx esta el stack de modo usuario que será copiado a un stack del kernel. Finalmente, la siguiente instrucción sysenter realiza la transición a modo kernel. Sin embargo esta transición conlleva a mas cosas aun. Cuando el cpu realiza la transición a modo kernel, necesita de una funcion en este modo que despache el pedido, el cual en el kernel es llamado KiFastCallEntry, y el cpu guarda esta dirección en los registros llamados “Model Specific Registers” (MSR). Uno de ellos del cual hablamos que contiene la dirección de KiFastCallEntry es el registro SYSENTER_EIP_MSR, sin embargo no es el único registro, también existen otros dos elementales, el SYSENTER_CS_MSR el cual contiene el selector de segmento de código, es decir, el selector que nos dice que KiFastCallEntry reside en el segmento del kernel, y el SYSENTER_ESP_MSR que nos dice en que dirección esta el stack de modo kernel que usara la transición. Antes de analizar esta funcion es importante que expliquemos que es la KeServiceDescriptorTable. La KeServiceDescriptorTable es un arreglo de cuatro estructuras del tipo SERVICE_DESCRIPTOR_TABLE:

typedef struct _SERVICE_DESCRIPTOR_TABLE {
PVOID *ServiceTable;
PULONG CounterTable;
ULONG TableSize;
PUCHAR ArgumentTable;
} SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;

Los cuales están declarados en el kernel de la siguiente forma:

SERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable[4];
SERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTableShadow[4];

Podemos ver que son dos tipos de estas tablas, las dos residen en ntoskrnl.exe, pero una de ellas no esta exportada, hablo de la KeServiceDescriptorTableShadow. Podemos ver que cada una de estas tablas tienen 4 campos, siendo la mas importante el arreglo de punteros llamado ServiceTable. ServiceTable tiene tantos elementos como TableSize tenga indicado el cual sirve de guía para verificar que KiFastCallEntry no se exceda en cuanto al índice del arreglo. ServiceTable es un arreglo de punteros que contienen las direcciones de memoria de cada uno de los Servicios NT del kernel, cuando analizamos la llamada a SYSENTER vimos que en eax almacenaba un numero, justamente este numero vendría siendo el índice de esta funcion en este arreglo. Sin embargo en el kernel hay declarados oficialmente dos tipos de ServiceTable, uno de ellos se llama KiServiceTable, este tiene todas las funciones de los servicios nt del kernel, por ejemplo NtWriteVirtualMemory, NtWriteFile, NtMapViewOfSection, etc…, hay otra cuyo nombre es W32pServiceTable y tiene las funciones del driver win32k.sys que vendría siendo el kernel de las funciones gui, por ejemplo, NtUserSendInput, NtUserCreateWindowEx, NtUserGetDc, etc… Sin embargo, W32pServiceTable no se encuentra dentro de ntoskrnl.exe sino en win32k.sys, esta tabla no esta accesible para threads que se ejecuten en el contexto del proceso de System, es decir, si se intenta acceder desde el DriverEntry de un driver, lo mas probable es que de una excepción de acceso de memoria invalido. KeServiceDescriptorTable solo usa su primera estructura, las otras tres están sin inicializar, en cambio KeServiceDescriptorTableShadow tiene dos inicializadas, las otras dos están sin inicializar. KeServiceDescriptorTable tiene en su única y primera estructura el campo de ServiceTable inicializado como la tabla KiServiceTable. El campo CounterTable no es utilizado, solo en versiones de debug es un contador que se incrementa cada vez que el servicio sea llamado. El campo TableSize contiene el numero de Servicios que KiServiceTable tiene, EAX no puede exceder ese numero tiene que ser siempre menor o simplemente KiFastCallEntry retornara sin ejecutar nada. Finalmente el campo ArgumentTable es como la KiServiceTable, pero es un arreglo de chars que contiene el numero de bytes que tienen de longitud todos los argumentos que son pasados a la funcion que se va a ejecutar de KiServiceTable. La KeServiceDescriptorTableShadow en su primera estructura es exactamente igual como la primera y única estructura de KeServiceDescriptorTable, pero su segunda estructura es diferente ya que contiene los servicios de win32k.sys, teniendo como ServiceTable al arreglo de punteros W32pServiceTable y claro esta, con sus correspondientes TableSize, CounterTable y ArgumentTable.

Precisamente estas tablas son la principal interface de las Apis de Windows con las Apis nativas del kernel, todos los procesos terminan usando las misma tablas, ya que la memoria del kernel es compartida por todos los procesos, asi pues, cualquier cambio que se haga a la misma afectara a todos, y eso es precisamente lo que hacen diversos softwares, desde antivirus, rootkits, antispywares, etc… Modifican tanto la KiServiceTable o la W32pServiceTable con sus propias rutinas que denominaremos hooks, interceptan los parámetros y ellos deciden si llamar después a la funcion original o simplemente bloquearla. Sin embargo estos métodos se verán en próximos artículos.

En este articulo se vio lo básico de la interfaz entre modo usuario y modo kernel. En el próximo articulo ya nos adentraremos en el kernel y analizaremos la funcion KiFastCallEntry viendo el proceso de llamar al Servicio nt a través del índice y el Stack pasado, hasta la próxima xD.

jueves, 26 de junio de 2008

Articulo de Programacion #4 (Parte 1)

Las apis nativas

Vimos en el articulo anterior como hacer un taskmanager, sin embargo, se pueden observar ciertas limitaciones, sobre todo una notable es que no recaba todos los procesos del sistema, la razón es que, al faltar del privilegio SE_DEBUG_PRIVILEGE, OpenProcess no puede abrir procesos fundamentales de sistema como winlogon o csrss, sin embargo, la funcion del psapi.dll EnumProcesses si incluye la ProcessId en su buffer, en el código del taskmanager no incluye las pids que no puedan ser referenciadas usando OpenProcess. Sin embargo, esto es porque EnumProcesses nos dio la información de una forma manejada y mas simplificada, nosotros podríamos obtener la información de los procesos de una forma directa y obtener una información mas amplia de los mismos. EnumProcesses y cualquier otra funcion de modo usuario que necesite recabar información de sistema tiene forzosamente que tener acceso a la memoria del kernel, se podría generalizar en que de los 4 GB de memoria virtual los 2 GB inferiores pertenecen a la memoria del modo usuario y los 2 GB superiores pertenecen a la memoria de modo kernel. Especificamente estos limites están especificados en variables de sistema para referencia, siendo una de ellas MmHighestUserAddress. Generalmente las Apis de Windows contenidas en las librerías kernel32.dll, user32.dll, gdi32.dll, etc… la mayoría de ellas su real funcionamiento esta en las funciones del kernel como ya se había mencionado anteriormente, por ejemplo, como vimos con la funcion OpenProcess, esta funcion prepara sus argumentos, por lo tanto analizemos esta funcion. El siguiente código fue sacado directamente de la librería kernel32.dll de Windows, con el desensamblador llamado KDLive de sysinternals.

kernel32!OpenProcess:
7c830941 8bff mov edi,edi
7c830943 55 push ebp
7c830944 8bec mov ebp,esp
7c830946 83ec20 sub esp,20h
7c830949 8b4510 mov eax,dword ptr [ebp+10h]
7c83094c 8945f8 mov dword ptr [ebp-8],eax
7c83094f 8b450c mov eax,dword ptr [ebp+0Ch]
7c830952 56 push esi
7c830953 33f6 xor esi,esi
7c830955 f7d8 neg eax
7c830957 1bc0 sbb eax,eax
7c830959 83e002 and eax,2
7c83095c 8945ec mov dword ptr [ebp-14h],eax
7c83095f 8d45f8 lea eax,[ebp-8]
7c830962 50 push eax
7c830963 8d45e0 lea eax,[ebp-20h]
7c830966 50 push eax
7c830967 ff7508 push dword ptr [ebp+8]
7c83096a 8d4510 lea eax,[ebp+10h]
7c83096d 50 push eax
7c83096e 8975fc mov dword ptr [ebp-4],esi
7c830971 c745e018000000 mov dword ptr [ebp-20h],18h
7c830978 8975e4 mov dword ptr [ebp-1Ch],esi
7c83097b 8975e8 mov dword ptr [ebp-18h],esi
7c83097e 8975f0 mov dword ptr [ebp-10h],esi
7c830981 8975f4 mov dword ptr [ebp-0Ch],esi
7c830984 ff150c11807c call dword ptr [kernel32!_imp__NtOpenProcess (7c8011
0c)]
7c83098a 3bc6 cmp eax,esi
7c83098c 5e pop esi
7c83098d 0f8cdf710000 jl kernel32!OpenProcess+0x53 (7c837b72)

kernel32!OpenProcess+0x4e:
7c830993 8b4510 mov eax,dword ptr [ebp+10h]
7c830996 c9 leave
7c830997 c20c00 ret 0Ch

Lo anterior vendría siendo la rutina OpenProcess que usamos en el MiniTaskmanager. Podemos analizar el código:

7c830944 8bec mov ebp,esp
7c830946 83ec20 sub esp,20h

Guarda el puntero de pila en el registro de base de pila (EBP), después modifica el registro de puntero de pila al restarle 32 bytes, esto lo hace con el fin de reservar 32 bytes de memoria para variables locales de la funcion. (La pila crece de abajo hacia arriba, es decir va creciendo hacia la dirección mas baja).

La funcion OpenProcess recibe 3 parametros, el tipo de acceso, el booleano que nos dice si se puede heredar el handle, y finalmente el process id. Al ser una funcion con convención stdcall, los parámetros se empujan del ultimo al primero, en ese orden. Por lo tanto, la pila justo en el momento al entrar la funcion esta de la siguiente forma:

ESP+0 ReturnAddress
ESP+4 dwDesiredAccess
ESP+8 bInheritHandle (el tipo BOOL es de 4 bytes)
ESP+C dwProcessId

Sin embargo, vimos que en los primeros bytes de la funcion se le resto 32 bytes al puntero de pila, además EBP fue empujado, asi pues el stack queda de la siguiente forma

ESP+0 VariablesLocales
ESP+20 EBP_Guardado
ESP+24 ReturnAddress
ESP+28 dwDesiredAccess
ESP+2C bInheritHandle
ESP+30 dwProcessId

Sin embargo, por algo la function almaceno antes de reservar los 32 bytes par alas variables el puntero en EBP, por lo tanto EBP apunta asi:
EBP-20 VariablesLocales
EBP+0 EBP_Guardado
EBP+4 ReturnAddress
EBP+8 dwDesiredAccess
EBP+C bInheritHandle
EBP+10 dwProcessId

Vemos que en consecuencia ejecuta las siguientes instrucciones:

7c830949 8b4510 mov eax,dword ptr [ebp+10h]
7c83094c 8945f8 mov dword ptr [ebp-8],eax
7c83094f 8b450c mov eax,dword ptr [ebp+0Ch]

Podemos deducir los siguientes movimientos con el stack analizado

[VariablesLocales+18h]=dwProcessId
mov eax, bInheritHandle

Despues analizamos las siguientes instrucciones:

7c830952 56 push esi
7c830953 33f6 xor esi,esi
7c830955 f7d8 neg eax
7c830957 1bc0 sbb eax,eax
7c830959 83e002 and eax,2

Analizamos la primera instrucción de este pedazo, se empuja esi, registramos como quedaría la pila por este movimiento:

ESP+0 ESI_Guardado
ESP+4 VariablesLocales
ESP+24 EBP_Guardado
ESP+28 ReturnAddress
ESP+2C dwDesiredAccess
ESP+30 bInheritHandle
ESP+34 dwProcessId

Despues continuamos con las siguientes instrucciones

xor esi, esi  esi=0

Tenemos que eax era igual a bInheritHandle:

neg eax
sbb eax, eax

Podemos analizar estas dos peculiares instrucciones es poner en -1 eax si eax tiene un valor distinto de 0, y 0 si tiene un valor de 0.

and eax, 2

Podemos ver de estas dos instrucciones que si eax vale -1, tiene todos sus bits en 1, asi que al aplicarle la mascara de 2, su valor será 2, es decir, si el valor de eax era distinto de cero, ahora su valor sea 2, en caso contrario será 0.

7c83095c 8945ec mov dword ptr [ebp-14h],eax
7c83095f 8d45f8 lea eax,[ebp-8]
7c830962 50 push eax
7c830963 8d45e0 lea eax,[ebp-20h]
7c830966 50 push eax
7c830967 ff7508 push dword ptr [ebp+8]
7c83096a 8d4510 lea eax,[ebp+10h]
7c83096d 50 push eax

Analizando el trozo de codigo anterior:

[VariablesLocales+C]=bInheritHandle
push &[VariablesLocales+18h]
push &[VariablesLocales+0]
push dwDesiredAccess
push &Variable

El Stack quedaria de la siguiente forma

ESP+4 &Variable
ESP+8 dwDesiredAccess
ESP+C &Estructura2
ESP+10 &Estructura1
ESP+14 ESI_Guardado
ESP+18 VariablesLocales
ESP+38 EBP_Guardado
ESP+3C ReturnAddress
ESP+40 dwDesiredAccess
ESP+44 bInheritHandle
ESP+48 dwProcessId

Continuamos con el analisis del código

7c83096e 8975fc mov dword ptr [ebp-4],esi
7c830971 c745e018000000 mov dword ptr [ebp-20h],18h
7c830978 8975e4 mov dword ptr [ebp-1Ch],esi
7c83097b 8975e8 mov dword ptr [ebp-18h],esi
7c83097e 8975f0 mov dword ptr [ebp-10h],esi
7c830981 8975f4 mov dword ptr [ebp-0Ch],esi
7c830984 ff150c11807c call dword ptr [kernel32!_imp__NtOpenProcess (7c8011
0c)]

Lo cual lo podemos traducir asi:

Anteriormente sabíamos que esi valia 0 entonces…

VariableLocal+1Ch=0
VariableLocal+0=18h
VariableLocal+4h=0
VariableLocal+8h=0
VariableLocal+10h=0
VariableLocal+14h=0

Tenemos pues que el stack contenía las variables locales, efectivamente NtOpenProcess requiere que sean empujados los 32 bytes que acabamos de manipular, asi que simplemente la funcion hace un call a esta misma, además de el puntero al manejador de salida del proceso, el prototipo de NtOpenProcess es asi:

NTSTATUS NtOpenProcess(PHANDLE ProcessHandle,
ACCESS_MASK AccessMask,
POBJECT_ATTRIBUTES ObjectAttributes
PCLIENT_ID ClientId);

Pudimos observar como OpenProcess prepara los argumentos para su llamada a la api nativa NtOpenProcess, podemos recrear el código analizado en ensamblador a C:
Pudimos observar que la funcion reservo 32 bytes, 20 de los cuales pertenecen a la estructura ObjectAttributes, 12 pertenecen a la estructura CLIENT_ID:
typedef struct _CLIENT_ID {
HANDLE UniqueProcess;
HANDLE UniqueThread;
} CLIENT_ID;

typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES;

Y analizando mas a fondo el codigo analizado en ensamblador anterior:
VariableLocal+1Ch=0 //ClientId.UniqueThread=0
VariableLocal+0=18h //ObjectAttributes.Length=18h
VariableLocal+4h=0 //ObjectAttributes.RootDirectory=0
VariableLocal+8h=0 //ObjectAttributes.ObjectName=NULL
VariableLocal+10h=0 //ObjectAttributes.SecurityDescriptor=NULL
VariableLocal+14h=0 //ObjectAttributes.SecurityQualityOfService=NULL

Y Recordando el trozo de codigo anterior:

[VariablesLocales+C]=bInheritHandle
push &[VariablesLocales+18h]
push &[VariablesLocales+0]
push dwDesiredAccess
push &Variable

VariablesLocales+C actualmente vendria siendo la estructura ObjectAttributes en su campo Attributes, que es 2 si bInheritHandle vale diferente de 0 y es cero si vale 0, es decir, si es verdadero se le pone como Attributes el valor OBJ_INHERIT y si es falso pues se le pone 0.

Uniendo todo el análisis el código en C del análisis anterior vendría siendo asi:

HANDLE OpenProcess(DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId){
HANDLE ProcessHandle;
CLIENT_ID ClientId;
OBJECT_ATTRIBUTES ObjectAttributes;

CliendId.UniqueThread=0;
ClientId.UniqueProcess=dwProcessId

ObjectAttributes.Length=(DWORD)0x18;
ObjectAttributes.RootDirectory=0;
ObjectAttributes.ObjectName=NULL;
ObjectAttributes.SecurityDescriptor=NULL;
ObjectAttributes.SecurityQualityOfService=NULL;

Sin embargo, en vez de inicializar la estructura ObjectAttributes de esa forma, podemos usar la macro InitializeObjectAttributes, el cual tiene este formato:

VOID
InitializeObjectAttributes(
OUT POBJECT_ATTRIBUTES InitializedAttributes,
IN PUNICODE_STRING ObjectName,
IN ULONG Attributes,
IN HANDLE RootDirectory,
IN PSECURITY_DESCRIPTOR SecurityDescriptor
);

Por lo tanto deberíamos llamar esta macro asi:

InitializeObjectAttributes(
&ObjectAttributes,
NULL,
(bInheritHandle ? OBJ_INHERIT : 0),
NULL,
NULL
);

Y finalmente llamamos a la funcion:

NtOpenProcess(
&ProcessHandle,
(ACCESS_MASK)dwDesiredAccess,
&ObjectAttributes,
&ClientId
);

Finalmente quedaría algo asi:

HANDLE OpenProcess(DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId){
DWORD ErrorCode;
NTSTATUS Status;
HANDLE ProcessHandle;
CLIENT_ID ClientId;
OBJECT_ATTRIBUTES ObjectAttributes;

CliendId.UniqueThread=0;
ClientId.UniqueProcess=dwProcessId

InitializeObjectAttributes(
&ObjectAttributes,
NULL,
(bInheritHandle ? OBJ_INHERIT : 0),
NULL,
NULL
);

Status= NtOpenProcess(
&ProcessHandle,
(ACCESS_MASK)dwDesiredAccess,
&ObjectAttributes,
&ClientId
);

If(Status==STATUS_SUCCESS)
return ProcessHandle;
else{
ErrorCode=RtlNtStatusToDosError(Status);
SetLastError(ErrorCode);
return NULL;
}
}

Ahora falta ver la llamada a la api nativa NtOpenProcess, esta funcion no se encuentra en kernel32.dll si no en ntdll.dll, que vendría siendo el puente de enlace de modo usuario a modo kernel para las Apis Win32, pero eso lo veremos en el próximo articulo xD.

miércoles, 21 de mayo de 2008

Articulo de Programacion #3 (Parte 2)

Recabando información del sistema 2

Ya vimos lo necesario para recabar procesos y su información de cada uno, como fue dicho en el articulo anterior, ahora vamos a poner todo en practica, crearemos un sencillo taskmanager dándonos información básica de los procesos y haremos operaciones sobre ellos. En este ejemplo usaremos el compilador Dev C++, y un programa en modo consola (el gui se vera en otro articulo).

Como siempre, en cualquier programa que hagamos donde usemos las Apis Win32. Tenemos que incluir la librería Windows.h asi como es stdio para los programas en modo consola de c. En este fichero de inclusión de encuentran las declaraciones y estructuras de estas Apis, las definiciones de tipos y las importaciones. Tambien requeriremos usar las funciones del psapi mencionado en el articulo anterior, el problema es que estas definiciones no se encuentran en Windows.h y estas Apis tampoco están incluidas en las librerías kernel32.dll o ntdll.dll, aunque las funciones mencionadas hacen uso de las funciones ntdll.dll, estas serian las Apis nativas que veremos mas adelante. Tenemos entonces que hacer un enlace a psapi.dll mediante su lib, psapi.lib, sin embargo, vamos a aplicar lo aprendido sobre dlls, entonces nosotros dinámicamente enlazaremos nuestro programa con estas funciones. Debemos asegurarnos sin embargo que la dll psapi.dll este en nuestro sistema, generalmente esta ya se encuentra instalada en los sistemas operativos desde Windows 2000.

Tenemos entonces las definiciones de las funciones, por ejemplo, la de EnumProcesses:

BOOL WINAPI EnumProcesses(

__out DWORD* pProcessIds,

__in DWORD cb,

__out DWORD* pBytesReturned

);

Lo que hay que hacer entonces es declararlo como un puntero a función, quedaría de la siguiente forma:


typedef BOOL (WINAPI* TEnumProcesses)(PDWORD, DWORD, PDWORD);


Es importante especificar la opcion de declaracion WINAPI, que es un typedef a la convencion stdcall, lo cual es importante declarar explicitamente ya que de lo contrario se usara otra convencion de llamado de funcion que ocasionara que los parámetros sean empujados a la pila de forma inversa y por lo tanto ocurrirá una excepción. De la misma forma haremos lo mismo para las demás funciones:


typedef DWORD (WINAPI* TGetProcessImageFileName)(HANDLE, LPSTR, DWORD);

typedef DWORD (WINAPI* TGetModuleFileNameEx)(HANDLE, HMODULE, LPSTR, DWORD);

typedef BOOL (WINAPI* TEnumProcessModules)(HANDLE, HMODULE*, PDWORD);

typedef DWORD (WINAPI* TGetModuleBaseName)(HANDLE, HMODULE, LPSTR, DWORD);

typedef BOOL (WINAPI* TEnumProcesses)(PDWORD, DWORD, PDWORD);


Ahora usaremos la api LoadLibrary como vimos en el primer articulo para cargar la dll en el proceso, en caso de que ya este cargada, esta api solo incrementara la referencia de cargado una unidad mas. Esta funcion nos retornara la dirección base de la dll cargada, que en este caso será la psapi.dll.

Usaremos entonces la técnica de punteros a funciones que ofrece C, esto es de las declaraciones que hicimos vamos a declarar estas variables:


TGetProcessImageFileName GetProcessImageFileName;

TGetModuleFileNameEx GetModuleFileNameEx;

TEnumProcessModules EnumProcessModules;

TGetModuleBaseName GetModuleBaseName;

TEnumProcesses EnumProcesses;


Ahora bien, tenemos que depositar en ellos una dirección de memoria, para saber a donde deben empujar los parámetros y hacer el call respectivo, en este usamos la api GetProcAddress y casteamos el tipo que retorna al tipo de puntero de nuestro contenedor, por ejemplo:


GetProcessImageFileName=(TGetProcessImageFileName)GetProcAddress(PspAddress, "GetProcessImageFileNameA");


Codigo:


bool Inicializa(){

HMODULE PspAddress;

PspAddress=LoadLibrary("psapi.dll");

if(!PspAddress){

printf("No se pudo cargar psapi.dll");

return false;

}

GetProcessImageFileName=(TGetProcessImageFileName)GetProcAddress(

PspAddress, "GetProcessImageFileNameA");

if(!GetProcessImageFileName){

printf("No se encontro funcion GetProcessImageFileName");

return false;

}

GetModuleFileNameEx=(TGetModuleFileNameEx)GetProcAddress(

PspAddress, "GetModuleFileNameExA");

if(!GetModuleFileNameEx){

printf("No se encontro funcion GetModuleFileNameEx");

return false;

}

EnumProcessModules=(TEnumProcessModules)GetProcAddress(

PspAddress, "EnumProcessModules");

if(!EnumProcessModules){

printf("No se encontro funcion EnumProcessModules");

return false;

}

GetModuleBaseName=(TGetModuleBaseName)GetProcAddress(

PspAddress, "GetModuleBaseNameA");

if(!GetModuleBaseName){

printf("No se encontro funcion EnumProcessModules");

return false;

}

}


Ahora si ya podremos usar estas funciones, comenzaremos primero listando los procesos en el sistema mediante la ya explicada técnica del EnumProcesses.


void MostrarInformacion(HANDLE hProcess, DWORD ProcessId){

LPSTR buffer;

buffer=(char*)malloc(MAX_PATH);

memset(buffer, 0, MAX_PATH);

printf("ProcessId: %d\n", ProcessId);

if(!GetModuleBaseName(hProcess, NULL, buffer, MAX_PATH)){

printf("Proceso: ?????\n");

}

else{

printf("Proceso: %s\n", buffer);

}

if(!GetModuleFileNameEx(hProcess, NULL, buffer, MAX_PATH)){

printf("Ruta: ??????\n");

}

else{

printf("Ruta: %s\n\n", buffer);

}

free(buffer);

}


bool ListaProcesos(){

HANDLE hProcess;

DWORD Returned, i;

PDWORD Lista;

Lista=(PDWORD)malloc(100*sizeof(DWORD));

memset(Lista, 0, 100*sizeof(DWORD));

if(!EnumProcesses(Lista, 100*sizeof(DWORD), &Returned)){

printf("Ocurrio un error al listar los procesos");

free(Lista);

return false;

}

for(i=0; i<(Returned/sizeof(DWORD)); i++){

hProcess=OpenProcess(PROCESS_ALL_ACCESS, FALSE, Lista[i]);

if(hProcess){

MostrarInformacion(hProcess, Lista[i]);

CloseHandle(hProcess);

}

}

free(Lista);

return true;

}


Como podemos ver en la funcion ListaProcesos, primero alojamos un buffer los suficientemente largo para poder obtener los Process Ids de todos los procesos del sistema, no sabemos cuantos pudiesen ser, incluso pueden llegar a ser mas de 100 (lo máximo que nuestro buffer alojado en esa funcion pudiese soportar), para poder determinar el numero de procesos en el sistema antes de guardarlos en el buffer hay que llamar una vez a la funcion y lo que nos retorne el parámetro de bytes returned usarlo en otra llamada a EnumProcesses, de esta forma ahora ya sabemos que tamaño debe tener el buffer para alojar los PIDs. Si la funcion falla retorna 0 y si no retorna un valor distinto a este lo cual lo checamos en nuestro if. Una vez que nuestro buffer ha sido llenado, el parámetro bytes returned nos da el numero de bytes escritos a este, para saber cuantos elementos son en nuestro arreglo tenemos que dividir el numero de bytes entre 4, asi sabemos cuantas casillas de nuestro arreglo tenemos. Despues solo queda usar cada Process Id recabado y abrirlo con OpenProcess y si podemos obtener referencia a el (existe), lo pasamos a nuestra subrutina MostrarInformacion. En esta subrutina aplicamos la teoría del articulo anterior para obtener el nombre y la ruta completa del proceso.

El ejemplo posee tres funciones sobre los procesos, terminarlos, congelarlos y descongelarlos. Para terminar un proceso, usamos la api de la dll kernel32, TerminateProcess. A esta api recibe dos parámetros, el primero es el handle del proceso a terminar y el segundo el código de la razón de terminación. Las otras dos funciones no se encuentran dentro de las Apis de la categoría win32, son nativas en la dll ntdll.dll. Para poder usarlas, usamos la misma técnica que usamos con las funciones del psapi.dll, las declaramos, obtenemos los punteros de las funciones y las usamos finalmente:


typedef DWORD (WINAPI *TNtSuspendProcess)(HANDLE);

typedef DWORD (WINAPI *TNtResumeProcess)(HANDLE);

ntdllAddress=GetModuleHandle("ntdll.dll");

NtSuspendProcess=(TNtSuspendProcess)GetProcAddress(ntdllAddress, "ZwSuspendProcess");

NtResumeProcess=(TNtResumeProcess)GetProcAddress(ntdllAddress, "NtResumeProcess");


Estas apis nativas reciben en su primer parametro una referencia del proceso a suspender o continuar, mas adelante se analizaran mas a fondo estas apis nativas.

Y con esto termina este articulo de programación, jaja espero que haya sido de tu agrado, hasta la próxima XD.


Descarga el código fuente completo:


sábado, 10 de mayo de 2008

Articulo de Programacion #3 (Parte 1)

Recabando información del sistema 1

Bueno este es el inicio de un nuevo articulo. Ahora veremos como listar los procesos del sistema operativo y obtener sus características para fines diversos, por ejemplo, acceder la memoria virtual del proceso, alojar memoria remotamente como vimos en el ejemplo de la inyección de procesos y hasta modificarlos. Existe la forma difícil y la forma fácil. La forma difícil es la que usa el sistema operativo por naturaleza, mediante el uso de las Apis nativas. La api nativa usada por el sistema operativo ZwQuerySystemInformation, esta api recorre una variable del sistema llamada PsActiveProcessHead que es un puntero a una estructura LIST_ENTRY:

typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;

Esta estructura viene siendo la cabeza de una lista enlazada que contiene a todos los objetos ejecutivos de los procesos (EPROCESS). De esta forma esta api guarda en un buffer la información mas elemental de los procesos en esa lista según el parámetro especificado (esta api se analizara con detenimiento en otro articulo). Existe sin embargo otras Apis que pueden recabarnos una lista de procesos sin tener que usar la api nativa ZwQuerySystemInformation, esta serie de funciones es lo que a partir de los Windows NT se encuentra disponible, PSAPI (Process API). Estas serie de funciones hacen uso de Apis nativas como ZwQueryInformationProcess o ZwQuerySystemInformation en una capa de abstracción que le da al proceso que la usa la información de forma directa ya desglosada.

Veremos una de las funciones mas importantes, EnumProcesses:

BOOL WINAPI EnumProcesses(
  __out  DWORD* pProcessIds,
  __in   DWORD cb,
  __out  DWORD* pBytesReturned
);

Esta función en su primer parámetro es un puntero a un arreglo donde se almacenaran todos los identificadores de procesos que estén ejecutándose o estén pausados (objetos EPROCESS existentes). El segundo parámetro es el tamaño de todo el arreglo pasado en el primer parámetro en bytes y finalmente el tercer parámetro es un puntero a una variable donde se recibirá el numero de bytes que se escribieron en el arreglo del primer parámetro.

Una vez teniendo el arreglo de process ids lleno necesitamos saber información de cada uno de ellos, en este caso el nombre del proceso, por lo cual, se usara la ya vista api OpenProcess para obtener una referencia a los objetos de los procesos el cual usaremos en una llamada a la api GetProcessImageFileName:

DWORD WINAPI GetProcessImageFileName(
  __in   HANDLE hProcess,
  __out  LPTSTR lpImageFileName,
  __in   DWORD nSize
);

El primer parámetro es una referencia obtenida con OpenProcess, el siguiente parámetro es un puntero a un arreglo de chars donde se almacenara el nombre del proceso y finalmente el tercer parámetro es el tamaño de este buffer.

Si tenemos dos procesos con el mismo nombre podemos distinguirlos todavía mas por su ruta completa lo cual lo lograremos con la api GetModuleFileNameEx:

DWORD WINAPI GetModuleFileNameEx(

__in HANDLE hProcess,

__in_opt HMODULE hModule,

__out LPTSTR lpFilename,

__in DWORD nSize

);

El primer parámetro es una referencia al proceso del cual vamos a obtener la ruta completa, el segundo parámetro se refiere al modulo ejecutable del proceso, en este caso pasaremos NULL haciendo referencia al modulo .exe. El siguiente parámetro es un buffer donde se almacenara la ruta completa al proceso y el parámetro final es el tamaño de este buffer.

Todavia podemos obtener mas información del proceso, por ejemplo, las dlls que tenga cargadas el proceso (modulos), para lo cual usamos la api EnumProcessModules:

BOOL WINAPI EnumProcessModules(
  __in   HANDLE hProcess,
  __out  HMODULE* lphModule,
  __in   DWORD cb,
  __out  LPDWORD lpcbNeeded
);

El primer parámetro es una referencia al proceso del cual vamos a obtener las direcciones base de las dlls, el siguiente es un puntero a un arreglo de variables de 4 bytes donde se almacenaran estas direcciones (mismo funcionamiento que la api EnumProcesses), las ultimas dos nos dicen el tamaño del arreglo y el numero de bytes almacenados.

Con esta api obtuvimos las direcciones base de las dlls, pero ahora requerimos saber mas información acerca de estas, para lo cual usamos otra api para saber por ejemplo, el nombre de la dll, esta api es GetModuleBaseName:

DWORD WINAPI GetModuleBaseName(
  __in      HANDLE hProcess,
  __in_opt  HMODULE hModule,
  __out     LPTSTR lpBaseName,
  __in      DWORD nSize
);

El primer parámetro es la referencia al proceso, la siguiente es la dirección base de la dll cuyo nombre queremos saber, el siguiente parámetro es un buffer donde se almacenara el nombre del modulo y el ultimo parámetro es el tamaño de este buffer. Si queremos saber el path completo a la dll usamos la ya explicada api GetModuleFileNameEx pasando como parámetro hModule la dirección base de la dll en vez de pasar NULL.

Con esto terminamos este breve articulo, en el siguiente se explicara como hacer una aplicación similar al taskmanager que nos permita terminar procesos, hasta la próxima!!!

Articulo de Programacion #2 (Parte 3)

Dll Injection (Parte 2)


En la pasada parte de este articulo de programacion #2 vimos algunas de las apis necesarias para realizar el cometido de una inyeccion de una dll en el contexto de un proceso remoto, ahora veremos ya la aplicacion de las mismas.

Una de las funciones vistas crucial para lograr ejecutar esta tecnica es la funcion CreateRemoteThread, esta funcion no es mas que la funcion final en la cadena de funciones para crear un hilo. CreateRemoteThread termina invocando la funcion NtCreateThread en la libreria ntdll.dll, que asu vez invoca el servicio Nt NtCreateThread en el kernel (ntoskrnl.exe) este ultimo se encarga de crear el objeto tipo Thread necesario para el manejo y funcionamiento del mismo, el objeto que es el cuerpo del hilo creado. Es asi como esta funcion recaba un manejador o handle al proceso destino, el cual obtenemos mediante OpenProcess, la cadena es algo como:


Modo usuario:CreateThread->CreateRemoteThread->NtCreateThread|Modo Kernel:KiServiceTable->NtCreateThread->PspCreateThread

Ahora bien, cuando un thread se crea en nuestro propio proceso, tenemos la certeza que el codigo ejecutado va a ejecutarse de la manera como lo programamos, ya que las variables y direcciones las tenemos localizadas en el contexto de memoria de nuestro proceso de la memoria virtual. Como con un thread o hilo remoto estaremos en un contexto diferente, las direcciones que querramos acceder no contendran la misma informacion que la del contexto de nuestro proceso, incluso podrian ser invalidas, recordemos el manejo de la memoria virtual, que permite que las direcciones apunten a diferentes paginas de la memoria fisica para cada proceso, diferentes mundos distintos de memoria, apuntando a una misma memoria ram.

La tecnica de inyeccion de una dll consiste en obligar a un proceso a cargar una dll, como bien aprendimos, para cargar una dll necesitamos usar la funcion LoadLibrary, pero en este caso haremos que el proceso remoto lo use sin que lo tenga en sus planes de hacerlo =), para cumplir esto, necesitamos crear un thread alterno en su contexto, ya con esto concluimos que necesitamos de crear un thread en el proceso donde queremos que cargue la dll usando CreateRemoteThread y hacer que ejecute la funcion LoadLibraryA.


HMODULE WINAPI LoadLibrary(

LPCTSTR lpFileName

);


Esta funcion requiere un parametro el cual es la ruta al archivo de la dll que va a ser cargada, esa ruta debe estar escrita en alguna direccion de memoria del proceso remoto, no es lo mismo tener la cadena de caracteres en nuestro proceso, y usar esa direccion en el proceso remoto, la direccion virtual no apunta a la misma pagina de memoria fisica, por lo cual necesitamos acceder al otro proceso escribir los datos que vamos a usar.

Programando en lenguaje c o cualquier otro lenguaje, hemos visto la necesidad de alojar memoria dinamicamente, segun las necesidades de nuestro programa y no tener que estar haciendo uso de muchas variables declaradas o arreglos, asi bien, se haria con un malloc usando la libreria estandar, o hasta usando la funcion new en c++; estados dos funciones al final hacen uso de la apis de windows, la cual invoca a VirtualAlloc una api para reserver o alojar nueva memoria virtual con respecto a una nueva pagina de memoria fisica tomada del directorio de paginas de la misma. En este caso necesitamos tener un espacio de memoria en el proceso remoto, para ello no podriamos usar malloc, new o VirtualAlloc, para ello windows nos ofrece otra api llamada VirtualAllocEx, esta nos permite alojar memoria en el contexto de un proceso que no sea el nuestro, es la funcion que es invocada por VirtualAlloc usando como handle NtCurrentProcess ((HANDLE)0xFFFFFFFF). Veamos la definicion de VirtualAllocEx:


LPVOID VirtualAllocEx(

HANDLE hProcess,

LPVOID lpAddress,

SIZE_T dwSize,

DWORD flAllocationType,

DWORD flProtect

);


Veamos los parametros de esta funcion, como ya pudimos ver en la entrada anterior, esta funcion requiere un handle o manejador al proceso donde se va alojar el espacio, este se obtiene usando OpenProcess con el ProcessId del proceso remoto, lpAddress: este parametro se indica la direccion donde queremos que se aloje la memoria, si especificamos un valor y ese espacio ya esta reservado o alojado la funcion VirtualAllocEx fallara, por lo cual, en este parametro debemos pasar 0 o NULL, de esta manera el sistema operativo usara la direccion de memoria que mas convenga y donde encuentre espacio, el siguiente parametro, dwSize especifica la cantidad de bytes que se necesitan alojar, estos se redondean al tamaño de pagina mas proximo (generalmente 4096 bytes o 4 kb), el siguiente parametro se especifica el tipo de alojamiento ya sea para reservar o para alojar, cuando es una reservacion, la memoria virtual no es accesible pero tampoco puede ser reservado otra vez (MEM_RESERVE), cuando es alojado (MEM_COMMIT) la memoria virtual es referenciada con una pagina de la memoria fisica y a partir de entonces se vuelve valida, cabe decir que una direccion de memoria no puede ser alojado sin que haya sido reservada primero, es por ello que en esta funcion por lo general se usan los dos flags combinados (MEM_RESERVE|MEM_COMMIT), el ultimo parametro es el tipo de proteccion que va a tener la pagina alojada, existen diferentes tipos de protecciones, entre las mas importantes se encuentran PAGE_READ, PAGE_WRITE, PAGE_READWRITE, PAGE_NOACCESS, PAGE_EXECUTE, PAGE_EXECUTE_READWRITE, despues se explicaran cada uno de ellos.


Ahora ya explicada esa funcion necesitaremos de poder escribir en la memoria del proceso remoto, esto lo haremos con una funcion para depuradores de la api Win32 llamado WriteProcessMemory, veamos su definicion:


BOOL WriteProcessMemory(

HANDLE hProcess,

LPVOID lpBaseAddress,

LPCVOID lpBuffer,

SIZE_T nSize,

SIZE_T* lpNumberOfBytesWritten

);


El primer parametro es un manejador al proceso remoto, el segundo nos indica la direccion virtual en el CONTEXTO del proceso remoto donde vamos a escribir los datos contenidos en el tercer parametro (lpBuffer), este tercer parametro es un puntero dentro del contexto de nuestro PROPIO proceso, es decir, puede ser una variable o buffer alojado dinamicamente con malloc o VirtualAlloc dentro de nuestro proceso, este buffer sera copiado exactamente igual en la direccion especificada en el parametro anterior, y el numero de bytes a copiar seran los que sean especificados en el cuarto parametro (nSize), el ultimo parametro es opcional y podemos pasar NULL o 0, aunque si queremos saber cuantos bytes se copiaron, podemos pasar la direccion de memoria de una variable la cual sera llenada por esta funcion con el numero de bytes copiados.

Ahora si, con todo esta teoria ya podemos empezar aplicar la tecnica =), lo que haremos es inyectar una dll llamada hack.dll en la unidad C:\ de nuestro disco en el proceso del buscaminas:


char DllPath[MAX_PATH]="C:\\hack.dll";
HWND Buscaminas;
DWORD BuscaminasPID;
HANDLE BuscaminasHandle;
HMODULE hKernel;
PVOID LoadLibraryAddress, RemoteVirtualAddress;
BOOL bSuccess;
HANDLE hRemoteThread;

/*Obtenemos la direccion base de la libreria kernel32 donde reside LoadLibraryA*/
hKernel=GetModuleHandle("kernel32.dll");

/*Buscamos en la libreria la funcion exportada*/
LoadLibraryAddress=(PVOID)GetProcAddress(hKernel, "LoadLibraryA");


/*Nota: Aqui no nos preocupamos por la memoria virtual del contexto del otro proceso con respecto a las funciones que acabos de recabar, ya que todas son proyectadas en la misma direccion de memoria en todos los procesos =)*/

/*Intentamos encontrar el handle a la ventana del buscaminas*/

Buscaminas=FindWindow(NULL, "Buscaminas");


if(!Buscaminas)
return; //error

/*Recabamos el processId del proceso que posee la ventana*/
GetWindowThreadProcessId(Buscaminas, &BuscaminasPID);

/*Abrimos una referencia al proceso con el process id obtenido*/
BuscaminasHandle=OpenProcess(PROCESS_ALL_ACCESS, FALSE, BuscaminasPID);

/*Alojamos memoria en el proceso remoto =)*/
RemoteVirtualAddress=VirtualAllocEx(BuscaminasHandle, NULL, strlen(DllPath), MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);


if(!RemoteVirtualAddress)
return; //error

/*Escribimos en el espacio remoto que acabamos de alojar el string al camino de la dll*/
bSuccess=WriteProcessMemory(BuscaminasHandle, RemoteVirtualAddress, DllPath, strlen(DllPath), NULL);


if(!bSuccess)
return; //error

/*Creamos el hilo remoto y pasamos de parametro la direccion de memoria donde se encuentra nuestra cadena de caracteres que tiene la ruta de nuestro ejecutable =D*/
hRemoteThread=CreateRemoteThread(BuscaminasHandle, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryAddress, (LPVOID)RemoteVirtualAddress, 0);


if(!hRemoteTHread)
return; //error

/*Esperamos a que el thread remoto termine de ejecutarse, este paso es opcional*/

WaitForSingleObject(hRemoteThread, INFINITE);


Y con esto terminamos este segundo articulo de programacion =D, espero que te haya gustado, jajaja, proximamente hare el tercero donde explicare una tecnica fundamental en la administracion del sistema operativo, recabar los procesos en el sistema, hasta la proxima xD.

Articulo de Programacion #2 (Parte 2)

Dll Injection (Parte 1)


Ya vimos lo que es un proceso y un hilo o thread en la parte anterior, sabiendo como son los procesos, la unica manera de poder ejecutar una funcion en su contexto seria creando un hilo dentro de el, la pregunta seria como hacerlo, ya que normalmente un proceso solo puede crear hilos dentro de si mismo, Microsoft nos dio un regalo para superar esa adversidad bajo usermode, y nos brinda la funcion llamada CreateRemoteThread:


HANDLE WINAPI CreateRemoteThread(

HANDLE hProcess,

LPSECURITY_ATTRIBUTES lpThreadAttributes,

SIZE_T dwStackSize,

LPTHREAD_START_ROUTINE lpStartAddress,

LPVOID lpParameter,

DWORD dwCreationFlags,

LPDWORD lpThreadId

);


Analicemos la funcion, el primer parametro es un handle representando el proceso en el que queremos crear el thread, lpThreadAttributes son los atributos de seguridad del thread a crear, generalmente este parametro no lo usamos, dWStackSize es el tamaño que tendra la pila del thread creado, lpStartAddress seria el parametro mas importante, especifica la direccion donde empieza el thread creado, lpParameter es un puntero a una variable que le podemos pasar la funcion del thread, ojo, como estamos en contexto de otro proceso, ese parametro lo tenemos que alojar en el proceso remoto (mas adelante se explicara), dwCreationFlags significa la manera de como se crea el nuevo Thread, se puede dejar en tres estados, los mas importantes, son en estado Pausado o Resumido, el ultimo parametro es un puntero que recibira el ThreadId del nuevo thread creado.

Primeramente para poder usar la funcion, necesitamos sacar el handle del proceso en el que queremos crear el thread, como se explico, un proceso es un objeto ejecutivo en el kernel de windows, un handle es una representacion abstracta de ese objeto que hace referencia al mismo. Para poder obtener el handle, primero necesitamos obtener el ProcessId del proceso, el processid es una manera de simplificar el significado del proceso, y es que realmente es un handle que hace referencia a su localizacion en la PspCidTable, una tabla de handles que mantiene un registro de todos los procesos creados. Una de las tantas maneras de obtener la pid es localizando el nombre de la ventana del proceso, por ejemplo digamos que el buscamina muestre un cuadro de dialogo que pregunte si deseas cerrar la aplicacion o no, esto de manera inmediata y sin permiso previo del programa, primero necesitamos localizarlo, esto se haria usando la funcion FindWindow:


HWND FindWindow(
LPCTSTR lpClassName,

LPCTSTR lpWindowName

);


El primer parametro de esta funcion lo pasamos para especificar el nombre de la clase de la ventana a encontrar, el segundo para especificar solamente el nombre de la ventana (es la que usariamos mas habitualmente), se pueden pasar ambos parametros tambien de ser necesario. Si la funcion es un exito, regresa el HWND de la ventana encontrada, un HWND es tambien un handle que hace referencia a la ventana requerida. Ahora una vez que la ventana ha sido encontrada, necesitamos obtener el process Id de quien la ventana pertenece, eso lo hacemos con la funcion GetWindowThreadProcessId:


DWORD GetWindowThreadProcessId(
HWND hWnd,

LPDWORD lpdwProcessId

);


El primer parametro de esta funcion es el HWND de la ventana de la que queremos saber el process id del proceso a quien pertenece, el segundo parametro es un puntero que recibe el processid de la ventana requerida, la funcion retorna el ThreadId del HWND de la ventana pasada a esta funcion. Ahora que ya tenemos el processid, ya podemos obtener el handle a este, el cual obtendremos con la funcion OpenProcess:


HANDLE WINAPI OpenProcess(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

DWORD dwProcessId

);


El primer parametro de esta funcion es el acceso que tendra el handle referenciado, por lo general siempre se usa la constante PROCESS_ALL_ACCESS. El segundo parametro es para indicar si el handle es heredado de un proceso padre, por lo general este parametro es puesto en falso, por ultimo el parametro mas importante, dwProcessId, aqui se pasa el processid del proceso a referenciar. Como resultado la funcion retorna la referencia al objeto del proceso, es decir el handle.

Y ahora si, tenemos lo mas importante para crear un Thread en el proceso, ahora es cuestion de poner todo esto en orden y ponerlo a la practica, pero eso lo veremos en la tercera parte de este articulo xD.