アセンブリ言語で直接CPUへ命令(システムコール)
をすることによって、"Hello World"を出力させるプログラムを作りました。
1、winAPIを使ったパターン
(winAPI関数を2回使用・・・)
s
1LFB10: 2 3 sub $20,%esp 4 pushl $-11 5 call _GetStdHandle@4 6 pushl $0 7 leal 4(%esp), %ebx 8 pushl $0 9 pushl %ebx 10 pushl $14 11 pushl $LC0 12 pushl %eax 13 call _WriteFile@20 14 add $20,%esp 15 16 LC0: 17 .ascii "Hello, world!\n" 18
2、printf関数を読んだパターン
s
1.text 2 msg: .ascii "%s\0"; 3 msg4: .ascii "Hello World!\0" 4 .globl _main 5 6_main: 7LFB10: 8 9sub $20,%esp 10movl $msg4,4(%esp) 11movl $msg,(%esp) 12call _printf 13add $20,%esp
スライディングコード
実行される位置がランダムになってしまうため
無駄な処理をシェルコード先頭に配置してい置き
本命のコードが実行されることを期待する・・・
ための工夫(多分)
設定でランダムは消せると思うのでたぶんいらない。
バッファオーバーフロー
でシェルコードを実行させたいときは工夫が必要いわれていますが
私は自分のパソコンで作成したシェルコードを
自分のパソコンで実行させようとしています。
それなら、アドレスのランダム化も無効化してしまえばどうにかなるし
ヘッダーファイルの場所を記録するIATも
同じ端末で実行させるなら考慮しなくていいはずなので
ほぼコードを変える必要性が無いと思うのですが
IDA Proも導入したはいいものも・・・
(実行ファイルの中身を16進数で表したもの)
膨大な数のこのコード(?)
バッファオーバーフローでシェルコードとして使いたい場合
どのようなところをアレンジする必要があるのでしょうか?
分からないのでお願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/27 03:50
回答3件
0
ベストアンサー
先の質問に対する回答の中でも触れましたが、シェルコードとして実行させるというのは実行中の別の (脆弱性がある) プログラムの中に差し込んで実行させます。 それによって既に走っているプログラムの権限を乗っ取るのが基本的な形です。 ですから、シェルコード自体にはセクションも何もないです。
しいて言えば .text
相当のみがある状態と言えます。
失礼な言葉になってしまうかもしれませんが、質問者はシェルコードがどういうものなのか理解するのに充分な前提知識がありません。 先に触れたことを理解していな状態で似たような次の質問をどんどん連投するのはあまりよくないですね。 まずは普通のアプリケーションプログラムを満足に書ける程度にはなってから次の段階へ移るべきだと思いますが、どうしてもシェルコードというものに興味があるのだということであれば「Hacking: 美しき策謀 ―脆弱性攻撃の理論と実際」という本が手頃だと思います。 安価ですし。
投稿2019/10/27 03:29
総合スコア5684
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
インポートライブラリを使うことは無理なので(詳しくはデバッガや逆アセンブラで、生成されている機械語を見てください)
まず、アドレスを特定する
c
1#include <windows.h> 2#include <stdio.h> 3 4int main(){ 5 HINSTANCE KernelDll = GetModuleHandle("Kernel32.dll"); 6 void* WriteFile = GetProcAddress(KernelDll, "WriteFile"); 7 void* GetStdHandle = GetProcAddress(KernelDll, "GetStdHandle"); 8 void* ExitProcess = GetProcAddress(KernelDll, "ExitProcess"); 9 10 printf("KernelDll = %p\n%%define WriteFile 0x%p\n%%define GetStdHandle 0x%p\n", KernelDll, WriteFile, GetStdHandle); 11 printf("%%define ExitProcess 0x%p", ExitProcess); 12}
それを用いて
nasm
1; 注: gasではなくnasmアセンブラを用いること 2bits 32 3; 以下に上のプログラムで特定したアドレスを入れる 4%define WriteFile 0xFFFFFFFF 5%define GetStdHandle 0xFFFFFFFF 6%define ExitProcess 0xFFFFFFFF 7; global _main 8; section .text 9; _main: 10xor ebx, ebx 11push -11 12mov eax, GetStdHandle ; GetStdHandle(-11) 13call eax 14 15push ebx ; "\0" 16push 0x0A0D444C ; "LD\r\n" 17push 0x524F574F ; "OWOR" 18push 0x4C4C4548 ; "HELL" 19mov edx, esp ; edx = "HELLOWORLD\r\n\0" 20 21push ebx; NULL 22push esp; &written 23push 13 ; len 24push edx; buf 25push eax; stdout 26mov eax, WriteFile ; WriteFile(stdout, buf, len, &written, NULL) 27call eax 28 29push ebx 30mov eax, ExitProcess 31call eax
にてそれっぽいものができました。
投稿2019/10/28 13:02
編集2019/10/28 14:41総合スコア15149
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/28 20:57
2019/11/01 03:47
2019/11/01 04:10
2019/11/01 04:12
2019/11/01 04:15 編集
2019/11/01 04:25
2019/11/01 05:27
2019/11/01 05:35
0
アレンジ可能かどうか具体的なアドバイスは可能だと思う、と書いた手前、回答してみる。
- 攻撃対象のプログラムは scanf() 等で、スタック上の文字配列(ローカル変数)にシェルコードを読み込み、バッファオーバーランを起こす
- 攻撃対象プログラムの文字配列の先頭アドレスがわかる
- 攻撃対象プログラムの_printf のアドレスがわかる
- 上記の printf() を呼ぶパターン
という前提で考えてみた。SaitoAtsushiさん曰く
しいて言えば .text 相当のみがある状態
であって、〜〜ヘッダ領域などは一切不要。
さらに切り詰めれば、おおよそ次のようなコードがあれば良いのではないか。
gas
1shcode: 2 movl $shcode-8, %esp # スタックポインタを調整 3 movl $msg,(%esp) 4 call _printf # printf("Hello World!\n") ; 5 ret # 表示後、どうすれば良い? 6 7msg: 8 .ascii "Hello World!\n\0" 9 .rept 20 # バッファオーバーランするバイト数 10 .byte 0x90 11 .endr 12 .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する部分はこう。
gas
1 xor rcx, rcx /* rcx := 0 */ 2 push rcx /* push した 0 が '\0' の代り */ 3 mov rax, 0x68732f2f6e69622f /* == "/bin//sh" */ 4 push rax /* スタックに 8 文字を push */ 5 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/10/27 09:47
編集2019/11/08 15:06総合スコア1382
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/27 10:08
2019/10/27 10:21 編集
2019/10/27 14:11
2019/10/27 15:09
2019/10/27 21:33
2019/10/28 06:57
2019/10/28 11:40 編集
2019/10/28 11:47
2019/10/28 13:54 編集
2019/10/28 13:49
2019/10/28 20:45
2019/10/28 21:00
2019/11/07 23:14
2019/11/07 23:44 編集
2019/11/08 00:19
2019/11/08 00:37
2019/11/08 15:14
2019/11/08 15:22 編集
2019/11/08 20:35
2019/11/09 01:21
2019/11/09 02:48 編集
2019/11/09 02:49
2019/11/09 03:02
2019/11/09 03:13
2019/11/09 03:17
2019/11/09 03:20
2019/11/09 03:32
2019/11/09 03:56
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。