バッファオーバーフローは初めてで、既成の簡単に実行できるスクリプトを使用する前に、自分が何をしているかを正確に理解しようとしています。私の目標はシェルをスポーンすることなので、execve("/bin/sh")
を実行するasmコードを見つけました。シェルコードは Shell-storm から取得されます。 :
_char *shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
_
チュートリアルとスタックのドキュメントを調べたところ、スタックが下がっていくことがわかりました(データを追加すると、アドレスが上位から下位に)。
とりあえず、このシェルコードが_0x00
_で始まるローカルバッファーに格納されているとしましょう。
シェルコードをそのように保存しますか?
_0x00 31 c0 50 68
0x04 2f 2f 73 68
0x08 .....
_
スタックでは次のようになります。
_0x08 .....
0x04 2f 2f 73 68
0x00 31 c0 50 68
_
scanf("%d",myInt)
が実行されると、_"9"
_と_&myInt = 0x04
_を指定すると、_0x04 : 09 00 00 00
_になることを理解しています。 _"09"
_がアドレス_0x04
_にあることを意味します。アドレスが4の倍数で、これがリトルエンディアンである場合にのみアクセス可能であっても。
scanf
は_%s
_とどのように連携しますか? charを4つずつ読み取って、最初の文字を(リトルエンディアン)の最も低い0x04アドレスに格納し、4番目を_0x07
_に格納しますか?
だから本当の質問は:
通常、格納されたIPの代わりに表示したいアドレスを逆にすることを考慮してください。シェルコードについても同じようにする必要がありますか?スタックのIPレジスタを_0x01020304
_で上書きする場合、通常はスタックに上向きに書き込むため、ペイロードに_\x04\03\02\01
_を書き込みます。つまり、最初にアドレスの最下位ビットにヒットします。
// test.c
#include <stdio.h>
int main(){
char buf[8] = "ABCDEFGH";
int n = 9;
printf("%s\n", buf);
printf("%d\n", n);
return 0;
}
と:
$ apt install gcc-multilib
$ gcc -g test.c -o test -m32
次に、gdbでバイナリを実行し、printfの実行前に中断して、スタックを確認します。
$ gdb ./test -q
Reading symbols from ./test...done.
(gdb) disas main
Dump of assembler code for function main:
[...]
0x000011db <+50>: sub $0xc,%esp
0x000011de <+53>: lea -0x14(%ebp),%eax
0x000011e1 <+56>: Push %eax
0x000011e2 <+57>: call 0x1040 <puts@plt>
[...]
End of assembler dump.
// printf is replaced by puts system call here
(gdb) b *main+57
Breakpoint 1 at 0x11e2: file test.c, line 6.
(gdb) r
Starting program: /root/test
Breakpoint 1, 0x565561e2 in main () at test.c:6
6 printf("%s\n", buf);
(gdb) x/20x $esp
0xffffd280: 0xffffd294 0x56559000 0xffffd35c 0x565561c0
0xffffd290: 0x00000001 0x44434241 0x48474645 0x00000009
0xffffd2a0: 0xffffd2c0 0x00000000 0x00000000 0xf7deab41
0xffffd2b0: 0xf7faa000 0xf7faa000 0x00000000 0xf7deab41
0xffffd2c0: 0x00000001 0xffffd354 0xffffd35c 0xffffd2e4
'A'のASCIIコードは0x41、 'B' 0x42、...なので、2行目にABCDEFGHが次のように格納されていることがわかります。
0x94: DCBA
0x98: HGFE
0x9B: 0x00000009
文字列の終わりが上位アドレスにあることに注意してください。
$ cat test.c
#include <stdio.h>
int main(){
char input[8];
scanf("%s\n", input);
printf("%s\n", input);
return 0;
}
$ gcc -g test.c -o test -m32
$ gdb ./test -q
(gdb) disas main
[...]
0x000011d4 <+43>: call 0x1050 <__isoc99_scanf@plt>
0x000011d9 <+48>: add $0x10,%esp
0x000011dc <+51>: sub $0xc,%esp
0x000011df <+54>: lea -0x10(%ebp),%eax
0x000011e2 <+57>: Push %eax
0x000011e3 <+58>: call 0x1030 <puts@plt>
[...]
(gdb) b * main+58
Breakpoint 1 at 0x11e3: file test.c, line 6.
(gdb) r < <(echo "ABCDEFG")
Starting program: /root/test < <(echo "ABCDEFG")
Breakpoint 1, 0x565561e3 in main () at test.c:6
6 printf("%s\n", input);
(gdb) x/20x $esp
0xffffd280: 0xffffd298 0xffffd298 0xffffd35c 0x565561c0
0xffffd290: 0x00000001 0xffffd354 0x44434241 0x00474645
0xffffd2a0: 0xffffd2c0 0x00000000 0x00000000 0xf7deab41
0xffffd2b0: 0xf7faa000 0xf7faa000 0x00000000 0xf7deab41
0xffffd2c0: 0x00000001 0xffffd354 0xffffd35c 0xffffd2e4
(gdb)
シェルコードは、メモリに格納する一連のアセンブリ命令であり、次の文字列は入力の準備ができているため、変更する必要はありません。
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
入力後、コードセグメントのように、メモリ内に一連のオペコードがあります。
スタックは大きくなりますが、文字列は「通常どおりに」成長し(「ABCDEFGH」を覚えておいてください)、eipは各命令の後にインクリメントされるため、文字列内の命令の順序に従います。
Disassembly:
0: 31 c0 xor eax,eax
2: 50 Push eax
String Literal:
"\x31\xC0\x50"
リトルエンディアンについて心配する必要はありません。これは、データの保存方法に過ぎず、アドレスを作成するときに重要です(保存されたeipの上書きなど)。