次のNASMコードが動作しています。
global _start
section .text
_start:
mov eax, 0x4
mov ebx, 0x1
mov ecx, message
mov edx, 0xF
int 0x80
mov eax, 0x1
mov ebx, 0x0
int 0x80
section .data
message: db "Hello, World!", 0dh, 0ah
「Hello、World!\ n」が画面に出力されます。以前のNASMオブジェクトコードを含む次のCラッパーもあります。
char code[] =
"\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";
int main(void)
{
(*(void(*)())code)();
}
ただし、コードを実行すると、アセンブラコードは実行されないように見えますが、プログラムは正常に終了します。何か案は?
ありがとう
このシェルコードを注入すると、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 8048083 <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 8048065 <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;
}
テストしてみましょう:
$ gcc test.c -o test
$ ./test
Hello wolrd!
$
できます。
[〜#〜] bsh [〜#〜] のように、シェルコードにはメッセージバイトが含まれていません。 MESSAGE
ラベルにジャンプし、GOBACK
バイトを定義する直前にmsg
ルーチンを呼び出すことは、msgのアドレスが戻り値としてスタックの先頭にあるため、良い動きでした。 msgのアドレスが格納されているecx
にポップできるアドレス。
ただし、あなたのコードと [〜#〜] 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
があります。シェルコードを挿入する前に、特にバッファオーバーフローが発生している場合は、これを無効にする必要があります。
root@localhost:~# echo 0 > /proc/sys/kernel/randomize_va_space