Leer un PE y reconstruir llamadas a la API de Windows a partir de sus argumentos.
Notas de la práctica
El análisis estático de un PE puede hacerse desde Linux. La ejecución y la depuración de las llamadas de Windows requieren un entorno Windows preparado para la práctica.
Edición parcial: diferencias con el original
Se excluye la implementación que recorre una carpeta y transforma los archivos en el mismo destino. Se conservan las operaciones ordinarias de archivos y el análisis de la transformación de buffers.
Trabaja sobre el mismo ejemplo
Selecciona este ejemplo en el companion. El número del comando corresponde al identificador original del laboratorio.
./coursectl chat 14 --example helloEstoy siguiendo «Windows: API y operaciones de archivos» del curso de Lobera. Leer un PE y reconstruir llamadas a la API de Windows a partir de sus argumentos. Dame primero una pista. Después contrasta la explicación con las instrucciones y guarda las evidencias que hayamos comprobado.
El agente y la consola comparten el estado de radare2. Preparación y funcionamiento.
La API de Windows proporciona funciones para manejar ventanas, archivos y otros objetos del sistema. Para reconocer esas operaciones en un PE seguiremos sus importaciones, los argumentos de cada llamada y sus valores de retorno.
WindowsAPI HelloWorld
El primer programa siempre es el hello world, y eso es lo que vamos a hacer ahora.
La Windows API es lo que usamos para interactuar con el sistema operativo Windows desde el espacio de usuario. Según Wikipedia:
"La Windows API, informalmente WinAPI, es el conjunto principal de interfaces de programación de aplicaciones (APIs) de Microsoft disponibles en los sistemas operativos Microsoft Windows. El nombre Windows API hace referencia colectiva a varias implementaciones de plataforma distintas, que a menudo se conocen por sus propios nombres (por ejemplo, Win32 API); véase la sección de versiones. Casi todos los programas de Windows interactúan con la Windows API. En la línea de sistemas operativos Windows NT, un número reducido de programas (como los que se inician en las primeras fases del arranque de Windows) utilizan la API nativa."
Una llamada de la API de Windows puede realizar trabajo en espacio de usuario y apoyarse en servicios del sistema. Conviene distinguir la función importada, sus llamadas internas y la transición al kernel.
En Linux, nombres como write y open suelen corresponder a wrappers de la biblioteca C. La instrucción que entra en el kernel se encuentra en esa implementación o en código que invoque el servicio directamente.
Vamos al código:
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
int main()
{
int msgboxID = MessageBox(NULL, "Hello dear user", "Artikblue's magic msgbox", MB_ICONWARNING | MB_CANCELTRYCONTINUE);
switch (msgboxID)
{
case IDCANCEL:
printf("CANCELING");
Beep( 750, 300 );
break;
case IDTRYAGAIN:
printf("Shall we try again?");
Beep( 250, 100 );
break;
case IDCONTINUE:
printf("The show must go on");
Beep( 950, 500 );
break;
}
return 0;
}
El siguiente código usa la Windows API para mostrar un messagebox, un pequeño diálogo que requiere la interacción del usuario. Según la acción del usuario, el programa ejecutará una u otra cosa, entre ellas emitir un simple «beep» por el audio del sistema (los parámetros corresponden a la frecuencia). Lo importante aquí es que MessageBox y Beep hacen uso de las funciones del sistema operativo; esas funciones no están relacionadas con el lenguaje concreto, ni con cálculos que queramos realizar, ni con estructuras de datos o algoritmos: son ACCIONES que queremos que el SISTEMA WINDOWS realice por nosotros. Usamos la API para gestionar acciones del sistema operativo como generar una ventana o emitir un sonido. Gran parte del malware de Windows hace uso de la Windows API porque el malware trabaja con cosas como el sistema de ficheros, la memoria del sistema, el registro, dispositivos o la red.
Tener un buen conocimiento de la Windows API facilita el análisis de malware.
La información relativa a esta llamada a MessageBox se puede encontrar aquí: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox
Primero listemos las funciones detectadas con afl:
[0x77953840]> afl
0x00401000 1 1 sym.__mingw_invalidParameterHandler
0x00401010 14 273 -> 266 sym.pre_c_init
0x00401130 1 73 sym.pre_cpp_init
0x00401180 44 850 -> 809 sym.__tmainCRTStartup
0x004014e0 1 34 entry0
0x00401510 1 25 sym.atexit
0x00401530 1 12 sym.__gcc_register_frame
0x00401540 1 1 sym.__gcc_deregister_frame
0x00401550 7 177 sym.main
0x00401610 3 53 sym.__do_global_dtors
0x00401650 9 102 -> 99 sym.__do_global_ctors
0x004016c0 3 31 -> 26 sym.__main
0x004016e0 1 7 sym.my_lconv_init
0x004016f0 1 3 sym._setargv
0x00401700 6 214 -> 206 sym.__security_init_cookie
0x004017e0 4 248 sym.__report_gsfailure
0x004018e0 4 47 -> 38 sym.__dyn_tls_dtor
0x00401910 11 115 -> 113 sym.__dyn_tls_init
0x00401990 1 3 sym.__tlregdtor
0x004019a0 3 65 sym.__mingw_raise_matherr
0x004019f0 1 12 -> 18 sym.__mingw_setusermatherr
0x00401a00 2 49 sym._matherr
0x00401a38 1 96 loc.00401a38
0x00401b00 1 3 sym._fpreset
0x00401b10 1 4 sym._decode_pointer
0x00401b20 1 4 sym._encode_pointer
0x00401b30 26 464 sym.__write_memory.part.0
0x00401d00 66 1120 -> 1076 sym._pei386_runtime_relocator
0x00402160 12 236 -> 232 sym.__mingw_init_ehandler
0x00402250 36 487 -> 458 sym._gnu_exception_handler
0x00402440 7 106 sym.__mingwthr_run_key_dtors.part.0
0x004024b0 5 127 sym.___w64_mingwthr_add_key_dtor
0x00402530 13 160 -> 141 sym.___w64_mingwthr_remove_key_dtor
0x004025d0 17 218 -> 205 sym.__mingw_TLScallback
0x004026b0 3 30 sym._ValidateImageBase.part.0
0x004026d0 3 18 -> 12 sym._ValidateImageBase
0x004026f0 7 68 sym._FindPESection
0x00402740 9 141 -> 139 sym._FindPESectionByName
0x004027d0 9 116 sym.__mingw_GetSectionForAddress
0x00402850 4 62 -> 59 sym.__mingw_GetSectionCount
0x00402890 10 111 sym._FindPESectionExec
0x00402900 3 55 -> 49 sym._GetPEImageBase
0x00402940 10 145 -> 142 sym._IsNonwritableInCurrentImage
0x004029e0 16 166 -> 162 sym.__mingw_enum_import_library_names
0x00402a90 3 50 fcn.00402a90
0x00402ad0 1 6 sym.vfprintf
0x00402ad8 1 6 sym.strncmp
0x00402ae0 1 6 sym.strlen
0x00402ae8 1 6 sym.signal
0x00402af0 1 6 sym.printf
0x00402af8 1 6 sym.memcpy
0x00402b00 1 6 sym.malloc
0x00402b08 1 6 sym.fwrite
0x00402b10 1 6 sym.free
0x00402b18 1 6 sym.fprintf
0x00402b20 1 6 sym.exit
0x00402b28 1 6 sym.calloc
0x00402b30 1 6 sym.abort
0x00402b38 1 6 sym._onexit
0x00402b40 1 6 sym._initterm
0x00402b48 1 6 sym._cexit
0x00402b50 1 6 sym._amsg_exit
0x00402b58 1 6 sym.__setusermatherr
0x00402b60 1 6 sym.__set_app_type
0x00402b70 1 6 sym.__getmainargs
0x00402b80 1 31 sym.__acrt_iob_func
0x00402ba0 1 8 sym.mingw_get_invalid_parameter_handler
0x00402bb0 1 11 sym.mingw_set_invalid_parameter_handler
0x00402bc0 1 11 sym.__p__acmdln
0x00402bd0 1 11 sym.__p__fmode
0x00402be0 1 6 sym.__iob_func
0x00402cc0 1 117 sym.__report_error
[0x77953840]>
Observa la primera diferencia respecto al tutorial anterior de Linux. Como puedes ver, aparecen MUCHAS funciones, casi todas ellas no usadas directamente, relacionadas con la Windows API.
El listado incluye funciones del ejecutable, del runtime y de las bibliotecas reconocidas. Las referencias y llamadas permiten determinar cuáles intervienen realmente en la ruta de main.
Como es habitual en estos tutoriales, el programa se define dentro de la función main:
[0x004015a5]> pdf
/ (fcn) sym.main 177
| sym.main ();
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x004013c2 (sym.__tmainCRTStartup)
| 0x00401550 55 push rbp
| 0x00401551 4889e5 mov rbp, rsp
| 0x00401554 4883ec30 sub rsp, 0x30 ; '0'
| 0x00401558 e863010000 call sym.__main
| 0x0040155d 41b936000000 mov r9d, 0x36 ; '6' ; 54
| 0x00401563 4c8d05962a00. lea r8, str.Artikblue_s_magic_msgbox ; section..rdata ; 0x404000 ; "Artikblue's m
| 0x0040156a 488d15a82a00. lea rdx, str.Hello_dear_user ; 0x404019 ; "Hello dear user"
| 0x00401571 b900000000 mov ecx, 0
| 0x00401576 488b05236e00. mov rax, qword sym.imp.USER32.dll_MessageBoxA ; [0x4083a0:8]=0x77751304
| 0x0040157d ffd0 call rax
| 0x0040157f 8945fc mov dword [local_4h], eax
| 0x00401582 837dfc0a cmp dword [local_4h], 0xa ; [0xa:4]=-1 ; 10
| ,=< 0x00401586 742d je 0x4015b5
| | 0x00401588 837dfc0b cmp dword [local_4h], 0xb ; [0xb:4]=-1 ; 11
| ,==< 0x0040158c 7448 je 0x4015d6
| || 0x0040158e 837dfc02 cmp dword [local_4h], 2 ; [0x2:4]=-1 ; 2
| ,===< 0x00401592 7562 jne 0x4015f6
| ||| 0x00401594 488d0d8e2a00. lea rcx, str.CANCELING ; 0x404029 ; "CANCELING"
| ||| 0x0040159b e850150000 call sym.printf ; int printf(const char *format)
| ||| 0x004015a0 ba2c010000 mov edx, 0x12c ; rdx
| ||| 0x004015a5 b9ee020000 mov ecx, 0x2ee ; 750
| ||| 0x004015aa 488b054f6c00. mov rax, qword sym.imp.KERNEL32.dll_Beep ; [0x408200:8]=0x778478f0
| ||| 0x004015b1 ffd0 call rax
| ,====< 0x004015b3 eb41 jmp 0x4015f6
| |||| ; JMP XREF from 0x00401586 (sym.main)
| |||`-> 0x004015b5 488d0d772a00. lea rcx, str.Shall_we_try_again ; 0x404033 ; "Shall we try again?"
| ||| 0x004015bc e82f150000 call sym.printf ; int printf(const char *format)
| ||| 0x004015c1 ba64000000 mov edx, 0x64 ; 'd' ; 100
| ||| 0x004015c6 b9fa000000 mov ecx, 0xfa ; 250
| ||| 0x004015cb 488b052e6c00. mov rax, qword sym.imp.KERNEL32.dll_Beep ; [0x408200:8]=0x778478f0
| ||| 0x004015d2 ffd0 call rax
| |||,=< 0x004015d4 eb20 jmp 0x4015f6
| |||| ; JMP XREF from 0x0040158c (sym.main)
| ||`--> 0x004015d6 488d0d6a2a00. lea rcx, str.The_show_must_go_on ; 0x404047 ; "The show must go on"
| || | 0x004015dd e80e150000 call sym.printf ; int printf(const char *format)
| || | 0x004015e2 baf4010000 mov edx, 0x1f4 ; 500
| || | 0x004015e7 b9b6030000 mov ecx, 0x3b6 ; 950
| || | 0x004015ec 488b050d6c00. mov rax, qword sym.imp.KERNEL32.dll_Beep ; [0x408200:8]=0x778478f0
| || | 0x004015f3 ffd0 call rax
| || | 0x004015f5 90 nop
| || | ; JMP XREF from 0x004015d4 (sym.main)
| || | ; JMP XREF from 0x004015b3 (sym.main)
| || | ; JMP XREF from 0x00401592 (sym.main)
| ``-`-> 0x004015f6 b800000000 mov eax, 0
| 0x004015fb 4883c430 add rsp, 0x30 ; '0'
| 0x004015ff 5d pop rbp
\ 0x00401600 c3 ret
[0x004015a5]>
Podemos dividir el programa en fragmentos interesantes para entenderlo mejor:
| ; CALL XREF from 0x004013c2 (sym.__tmainCRTStartup)
| 0x00401550 55 push rbp
| 0x00401551 4889e5 mov rbp, rsp
| 0x00401554 4883ec30 sub rsp, 0x30 ; '0'
| 0x00401558 e863010000 call sym.__main
| 0x0040155d 41b936000000 mov r9d, 0x36 ; '6' ; 54
| 0x00401563 4c8d05962a00. lea r8, str.Artikblue_s_magic_msgbox ; section..rdata ; 0x404000 ; "Artikblue's magic msgbox"
| 0x0040156a 488d15a82a00. lea rdx, str.Hello_dear_user ; 0x404019 ; "Hello dear user"
| 0x00401571 b900000000 mov ecx, 0
| 0x00401576 488b05236e00. mov rax, qword sym.imp.USER32.dll_MessageBoxA ; [0x4083a0:8]=0x86d8 reloc.USER32.dll_MessageBoxA
| 0x0040157d ffd0 call rax
| 0x0040157f 8945fc mov dword [local_4h], eax
El prólogo reserva 0x30 bytes de pila. La llamada a MessageBox combina el tipo de botones con el icono: 0x30 | 0x6 = 0x36. Son flags del argumento uType; los valores que devuelve cada botón se interpretan después. Véase MessageBoxA.
Puedes consultar la lista completa de flags para la creación de ventanas aquí: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox
La llamada carga la dirección de MessageBoxA a través de su entrada de importación y utiliza esa dirección como destino. El cargador resuelve las importaciones al preparar la imagen del proceso.
A continuación entra en juego el switch:
| 0x0040157f 8945fc mov dword [local_4h], eax
| 0x00401582 837dfc0a cmp dword [local_4h], 0xa ; [0xa:4]=-1 ; 10
| ,=< 0x00401586 742d je 0x4015b5
| | 0x00401588 837dfc0b cmp dword [local_4h], 0xb ; [0xb:4]=-1 ; 11
| ,==< 0x0040158c 7448 je 0x4015d6
| || 0x0040158e 837dfc02 cmp dword [local_4h], 2 ; [0x2:4]=-1 ; 2
La ventana de MessageBox ofrece la posibilidad de realizar tres acciones. Al ser un proceso síncrono, el programa no continuará hasta que el usuario haga clic. Al retornar, eax contendrá un valor relacionado con la elección del usuario, que corresponderá al botón pulsado.
Los códigos se pueden consultar aquí: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox
Tras el clic, el programa comprueba la acción del usuario y ejecuta el fragmento de código correspondiente:
De nuevo vemos esta diferencia: printf se llama directamente y luego la función Beep (perteneciente a la biblioteca kernel32); los parámetros de frecuencia del beep se pasan a través de esos registros y luego se llama a la función.
| ,===< 0x00401592 7562 jne 0x4015f6
| ||| 0x00401594 488d0d8e2a00. lea rcx, str.CANCELING ; 0x404029 ; "CANCELING"
| ||| 0x0040159b e850150000 call sym.printf ; int printf(const char *format)
| ||| 0x004015a0 ba2c010000 mov edx, 0x12c ; 300
| ||| 0x004015a5 b9ee020000 mov ecx, 0x2ee ; 750
| ||| 0x004015aa 488b054f6c00. mov rax, qword sym.imp.KERNEL32.dll_Beep ; [0x408200:8]=0x83b0 reloc.KERNEL32.dll_Beep
| ||| 0x004015b1 ffd0 call rax
| ,====< 0x004015b3 eb41 jmp 0x4015f6
No hay mucho más que comentar aquí. Simplemente quería mostrar cómo un programa en C puede interactuar fácilmente con las funcionalidades del sistema operativo Windows (de forma algo similar a las syscalls de Linux).
Pasemos ahora a algo más cercano a la «programación de sistemas»: el modo en que la WinAPI gestiona ficheros.
Escribir un fichero en disco: CreateFile y WriteFile
Continuamos con lo más básico. Como puedes ver, estas funciones de MSDN funcionan de forma relativamente similar a las syscalls de Linux: abrimos un fichero para lectura/escritura creándolo si no existe con CreateFile y obtenemos un handle al fichero. Un file handle es conceptualmente similar a un descriptor de fichero: mediante él podemos referenciar el fichero, y el sistema mantendrá internamente información relacionada con él, como un puntero a la posición actual dentro del fichero, que se actualizará tras cada llamada de lectura, escritura o seek.
Siguiendo esta lógica, podemos escribir bytes en el fichero con WriteFile usando el file handle y un buffer con los bytes a escribir. Hay que tener en cuenta que en Linux los descriptores de fichero son un concepto más general: write puede escribir en pantalla, en un fichero, en un socket, en una tubería, etc., porque «en Linux todo es un fichero». En Windows el enfoque es «todo es un objeto», de modo que, aunque la arquitectura es similar en ciertos aspectos, Windows dispone de una llamada específica para escribir en un fichero, otra para enviar información a pantalla, etc.
En cualquier caso, aquí está el código:
#include <windows.h>
#include <stdio.h>
int main(void)
{
DWORD at;
HANDLE hFile;
LPCWSTR fname = "C:\\samples\\newfile.txt";
DWORD lpdwFlags[100];
BOOL test;
hFile = CreateFile(fname, GENERIC_WRITE,FILE_SHARE_WRITE,NULL,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL |FILE_ATTRIBUTE_ARCHIVE | SECURITY_IMPERSONATION,NULL);
if(hFile == INVALID_HANDLE_VALUE)
printf("Could not open %s file, error %d\n", fname, GetLastError());
else{
printf("File's HANDLE is OK!\n");
printf("f handle = %d \n", hFile);
char arr[20] = "SAMPLESAMPLESAMPLETEXT";
printf("%s \n", arr);
WriteFile(hFile, &arr, 5,&at,NULL);
printf("Bytes written: %d \n", at);
CloseHandle(hFile);
}
return 0;
}
El programa abre un fichero para escritura, comprueba que todo ha ido bien con la apertura y llama a WriteFile para escribir algunos bytes (arr).
Vamos a desensamblarlo:
[0x00401550]> pdf
/ (fcn) sym.main 343
| sym.main (int arg_17ch, int arg_180h, int arg_188h);
| ; var int local_40h @ rbp-0x40
| ; var int local_38h @ rbp-0x38
| ; var int local_30h_2 @ rbp-0x30
| ; arg int arg_17ch @ rbp+0x17c
| ; arg int arg_180h @ rbp+0x180
| ; arg int arg_188h @ rbp+0x188
| ; var int local_20h @ rsp+0x20
| ; var int local_28h @ rsp+0x28
| ; var int local_30h @ rsp+0x30
| ; var int local_80h @ rsp+0x80
| ; CALL XREF from 0x004013c2 (sym.__tmainCRTStartup)
| 0x00401550 55 push rbp
| 0x00401551 4881ec100200. sub rsp, 0x210
| 0x00401558 488dac248000. lea rbp, [local_80h] ; 0x80 ; 128
| 0x00401560 e8fb010000 call sym.__main
| 0x00401565 488d05942a00. lea rax, str.C:__samples___ewfile.txt ; section..rdata ; 0x404000 ; "C:\samples\newfile.txt"
| 0x0040156c 488985880100. mov qword [arg_188h], rax ; [0x188:8]=-1 ; 392
| 0x00401573 488b85880100. mov rax, qword [arg_188h] ; [0x188:8]=-1 ; 392
| 0x0040157a 48c744243000. mov qword [local_30h], 0
| 0x00401583 c7442428a000. mov dword [local_28h], 0x200a0 ; [0x200a0:4]=-1
| 0x0040158b c74424200200. mov dword [local_20h], 2
| 0x00401593 41b900000000 mov r9d, 0
| 0x00401599 41b802000000 mov r8d, 2
| 0x0040159f ba00000040 mov edx, 0x40000000
| 0x004015a4 4889c1 mov rcx, rax
| 0x004015a7 488b054e6c00. mov rax, qword sym.imp.KERNEL32.dll_CreateFileA ; [0x4081fc:8]=0x83ba reloc.KERNEL32.dll_CreateFileA
| 0x004015ae ffd0 call rax
| 0x004015b0 488985800100. mov qword [arg_180h], rax ; [0x180:8]=-1 ; 384
| 0x004015b7 4883bd800100. cmp qword [arg_180h], 0xffffffffffffffff
| ,=< 0x004015bf 7529 jne 0x4015ea
| | 0x004015c1 488b05646c00. mov rax, qword sym.imp.KERNEL32.dll_GetLastError ; [0x40822c:8]=0x8438 reloc.KERNEL32.dll_GetLastError ; "8\x84"
| | 0x004015c8 ffd0 call rax
| | 0x004015ca 89c2 mov edx, eax
| | 0x004015cc 488b85880100. mov rax, qword [arg_188h] ; [0x188:8]=-1 ; 392
| | 0x004015d3 4189d0 mov r8d, edx
| | 0x004015d6 4889c2 mov rdx, rax
| | 0x004015d9 488d0d382a00. lea rcx, str.Could_not_open__s_file__error__d ; 0x404018 ; "Could not open %s file, error %d\n"
| | 0x004015e0 e8b3150000 call sym.printf ; int printf(const char *format)
| ,==< 0x004015e5 e9af000000 jmp 0x401699
| || ; JMP XREF from 0x004015bf (sym.main)
| |`-> 0x004015ea 488d0d492a00. lea rcx, str.File_s_HANDLE_is_OK ; 0x40403a ; "File's HANDLE is OK!"
| | 0x004015f1 e89a150000 call sym.puts ; int puts(const char *s)
| | 0x004015f6 488b85800100. mov rax, qword [arg_180h] ; [0x180:8]=-1 ; 384
| | 0x004015fd 4889c2 mov rdx, rax
| | 0x00401600 488d0d482a00. lea rcx, str.f_handle____d ; 0x40404f ; "f handle = %d \n"
| | 0x00401607 e88c150000 call sym.printf ; int printf(const char *format)
| | 0x0040160c 48b853414d50. movabs rax, 0x4153454c504d4153
| | 0x00401616 488945c0 mov qword [local_40h], rax
| | 0x0040161a 48b84d504c45. movabs rax, 0x504d4153454c504d
| | 0x00401624 488945c8 mov qword [local_38h], rax
| | 0x00401628 c745d04c4554. mov dword [local_30h_2], 0x4554454c
| | 0x0040162f 488d45c0 lea rax, [local_40h]
| | 0x00401633 4889c2 mov rdx, rax
| | 0x00401636 488d0d222a00. lea rcx, str.s ; 0x40405f ; "%s \n"
| | 0x0040163d e856150000 call sym.printf ; int printf(const char *format)
| | 0x00401642 488d957c0100. lea rdx, [arg_17ch] ; 0x17c ; 380
| | 0x00401649 488d45c0 lea rax, [local_40h]
| | 0x0040164d 488b8d800100. mov rcx, qword [arg_180h] ; [0x180:8]=-1 ; 384
| | 0x00401654 48c744242000. mov qword [local_20h], 0
| | 0x0040165d 4989d1 mov r9, rdx
| | 0x00401660 41b805000000 mov r8d, 5
| | 0x00401666 4889c2 mov rdx, rax
| | 0x00401669 488b054c6c00. mov rax, qword sym.imp.KERNEL32.dll_WriteFile ; [0x4082bc:8]=0x85b0 reloc.KERNEL32.dll_WriteFile
| | 0x00401670 ffd0 call rax
| | 0x00401672 8b857c010000 mov eax, dword [arg_17ch] ; [0x17c:4]=-1 ; 380
| | 0x00401678 89c2 mov edx, eax
| | 0x0040167a 488d0de32900. lea rcx, str.Bytes_written:__d ; 0x404064 ; "Bytes written: %d \n"
| | 0x00401681 e812150000 call sym.printf ; int printf(const char *format)
| | 0x00401686 488b85800100. mov rax, qword [arg_180h] ; [0x180:8]=-1 ; 384
| | 0x0040168d 4889c1 mov rcx, rax
| | 0x00401690 488b055d6b00. mov rax, qword sym.imp.KERNEL32.dll_CloseHandle ; [0x4081f4:8]=0x83ac reloc.KERNEL32.dll_CloseHandle
| | 0x00401697 ffd0 call rax
| | ; JMP XREF from 0x004015e5 (sym.main)
| `--> 0x00401699 b800000000 mov eax, 0
| 0x0040169e 4881c4100200. add rsp, 0x210
| 0x004016a5 5d pop rbp
\ 0x004016a6 c3 ret
[0x00401550]>
Las cosas empiezan a ponerse interesantes. Al principio, el programa llama a CreateFile para crear un fichero en disco. CreateFile puede usarse tanto para crear un fichero nuevo como para abrir uno existente.
Lo interesante de esta función es que devuelve un handle al fichero recién creado o abierto. Ese handle se usará para las operaciones de lectura, escritura y seek sobre el fichero.
| 0x0040158b c74424200200. mov dword [local_20h], 2
| 0x00401593 41b900000000 mov r9d, 0
| 0x00401599 41b802000000 mov r8d, 2
| 0x0040159f ba00000040 mov edx, 0x40000000
| 0x004015a4 4889c1 mov rcx, rax
| 0x004015a7 488b054e6c00. mov rax, qword sym.imp.KERNEL32.dll_CreateFileA ; [0x4081fc:8]=0x83ba reloc.KERNEL32.dll_CreateFileA
| 0x004015ae ffd0 call rax
| 0x004015b0 b 488985800100. mov qword [arg_180h], rax ; [0x180:8]=-1 ; 384
| 0x004015b7 4883bd800100. cmp qword [arg_180h], 0xffffffffffffffff
| ,=< 0x004015bf 7529 jne 0x4015ea
| | 0x004015c1 488b05646c00. mov rax, qword sym.imp.KERNEL32.dll_GetLastError ; [0x40822c:8]=0x8438 reloc.KERNEL32.dll_GetLastError ; "8\x84"
| | 0x004015c8 ffd0 call rax
Aquí está el handle tras abrir el fichero:
[0x004015b0]> dr rax
0x00000020
CreateFile devuelve un HANDLE. El código compara el resultado con INVALID_HANDLE_VALUE, representado aquí con todos sus bits a uno, para detectar un fallo de apertura.
| 0x004015b0 488985800100. mov qword [arg_180h], rax ; [0x180:8]=-1 ; 384
;-- rip:
| 0x004015b7 b 4883bd800100. cmp qword [arg_180h], 0xffffffffffffffff
| ,=< 0x004015bf 7529 jne 0x4015ea
| | 0x004015c1 488b05646c00. mov rax, qword sym.imp.KERNEL32.dll_GetLastError ; [0x40822c:8]=0x77801300
| | 0x004015c8 ffd0 call rax
En este punto del programa, el fichero «newfile.txt» existirá en el sistema de ficheros sin contenido. Si intentamos borrarlo no será posible, porque el fichero está registrado como abierto por nuestro programa (mientras tengamos el file handle, el fichero está abierto y solo puede accederse a él según los flags que acabas de ver en el código).
A continuación el programa declara un array de chars (buffer) y llama a WriteFile.
| | 0x00401669 488b054c6c00. mov rax, qword sym.imp.KERNEL32.dll_WriteFile ; [0x4082bc:8]=0x77801a
| ;-- rip:
| | 0x00401670 b ffd0 call rax
| | 0x00401672 b 8b857c010000 mov eax, dword [arg_17ch] ; [0x17c:4]=-1 ; 380
| | 0x00401678 89c2 mov edx, eax
| | 0x0040167a 488d0de32900. lea rcx, str.Bytes_written:__d ; 0x404064 ; "Bytes written: %d \n"
Justo antes de la llamada, la memoria del programa tiene este aspecto:
[0x00401670]> afvd
var arg_188h = 0x0022fe48 0x0000000000404000 .@@..... (IMAGE winapi_CREATEFILE.exe | .data) section..rdata ascii R 0x6c706d61735c3a43 (C:\samples\newfi
var arg_180h = 0x0022fe40 0x0000000000000020 ....... rcx ascii
var local_40h = 0x0022fc80 0x4153454c504d4153 SAMPLESA @rdx ascii
var local_38h = 0x0022fc88 0x504d4153454c504d MPLESAMP ascii
var local_30h_2 = 0x0022fc90 0x000000004554454c LETE.... ascii
var arg_17ch = 0x0022fe3c 0x0000002000000000 .... ... @r9 ascii
[0x00401670]>
[0x00401670]> pxw @ 0x0022fc80
0x0022fc80 0x504d4153 0x4153454c 0x454c504d 0x504d4153 SAMPLESAMPLESAMP
0x0022fc90 0x4554454c 0x00000000 0x00611f50 0x00000000 LETE....P.a.....
0x0022fca0 0x77976850 0x00000000 0x00000001 0x00000000 Ph.w............
0x0022fcb0 0x00610000 0x00000000 0x14070013 0x00000000 ..a.............
El programa mantiene múltiples referencias que corresponden a partes del array, lo cual tiene sentido porque el contenido del array de chars (la cadena) se ha inicializado en memoria para el programa.
WriteFile recibe el buffer y el número de bytes solicitado. En una operación síncrona, otro argumento apunta a un DWORD donde la función registra los bytes escritos. Ese contador ocupa 32 bits y debe comprobarse junto con el resultado de la llamada.
La variable byteswritten se actualizará tras el WriteFile. Después de la escritura, arg_17ch en este caso se actualizará con 5 (si todo ha ido bien).
[0x00401670]> dr
rax = 0x00000005
```
In this case the file won't be available (for reading) after the handle is freed byt CloseHandle. Let's see
```
| | 0x0040168d 4889c1 mov rcx, rax
| | 0x00401690 488b055d6b00. mov rax, qword sym.imp.KERNEL32.dll_CloseHandle ; [0x4081f4:8]=0x778014b0
| | 0x00401697 ffd0 call rax
| | ; JMP XREF from 0x004015e5 (sym.main)
| `--> 0x00401699 b800000000 mov eax, 0
```
CloseHandle frees the file, and thus can be normally used in other programs.
All of the details regarding to WriteFile can be found on the msdn here: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile
Now that you get general idea on CreateFile and WriteFile I advice you to read this one: https://docs.microsoft.com/en-us/windows/win32/fileio/file-buffering if you want to know more about how WriteFile deals with this topic (temporal buffering and such)
#### Read and Write files ReadFile and WriteFile
As you can guess if we have a WriteFile call, we also have a ReadFile call as well. ReadFile works kind of the same, we need to pass similar variables to it such as the file handle, a buffer for the bytes to be read, the num of bytes to read and a pointer for "bytesread".
With Write/Read we can also use overlapping but I won't go deep on the topic (FOR NOW), read it here: https://docs.microsoft.com/en-us/windows/win32/devio/overlapped-operations
So, the following program opens two files, one for Read and the other for Write, then it starts "transfering" bytes from one file to the other.
```C
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#define BUFFERSIZE 180
int main()
{
printf("Reading %d bytes: \n", BUFFERSIZE);
DWORD dwBytesRead = 0;
char ReadBuffer[BUFFERSIZE] = {0};
int err;
int totalBytesRead = 0;
int r = 0;
int hFile = CreateFile("C:\\samples\\newfile.txt", // file to open
GENERIC_READ, // open for read
FILE_SHARE_READ, // share for read
NULL, // default security
OPEN_EXISTING, // existing file only
FILE_ATTRIBUTE_NORMAL, // normal file
NULL);
int hFile2 = CreateFile("C:\\samples\\newfile2.txt", // file to open
GENERIC_WRITE, // open for write
FILE_SHARE_WRITE, // share for reading
NULL, // default security
OPEN_EXISTING, // existing file only
FILE_ATTRIBUTE_NORMAL, // normal file
NULL);
if (hFile == INVALID_HANDLE_VALUE || hFile2 == INVALID_HANDLE_VALUE)
{
err = GetLastError();
printf("Error reading file, error code: %d \n", err);
}
else{
int fsize = GetFileSize(hFile, NULL);
printf("File size in bytes: %d \n", fsize);
printf("File content: \n");
while(totalBytesRead < fsize){
ReadFile(hFile, ReadBuffer, BUFFERSIZE-1, &dwBytesRead, NULL);
printf("%s", ReadBuffer);
totalBytesRead += dwBytesRead;
WriteFile(hFile2, ReadBuffer, dwBytesRead,NULL,NULL);
}
}
CloseHandle(hFile);
CloseHandle(hFile2);
return 0;
}
```
Let's check it, be ready cause the following disams is LARGE
```
[0x77953840]> s sym.main
[0x00401550]> pdf
/ (fcn) sym.main 580
| sym.main (int arg_80h, int arg_84h, int arg_88h, int arg_8ch, int arg_90h, int arg_94h, int arg_98h, int arg_9ch);
| ; var int local_40h @ rbp-0x40
| ; arg int arg_80h @ rbp+0x80
| ; arg int arg_84h @ rbp+0x84
| ; arg int arg_88h @ rbp+0x88
| ; arg int arg_8ch @ rbp+0x8c
| ; arg int arg_90h @ rbp+0x90
| ; arg int arg_94h @ rbp+0x94
| ; arg int arg_98h @ rbp+0x98
| ; arg int arg_9ch @ rbp+0x9c
| ; var int local_20h @ rsp+0x20
| ; var int local_28h @ rsp+0x28
| ; var int local_30h @ rsp+0x30
| ; var int local_80h @ rsp+0x80
| ; CALL XREF from 0x004013c2 (sym.__tmainCRTStartup)
| 0x00401550 55 push rbp
| 0x00401551 57 push rdi
| 0x00401552 4881ec280100. sub rsp, 0x128
| 0x00401559 488dac248000. lea rbp, [local_80h] ; 0x80 ; 128
| 0x00401561 e8ea020000 call sym.__main
| 0x00401566 bab4000000 mov edx, 0xb4 ; 180
| 0x0040156b 488d0d8e2a00. lea rcx, str.Reading__d_bytes: ; section..rdata ; 0x404000 ; "Reading %d bytes: \n"
| 0x00401572 e811170000 call sym.printf ; int printf(const char *format)
| 0x00401577 c78580000000. mov dword [arg_80h], 0 ; [0x80:4]=-1 ; 0
| 0x00401581 c78598000000. mov dword [arg_98h], 0 ; [0x98:4]=-1 ; 0
| 0x0040158b 488d55c0 lea rdx, [local_40h]
| 0x0040158f b800000000 mov eax, 0
| 0x00401594 b916000000 mov ecx, 0x16 ; 22
| 0x00401599 4889d7 mov rdi, rdx
| 0x0040159c f348ab rep stosq qword [rdi], rax
| 0x0040159f 4889fa mov rdx, rdi
| 0x004015a2 8902 mov dword [rdx], eax
| 0x004015a4 4883c204 add rdx, 4
| 0x004015a8 c7859c000000. mov dword [arg_9ch], 0 ; [0x9c:4]=-1 ; 0
| 0x004015b2 c78594000000. mov dword [arg_94h], 0 ; [0x94:4]=-1 ; 0
| 0x004015bc 48c744243000. mov qword [local_30h], 0
| 0x004015c5 c74424288000. mov dword [local_28h], 0x80 ; [0x80:4]=-1 ; 128
| 0x004015cd c74424200300. mov dword [local_20h], 3
| 0x004015d5 41b900000000 mov r9d, 0
| 0x004015db 41b801000000 mov r8d, 1
| 0x004015e1 ba00000080 mov edx, 0x80000000
| 0x004015e6 488d0d272a00. lea rcx, str.C:__samples___ewfile.txt ; 0x404014 ; "C:\samples\newfile.txt"
| 0x004015ed 488b05186c00. mov rax, qword sym.imp.KERNEL32.dll_CreateFileA ; [0x40820c:8]=0x83da reloc.KERNEL32.dll_CreateFileA
| 0x004015f4 ffd0 call rax
| 0x004015f6 898590000000 mov dword [arg_90h], eax ; [0x90:4]=-1 ; 144
| 0x004015fc 48c744243000. mov qword [local_30h], 0
| 0x00401605 c74424288000. mov dword [local_28h], 0x80 ; [0x80:4]=-1 ; 128
| 0x0040160d c74424200300. mov dword [local_20h], 3
| 0x00401615 41b900000000 mov r9d, 0
| 0x0040161b 41b802000000 mov r8d, 2
| 0x00401621 ba00000040 mov edx, 0x40000000
| 0x00401626 488d0dfe2900. lea rcx, str.C:__samples___ewfile2.txt ; 0x40402b ; "C:\samples\newfile2.txt"
| 0x0040162d 488b05d86b00. mov rax, qword sym.imp.KERNEL32.dll_CreateFileA ; [0x40820c:8]=0x83da reloc.KERNEL32.dll_CreateFileA
| 0x00401634 ffd0 call rax
| 0x00401636 89858c000000 mov dword [arg_8ch], eax ; [0x8c:4]=-1 ; 140
| 0x0040163c 83bd90000000. cmp dword [arg_90h], 0xffffffffffffffff
| ,=< 0x00401643 7409 je 0x40164e
| | 0x00401645 83bd8c000000. cmp dword [arg_8ch], 0xffffffffffffffff
| ,==< 0x0040164c 7528 jne 0x401676
| || ; JMP XREF from 0x00401643 (sym.main)
| |`-> 0x0040164e 488b05ef6b00. mov rax, qword sym.imp.KERNEL32.dll_GetLastError ; [0x408244:8]=0x8466 reloc.KERNEL32.dll_GetLastError ; "f\x84"
| | 0x00401655 ffd0 call rax
| | 0x00401657 898584000000 mov dword [arg_84h], eax ; [0x84:4]=-1 ; 132
| | 0x0040165d 8b8584000000 mov eax, dword [arg_84h] ; [0x84:4]=-1 ; 132
| | 0x00401663 89c2 mov edx, eax
| | 0x00401665 488d0ddc2900. lea rcx, str.Error_reading_file__error_code:___d ; 0x404048 ; "Error reading file, error code: %d \n"
| | 0x0040166c e817160000 call sym.printf ; int printf(const char *format)
| |,=< 0x00401671 e9e7000000 jmp 0x40175d
| || ; JMP XREF from 0x0040164c (sym.main)
| `--> 0x00401676 8b8590000000 mov eax, dword [arg_90h] ; [0x90:4]=-1 ; 144
| | 0x0040167c 4898 cdqe
| | 0x0040167e ba00000000 mov edx, 0
| | 0x00401683 4889c1 mov rcx, rax
| | 0x00401686 488b05af6b00. mov rax, qword sym.imp.KERNEL32.dll_GetFileSize ; [0x40823c:8]=0x8458 reloc.KERNEL32.dll_GetFileSize ; "X\x84"
| | 0x0040168d ffd0 call rax
| | 0x0040168f 898588000000 mov dword [arg_88h], eax ; [0x88:4]=-1 ; 136
| | 0x00401695 8b8588000000 mov eax, dword [arg_88h] ; [0x88:4]=-1 ; 136
| | 0x0040169b 89c2 mov edx, eax
| | 0x0040169d 488d0dca2900. lea rcx, str.File_size_in_bytes:__d ; 0x40406e ; "File size in bytes: %d \n"
| | 0x004016a4 e8df150000 call sym.printf ; int printf(const char *format)
| | 0x004016a9 488d0dd72900. lea rcx, str.File_content: ; 0x404087 ; "File content: "
| | 0x004016b0 e8cb150000 call sym.puts ; int puts(const char *s)
| ,==< 0x004016b5 e991000000 jmp 0x40174b
| || ; JMP XREF from 0x00401757 (sym.main)
| .---> 0x004016ba 8b8590000000 mov eax, dword [arg_90h] ; [0x90:4]=-1 ; 144
| :|| 0x004016c0 4898 cdqe
| :|| 0x004016c2 4889c1 mov rcx, rax
| :|| 0x004016c5 488d95800000. lea rdx, [arg_80h] ; 0x80 ; 128
| :|| 0x004016cc 488d45c0 lea rax, [local_40h]
| :|| 0x004016d0 48c744242000. mov qword [local_20h], 0
| :|| 0x004016d9 4989d1 mov r9, rdx
| :|| 0x004016dc 41b8b3000000 mov r8d, 0xb3 ; 179
| :|| 0x004016e2 4889c2 mov rdx, rax
| :|| 0x004016e5 488b05906b00. mov rax, qword sym.imp.KERNEL32.dll_ReadFile ; [0x40827c:8]=0x8500 reloc.KERNEL32.dll_ReadFile
| :|| 0x004016ec ffd0 call rax
| :|| 0x004016ee 488d45c0 lea rax, [local_40h]
| :|| 0x004016f2 4889c2 mov rdx, rax
| :|| 0x004016f5 488d0d9a2900. lea rcx, [0x00404096] ; "%s"
| :|| 0x004016fc e887150000 call sym.printf ; int printf(const char *format)
| :|| 0x00401701 8b959c000000 mov edx, dword [arg_9ch] ; [0x9c:4]=-1 ; 156
| :|| 0x00401707 8b8580000000 mov eax, dword [arg_80h] ; [0x80:4]=-1 ; 128
| :|| 0x0040170d 01d0 add eax, edx
| :|| 0x0040170f 89859c000000 mov dword [arg_9ch], eax ; [0x9c:4]=-1 ; 156
| :|| 0x00401715 488d85800000. lea rax, [arg_80h] ; 0x80 ; 128
| :|| 0x0040171c 89c2 mov edx, eax
| :|| 0x0040171e 8b858c000000 mov eax, dword [arg_8ch] ; [0x8c:4]=-1 ; 140
| :|| 0x00401724 4898 cdqe
| :|| 0x00401726 4889c1 mov rcx, rax
| :|| 0x00401729 488d45c0 lea rax, [local_40h]
| :|| 0x0040172d 48c744242000. mov qword [local_20h], 0
| :|| 0x00401736 41b900000000 mov r9d, 0
| :|| 0x0040173c 4189d0 mov r8d, edx
| :|| 0x0040173f 4889c2 mov rdx, rax
| :|| 0x00401742 488b05936b00. mov rax, qword sym.imp.KERNEL32.dll_WriteFile ; [0x4082dc:8]=0x85ea reloc.KERNEL32.dll_WriteFile
| :|| 0x00401749 ffd0 call rax
| :|| ; JMP XREF from 0x004016b5 (sym.main)
| :`--> 0x0040174b 8b859c000000 mov eax, dword [arg_9ch] ; [0x9c:4]=-1 ; 156
| : | 0x00401751 3b8588000000 cmp eax, dword [arg_88h] ; [0x88:4]=-1 ; 136
| `===< 0x00401757 0f8c5dffffff jl 0x4016ba
| | ; JMP XREF from 0x00401671 (sym.main)
| `-> 0x0040175d 8b8590000000 mov eax, dword [arg_90h] ; [0x90:4]=-1 ; 144
| 0x00401763 4898 cdqe
| 0x00401765 4889c1 mov rcx, rax
| 0x00401768 488b05956a00. mov rax, qword sym.imp.KERNEL32.dll_CloseHandle ; [0x408204:8]=0x83cc reloc.KERNEL32.dll_CloseHandle
| 0x0040176f ffd0 call rax
| 0x00401771 8b858c000000 mov eax, dword [arg_8ch] ; [0x8c:4]=-1 ; 140
| 0x00401777 4898 cdqe
| 0x00401779 4889c1 mov rcx, rax
| 0x0040177c 488b05816a00. mov rax, qword sym.imp.KERNEL32.dll_CloseHandle ; [0x408204:8]=0x83cc reloc.KERNEL32.dll_CloseHandle
| 0x00401783 ffd0 call rax
| 0x00401785 b800000000 mov eax, 0
| 0x0040178a 4881c4280100. add rsp, 0x128
| 0x00401791 5f pop rdi
| 0x00401792 5d pop rbp
\ 0x00401793 c3 ret
[0x00401550]>
```
Eventhough the program looks confusing, the magic happens here:
```
| ,==< 0x004016b5 e991000000 jmp 0x40174b
| || ; JMP XREF from 0x00401757 (sym.main)
| .---> 0x004016ba 8b8590000000 mov eax, dword [arg_90h] ; [0x90:4]=-1 ; 144
| :|| 0x004016c0 4898 cdqe
| :|| 0x004016c2 4889c1 mov rcx, rax
| :|| 0x004016c5 488d95800000. lea rdx, [arg_80h] ; 0x80 ; 128
| :|| 0x004016cc 488d45c0 lea rax, [local_40h]
| :|| 0x004016d0 48c744242000. mov qword [local_20h], 0
| :|| 0x004016d9 4989d1 mov r9, rdx
| :|| 0x004016dc 41b8b3000000 mov r8d, 0xb3 ; 179
| :|| 0x004016e2 4889c2 mov rdx, rax
| :|| 0x004016e5 488b05906b00. mov rax, qword sym.imp.KERNEL32.dll_ReadFile ; [0x40827c:8]=0x8500 reloc.KERNEL32.dll_ReadFile
| :|| 0x004016ec ffd0 call rax
| :|| 0x004016ee 488d45c0 lea rax, [local_40h]
| :|| 0x004016f2 4889c2 mov rdx, rax
| :|| 0x004016f5 488d0d9a2900. lea rcx, [0x00404096] ; "%s"
| :|| 0x004016fc e887150000 call sym.printf ; int printf(const char *format)
| :|| 0x00401701 8b959c000000 mov edx, dword [arg_9ch] ; [0x9c:4]=-1 ; 156
| :|| 0x00401707 8b8580000000 mov eax, dword [arg_80h] ; [0x80:4]=-1 ; 128
| :|| 0x0040170d 01d0 add eax, edx
| :|| 0x0040170f 89859c000000 mov dword [arg_9ch], eax ; [0x9c:4]=-1 ; 156
| :|| 0x00401715 488d85800000. lea rax, [arg_80h] ; 0x80 ; 128
| :|| 0x0040171c 89c2 mov edx, eax
| :|| 0x0040171e 8b858c000000 mov eax, dword [arg_8ch] ; [0x8c:4]=-1 ; 140
| :|| 0x00401724 4898 cdqe
| :|| 0x00401726 4889c1 mov rcx, rax
| :|| 0x00401729 488d45c0 lea rax, [local_40h]
| :|| 0x0040172d 48c744242000. mov qword [local_20h], 0
| :|| 0x00401736 41b900000000 mov r9d, 0
| :|| 0x0040173c 4189d0 mov r8d, edx
| :|| 0x0040173f 4889c2 mov rdx, rax
| :|| 0x00401742 488b05936b00. mov rax, qword sym.imp.KERNEL32.dll_WriteFile ; [0x4082dc:8]=0x85ea reloc.KERNEL32.dll_WriteFile
| :|| 0x00401749 ffd0 call rax
| :|| ; JMP XREF from 0x004016b5 (sym.main)
| :`--> 0x0040174b 8b859c000000 mov eax, dword [arg_9ch] ; [0x9c:4]=-1 ; 156
| : | 0x00401751 3b8588000000 cmp eax, dword [arg_88h] ; [0x88:4]=-1 ; 136
| `===< 0x00401757 0f8c5dffffff jl 0x4016ba
| | ; JMP XREF from 0x00401671 (sym.main)
```
I strongly recommend you to always check for loops, they usually represent solid works of code that run interesting stuff.
They key of this block and the new thing here is the ReadFile call
| :|| 0x004016c2 4889c1 mov rcx, rax
| :|| 0x004016c5 488d95800000. lea rdx, [arg_80h] ; 0x80 ; 128
| :|| 0x004016cc 488d45c0 lea rax, [local_40h]
| :|| 0x004016d0 48c744242000. mov qword [local_20h], 0
| :|| 0x004016d9 4989d1 mov r9, rdx
| :|| 0x004016dc 41b8b3000000 mov r8d, 0xb3 ; 179
| :|| 0x004016e2 4889c2 mov rdx, rax
| :|| 0x004016e5 488b05906b00. mov rax, qword sym.imp.KERNEL32.dll_ReadFile ; [0x40827c:8]=0x8500 reloc.KERNEL32.dll_ReadFile
| :|| 0x004016ec b ffd0 call rax
| :|| 0x004016ee 488d45c0 lea rax, [local_40h]
| :|| 0x004016f2 b 4889c2 mov rdx, rax
| :|| 0x004016f5 488d0d9a2900. lea rcx, [0x00404096] ; "%s"
| :|| 0x004016fc e887150000 call sym.printf ; int printf(const char *format)
For us the most important parameters are the pointer to the mem space that will hold the bytes read and then the num of bytes to read. On this case 0xb3 is the num of bytes to read and those will be loaded inside local_40h
As we are already open for debug, we can just figure that out by setting breakpoints before and after the call and examining the memory:
[0x004016ec]> afvd
var arg_80h = 0x0022fe20 0x0000000000000000 ........ @r9 r15
var arg_98h = 0x0022fe38 0x0000000000000000 ........ r15
var local_40h = 0x0022fd60 0x0000000000000000 ........ @rdx r15
var arg_9ch = 0x0022fe3c 0x00371f8000000000 ......7.
var arg_94h = 0x0022fe34 0x0000000000000000 ........ r15
var arg_90h = 0x0022fe30 0x0000000000000020 ....... rcx ascii
var arg_8ch = 0x0022fe2c 0x0000002000000024 $... ... ascii
var arg_84h = 0x0022fe24 0x0000000500000000 ........
var arg_88h = 0x0022fe28 0x0000002400000005 ....$...
[0x004016ec]> dc
hit breakpoint at: 4016f2
[0x004016ec]> afvd
var arg_80h = 0x0022fe20 0x0000000000000005 ........
var arg_98h = 0x0022fe38 0x0000000000000000 ........ rdx
var local_40h = 0x0022fd60 0x0000004c504d4153 SAMPL... @rax ascii
var arg_9ch = 0x0022fe3c 0x00371f8000000000 ......7.
var arg_94h = 0x0022fe34 0x0000000000000000 ........ rdx
var arg_90h = 0x0022fe30 0x0000000000000020 ....... ascii
var arg_8ch = 0x0022fe2c 0x0000002000000024 $... ... ascii
var arg_84h = 0x0022fe24 0x0000000500000000 ........
var arg_88h = 0x0022fe28 0x0000002400000005 ....$...
[0x004016ec]>
Voilà
Then the num of bytes read is added every time to a particular variable (that CMPed at the end of the loop)
| :|| 0x004016f5 488d0d9a2900. lea rcx, [0x00404096]
| :|| 0x004016fc e887150000 call sym.printf
| :|| 0x00401701 8b959c000000 mov edx, dword [arg_9ch]
| :|| 0x00401707 8b8580000000 mov eax, dword [arg_80h]
| :|| 0x0040170d 01d0 add eax, edx
| :|| 0x0040170f 89859c000000 mov dword [arg_9ch], eax
| :|| 0x00401715 488d85800000. lea rax, [arg_80h]
| :|| 0x0040171c 89c2 mov edx, eax
That is probably the counter
We can see that the program is using a 179 byte buffer to temp save those bytes read and then to WriteFile them into the other file. If we are going quick and blind we can inspect the status of the variable like this
````
0x0022fe00 0x4f444e41 0x4554474d 0x41525458 0x4d4f444e ANDOMGTEXTRANDOM
0x0022fe10 0x00455447 0x00000000 0x00000000 0x00000000 GTE.............
0x0022fe20 0x000000b3 0x00000000 0x00000861 0x00000024 ........a...$...
0x0022fe30 0x00000020 0x00000000 0x00000000 0x00000166 ...........f...
0x0022fe40 0x002e1f80 0x00000000 0x002e1f80 0x00000000 ................
0x0022fe50 0x002e1f40 0x00000000 0x004013c7 0x00000000 @.........@.....
[0x004016ee]> dc
hit breakpoint at: 4016ee
[0x004016ee]> pxw @ 0x0022fd60
0x0022fd60 0x41525458 0x4d4f444e 0x58455447 0x4e415254 XTRANDOMGTEXTRAN
0x0022fd70 0x474d4f44 0x54584554 0x444e4152 0x54474d4f DOMGTEXTRANDOMGT
El texto continúa a continuación del texto cargado anteriormente. Con esto terminamos este ejemplo; a continuación se llama a CloseHandle y el programa finaliza. Pasemos al siguiente ejemplo.
CopyFile
Como hemos visto, hemos estado copiando ese fichero MANUALMENTE cargando el contenido de un fichero y volcándolo progresivamente en el otro. De forma similar a lo que hicimos en el tutorial anterior con SendFile (aunque no es exactamente lo mismo), podemos hacer una sola llamada para copiar el fichero al destino sin realizar el trabajo manualmente; la función se encargará de todo internamente.
Observa esto:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main()
{
printf("Copying file using 'CopyFile'");
int b = CopyFile("C:\\samples\\newfile.txt","C:\\samples\\newfile2.txt",0);
if (!b) {
int err = GetLastError();
printf("Error: %d",err);
} else {
printf("File copied\n");
}
return 0;
}
Estas llamadas reciben las rutas de los archivos, sin utilizar file handles.
[0x00401550]> pdf
/ (fcn) sym.main 117
| sym.main ();
| ; var int local_8h @ rbp-0x8
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x004013c2 (sym.__tmainCRTStartup)
| 0x00401550 55 push rbp
| 0x00401551 4889e5 mov rbp, rsp
| 0x00401554 4883ec30 sub rsp, 0x30 ; '0'
| 0x00401558 e823010000 call sym.__main
| 0x0040155d 488d0d9c2a00. lea rcx, str.Copying_file_using__CopyFile ; section..rdata ; 0x404000 ; "Copying file using 'CopyFile'"
| 0x00401564 e84f150000 call sym.printf ; int printf(const char *format)
| 0x00401569 41b800000000 mov r8d, 0
| 0x0040156f 488d15a82a00. lea rdx, str.C:__samples___ewfile2.txt ; 0x40401e ; "C:\samples\newfile2.txt"
| 0x00401576 488d0db92a00. lea rcx, str.C:__samples___ewfile.txt ; 0x404036 ; "C:\samples\newfile.txt"
| 0x0040157d 488b05606c00. mov rax, qword sym.imp.KERNEL32.dll_CopyFileA ; [0x4081e4:8]=0x838c reloc.KERNEL32.dll_CopyFileA
| 0x00401584 ffd0 call rax
| 0x00401586 8945fc mov dword [local_4h], eax
| 0x00401589 837dfc00 cmp dword [local_4h], 0
| ,=< 0x0040158d 751f jne 0x4015ae
| | 0x0040158f 488b057e6c00. mov rax, qword sym.imp.KERNEL32.dll_GetLastError ; [0x408214:8]=0x8408 reloc.KERNEL32.dll_GetLastError
| | 0x00401596 ffd0 call rax
| | 0x00401598 8945f8 mov dword [local_8h], eax
| | 0x0040159b 8b45f8 mov eax, dword [local_8h]
| | 0x0040159e 89c2 mov edx, eax
| | 0x004015a0 488d0da62a00. lea rcx, str.Error:__d ; 0x40404d ; "Error: %d"
| | 0x004015a7 e80c150000 call sym.printf ; int printf(const char *format)
| ,==< 0x004015ac eb0c jmp 0x4015ba
| || ; JMP XREF from 0x0040158d (sym.main)
| |`-> 0x004015ae 488d0da22a00. lea rcx, str.File_copied ; 0x404057 ; "File copied"
| | 0x004015b5 e8f6140000 call sym.puts ; int puts(const char *s)
| | ; JMP XREF from 0x004015ac (sym.main)
| `--> 0x004015ba b800000000 mov eax, 0
| 0x004015bf 4883c430 add rsp, 0x30 ; '0'
| 0x004015c3 5d pop rbp
\ 0x004015c4 c3 ret
[0x00401550]>
No hay nada destacable aquí; simplemente se ve que CopyFileA se llama con esas dos rutas de fichero. Sigamos.
Desplazamiento dentro de archivos LZSEEK
Como en las operaciones de fseek estudiadas en Linux, una operación de posicionamiento cambia el lugar del siguiente acceso a un archivo. Una lectura o escritura ordinaria avanza la posición según los bytes transferidos. El ejemplo original utiliza la API heredada LZSeek.
Veamos esto:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main()
{
printf("Hello world!\n");
char message[10] = "secret msg";
int hFile = CreateFile("C:\\samples\\cat.jpg", // file to open
GENERIC_WRITE, // open for write
FILE_SHARE_WRITE, // share for reading
NULL, // default security
OPEN_EXISTING, // existing file only
FILE_ATTRIBUTE_NORMAL, // normal file
NULL);
LZSeek(hFile,0,2); //0 bytes from the end
WriteFile(hFile, message, 10,NULL,NULL);
CloseHandle(hFile);
return 0;
}
Este programa es interesante porque presenta un concepto que puede o no ser nuevo para ti, pero que se usa ampliamente en CTFs e incluso en algunos malware.
El programa abre un JPEG, se sitúa al final y añade un mensaje. Un JPEG comienza con el marcador SOI, FF D8; los marcadores posteriores dependen del archivo. EOI, FF D9, señala el final de la imagen. El mensaje del ejemplo se encuentra después de ese límite y no forma parte de los píxeles que muestra el visor utilizado.
La captura original permite examinar el archivo después de añadir ese texto. La imagen sigue abriéndose en el visor del caso, aunque conserva datos adicionales. El comportamiento de otros lectores puede variar. Primero se inspecciona la cabecera:
[0x00000000]> pxw
0x00000000 0xe0ffd8ff 0x464a1000 0x01004649 0x60000101 ......JFIF.....`
0x00000010 0x00006000 0x4300dbff 0x01010200 0x02010101 .`.....C........
0x00000020 0x02010101 0x02020202 0x02020304 0x04050202 ................
0x00000030 0x06040304 0x06060605 0x06060605 0x06080907 ................
0x00000040 0x06070907 0x080b0806 0x0a0a0a09 0x08060a0a ................
0x00000050 0x0a0b0c0b 0x0a0a090c 0x00dbff0a 0x02020143 ............C...
0x00000060 0x02020202 0x05030305 0x0706070a 0x0a0a0a0a ................
0x00000070 0x0a0a0a0a 0x0a0a0a0a 0x0a0a0a0a 0x0a0a0a0a ................
0x00000080 0x0a0a0a0a 0x0a0a0a0a 0x0a0a0a0a 0x0a0a0a0a ................
0x00000090 0x0a0a0a0a 0x0a0a0a0a 0x0a0a0a0a 0xc2ff0a0a ................
0x000000a0 0x02081100 0x03200358 0x02002201 0x11030111 ....X. .."......
0x000000b0 0x00c4ff01 0x0200001c 0x01010302 0x00000000 ................
0x000000c0 0x00000000 0x05040000 0x02010603 0xff080007 ................
0x000000d0 0x011a00c4 0x01010300 0x00000101 0x00000000 ................
0x000000e0 0x00000000 0x00030201 0xff060504 0x030c00da ................
0x000000f0 0x10020001 0x00001003 0x4ae1e801 0x497e596c ...........JlY~Is?
Como se puede ver, el archivo presenta el formato de imagen; podemos desplazarnos al final con sG y movernos -+ bytes con s-+ num
[0x00022f74]> px
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x00022f74 ac71 8a48 5640 a2f8 693c e3d2 0a2d 0dc7 .q.HV@..i<...-..
0x00022f84 1c13 4c68 468f 0af5 841c 26a4 8445 8705 ..LhF.....&..E..
0x00022f94 6f10 0008 1a88 4aaf fb58 8c06 0a94 9f77 o.....J..X.....w
0x00022fa4 3930 dc8e 9b53 904e da85 843f 2fce 5b2e 90...S.N...?/.[.
0x00022fb4 c09a d499 d833 0fc0 8ee8 c874 1644 08f6 .....3.....t.D..
0x00022fc4 df3e c0c0 7f09 2555 4f0f a7d6 42a5 8e78 .>....%UO...B..x
0x00022fd4 383e 3de0 4605 9aae 7d4c 61f1 1a31 0430 8>=.F...}La..1.0
0x00022fe4 b0de bef3 91b6 1586 a035 38a9 2ae2 8956 .........58.*..V
0x00022ff4 d9eb 1e90 8890 8204 be79 fac7 4994 0d28 .........y..I..(
0x00023004 649e 262a 311c a58a 08a2 0ff7 789c d558 d.&*1.......x..X
0x00023014 55d0 2719 1a5d 0d32 b712 123f c39a 02ba U.'..].2...?....
0x00023024 6c9b 2b7c 9382 9bba 054b 7491 efce 3183 l.+|.....Kt...1.
0x00023034 70bc d7e1 c51c f34b 4b07 5ffb 8cab 2c19 p......KK._...,.
0x00023044 d9dc 5ff1 926d 4135 7953 edc1 ac11 0661 .._..mA5yS.....a
0x00023054 1e7e b1f1 8090 db56 ff00 cc10 18c3 2822 .~.....V......("
0x00023064 be4f f39f ffd9 7365 6372 6574 206d 7367 .O....secret msg
[0x00022f74]> s+20
[0x00022f88]> px
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x00022f88 468f 0af5 841c 26a4 8445 8705 6f10 0008 F.....&..E..o...
0x00022f98 1a88 4aaf fb58 8c06 0a94 9f77 3930 dc8e ..J..X.....w90..
0x00022fa8 9b53 904e da85 843f 2fce 5b2e c09a d499 .S.N...?/.[.....
0x00022fb8 d833 0fc0 8ee8 c874 1644 08f6 df3e c0c0 .3.....t.D...>..
0x00022fc8 7f09 2555 4f0f a7d6 42a5 8e78 383e 3de0 ..%UO...B..x8>=.
0x00022fd8 4605 9aae 7d4c 61f1 1a31 0430 b0de bef3 F...}La..1.0....
0x00022fe8 91b6 1586 a035 38a9 2ae2 8956 d9eb 1e90 .....58.*..V....
0x00022ff8 8890 8204 be79 fac7 4994 0d28 649e 262a .....y..I..(d.&*
0x00023008 311c a58a 08a2 0ff7 789c d558 55d0 2719 1.......x..XU.'.
0x00023018 1a5d 0d32 b712 123f c39a 02ba 6c9b 2b7c .].2...?....l.+|
0x00023028 9382 9bba 054b 7491 efce 3183 70bc d7e1 .....Kt...1.p...
0x00023038 c51c f34b 4b07 5ffb 8cab 2c19 d9dc 5ff1 ...KK._...,..._.
0x00023048 926d 4135 7953 edc1 ac11 0661 1e7e b1f1 .mA5yS.....a.~..
0x00023058 8090 db56 ff00 cc10 18c3 2822 be4f f39f ...V......(".O..
0x00023068 ffd9 7365 6372 6574 206d 7367 ffff ffff ..secret msg....
0x00023078 ffff ffff ffff ffff ffff ffff ffff ffff ................
Aquí se muestra el mensaje obtenido.
Listar directorios: FindFirstFile y FindNextFile
Con la Windows API se puede realizar todo tipo de operaciones relacionadas con el sistema de ficheros. De hecho, es probablemente la mejor forma de gestionar operaciones sobre el sistema de ficheros en Windows. Al final, al hacer reversing de la mayoría de muestras de malware encontrarás multitud de llamadas a la Windows API, ya que el malware recorre el sistema de ficheros para infectar ficheros, ocultar información, extraer información, etc.
La WinAPI ofrece funciones para buscar ficheros en disco: FindFirstFile y FindNextFile.
El siguiente programa buscará cada fichero .txt en el sistema de ficheros:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <string.h>
int main()
{
printf("Hello world!\n");
WIN32_FIND_DATA FindFileData;
HANDLE hFind;
char base_path[MAX_DIR_LEN] = "C:\\samples\\stor\\";
hFind = FindFirstFile("C:\\samples\\stor\\*.txt",&FindFileData);
do{
memset(base_path,0,MAX_DIR_LEN);
strcpy(base_path,"C:\\samples\\stor\\");
strcat(base_path,FindFileData.cFileName);
printf("Name= %s \n",base_path );
}
while(FindNextFile(hFind, &FindFileData) != 0);
printf("exit");
CloseHandle(hFind);
return 0;
}
FindFirstFile inicia una búsqueda para el patrón indicado, devuelve un handle de búsqueda y rellena WIN32_FIND_DATA con el primer resultado. FindNextFile reutiliza ese handle y actualiza la estructura; su retorno indica si encontró otra entrada. Al terminar se libera la búsqueda con FindClose. Véase FindNextFileA.
El desensamblado de la función main tendrá este aspecto:
[0x00401550]> pdf
/ (fcn) sym.main 312
| sym.main (int arg_b0h, int arg_1f8h);
| ; var int local_60h @ rbp-0x60
| ; var int local_58h @ rbp-0x58
| ; var int local_50h @ rbp-0x50
| ; var int local_48h @ rbp-0x48
| ; arg int arg_b0h @ rbp+0xb0
| ; arg int arg_1f8h @ rbp+0x1f8
| ; var int local_80h @ rsp+0x80
| ; CALL XREF from 0x004013c2 (sym.__tmainCRTStartup)
| 0x00401550 55 push rbp
| 0x00401551 57 push rdi
| 0x00401552 4881ec880200. sub rsp, 0x288
| 0x00401559 488dac248000. lea rbp, [local_80h] ; 0x80 ; 128
| 0x00401561 e8da010000 call sym.__main
| 0x00401566 488d0d932a00. lea rcx, str.Hello_world ; section..rdata ; 0x404000 ; "Hello world!"
| 0x0040156d e806160000 call sym.puts ; int puts(const char *s)
| 0x00401572 48b8433a5c73. movabs rax, 0x6c706d61735c3a43
| 0x0040157c 488945a0 mov qword [local_60h], rax
| 0x00401580 48b865735c73. movabs rax, 0x5c726f74735c7365
| 0x0040158a 488945a8 mov qword [local_58h], rax
| 0x0040158e 48c745b00000. mov qword [local_50h], 0
| 0x00401596 488d55b8 lea rdx, [local_48h]
| 0x0040159a b800000000 mov eax, 0
| 0x0040159f b91d000000 mov ecx, 0x1d ; 29
| 0x004015a4 4889d7 mov rdi, rdx
| 0x004015a7 f348ab rep stosq qword [rdi], rax
| 0x004015aa 4889fa mov rdx, rdi
| 0x004015ad 8902 mov dword [rdx], eax
| 0x004015af 4883c204 add rdx, 4
| 0x004015b3 488d85b00000. lea rax, [arg_b0h] ; 0xb0 ; 176
| 0x004015ba 4889c2 mov rdx, rax
| 0x004015bd 488d0d492a00. lea rcx, str.C:__samples__stor___.txt ; 0x40400d ; "C:\samples\stor\*.txt"
| 0x004015c4 488b05516c00. mov rax, qword sym.imp.KERNEL32.dll_FindFirstFileA ; [0x40821c:8]=0x840a reloc.KERNEL32.dll_FindFirstFileA ; "\n\x8
| 0x004015cb ffd0 call rax
| 0x004015cd 488985f80100. mov qword [arg_1f8h], rax ; [0x1f8:8]=-1 ; 504
| ; JMP XREF from 0x00401654 (sym.main)
| .-> 0x004015d4 488d45a0 lea rax, [local_60h]
| : 0x004015d8 41b804010000 mov r8d, 0x104 ; 260
| : 0x004015de ba00000000 mov edx, 0
| : 0x004015e3 4889c1 mov rcx, rax
| : 0x004015e6 e89d150000 call sym.memset ; void *memset(void *s, int c, size_t n)
| : 0x004015eb 488d45a0 lea rax, [local_60h]
| : 0x004015ef 48bf433a5c73. movabs rdi, 0x6c706d61735c3a43
| : 0x004015f9 488938 mov qword [rax], rdi
| : 0x004015fc 48bf65735c73. movabs rdi, 0x5c726f74735c7365
| : 0x00401606 48897808 mov qword [rax + 8], rdi
| : 0x0040160a c6401000 mov byte [rax + 0x10], 0
| : 0x0040160e 488d85b00000. lea rax, [arg_b0h] ; 0xb0 ; 176
| : 0x00401615 488d502c lea rdx, [rax + 0x2c] ; ',' ; 44
| : 0x00401619 488d45a0 lea rax, [local_60h]
| : 0x0040161d 4889c1 mov rcx, rax
| : 0x00401620 e843150000 call sym.strcat ; char*strcat(char *s1, const char *s2)
| : 0x00401625 488d45a0 lea rax, [local_60h]
| : 0x00401629 4889c2 mov rdx, rax
| : 0x0040162c 488d0df02900. lea rcx, str.Name___s ; 0x404023 ; "Name= %s \n"
| : 0x00401633 e848150000 call sym.printf ; int printf(const char *format)
| : 0x00401638 488d85b00000. lea rax, [arg_b0h] ; 0xb0 ; 176
| : 0x0040163f 488b8df80100. mov rcx, qword [arg_1f8h] ; [0x1f8:8]=-1 ; 504
| : 0x00401646 4889c2 mov rdx, rax
| : 0x00401649 488b05d46b00. mov rax, qword sym.imp.KERNEL32.dll_FindNextFileA ; [0x408224:8]=0x841c reloc.KERNEL32.dll_FindNextFileA
| : 0x00401650 ffd0 call rax
| : 0x00401652 85c0 test eax, eax
| `=< 0x00401654 0f857affffff jne 0x4015d4
| 0x0040165a 488d0dcd2900. lea rcx, str.exit ; 0x40402e ; "exit"
| 0x00401661 e81a150000 call sym.printf ; int printf(const char *format)
| 0x00401666 488b85f80100. mov rax, qword [arg_1f8h] ; [0x1f8:8]=-1 ; 504
| 0x0040166d 4889c1 mov rcx, rax
| 0x00401670 488b058d6b00. mov rax, qword sym.imp.KERNEL32.dll_CloseHandle ; [0x408204:8]=0x83cc reloc.KERNEL32.dll_CloseHandle
| ; JMP XREF from 0x00401601 (sym.main)
| 0x00401677 ffd0 call rax
| 0x00401679 b800000000 mov eax, 0
| 0x0040167e 4881c4880200. add rsp, 0x288
| 0x00401685 5f pop rdi
| 0x00401686 5d pop rbp
\ 0x00401687 c3 ret
[0x00401550]>
Vamos paso a paso. Como el código es algo extenso y ya conocemos casi todo lo que aparece, nos centraremos en las novedades.
El sufijo A indica la variante que utiliza cadenas de bytes según una página de códigos de Windows; W corresponde a cadenas UTF-16. El nombre sin sufijo puede ser una macro que selecciona la variante al compilar. La documentación de prototipos explica esta convención.
La llamada devuelve un handle de búsqueda, un identificador para la búsqueda en curso que apunta a una estructura interna que registra en qué punto se encuentra el programa. A continuación se actualiza una estructura «de usuario» relacionada con el último fichero encontrado.
| 0x004015bd 488d0d492a00. lea rcx, str.C:__samples__stor___.txt ; 0x40400d ; "C:\samples\stor\*.txt"
| 0x004015c4 488b05516c00. mov rax, qword sym.imp.KERNEL32.dll_FindFirstFileA ; [0x40821c:8]=0x7751c530 ; "0\xc5Qw"
| 0x004015cb ffd0 call rax
;-- rip:
| 0x004015cd b 488985f80100. mov qword [arg_1f8h], rax ; [0x1f8:8]=-1 ; 504
| ; JMP XREF from 0x00401654 (sym.main)
[0x004015cd]> dr rax
0x00255850
La estructura (WIN32_FIND_DATA) con la información del fichero queda cargada en nuestra variable arg_b0h.
[0x004015cd]> afvd
var local_60h = 0x0022fbe0 0x6c706d61735c3a43 C:\sampl ascii
var local_58h = 0x0022fbe8 0x5c726f74735c7365 es\stor\ ascii
var local_50h = 0x0022fbf0 0x0000000000000000 ........ r15
var local_48h = 0x0022fbf8 0x0000000000000000 ........ r15
var arg_b0h = 0x0022fcf0 0xcadba01200000020 .......
var arg_1f8h = 0x0022fe38 0x000000000000002c ,....... rsi ascii
[0x004015cd]> pxw @ 0x0022fcf0
0x0022fcf0 0x00000020 0xcadba012 0x01d632af 0xcadba012 ........2......
0x0022fd00 0x01d632af 0x0bff61f4 0x01d632b3 0x00000000 .2...a...2......
0x0022fd10 0x00000005 0x00000000 0x004a1f00 0x66647367 ..........J.gsdf
0x0022fd20 0x66647367 0x78742e67 0x50000074 0x00000000 gsdfg.txt..P....
0x0022fd30 0x004a0001 0x00000000 0x00000100 0x00000000 ..J.............
0x0022fd40 0x004a2cd0 0x00000000 0x00000010 0x00000000 .,J.............
0x0022fd50 0x004a2cd8 0x00000000 0x777741df 0x00000000 .,J......Aww....
0x0022fd60 0x004a0000 0x00000000 0x50000061 0x00000000 ..J.....a..P....
0x0022fd70 0x004a0000 0x00000000 0x00000008 0x00000000 ..J.............
La estructura WIN32_FIND_DATA tiene el siguiente aspecto:
typedef struct _WIN32_FIND_DATA { // wfd
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ];
TCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATA;
Ponemos un breakpoint tras la llamada a FindFirstFile, ejecutamos el programa hasta ese punto y mapeamos la estructura como ya tenemos por costumbre:
[0x004015cd]> "td struct w32find {int dwFileAttributes; long long ftCreationTime; long long ftLastAccessTime; long long ftLastWriteTime; int nFileSizeHigh; int
eserved0; int dwReserved1; char* cFileName; char* cAlternateFileName; };";
[0x004015cd]> tp w32find @ 0x0022fcf0
dwFileAttributes : 0x0022fcf0 = 32
ftCreationTime : 0x0022fcf4 = (qword)0x01d632afcadba012
ftLastAccessTime : 0x0022fcfc = (qword)0x01d632afcadba012
ftLastWriteTime : 0x0022fd04 = (qword)0x01d632b30bff61f4
nFileSizeHigh : 0x0022fd0c = 0
nFileSizeLow : 0x0022fd10 = 5
dwReserved0 : 0x0022fd14 = 0
dwReserved1 : 0x0022fd18 = 4857600
cFileName : 0x0022fd1c = gsdfgsdfg.txt
cAlternateFileName : 0x0022fd2a =
[0x004015cd]>
Tener un conocimiento sólido de los tipos de datos y saber buscar las estructuras internas del SO, el lenguaje o las bibliotecas es fundamental. Tener esta estructura mapeada ahorrará mucho tiempo, especialmente aquí, donde hay un bucle que carga esa estructura con los datos de cada fichero.
Así podemos obtener fácilmente el nombre y el tamaño del fichero.
La ventaja de mapear la estructura es que ahora basta con poner otro breakpoint ahí, ejecutar dc y pulsar la flecha arriba dos veces seguida de Enter para obtener la información de cada nuevo fichero.
[0x004015cd]> db 0x00401652
[0x004015cd]> dc
hit breakpoint at: 401652
[0x00401652]> tp w32find @ 0x0022fcf0
dwFileAttributes : 0x0022fcf0 = 32
ftCreationTime : 0x0022fcf4 = (qword)0x01d632afc88551c7
ftLastAccessTime : 0x0022fcfc = (qword)0x01d632afc88551c7
ftLastWriteTime : 0x0022fd04 = (qword)0x01d631fb3a23cec3
nFileSizeHigh : 0x0022fd0c = 0
nFileSizeLow : 0x0022fd10 = 5
dwReserved0 : 0x0022fd14 = 7536743
dwReserved1 : 0x0022fd18 = 6684772
cFileName : 0x0022fd1c = newfile2 (2).txt
cAlternateFileName : 0x0022fd2d =
[0x00401652]>
[0x00401652]>
Y ahí están los datos. Si te preguntas qué significan esos números hexadecimales relacionados con los datos de tiempo, mira:
0x01d632afc88551c7 = 132348969618854343 en decimal, y ese es un TIMESTAMP = lunes, 25 de mayo de 2020 16:16:01. Puedes usar multitud de herramientas online para parsearlo, como esta: https://www.epochconverter.com/ldap. La estructura está bien definida y los valores tienen sentido.
Es muy habitual encontrar timestamps como ese en muchos programas; internamente son la mejor forma de representar variables de fecha y hora.
Pasemos ahora a un ejemplo más complejo:
Reconocer una transformación de buffers
El último ejemplo del original relaciona enumeración de archivos, lectura por bloques, una operación XOR y escritura. Para analizar ese comportamiento conviene seguir por separado la selección de archivos y la transformación de los bytes.
En la rutina que procesa cada bloque, identifica el buffer de entrada, los bytes realmente leídos, el límite del bucle y el destino de la escritura. La presencia de XOR orienta sobre la operación, pero su efecto depende de los operandos: puede intervenir en una máscara, en una transformación reversible o en otro cálculo.
| Observación | Qué permite comprobar |
|---|---|
| Lectura y contador de bytes | Qué parte del buffer contiene datos válidos. |
| Límite del bucle y acceso indexado | Qué bytes utiliza la transformación. |
| Origen del segundo operando de XOR | Si el valor es constante, periódico o calculado. |
| Posición y destino de escritura | Si el resultado reemplaza datos anteriores o se guarda en otra ubicación. |
Hay un detalle de C que afecta a esta lectura: un parámetro declarado como char buf[] se ajusta a puntero dentro de la función. Aplicar sizeof(buf) allí obtiene el tamaño del puntero, no la longitud del bloque recibido. Para interpretar un bucle hay que contrastar su límite con el contador de bytes de la llamada que lo alimenta.
La conclusión del análisis debe describir el recorrido de los datos y los accesos observados. Una importación o una instrucción aislada aporta una pista; las relaciones entre ellas permiten explicar el comportamiento.
Vuelve al binario
Leer un PE y reconstruir llamadas a la API de Windows a partir de sus argumentos. Identifica al menos una instrucción y el dato que utiliza; conserva la compilación con la que lo has observado.
Puedes utilizar la consola del companion sin conversar con el agente, o abrir el ejecutable en tu desensamblador habitual.
./coursectl start 14 --example hello
./coursectl consoleCierra la sesión anterior con ./coursectl stop antes de cambiar de laboratorio. Ver preparación.
Antes de continuar
Explica con tus palabras la operación estudiada y señala las instrucciones o capturas que la justifican. Distingue los datos observados de los nombres y tipos que has reconstruido.
El progreso se guarda en este navegador. Puedes recorrer las lecciones en cualquier orden.