BSH が述べたように、シェルコードにはメッセージ バイトが含まれていません。 MESSAGE
にジャンプ ラベルと GOBACK
の呼び出し msg
を定義する直前のルーチン msg のアドレスは ecx
にポップできる戻りアドレスとしてスタックの一番上にあるため、byte は適切な動きでした。 、msg のアドレスが保存されます。
ただし、あなたのコードと BSH のコードの両方には、わずかな制限があります。NULL bytes ( \x00 )
が含まれています。 関数ポインターによって逆参照されると、文字列の末尾と見なされます。
これを回避する賢い方法があります。 eax, ebx and edx
に格納する値 al, bl and dl
にアクセスすることで、それぞれのレジスタの下位ニブルに一度に直接書き込むことができるほど小さい 上部のニブルにはジャンク値が含まれている可能性があるため、xored にすることができます。
b8 04 00 00 00 ------ mov $0x4,%eax
になる
b0 04 ------ mov $0x4,%al
31 c0 ------ xor %eax,%eax
以前の命令セットとは異なり、新しい命令セットには NULL バイトが含まれていません。
したがって、最終的なプログラムは次のようになります:
global _start
section .text
_start:
jmp message
proc:
xor eax, eax
mov al, 0x04
xor ebx, ebx
mov bl, 0x01
pop ecx
xor edx, edx
mov dl, 0x16
int 0x80
xor eax, eax
mov al, 0x01
xor ebx, ebx
mov bl, 0x01 ; return 1
int 0x80
message:
call proc
msg db " y0u sp34k 1337 ? "
section .data
組み立てとリンク:
$ nasm -f elf hello.asm -o hello.o
$ ld -s -m elf_i386 hello.o -o hello
$ ./hello
y0u sp34k 1337 ? $
次に、hello バイナリからシェルコードを抽出します:
$ for i in `objdump -d hello | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\\x$i" ; done
出力:
\xeb\x19\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x12\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xb3\x01\xcd\x80\xe8\xe2\xff\xff\xff\x20\x79\x30\x75\x20\x73\x70\x33\x34\x6b\x20\x31\x33\x33\x37\x20\x3f\x20
これで、シェルコードを起動するドライバー プログラムを作成できます。
#include <stdio.h>
char shellcode[] = "\xeb\x19\x31\xc0\xb0\x04\x31\xdb"
"\xb3\x01\x59\x31\xd2\xb2\x12\xcd"
"\x80\x31\xc0\xb0\x01\x31\xdb\xb3"
"\x01\xcd\x80\xe8\xe2\xff\xff\xff"
"\x20\x79\x30\x75\x20\x73\x70\x33"
"\x34\x6b\x20\x31\x33\x33\x37\x20"
"\x3f\x20";
int main(int argc, char **argv) {
(*(void(*)())shellcode)();
return 0;
}
最新のコンパイラには、データ セグメントまたはスタックでのコードの実行を防止する NX 保護などの特定のセキュリティ機能があります。したがって、これらを無効にするコンパイラを明示的に指定する必要があります。
$ gcc -g -Wall -fno-stack-protector -z execstack launcher.c -o launcher
今 launcher
シェルコードを起動するために呼び出すことができます。
$ ./launcher
y0u sp34k 1337 ? $
より複雑なシェルコードの場合、別のハードルがあります。最新の Linux カーネルには ASLR または Address Space Layout Randomization
があります シェルコードを挿入する前に、これを無効にする必要がある場合があります。特に、バッファ オーバーフローによる場合です。
[email protected]:~# echo 0 > /proc/sys/kernel/randomize_va_space
このシェルコードを挿入すると、message
に何があるかわかりません :
mov ecx, message
注入されたプロセスでは、それは何でもかまいませんが、"Hello world!\r\n"
にはなりません テキストセクションのみをダンプしている間、データセクションにあるためです。シェルコードに "Hello world!\r\n"
がないことがわかります :
"\xb8\x04\x00\x00\x00"
"\xbb\x01\x00\x00\x00"
"\xb9\x00\x00\x00\x00"
"\xba\x0f\x00\x00\x00"
"\xcd\x80\xb8\x01\x00"
"\x00\x00\xbb\x00\x00"
"\x00\x00\xcd\x80";
これは、シェルコード開発でよくある問題です。これを回避する方法は次のとおりです:
global _start
section .text
_start:
jmp MESSAGE ; 1) lets jump to MESSAGE
GOBACK:
mov eax, 0x4
mov ebx, 0x1
pop ecx ; 3) we are poping into `ecx`, now we have the
; address of "Hello, World!\r\n"
mov edx, 0xF
int 0x80
mov eax, 0x1
mov ebx, 0x0
int 0x80
MESSAGE:
call GOBACK ; 2) we are going back, since we used `call`, that means
; the return address, which is in this case the address
; of "Hello, World!\r\n", is pushed into the stack.
db "Hello, World!", 0dh, 0ah
section .data
テキスト セクションをダンプします。
$ nasm -f elf shellcode.asm
$ ld shellcode.o -o shellcode
$ ./shellcode
Hello, World!
$ objdump -d shellcode
shellcode: file format elf32-i386
Disassembly of section .text:
08048060 <_start>:
8048060: e9 1e 00 00 00 jmp 8048083 <MESSAGE>
08048065 <GOBACK>:
8048065: b8 04 00 00 00 mov $0x4,%eax
804806a: bb 01 00 00 00 mov $0x1,%ebx
804806f: 59 pop %ecx
8048070: ba 0f 00 00 00 mov $0xf,%edx
8048075: cd 80 int $0x80
8048077: b8 01 00 00 00 mov $0x1,%eax
804807c: bb 00 00 00 00 mov $0x0,%ebx
8048081: cd 80 int $0x80
08048083 <MESSAGE>:
8048083: e8 dd ff ff ff call 8048065 <GOBACK>
8048088: 48 dec %eax <-+
8048089: 65 gs |
804808a: 6c insb (%dx),%es:(%edi) |
804808b: 6c insb (%dx),%es:(%edi) |
804808c: 6f outsl %ds:(%esi),(%dx) |
804808d: 2c 20 sub $0x20,%al |
804808f: 57 push %edi |
8048090: 6f outsl %ds:(%esi),(%dx) |
8048091: 72 6c jb 80480ff <MESSAGE+0x7c> |
8048093: 64 fs |
8048094: 21 .byte 0x21 |
8048095: 0d .byte 0xd |
8048096: 0a .byte 0xa <-+
$
私がマークした行は "Hello, World!\r\n"
です 文字列:
$ printf "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0d\x0a"
Hello, World!
$
したがって、C ラッパーは次のようになります。
char code[] =
"\xe9\x1e\x00\x00\x00" // jmp (relative) <MESSAGE>
"\xb8\x04\x00\x00\x00" // mov $0x4,%eax
"\xbb\x01\x00\x00\x00" // mov $0x1,%ebx
"\x59" // pop %ecx
"\xba\x0f\x00\x00\x00" // mov $0xf,%edx
"\xcd\x80" // int $0x80
"\xb8\x01\x00\x00\x00" // mov $0x1,%eax
"\xbb\x00\x00\x00\x00" // mov $0x0,%ebx
"\xcd\x80" // int $0x80
"\xe8\xdd\xff\xff\xff" // call (relative) <GOBACK>
"Hello wolrd!\r\n"; // OR "\x48\x65\x6c\x6c\x6f\x2c\x20\x57"
// "\x6f\x72\x6c\x64\x21\x0d\x0a"
int main(int argc, char **argv)
{
(*(void(*)())code)();
return 0;
}
-z execstack
を使ってテストしてみましょう read-implies-exec (名前に「スタック」が含まれていますが、プロセス全体) を有効にして、.data
でコードを実行できるようにします。 または .rodata
セクション:
$ gcc -m32 test.c -z execstack -o test
$ ./test
Hello wolrd!
できます。 (-m32
64 ビット システムでも必要です。 int $0x80
32 ビット ABI は .rodata
のような 64 ビット アドレスでは機能しません PIE 実行可能ファイルで。また、マシン コードは 32 ビット用にアセンブルされています。たまたま、同じバイト シーケンスが 64 ビット モードで同等の命令にデコードされることがありますが、常にそうとは限りません。)
最新の GNU ld
puts .rodata
.text
とは別のセグメントで であるため、実行不可になる可能性があります。以前は const char code[]
を使用するだけで十分でした 読み取り専用データのページに実行可能コードを配置します。少なくとも、自分自身を変更したくないシェルコードの場合.