Exemple d’espace d’ombre

MODIFIER:

J’ai accepté une réponse ci-dessous et ajouté les miennes à ma révision finale du code. Espérons que cela montre aux gens des exemples réels d’allocation de Shadow Space plutôt que plus de mots.

EDIT 2: J’ai également réussi à trouver un lien vers un PDF de conventions d’appel dans les Annotations d’une vidéo YouTube (de toutes choses) qui présente des informations intéressantes sur Shadow Space et la Red Zone sous Linux. On peut le trouver ici: http://www.agner.org/optimize/calling_conventions.pdf

ORIGINAL:

J’ai examiné quelques autres questions ici et partout sur Internet, mais je n’arrive pas à trouver un exemple approprié d’allocation de “Shadow Space” lors de l’appel d’une sous-routine / API Windows dans un assembly Windows 64 bits.

Ma compréhension est la suivante:

  • L’appelant doit sub rsp, avant d’ call callee
  • Callee devrait l’utiliser pour stocker des registres si besoin est (ou des variables locales, si l’enregistrement des registres n’est pas requirejs)
  • L’appelant le nettoie, par exemple: add rsp,
  • Le montant alloué doit être aligné sur 32 octets

Dans cet esprit, voici ce que j’ai essayé:

 section .text start: sub rsp,0x20 ; <---- Allocate 32 bytes of "Shadow space" mov rcx,msg1 mov rdx,msg1.len call write add rsp,0x20 mov rcx,NULL call ExitProcess ret write: mov [rsp+0x08],rcx ; <-- use the Shadow space mov [rsp+0x10],rdx ; <-- and again mov rcx,STD_OUTPUT_HANDLE ; Get handle to StdOut call GetStdHandle mov rcx,rax ; hConsoleOutput mov rdx,[rsp+0x08] ; lpBuffer mov r8,[rsp+0x10] ; nNumberOfCharsToWrite mov r9,empty ; lpNumberOfCharsWritten push NULL ; lpReserved call WriteConsoleA ret 

Mes deux chaînes sont “Hello” et “World! \ N”. Cela réussit à imprimer “Bonjour” avant de planter. Je soupçonne que je le fais correctement … sauf que je devrais nettoyer quelque part (et je ne sais pas trop comment).

Qu’est-ce que je fais mal? J’ai essayé une combinaison de tailles et j’ai également essayé “d’allouer Shadow Space” avant les appels WinAPI (je suis censé le faire?).

Il convient de noter que cela fonctionne parfaitement bien quand je ne me soucie pas du tout de Shadow Space . Cependant, j’essaie d’être compatible avec l’ABI car ma fonction d’ write appelle WinAPIs (et n’est donc pas une fonction feuille).

L’espace fantôme doit être fourni directement avant l’appel. Imaginez l’espace caché comme une relique de l’ancienne convention stdcall / cdecl: pour WriteFile vous aviez besoin de cinq WriteFile . L’espace d’ombre représente les quatre dernières poussées (les quatre premiers arguments). Maintenant, vous avez besoin de quatre registres, l’espace fantôme (juste l’espace, le contenu n’importe pas) et une valeur sur la stack après l’espace ombré (qui est en fait la première pression). Actuellement, l’adresse de retour de l’appelant ( start ) se trouve dans l’espace que WriteFile utilisera comme espace WriteFile -> crash.

Vous pouvez créer un nouvel espace d’ombre pour les fonctions GetStdHandle ( GetStdHandle et WriteConsoleA ) dans la fonction write :

 write: push rbp mov rbp, rsp sub rsp, (8 + 32) ; 5th argument of WriteConsoleA + Shadow space mov [rbp+16],rcx ; <-- use the Shadow space of `start` mov [rbp+24],rdx ; <-- and again mov rcx, -11 ; Get handle to StdOut call GetStdHandle mov rcx,rax ; hConsoleOutput mov rdx, [rbp+16] ; lpBuffer mov r8, [rbp+24] ; nNumberOfCharsToWrite mov r9,empty ; lpNumberOfCharsWritten mov qword [rsp+32],0 ; lpReserved - 5th argument directly behind the shadow space call WriteConsoleA leave ret 

Pour être complet, je poste ceci ici car c’est ce que j’ai fini par dire. Cela fonctionne parfaitement et, pour autant que je UNWIND_INFO , sauf les UNWIND_INFO / Exception Handling de x64 ASM sous Windows, cela est pratiquement d’actualité. Les commentaires sont, nous l’espérons, exacts.

MODIFIER:

Ceci est maintenant mis à jour après le commentaire de Raymonds ci-dessous. J’ai supprimé la conservation de rbp car elle n’était pas nécessaire et j’ai jeté mon alignement de stack plus loin que je ne le pensais.

 ; Windows APIs ; GetStdHandle ; ------------ ; HANDLE WINAPI GetStdHandle( ; _In_ DWORD nStdHandle ; ); extern GetStdHandle ; WriteFile ; ------------ ; BOOL WINAPI WriteFile( ; _In_ HANDLE hFile, ; _In_ LPCVOID lpBuffer, ; _In_ DWORD nNumberOfBytesToWrite, ; _Out_opt_ LPDWORD lpNumberOfBytesWritten, ; _Inout_opt_ LPOVERLAPPED lpOverlapped ; ); extern WriteFile ; ExitProcess ; ----------- ; VOID WINAPI ExitProcess( ; _In_ UINT uExitCode ; ); extern ExitProcess global start section .data STD_OUTPUT_HANDLE equ -11 NULL equ 0 msg1 db "Hello ", 0 msg1.len equ $-msg1 msg2 db "World!", 10, 0 msg2.len equ $-msg2 section .bss empty resd 1 section .text start: sub rsp,0x28 ; Allocate 32 bytes of Shadow Space + align it to 16 bytes (8 byte return address already on stack, so 8 + 40 = 16*3) mov rcx,msg1 mov rdx,msg1.len call write mov rcx,msg2 mov rdx,msg2.len call write mov rcx,NULL call ExitProcess add rsp,0x28 ; Restore the stack pointer before exiting ret write: ; Allocate another 40 bytes of stack space (the return address makes 48 total). Its 32 ; bytes of Shadow Space for the WinAPI calls + 8 more bytes for the fifth argument ; to the WriteFile API call. sub rsp,0x28 mov [rsp+0x30],rcx ; Argument 1 is 48 bytes back in the stack (40 for Shadow Space above, 8 for return address) mov [rsp+0x38],rdx ; Argument 2 is just after Argument 1 mov rcx,STD_OUTPUT_HANDLE ; Get handle to StdOut call GetStdHandle mov rcx,rax ; hFile mov rdx,[rsp+0x30] ; lpBuffer mov r8,[rsp+0x38] ; nNumberOfBytesToWrite mov r9,empty ; lpNumberOfBytesWritten ; Move the 5th argument directly behind the Shadow Space mov qword [rsp+0x20],0 ; lpOverlapped, Argument 5 (just after the Shadow Space 32 bytes back) call WriteFile add rsp,0x28 ; Restore the stack pointer (remove the Shadow Space) ret 

Ce qui se traduit par …:

Enfin travailler!