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.

1 comentario:

Anónimo dijo...

no pues quien sabe wey, nadamas puros locos saben eso...bueno
chido el blog!!!
fire!!!!!!!
pero se mas que tu comoquiera!!
jajajaja



El Meca!!