teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

2

当初の回答の一部を修正、.cfi_restoreについて追記

2019/05/08 15:22

投稿

dodox86
dodox86

スコア9380

answer CHANGED
@@ -1,8 +1,10 @@
1
+ **回答の一部を修正し、追記しました:**2019/05/09 00:22
2
+
1
3
  > mainの中でバッファオーバーフローをしようとすると、EIPを書き換える直前でESPの書き換わるような挙動
2
4
  ...
3
5
  > これは何かのセキュリティが働いているのでしょうか?
4
6
 
5
- セキュリティの問題ではなく、bof.c の`main`関数で実装しているコードに問題があり意図する動きを阻害しています。逆アセンブルした結果で、`ret`直前のコードが
7
+ ~~セキュリティの問題ではなく、~~bof.c の`main`関数で~~実装しているコードに問題があり~~意図する動きを阻害しています。逆アセンブルした結果で、`ret`直前のコードが
6
8
  ```
7
9
  0x5655622d <+116>: lea esp,[ecx-0x4] <= この部分でespに[ecx - 0x4](0x4141413d)がロードされる 
8
10
  0x56556230 <+119>: ret
@@ -141,7 +143,7 @@
141
143
 
142
144
  (gdb) stepi
143
145
  0x08048414 9 }
144
- (gdb) stepidisassemble
146
+ (gdb) disassemble
145
147
  Dump of assembler code for function main:
146
148
  0x080483db <+0>: push %ebp
147
149
  0x080483dc <+1>: mov %esp,%ebp
@@ -214,4 +216,79 @@
214
216
  Program received signal SIGSEGV, Segmentation fault.
215
217
  0x41414141 in ?? ()
216
218
  (gdb) quit
217
- ```
219
+ ```
220
+
221
+ ---
222
+ **以下、追記部分です:**2019/05/09 00:22
223
+
224
+ > mainの中でバッファオーバーフローをしようとすると、EIPを書き換える直前でESPの値が書き換わるような挙動を起こしてしまうためEIPを書き換えられません。
225
+
226
+ 追って調べたところ、言葉のとおりでした。`main`関数に限って、`ret`直前に `lea esp, [ecx-4]`のコードが入る場合と入らない場合があります。質問者であるkonataroさんのC言語コードでは入ってしまい、拙作の例示用C言語コードでは入りません。その為、動きに差が出ています。
227
+
228
+ bof.c を"-S"オプション付きでコンパイルしてアセンブリ言語ソースで出力すると、`main`関数の`ret`命令付近のエピローグコードは、以下のようになります。注目すべきコードである`leal -4(%ecx), %esp`が入っています。(質問者のkonataroさんが既に提示されたものとほぼ等価なものですが、説明の為に掲げています)
229
+ ```
230
+ call strcpy
231
+ addl $16, %esp
232
+ .loc 1 13 0
233
+ movl $0, %eax
234
+ .loc 1 14 0
235
+ movl -4(%ebp), %ecx
236
+ .cfi_def_cfa 1, 0
237
+ leave
238
+ .cfi_restore 5
239
+ leal -4(%ecx), %esp
240
+ .cfi_def_cfa 4, 4
241
+ ret
242
+ .cfi_endproc
243
+ ```
244
+ これをkonataroさんが既に指摘されているように、`main`ではなく、普通の関数`func`に変えてコンパイルすると、
245
+ ```C
246
+ //bof.c
247
+ #include<stdio.h>
248
+ #include<string.h>
249
+
250
+ char buffer[32];
251
+
252
+ int func(int argc, char *argv[])
253
+ {
254
+ char local[32];
255
+ printf("buffer 0x%x\n", &buffer);
256
+ fgets(local, 128, stdin);
257
+ strcpy(buffer,local);
258
+ return 0;
259
+ }
260
+
261
+ int main(int argc, char *argv[])
262
+ {
263
+ return func(argc, argv);
264
+ }
265
+ ```
266
+ アセンブリ言語のソースは、以下のように変わります。`leal -4(%ecx), %esp`は消滅しています。
267
+ ```
268
+ call strcpy
269
+ addl $16, %esp
270
+ .loc 1 13 0
271
+ movl $0, %eax
272
+ .loc 1 14 0
273
+ leave
274
+ .cfi_restore 5
275
+ .cfi_def_cfa 4, 4
276
+ ret
277
+ .cfi_endproc
278
+ ```
279
+
280
+ `.cfi_restore`とは何か?をWEBで検索してみると以下のas(gas)のドキュメントがヒットし、
281
+ [7.10 CFI directives](https://sourceware.org/binutils/docs/as/CFI-directives.html)
282
+
283
+ 内容として下の説明がありました。
284
+ > 7.10.17 .cfi_restore register
285
+ > .cfi_restore says that the rule for register is now the same as it was at the beginning of the function, after all initial instruction added by .cfi_startproc were executed.
286
+
287
+ どうも、このディレクティブで関数先頭でのレジスターの値と同じようにすることを指示するもののようで、`main`内で関数を呼び出すコードを書くと`leal -4(%ecx), %esp`のコードが挟み込まれます。拙作の`main`の例示コードでは関数を呼び出さない為、目的を達成できるコードが生成できたようです。あくまで勝手な推測ですが、SSPなどの仕組みの一部で、`main`を呼び出したCランタイムのスタートアップルーチンに正しく戻れるよう保証する動作の一端でしょうか。少し検索した限りでは、なぜそうなっているかを説明しているgccやbinutilのドキュメントは見つけることはできませんでした。この辺り、gcc周りの事情にお詳しい方にフォローや突っ込みをお願いしたいところです。
288
+
289
+ ですので、少なくとも本質問の内容の限りにおいては、gccに"-fno-stack-protector"オプションを追加した上で、
290
+ - main関数以外の関数内に目的のコードを書く。
291
+ - main関数内に書くときは、標準関数を含む他の関数を呼び出さない。
292
+ ようにすると所望の動作をするコードになるので、それを踏まえてコードを書く必要があると考えます。
293
+
294
+ 私自身の参考として - [IPA - 第10章 著名な脆弱性対策 - バッファオーバーフロー: #5 運用環境における防御](https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/c905.html)

1

記述を一部修正、ソースコード中にコメント追加

2019/05/08 15:22

投稿

dodox86
dodox86

スコア9380

answer CHANGED
@@ -2,7 +2,7 @@
2
2
  ...
3
3
  > これは何かのセキュリティが働いているのでしょうか?
4
4
 
5
- セキュリティの問題ではなく、bof.c の`main`関数で実装しているコードに問題があり意図する動きを阻害しているだけです。逆アセンブルした結果で、`ret`直前のコードが
5
+ セキュリティの問題ではなく、bof.c の`main`関数で実装しているコードに問題があり意図する動きを阻害していす。逆アセンブルした結果で、`ret`直前のコードが
6
6
  ```
7
7
  0x5655622d <+116>: lea esp,[ecx-0x4] <= この部分でespに[ecx - 0x4](0x4141413d)がロードされる 
8
8
  0x56556230 <+119>: ret
@@ -12,7 +12,7 @@
12
12
  シンプルな例で行うと`main`のreturn時に`eip`が書き換わることを確認できます。
13
13
  ```C
14
14
  /* t7.c simple bof */
15
- int i;
15
+ int i; /* ※ループ実行時に破壊されないよう、static領域に配置*/
16
16
  int main(int argc, char *argv[]) {
17
17
  char s[8];
18
18
  for (i = 0; i < 128; i++) {
@@ -56,7 +56,7 @@
56
56
  (gdb) run
57
57
  Starting program: /home/user01/t7
58
58
  ```
59
- breakした時点でスタックが`0x41`で上書きされているので、`argc`や`argv`が0x41...に書き換わっています。
59
+ ブレイクした時点でスタックが`0x41`で上書きされているので、`argc`や`argv`が0x41...に書き換わっていることも分かります。
60
60
  ```
61
61
  Breakpoint 1, main (argc=1094795585, argv=0x41414141) at t7.c:8
62
62
  8 return 0;