ゲストに割り当てられた物理メモリはQEMUの仮想アドレス空間にあります。ゲストの物理メモリが割り当てられている間はPLOT_EXECフラグ(plot引数はマッピングのメモリ保護をどのように行うかを指定する。_exec:ページは実行可能である)は可能にならないという点に注目してください。
QEMUはBIOSとROM用にメモリ領域を確保しています。マッピングはQEMUのmapsファイルにあります。
- 7f1824ecf000-7f1828000000 rw-p 00000000 00:00 0
- 7f1828000000-7f18a8000000 rw-p 00000000 00:00 0 [2 GB of RAM]
- 7f18a8000000-7f18a8992000 rw-p 00000000 00:00 0
- 7f18a8992000-7f18ac000000 ---p 00000000 00:00 0
- 7f18b5016000-7f18b501d000 r-xp 00000000 fd:00 262489 [first shared lib]
- 7f18b501d000-7f18b521c000 ---p 00007000 fd:00 262489 ...
- 7f18b521c000-7f18b521d000 r--p 00006000 fd:00 262489 ...
- 7f18b521d000-7f18b521e000 rw-p 00007000 fd:00 262489 ...
- ... [more shared libs]
- 7f18bc01c000-7f18bc5f4000 r-xp 00000000 fd:01 30022647 [qemu-system-x86_64]
- 7f18bc7f3000-7f18bc8c1000 r--p 005d7000 fd:01 30022647 ...
- 7f18bc8c1000-7f18bc943000 rw-p 006a5000 fd:01 30022647 ...
- 7f18bd328000-7f18becdd000 rw-p 00000000 00:00 0 [heap]
- 7ffded947000-7ffded968000 rw-p 00000000 00:00 0 [stack]
- 7ffded968000-7ffded96a000 r-xp 00000000 00:00 0 [vdso]
- 7ffded96a000-7ffded96c000 r--p 00000000 00:00 0 [vvar]
- ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
QEMUには2つのトランスレーション層があります。
・ゲストの仮想アドレスからゲストの物理アドレス
今回の攻撃では、DMAアクセスを必要とするネットワークカードデバイスを設定する必要があります(ネットワークカードデバイスを正しく設定するため、Tx/Rxバッファーの物理アドレスを設定します)。
DMAとは、CPUを使わず、バスを通じて周辺機器とメモリ間のデータ転送を直接行う機能です。
・ゲストの物理アドレスからQEMUの仮想アドレス空間
今回の攻撃では、(メモリリークするための)偽の構造体をインジェクトし、QEMUの仮想アドレス空間でのそれらの正確なアドレスを取得する必要があります。
x64システムでは、仮想アドレスはページオフセット(bits 0-11)とページナンバーで構成されています(ページサイズが4KB)。Linuxシステム上でページマップファイルは、各仮想ページがどの物理フレームにマップされているか調べるために、ユーザー空間プロセスをCAP_SYS_ADMINで有効にします。
ページマップファイルには、仮想ページごとに64bitが含まれています(kernel.org [5])。
- Bits 0-54 : physical frame number if present.
- Bit 55 : page table entry is soft-dirty.
- Bit 56 : page exclusively mapped.
- Bits 57-60 : zero
- Bit 61 : page is file-page or shared-anon.
- Bit 62 : page is swapped.
- Bit 63 : page is present
仮想アドレスを物理アドレスに変換します。
下記コード(mmu.c)でバッファーを割り当て、文字列「Where am I?」でバッファーを満たすことで、その物理アドレスを出力させます。
- #include <stdio.h>
- #include <string.h>
- #include <stdint.h>
- #include <stdlib.h>
- #include <fcntl.h>
- #include <assert.h>
- #include <inttypes.h>
- #define PAGE_SHIFT 12
- #define PAGE_SIZE (1 << PAGE_SHIFT)
- #define PFN_PRESENT (1ull << 63)
- #define PFN_PFN ((1ull << 55) - 1)
- int fd;
- uint32_t page_offset(uint32_t addr)
- {
- return addr & ((1 << PAGE_SHIFT) - 1);
- }
- uint64_t gva_to_gfn(void *addr)
- {
- uint64_t pme, gfn;
- size_t offset;
- offset = ((uintptr_t)addr >> 9) & ~7;
- lseek(fd, offset, SEEK_SET);
- read(fd, &pme, 8);
- if (!(pme & PFN_PRESENT))
- return -1;
- gfn = pme & PFN_PFN;
- return gfn;
- }
- uint64_t gva_to_gpa(void *addr)
- {
- uint64_t gfn = gva_to_gfn(addr);
- assert(gfn != -1);
- return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
- }
- int main()
- {
- uint8_t *ptr;
- uint64_t ptr_mem;
- fd = open("/proc/self/pagemap", O_RDONLY);
- if (fd < 0) {
- perror("open");
- exit(1);
- }
- ptr = malloc(256);
- strcpy(ptr, "Where am I?");
- printf("%s\n", ptr);
- ptr_mem = gva_to_gpa(ptr);
- printf("Your physical address is at 0x%"PRIx64"\n", ptr_mem);
- getchar();
- return 0;
- }
このコードをゲスト上で動かして、QEMUプロセスにgdbをアタッチすれば、ゲストに割り当てられた物理アドレス空間の中のバッファーがどこに位置しているか確認できます。出力されたアドレスは、ゲストの物理メモリのベースアドレスからのオフセットです。
root@debian:~# ./mmu Where am I? Your physical address is at 0x78b0d010 (gdb) info proc mappings process 14791 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x7fc314000000 0x7fc314022000 0x22000 0x0 0x7fc314022000 0x7fc318000000 0x3fde000 0x0 0x7fc319dde000 0x7fc31c000000 0x2222000 0x0 0x7fc31c000000 0x7fc39c000000 0x80000000 0x0 ... (gdb) x/s 0x7fc31c000000 + 0x78b0d010 0x7fc394b0d010: "Where am I?"
ここまでで、テスト環境構築を終えたので、次回は、CVE-2015-5165(メモリ情報漏えいの脆弱性)について、詳細に解説します。
慶應義塾大学 総合政策学部 2年生。中学高校はカナダのハリファックスとトロントに単身留学。将来はサイバーセキュリティの分野に進みたいと思っている。
Copyright © ITmedia, Inc. All Rights Reserved.
Security & Trust 記事ランキング