newbieからバイナリアンへ

newbieからバイナリアンへ

昨日は海を見に行きました

【pwn 36.0】Intel CETが、みんなの恋人ROPを殺す

keywords

Intel CET / shadow stack / indirect branch tracking / もらとりあむ

 

 

1: イントロ

こんにちは、ニートです。

1週間程前、Intelは第11世代Coreプロセッサ (コードネーム: Tiger Lake) を正式に発表しました。なんかGPUがどうのこうのとかWi-Fi6がどうのとかあるらしいですが、CPUよく知らないので知りません。

但し、重要な事としてこのアーキテクチャIntel CET という機能を実装しています。そしてこの CET は皆が大好きなexploit techniqueであるROPを殺してしまうと言われています。

本エントリでは、今更ながら Intel CET の概要について触れ、これによって今まで成功してきたexploitが Tiger Lake 以降の Intel CPU に於いてどうなってしまうのかを見てみたいと思います。尚、今回「参考」は一番下に纏めて掲載しています。

 

 

2: Intel CET 概観

もともと Intel CET が発表されたのは、もう3年以上前のことである。何度かの延期を経て、2020年末に Tiger Lake 搭載端末が発売されると言われている。Tiger Lakeが実装している機能の内今回注目するのは、Intel CETのみである。

Intel CET: Control-flow Enforcement Technology は、端的に言うと「ROPを殺す機構」である。大別して "Shadow Stack""Indirect Branch Tracking" という2つの要素から構成されている。その結果として、「スタックフレーム中の Return Addressの保護」「jmp/callに用いる free branch の保護」を実現する。

以下では、CETを構成する2大要素について見ていく。尚、以下の記述は参考Aのspecificationに大きく依っている(発売していない以上正解は仕様書にしか無い)。

 

【追記: 20200912】

同様の考えででROPを防ぐ方法は、過去にもRAPという名前で10年近く前から提唱されている。CETはこれをアーキテクチャレベルで実装している。RAPについては以下を参考のこと。

a: ももいろテクノロジー

http://inaz2.hatenablog.com/entry/2015/10/30/024234

b: gresecurity

https://grsecurity.net/rap_faq

 

 

3: Shadow Stack

Shadow Stackスタックフレーム中に保存してある RA: return address を保護する

Shadow Stack は従来の関数スタックに追加で別のスタックを確保される。以降は call 命令の度に従来のスタックと Shadow Stack の両方にRAをpushする。そして関数から ret する際にはやはり従来のスタックと Shadow Stack の両方から値を pop する。ここでpopした値が同じであるならば、スタックフレーム中のRAが書き換えられていないことが保証されるというわけである。尚、ここでpopした値が異なる場合にはプロセッサは #CP: Control Protection Exception という例外を通知する。

尚、CPL3間でのジャンプならば述べたとおりだが、CPL3とCPL012間でのジャンプの場合には Shadow Stack は使用されず、代わりに MSRが用いられる。Shadow Stack は CPL毎に初期化され、CPLの切り替わり時に通常のスタックのように該当するCPLのスタックに切り替わる (SSP: Shadow Stack Pointer が書き換えられる)。

 

Shadow Stack の switch

Shadow Stack の切り替えには RSTORSSP / SAVEPREVSSP という一対の命令が用いられる。

RSTORSSP は現在の Shadow Stack から切り替え先の Shadow Stack に SSP を書き換える命令である。同時に、新しい Shadow Stack の最も上に有る shadow stack restore token正当性を確認する。shadow stack restore token は、それがpushされている Shadow Stack のアドレスを示しているはずである(下位2bitを除く)。そうでなければやはり#CP例外を通知する。正当性を確認した後はその shadow stack restore token を遷移元である Shadow Stack の SSP で書き換えて previous SSP token とする。

SAVEPREVSSP は以前の Shadow Stack へと SSP を切り替える命令である。現在の Shadow Stack のてっぺんから previous SSP token をpopし、これが示すアドレスに shadow stack restore token (previous SSP tokenである自分と1bit目を除き同じ)をpushする。

 

f:id:smallkirby:20200911065912p:plain

引用: https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf

f:id:smallkirby:20200911065928p:plain

引用: https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf




 

  

じゃあ、ShadowStackを書き換えればいいじゃん?

不可能である。

CETの実装に伴い、ページテーブルに Shadow Stack という属性が追加された。これによって Shadow Stack とマークされたページに対するアクセスは control flow を変える命令からしか不可能になった。mov / xsave 等の命令によるアクセスは SEGV となる。

スタックだろ? アンダーフローさせちまえよ

不可能である。

前述したように Shadow Stack を操作し得る命令は限られている。これらの命令を呼んでスタックをアンダーフローさせるには、RIPを操作して該当命令を複数回呼んでpopを繰り返す必要が有る。しかし、そもそもにこのRIPを操作することがCETによってできなくなっている。まさに R.I.P. ってね....

 

 

 

4: Indirect Branch Tracking

CPUは indirect jump/ call 命令を追跡する為に状態を保持する。jmp / call 命令の前にはIDLE 状態であり、これらの命令が呼ばれると WAIT_FOR_ENDBRANCH 状態に切りかわる。WAIT_FOR_ENDBRANCH 状態の時に ENDBR32/64 命令以外が実行されると、#CP例外を通知する。換言すれば、jmp/call による遷移先は endbr32/64 でなければならないということである。

ハードウェアは Indirect Branch Tracking のためにCPL3用とそれ以外用として2つの状態機械を構成する。どちらの状態機械を用いるかは、その時のCPLに依存する。CPL3とCPL012間での追跡については多少煩雑であるため、参考Cを参照されたい。

尚、endbr32/64 命令はCET非対応アーキテクチャに於いてはただの NOP となる。また、対応機であっても有効化されていない場合にはただの NOP として扱われる。更に、対応機且つ有効化されていたとしても状態機械の状態評価に使われる以外は NOP と同じで環境に全く影響しない。

既に gcc 君は対応するコードを吐いてくれる。

 

 

じゃあendbr64から始まるアドレスに不正にJMPすることはできるんだろ?

Yes.

RA を書き換えて Shadow Stack に引っかかったりしないような場合、例えば関数テーブルを書き換えるような場合には Indirect Branch Tracking には引っかからない。但し、chain を組むことはできないため単発でできるようなことに限られてしまう。

 

 

5: CETが有効になる条件

まずは CR4 レジスタCET bit(24th) がオンになっていることが必要である。

その上で Shadow Stack や Indirect Branch Tracking はMSRによって別々に機能制限をすることができ、例えば IA32_U_CET で CPR=3 の際のCETのコンフィグを行うことができる。この値は cpuid によって知ることができる。

 

 

 

6: 実際にIntel CET上でROPをしようとしてみる

Intel SDE によって Tiger Lake をエミュレートする。

まずは、参考Cからtar.bzファイルをダウンロードしてきて展開する。基本的にはそれだけだが、Linuxに於いては ptrace できる範囲が通常 1: restricted ptrace (非rootは子プロセスのみ・rootは任意)になっているため、以下で 0: classic ptrace permissions にする必要が有る。(次のrebootまで有効)

# echo 0 > /proc/sys/kernel/yama/ptrace_scope

 

まずは Shadow Stack の働きをチェックする。使用するプログラムは以下の通り。

#include<stdio.h>
#include<stdlib.h>

char mora[] = "     ` (#!    `   `....        ...,......TN.`\n      j@`  ``.JgHH=?\"\"W%`     TB\"7!(\"TY` db.\n    `-#!     _ue    ...      `.de.......  .M;\n   ` dD      .dMY9_T\"TMb      ?MY?!_??T#_  dR`\n     W]    `  j#      dD      .Wl     j@`` J@   < I am Winner!!!\n     W]  `    .N+   .(#!       ?N.. .(H'  .W%\n     db. +#WNx _TBD`?=     `     ?!(\"=   .d$\n     .Me Wm(d9       . .. .-. .,        .dD\n      .T\\ _!`       .TH#WHB7HMYWH%     .\"!\n";

void win(void){
  printf("%s", mora);
  exit(1);
}

void evil(void){
  char buf[0x10];
  printf("I am evil moratorium.\n");

  *(unsigned long*)(buf+0x28) = (unsigned long)((char*)&win + 0x4);
}


int main(int argc, char *argv[])
{
  printf("Start\n");

  evil();
  printf("CET is winner...\n");

  return 0;
}

evil() 関数に於いて自身の RA を win()+4 に書き換えている。

 

これを 非CET環境 (Ubuntu20.04 glibc2.32 kernel5.4.0-47-generic)とCET環境に於いて動かした場合のそれぞれの結果は以下である。

f:id:smallkirby:20200911072718p:plain

Shadow Stack kills ROP

落ちている。ROPが死んだ...

SDEでは -debug オプションをつけることで勝手に gdb server を建ててくれるため容易にデバッグができる。見てみたところ、win() の最初の命令を実行する直前で落ちている。SEGVで落ちてる理由は、知らん、なんやコレ。 

f:id:smallkirby:20200910222934p:plain

GDBデバッグ

 

エラーを見てみると以下のようになっている。 

f:id:smallkirby:20200911071526p:plain

error dump due to "Control flow error"

Shadow Stack は 0x40120f の ret に於いて 0x40119a(main) をpopしたが、通常のスタックからは 0x401234(win) をpopしたためにエラーになったという旨である。ROPは、CETによって殺されてしまったようだ...

 

 

また、同様にして Indirect Branch Tracking に引っかかるような以下のコードも考える。

#include<stdio.h>
#include<stdlib.h>

char mora[] = "     ` (#!    `   `....        ...,......TN.`\n      j@`  ``.JgHH=?\"\"W%`     TB\"7!(\"TY` db.\n    `-#!     _ue    ...      `.de.......  .M;\n   ` dD      .dMY9_T\"TMb      ?MY?!_??T#_  dR`\n     W]    `  j#      dD      .Wl     j@`` J@   < I am Winner!!!\n     W]  `    .N+   .(#!       ?N.. .(H'  .W%\n     db. +#WNx _TBD`?=     `     ?!(\"=   .d$\n     .Me Wm(d9       . .. .-. .,        .dD\n      .T\\ _!`       .TH#WHB7HMYWH%     .\"!\n";
void (*fp)();

void win(void){
  printf("%s", mora);
  exit(1);
}

void f1(void){
  printf("In f1\n");
  return;
}

void f2(void){
  printf("In f2\n");
  return;
}

void evil(void){
  char buf[0x10];
  printf("I am evil moratorium.\n");

  fp = ((char*)f2) + 4; // skip endbr
  fp();
  *(unsigned long*)(buf+0x28) = (unsigned long)((char*)&win + 0x4);
}

int main(int argc, char *argv[])
{
  printf("Start\n");

  fp = f1;
  evil();
  printf("CET is winner...\n");

  return 0;
}
    

関数ポインタを書き換えているが、その際に endbr を飛ばしている。この状態でのCET/非CET環境での実行結果は以下の通り。

f:id:smallkirby:20200911073003p:plain

IBT also kills ROP...

Control flow ENDBRANCH error detected だそうだ
 

 

 

7: Bypass

わからん。

思いつかない。

でも DEP が実装されたと思ったら ROP ができたわけだから、CET が実装されたらなんか exploit 出てくるやろ。頭いい人、頼んだわ。

 

 

8: アウトロ

悲しいね...

 

 

 

0: 参考

A: CET Specification. by Intel

https://software.intel.com/sites/default/files/managed/4d/2a/control-flow-enforcement-technology-preview.pdf

B: CETのスライド. by H.J.Lu, Intel

https://www.linuxplumbersconf.org/event/2/contributions/147/attachments/72/83/CET-LPC-2018.pdf

C: Intel SDE

https://software.intel.com/content/www/us/en/develop/articles/intel-software-development-emulator.html

D: Blackhatのスライド

https://i.blackhat.com/asia-19/Thu-March-28/bh-asia-Sun-How-to-Survive-the-Hardware-Assisted-Control-Flow-Integrity-Enforcement.pdf

E: Userland Emulation of CET using Intel SDE

https://software.intel.com/content/www/us/en/develop/articles/emulating-applications-with-intel-sde-and-control-flow-enforcement-technology.html

F: XSTATE Internals

https://windows-internals.com/cet-on-windows/

 

 

 

 

 

 

続く・・・

 

 

 

 

 

You can cite code or comments in my blog as you like basically.
The exceptions are when the code belongs to some other license. In that case, follow it. Also, you can't use them for evil purpose. Finally, I don't take any responsibility for using my code or comment.
If you find my blog useful, I'll appreciate if you leave comments.