supersnail
Éleveur d'ornithorynques
      
Messages : 1,616
Sujets : 73
Points: 466
Inscription : Jan 2012
|
[NASM] Création d'un thread sous Linux uniquement avec les syscalls
Bonjour à tous,
Comme je me suis décidé à me mettre à la prog sys sous Linux (l'histoire de changer un peu des trucs funky sous win32), je me suis dit why not essayer de créer un thread sous Linux uniquement avec les syscalls, tout en essayant d'avoir un comportement "similaire" aux threads de win32 (càd que la création d'un thread ne crée pas un nouveau PID, et que la modif de l'espace mémoire depuis un thread soit globale au process, ce qui est plus pratique si on veut faire du GOT hooking par exemple).
Bref après quelques heures/jours de RTFM, j'ai finalement trouvé mon bonheur avec le syscall sys_clone, qui permet de créer un process qui partage certaines choses avec son parent (apparament, pour le kernel Linux, thread = process, et il gère les "process" par groupe de "PID" si on en croit le man de clone):
Citation :CLONE_THREAD
(depuis Linux 2.4.0-test8) Si CLONE_THREAD est présent, le fils est placé dans le même groupe de threads que le processus appelant. Pour rendre la suite de la discussion sur CLONE_THREAD plus lisible, le terme « thread » est utilisé pour faire référence au processus d'un groupe de threads.
Les groupes de threads sont une fonctionnalité ajoutée dans Linux 2.4 pour gérer la notion POSIX de threads d'un ensemble de threads qui partagent un même PID. De manière interne, ce PID partagé est ainsi appelé identifiant de groupe de threads (TGID) pour le groupe de threads. Depuis Linux 2.4, les appels à getpid(2) renvoient le TGID de l'appelant.
Les threads dans un groupe peuvent être différenciés par (à l'échelle du système) leur identifiant de thread unique (TID). Un nouveau TID est disponible comme le résultat d'une fonction renvoyé à l'appelant de clone(), et un thread peut obtenir son propre TID en utilisant gettid(2).
Lorsqu'un appel à clone() est effectué sans spécifier CLONE_THREAD, le thread résultant est placé dans un nouveau groupe de threads dont le TGID est identique au TID du thread. Ce thread est le leader de ce nouveau groupe de threads.
Un nouveau thread créé avec CLONE_THREAD a le même processus parent que l'appelant de clone() (c'est-à-dire comme CLONE_PARENT), ainsi, les appels à getppid(2) renvoient la même valeur pour tous les threads d'un groupe de threads. Lorsqu'un thread créé avec CLONE_THREAD se termine, le thread qui a appelé clone() pour le créer ne reçoit pas le signal SIGCHLD (ou tout autre signal de terminaison) ; on ne peut également pas obtenir l'état d'un tel thread en utilisant wait(2). (Le thread est dit détaché.)
Après que tous les threads d'un groupe de threads se terminent, un signal SIGCHLD (ou tout autre signal de terminaison) est envoyé au processus parent du groupe de threads.
Si l'un des threads dans un groupe de threads effectue un execve(2), tous les threads autres que le leader du groupe se terminent, et le nouveau programme s'exécute dans le leader du groupe de threads.
Si l'un des threads d'un groupe de threads crée un fils avec fork(2), n'importe lequel des threads du groupe peut attendre (wait(2)) ce fils.
Depuis Linux 2.5.35, flags doit inclure CLONE_SIGHAND si CLONE_THREAD est spécifié.
Un signal peut être envoyé à un groupe de threads dans son ensemble (c'est-à-dire à un TGID) en utilisant kill(2), ou bien à un thread en particulier (c'est-à-dire à un TID) en utilisant tgkill(2).
Les dispositions et actions des signaux sont à l'échelle du système : si un signal non géré est délivré à un thread, cela affectera (terminaison, stop, continuation, ignoré) tous les membres du groupe de threads.
Chaque thread a son propre masque de signaux, configuré avec sigprocmask(2), mais les signaux peuvent être en attente soit pour le processus dans son ensemble (c'est-à-dire pour chaque membre du groupe de threads), lorsqu'ils sont envoyés par kill(2) soit pour un thread particulier lorsqu'ils sont envoyés par tgkill(2). Un appel à sigpending(2) renvoie un ensemble de signaux qui est l'union des signaux en attente pour le processus entier et des signaux en attente pour le thread appelant.
Si kill(2) est utilisé pour envoyer un signal à un groupe de threads et que ce groupe a installé un gestionnaire pour ce signal, le gestionnaire sera invoqué dans exactement un des membres du groupe de threads, arbitrairement sélectionné, parmi ceux qui n'ont pas bloqué le signal. Si plusieurs threads dans un groupe sont en attente du même signal en utilisant sigwaitinfo(2), le noyau en sélectionnera arbitrairement un pour recevoir le signal envoyé avec kill(2).
Bref, pour avoir un comportement similaire à win32, il me faut initialiser le thread avec les flags CLONE_VM et CLONE_THREAD, et donc initialiser une stack "à la main" avec mmap. Ce qui donne finalement le code suivant:
Code ASM :
BITS 32
; Defines for syscall numbers
%define SYS_WRITE 4
%define SYS_EXIT 1
%define SYS_CLONE 120
%define SYS_MMAP 90
; some mmap constants
%define PROT_READ 1
%define PROT_WRITE 2
%define PROT_EXEC 4
%define PROT_NONE 0
%define MAP_SHARED 0x01
%define MAP_PRIVATE 0x02
%define MAP_ANONYMOUS 0x20
%define MAP_GROWSDOWN 0x00100
; define useful constants for clone :þ
%define CLONE_VM 0x00000100 ; Set if VM shared between processes.
%define CLONE_FS 0x00000200 ; Set if fs info shared between processes.
%define CLONE_FILES 0x00000400 ; Set if open files shared between processes.
%define CLONE_SIGHAND 0x00000800 ; Set if signal handlers shared.
%define CLONE_THREAD 0x00010000 ; Set to add to same thread group.
global _start
section .data
tapz db "I am a tapz !", 0dh, 0ah, 0
threadtapz db "I am another tapz !", 0dh, 0ah, 0
failed db "I fail'd faggot", 0dh, 0ah, 0
section .text
; Notre thread
threadproc:
push ebp
mov ebp, esp
.loop:
mov edx, 21
mov ecx, threadtapz
mov ebx, 1
mov eax, SYS_WRITE
int 0x80
jmp .loop
leave
ret
; Fonction principale
_start:
push ebp
mov ebp, esp
sub esp, 8
; On réserve de l'espace pour la stack du thread (4ko)
push 0 ; offset
push -1 ; fd
push MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN ; flags
push PROT_READ | PROT_WRITE ; protections
push 1000h ; 4ko
push 0 ; pas d'adresse où se mapper
mov ebx, esp
mov eax, SYS_MMAP
int 80h ; appel du syscall
mov dword [ebp-4], eax
cmp eax, -1
jnz .next ; si l'allocation a réussi, on saute en .next
.failed:
; On affiche un message d'erreur & on GTFO
mov edx, 17
mov ecx, failed
mov ebx, 1
mov eax, SYS_WRITE
int 0x80
mov ebx, -1
mov eax, SYS_EXIT
int 80h
.next:
; On crée le thread
mov eax, dword[ebp-4]
add eax, 1000h ; Adresse du haut de la pile
mov ebx, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_THREAD | CLONE_SIGHAND
mov ecx, eax
xor edx, edx
xor esi, esi
xor edi, edi
mov eax, SYS_CLONE
int 0x80 ; appel du syscall clone
test eax, eax ; on check le PID (comme pour un fork)
jnz .parent
leave
jmp threadproc
.parent:
.loop:
mov edx, 15
mov ecx, tapz
mov ebx, 1
mov eax, SYS_WRITE
int 0x80
;.loop:
jmp .loop
Pour le compiler:
Code : nasm -f elf fichier.asm
ld -o prog fichier.o
Mon blog
Code : push esp ; dec eax ; inc ebp ; and [edi+0x41],al ; dec ebp ; inc ebp
"VIM est merveilleux" © supersnail
|