すごいC言語、たのしく壊そう
素行の悪いCプログラミング入門
世の中にはひどい人もいたものである。素行の悪いCプログラマーがどのようなコードを書くか、その邪悪さの片鱗をご覧にいれようと思う。
未初期化変数の使用
言うまでもなく、初期化していない変数を参照外し (dereference) するのは「未定義の動作」を引き起こすため良くないことである。正しいCプログラマーはそんなことをしてはならない。
CERT Cコーディングスタンダードはもとより、未定義や実装定義の部分を排除するMISRA Cでも初期化していない変数を読み込んではならないとしている。
CERT C コーディングスタンダード EXP33-C. 初期化されていないメモリからの読み込みを行わない
MISRA C Rule 9.1 “The value of an object with automatic storage duration shall not be read before it has been set"
MISRA Cでは”objects with static storage duration” (要するに通常は.bssセクションか.dataセクションに配置されるグローバル変数)については0初期化されるから違反でないとしているし、CERTのように「メモリ」と言わず、ぼくのように「変数」とも言わず、Cの標準に則って「オブジェクト」と書いており、なかなか好感を持てるのだけど、それはそれとして...
行儀の悪いプログラマーは、これらのコーディングスタンダードから逸脱した次のようなコードを平気な顔をして書くのである。悪い猫である。
実を言うと、おそらく全ての場合において、予期せぬ動作は起こらず常に「a、bを0で初期化する」という動作となる。aが何であれ、a自身とのXORは0x00000000となるからだ。また、0とANDをとれば必ず0になるのだから、bもまた0以外になりようがない。
...とは言っても、こんなコードを書くべきでない。素直に
と書けばよいのだ。0x00を含まないシェルコードを書くわけじゃないのだから。CTFのpwnではないのだから。
もう一つ例をあげてみる。cは1で初期化され、のちに32を再代入され、dは0x100000000000000000000000000000000 (4294967296) になるかと思います。
バッファーオーバーフローによる代入
ローカル変数 (というかautomatic storage durationのobject) はほとんどのアーキテクチャにおいてautomatic storageとしてスタックに変数を積んでいく。そのため、スタックを意図的に溢れさせる (オーバーフローさせる) ことで変数の値を書き換える、言い換えると変数に意図した値を代入することができる。
これはセキュリティ技術者向けの頭の体操として、stack-based buffer overflowの練習問題として、猫のぼくがかつて作成したコードですが、もちろん正しいCプログラマーはこんなコードを書いてはいけない。
このコードを実行すると、cの下位32ビット (0x0000...000) はbにコピーされるが、bは32ビットであってcの上位32ビット分が溢れてしまう (バッファーオーバーフロー。) この溢れた32ビット分は 0x00...01 (=1) であり、これはbの隣のaにコピーされる。その結果として、a = 1, b = 0となる。
言うまでもなく、正しいCプログラマーは
と書くべきでしょう。
NULLポインターを配列として扱う
C言語においては、配列なんてものは結局のところただのポインター演算の構文糖に過ぎない。例えば、
という代入文の意味するところは、
となんら変わらない。なので、a + i = i + aであることから、なんなら
と書いても構わない。つまり、配列aの0番目の要素に10を代入したければ、
でなく、
と書いても構わない。とは言っても、読みにくくなるだけなので、こんなコードは書くべきでないし、次のようなコードを書くのは狂っているとしか言えない。
もう少しひねりを効かせて一目でNULLポインターとわからなくするには、例えばこのように書くと良い。
血迷って、"このように書くと良い”とは言ったが、このようなコードを書いてはならない。
main関数がない。かわりにmain変数がある
ご存知の通り、確かにmainというシンボルが見つからないときにはリンカーがエラーとして扱ってしまうため、mainシンボルが存在しない場合はexecutableを作ることはできない。
が、シンボルがあれば良いのであって、誰もmain 関数 が必要だなんて言っていない。main 変数 でも良い。そして、main変数に機械語をダイレクトに書いていけば、きちんと動作するプログラムを作成することができる。
例えばこんなの。x86でDEPが無効なら動くはず。
グローバル変数は通常、初期値ありなら.dataセクション、なしなら.bssセクションに配置されるが、これらのセクションは実行可能属性が付与されずDEP (データ実行保護) が有効なときは実行時例外を吐き出してSEGVで死ぬ。DEPが有効の環境では、GCCの拡張によって配置するセクションを指定する必要がある。
これなら動く。
動くには動くけど、言うまでもなく、こんなコードを書くのは素行の悪いプログラマーとみなされて当然であるし、人権を奪われて猫になるほかない。
ちなみに、上の機械語の列は、先日シェルコーディングの実習と称してぼくが寝転びながら (ネコがネコろぶ) てきとーに書いたもので、”/bin/cat /etc/passwd”を引数としてシステムコールexecveを呼び出す。具体的には下記のようなアセンブリから生成している。









