回答編集履歴
3
コメントに対して回答追加
answer
CHANGED
@@ -35,4 +35,47 @@
|
|
35
35
|
|
36
36
|
ただし、これを**アセンブルしただけでは、目的のシェルコードは得られない**。アセンブルさせてみた上で、バイナリエディタなどでファイル化するのが、愚直だが確実な方法だと思う。アセンブラ・リンカなど、正規の使い方で作れるものではないのだから工夫が要る。
|
37
37
|
|
38
|
+
---
|
39
|
+
> "Hello World!"を出力するプログラムを作ったとして
|
40
|
+
> コンパイルした機械語をアレンジする必要があるのですか?
|
41
|
+
|
42
|
+
「〜を出力するプログラム」は、自分自身の、通常のメモリ配置で動作するが、
|
43
|
+
シェルコードは他人(攻撃対象)が動作するメモリアドレスで動作しなければならない。プログラムコード領域(コードセグメント?)で動作する想定でコンパイルされるコードが、攻撃対象のスタック領域(スタックセグメント?)で動作することになる(のだよね?)。
|
44
|
+
|
45
|
+
つまり動作環境(メモリアドレス)がガラっと変わる事を忘れてはいけない。コードの配置場所が違ってしまうので、関数呼出し等ができなくなったり、文字列のようなデータの在り処(メモリアドレス)が変わってしまうという事。だからアレンジが必要になる。
|
46
|
+
|
47
|
+
結論的に言えそうな事は、**シェルコード中の命令で、オペランドにメモリアドレスが現れる命令が要注意**だと思う。私が示したコードでは、次の4行。
|
48
|
+
- movl $shcode-8, %esp
|
49
|
+
- movl $msg,(%esp)
|
50
|
+
- call _printf
|
51
|
+
- .long shcode
|
52
|
+
|
53
|
+
一方、[シェルコード書いてみた](https://qiita.com/slowsingle/items/59c139b747edec9157cc)のアセンブリコード connect.s には要注意な命令は無い。connect.s をアセンブルして得られた機械語はそのまま利用可能と思われる。その違いをまとめると次のようになる。
|
54
|
+
|
55
|
+
- スタックポインタを変更していない("movl $shcode-8,%esp" 相当の操作無し)
|
56
|
+
- 文字列 "/bin//sh" をそのままpushしている("movl $msg,(%esp)" に相当)
|
57
|
+
- syscall 命令を使う("call _printf" ではなく)
|
58
|
+
- リターンアドレスを上書きする値を含んでいない(".long shcode" が無い)
|
59
|
+
|
38
|
-
|
60
|
+
文字列をpushする部分はこう。
|
61
|
+
|
62
|
+
```gas
|
63
|
+
xor rcx, rcx /* rcx := 0 */
|
64
|
+
push rcx /* push した 0 が '\0' の代り */
|
65
|
+
mov rax, 0x68732f2f6e69622f /* == "/bin//sh" */
|
66
|
+
push rax /* スタックに 8 文字を push */
|
67
|
+
mov rdi, rsp /* その時の rsp が文字列先頭アドレス */
|
68
|
+
```
|
69
|
+
スタックに文字列の先頭アドレスを push するのではなく、8文字の文字列(8文字==64bit)そのものを直接 push するのがミソで、**push 直後のスタックポインタ rsp の値が文字列先頭アドレスになる**。このようなコードはスタック領域が何番地でも問題無く動くからアレンジは不要。
|
70
|
+
|
71
|
+
printf() を使わず、syscall を使うことは大きい。
|
72
|
+
まず、printf() 関数のアドレスを調べる必要が無い。そして、syscall で出力するなら、スタックポインタの変更(movl $shcode-8,%esp)をしなくても良い可能性が高い。
|
73
|
+
|
74
|
+
printf() は関数だから、その実行にはそれなりのスタック領域が必要だが、何バイト必要なのか簡単にはわからない。そこで私のコードは安全策をとって、スタックポインタを変更した。
|
75
|
+
一方、syscall 命令は、その時点でOS内部のコードへ処理が遷移すると同時に、スタックポインタがOS用のスタック領域に切り替わる。そのため、こちらのコードに必要なスタックサイズは少量で済み、かつ見積りが可能になる(=push するバイト数を数えれば良い)。
|
76
|
+
|
77
|
+
文字列をpushする事とsyscall命令を使う事は、メモリアドレスが現れる命令をなるべく使わないための、標準的な工夫と言えるだろう。
|
78
|
+
|
79
|
+
いずれにしても、(攻撃対象プロセスの)スタック領域を、どう使おうとしているか、目に見えていないことには、何が問題かも理解できないと思う。一命令ごとに動作をトレースできないようでは(略)。
|
80
|
+
|
81
|
+
ところで、リンクのページでは 167 バイトのシェルコードを得ている。しかし、リターンアドレスを上書きする値も、バッファを溢れさせる nop 命令列も無いので、**そのままでは攻撃できない**事に気づいているだろうか。結局、その2つを付け足すアレンジは最低限要るはずだ。
|
2
オーバーフローをオーバーランに変更
answer
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
アレンジ可能かどうか具体的なアドバイスは可能だと思う、と書いた手前、回答してみる。
|
2
|
-
- 攻撃対象のプログラムは scanf() 等で、スタック上の文字配列(ローカル変数)にシェルコードを読み込み、バッファオーバー
|
2
|
+
- 攻撃対象のプログラムは scanf() 等で、スタック上の文字配列(ローカル変数)にシェルコードを読み込み、バッファオーバーランを起こす
|
3
3
|
- 攻撃対象プログラムの文字配列の先頭アドレスがわかる
|
4
4
|
- 攻撃対象プログラムの_printf のアドレスがわかる
|
5
5
|
- 上記の printf() を呼ぶパターン
|
1
leal 命令を movl 命令に修正
answer
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
さらに切り詰めれば、おおよそ次のようなコードがあれば良いのではないか。
|
12
12
|
```gas
|
13
13
|
shcode:
|
14
|
-
|
14
|
+
movl $shcode-8, %esp # スタックポインタを調整
|
15
15
|
movl $msg,(%esp)
|
16
16
|
call _printf # printf("Hello World!\n") ;
|
17
17
|
ret # 表示後、どうすれば良い?
|