"main関数の無いプログラム"の解析
id:selvaggio:20061007:1160220826のやっていたmain関数のないプログラムの解析。
まずは、コードそのものを示しておく。
#include "stdio.h" void __stdcall put(char c) { putchar(c); } extern int ret[1]; unsigned int main[] = { 'Ah', 'HHH�\0', 'hHHH', (int)ret, 'hHHH', (int)put, -61, }; int ret[] = {-61};
まったくもってすばらしい。main関数がないこと自体は別にそれほど驚くことじゃないんだけど、その中身のほとんどがASCIIの範囲に収まっていることがすばらしい。
さて詳しく解説しようか。
main関数がなくてもいい理由
main配列がmain関数の代わりをしているから。
この辺りを詳しく書くと本が一冊できるので、非常に簡単に説明するよ。詳しく知りたいなら、GNU開発ツールを勧めとく。
まず、main関数の場合、mainというのは一連の処理(関数の中身)の先頭に付けられた名前になる。
そして、main配列の場合、mainというのは一連のデータ(配列の中身)の先頭につけられた名前になる。
さらに、昔からコンピュータは処理は"機械語の並び"というデータに変換している。
だから、main配列の中身を"機械語の並び"にしておけば、あたかもmain関数であるかのように扱える。
main配列の中身
問題は、main配列の中身。なんで、これがASCIIで書けるのかが肝。
シングルクオートの中に4文字も書いているのは、たぶんunsigned intの配列だからだろう。(sizeof(unsigned int) == 4 * sizeof(char))
まずは、文字を16進数に直す。
文字 | 16進数 |
---|---|
Ah | 00 00 41 68 |
HHH�\0 | 48 48 48 00 |
hHHH | 68 48 48 48 |
(int)ret | - |
hHHH | 68 48 48 48 |
(int)put | - |
-61 | ff ff ff c3 |
そして、これをmain配列の中身を逆アセンブルしたものと比較する。
0: 68 41 00 00 00 push $0x41 ; 'A'のASCIIコード 5: 48 dec %eax 6: 48 dec %eax 7: 48 dec %eax 8: 48 dec %eax 9: 48 dec %eax a: 48 dec %eax b: 68 1c 00 00 00 push $0x1c ; retへのポインタ 10: 48 dec %eax 11: 48 dec %eax 12: 48 dec %eax 13: 68 00 00 00 00 push $0x0 ; putへのポインタ 18: c3 ret 19: ff (bad) 1a: ff (bad) 1b: ff c3 inc %ebx
Intelシリーズは、リトルエンディアンだから、数字が下の桁から格納されていることに注意すれば、この2つが同じものなのは分かると思う。
ここから先はお手上げ。なんで、jmpもcalもないのに、関数が呼べるのよ。