質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.23%

関数へのポインタでの関数呼び出しについて

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 1,803

プログラミング初心者なので暖かい目で見ていただけると幸いです。

execve("/bin/sh", ["/bin/sh", NULL], NULL)を16進数に変換し、C言語で呼び出すことで、シェルを立ち上げるというやり方について、少し疑問があったので質問させていただきます。

#include <stdio.h>
typedef void (*func)();

char shellcode[] = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80";

int main(void) {
    ((func)&shellcode)();
//    ((func)shellcode)(); // こっちでも呼び出せる??

    return 0;
}

16進数をリトルエンディアンで文字列としてグローバル変数shellcodeに定義しておき、main関数内で呼び出しを行っています。

簡単のため、事前にtypedefにより、
引数を取らず、void型を返す関数へのポインタ型をfunc型
と定義しています。

私は、アスタリスク演算子はメモリ上のその変数がある番地の値を取得、アンパサンド演算子はメモリ上のその変数がある番地を取得(この部分について、少し自信がないので今回質問させていただきました)していると考えているのですが、
番地でも、その番地に格納された値でもfunc型にキャストして関数呼び出しができてしまうのは何故なのでしょうか?

ポインタの初歩的なところでつまづいてしまっていますが、どなたかご教授いただけないでしょうか?

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+4

shellcodeのように、配列の名前を式の中に書くと、自動で「配列の先頭アドレス」として処理されます。一方、明示的に&shellcodeとアドレスを取ると、それは「配列全体のアドレス」となります(参考)。型は違いますが位置は同じです。

つまり、shellcode&shellcodeも、キャストしてしまえば何も変わりません

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/01/29 15:54

    &shellcodeなんて自分で書いたことなかったので「配列全体」を指すとは知りませんでした。
    printf("%d\n", sizeof *shellcode);
    printf("%d\n", sizeof *&shellcode[0]);
    printf("%d\n", sizeof *&shellcode);
    で、違いが分かりますね。

    キャンセル

  • 2016/02/08 01:05

    連絡がおくれてしまって大変申し訳ないです。
    回答ありがとうございます。
    頂いた参考記事、とても勉強になりました。
    Cを触った時に、ポインタについても少し触れていたものの、理解が浅く、手間取ってしまう場面が多かったのですが、アプローチ(考え方?)が間違っていたようです。
    ありがとうございます。

    キャンセル

+2

(これは質問への直接回答ではありませんが)

execve("/bin/sh", ["/bin/sh", NULL], NULL)を16進数に変換し、C言語で呼び出すことで、シェルを立ち上げると
(略)
16進数をリトルエンディアンで文字列としてグローバル変数shellcodeに定義しておき、main関数内で呼び出し

目的と実装がマッチしていないのですが、大丈夫でしょうか?あなたのコードが実際に行っていることは、shellcodeに格納されているバイト列をマシン語命令とみなして、それを実行時に呼び出すという(かなり危険な)処理になっています。実際に動作させるとクラッシュするのでは?


一応、該当のマシン語命令を逆アセンブルしてみましたが全く意味のない命令列になっています。(Intel x86と仮定)

$ printf "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" > shellcode.bin
$ objdump -D -b binary -m i386 shellcode.bin
   0:    31 d2                    xor    %edx,%edx
   2:    52                       push   %edx
   3:    68 2f 2f 73 68           push   $0x68732f2f
   8:    68 2f 62 69 6e           push   $0x6e69622f
   d:    89 e3                    mov    %esp,%ebx
   f:    52                       push   %edx
  10:    53                       push   %ebx
  11:    89 e1                    mov    %esp,%ecx
  13:    8d 42 0b                 lea    0xb(%edx),%eax
  16:    cd 80                    int    $0x80

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/08 01:14

    申し訳ありません。
    私の日本語力がないばかりに、質問文が分かりづらい表現となってしまっていました。
    このやり方は、ご指摘頂いたとおり、危険なやり方になると思われます。
    shellcodeの箇所をアセンブラに戻すと、割り込みシステムコール呼び出しにより、execve("/bin/sh", ["/bin/sh", NULL], NULL);を呼び出すようになっています。
    もちろん、本来ならばこのようなやり方はおかしいはずで、ただインラインアセンブラを書きたいだけなのであれば__asm__()を呼び出すことで実現できると思います(あまり詳しくないので、これは適切じゃないかもしれません・・・)。
    ですが今回は、スタック上に置かれた配列に対し、ユーザからの入力によってバッファーオーバーフローが起きた際に、うまいことshellcodeの先頭部分にEIPが飛び、shellcodeが実行されてしまう場合について考えています。
    その勉強をしていたところ、質問文にあるような関数呼び出しが行われていたため、なぜ異なった呼び方なのに結果的には同じようにshellcodeが呼び出せてしまうのだろう・・・?と思い、質問させていただきました。

    キャンセル

  • 2016/02/08 01:15

    知見が浅いため、表現が不適切な箇所が散見されるかもしれません。
    申し訳ありません。

    キャンセル

  • 2016/02/08 11:54

    「shellcodeの箇所をアセンブラに戻すと、割り込みシステムコール呼び出し」
    なるほど言われてみれば、ちゃんと読むとそのようなアセンブラコードになっていますね。失礼しました。

    キャンセル

  • 2016/02/08 14:59

    貴重なご指摘をいただけて大変嬉しいです。
    明確に「こういう部分がいけないきがする」とご指摘いただけたので、より知見を深めることができました。
    よろしければ、今後ともご指摘いただけると幸いです。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.23%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る