この記事はKogakuin Univ Advent Calendar 2022 - Adventar 7日目です。

枠、空いてますよ(チラッ。

ご挨拶

こんばんは。metarinです。
無限に遅刻投稿する人になってますごめんなさい。

12/9はごえくんがCrypto系の話をするらしい(さっき見たときはまだ投稿されてなかった)ので僕からはCTF繋がりでROPの話でもしようと思います。

ROP #とは

Return Oriented Programmingの略で、バッファーオーバーフローによる任意コード実行攻撃の手法の1つです。
後述するNXビットと呼ばれるセキュリティ機構を回避するために用いられてきたものです。

バッファーオーバーフロー

以下のC言語のコードを見てみて下さい

#include <stdio.h>
int main() {
    printf("What's your name? : ")
    char name[20] = {0};
    scanf("%s", name);
    printf("Hello %s. Nice to meet you.\n", name);
    return 0;
}

プログラミング経験のある方なら一回は見たことがあるであろう、入力された文字列を特定のフォーマットで返すだけのプログラムです。
このプログラムではユーザー名として20B確保しているので19Bまでなら正常に動作しますがそれ以上だと壊れる可能性があります。

この確保した領域以上のデータが書き込まれることをバッファーオーバーフローと言います。

バッファーオーバーフローによる任意コード実行

ではバッファーオーバーフローが発生しているときにどういったことが起きているのでしょうか?
動作を追って見てみましょう。

関数が実行される際には呼び出し元のアドレス(リターンアドレス)がスタックに積まれます。
そして終了時にそのアドレスをpopして実行することで、関数呼び出しが終わったら元の処理に戻るという実行フローを実現しています。

ここで呼び出した関数が終了する前にリターンアドレスが書き変わっていた場合はそのアドレスに飛んでしまいます。

NXビット

NXビット (No eXecute bit) は、ノイマン型アーキテクチャのコンピュータにおいて特定のメモリ領域(に置かれたデータ)に付与する実行不可属性、またはその属性付与機能を指す。

https://ja.wikipedia.org/wiki/NX%E3%83%93%E3%83%83%E3%83%88

x86(x86_64ではない)の頃は、メモリ内のデータに対して設定可能な属性としてRead-OnlyかRead-Writeかの2つしかなかったのです。
この状態だとスタック等の本来はデータの保存に用いるような領域にあるデータでも実行できてしまいます。
なので適当にシェルコードを仕込んでどうにかしてそこに飛ばす攻撃が流行りました。

これによって任意アドレスに飛ばせたところで、そのプログラム内の命令しか実行できないので被害が小さくなると…思われてました。1

ROP

ところでLinuxでシェルを取るためにはレジスタを特定の内容に設定してsyscallを呼べばいいですね。

また、ほとんどのプログラムで関数の最後だけを切り出すと 任意のレジスタをpop→ret という流れになってると思います。

すごい頭いいハッカー「いろんな関数の最後をツギハギすれば任意の値にレジスタの値を変更できるのでは…?」

ということで生まれた攻撃手法がROPです。Return-Orientedというのはなんらかの処理+retをツギハギするところから来ています。
この攻撃では既存のプログラムの一部を使い回すためNXビットは効果を発揮しません。

Intel CET

Intelの第11世代以降のCPUにはIntel CET(Control-Flow Enforcement Technology)が導入されています。
この技術が有効になっている場合、スタックの写しを別所に取っておきそれと矛盾が発生した場合に強制終了するのでバッファーオーバーフローを殺すことができます。

やったね!!!

まとめ

バッファーオーバーフローの原理と攻撃方法についてさらっと話しました。
バッファーオーバーフローを埋めこむと、どう攻撃されるかのイメージについてふんわり掴めたかと思います。

これをきっかけに改めて脆弱性を埋めこまないように気をつけてもらえば幸いです。


  1. 実はOne Gadgetに対して無力なのでROP抜きでも結構致命的な攻撃があったりする