Seperti yang disebutkan BSH, kode shell Anda tidak berisi byte pesan. Melompat ke MESSAGE
beri label dan panggil GOBACK
rutin tepat sebelum mendefinisikan msg
byte adalah langkah yang baik karena alamat msg akan berada di bagian atas tumpukan sebagai alamat pengirim yang dapat dimunculkan ke ecx
, tempat alamat msg disimpan.
Tetapi kode milik Anda dan BSH memiliki sedikit batasan. Ini berisi NULL bytes ( \x00 )
yang akan dianggap sebagai akhir string saat direferensikan oleh penunjuk fungsi.
Ada cara cerdas untuk mengatasi hal ini. Nilai yang Anda simpan ke dalam eax, ebx and edx
cukup kecil untuk ditulis langsung ke nibble yang lebih rendah dari masing-masing register sekaligus dengan mengakses al, bl and dl
masing-masing. Gigitan bagian atas mungkin mengandung nilai sampah sehingga dapat di-xor.
b8 04 00 00 00 ------ mov $0x4,%eax
menjadi
b0 04 ------ mov $0x4,%al
31 c0 ------ xor %eax,%eax
Berbeda dengan set instruksi sebelumnya, set instruksi baru tidak berisi byte NULL.
Jadi, program terakhirnya terlihat seperti ini :
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
Merakit dan menghubungkan :
$ nasm -f elf hello.asm -o hello.o
$ ld -s -m elf_i386 hello.o -o hello
$ ./hello
y0u sp34k 1337 ? $
Sekarang ekstrak kode shell dari hello binary :
$ for i in `objdump -d hello | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\\x$i" ; done
keluaran:
\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
Sekarang kita dapat memiliki program driver untuk meluncurkan kode shell.
#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;
}
Ada fitur keamanan tertentu dalam kompiler modern seperti perlindungan NX yang mencegah eksekusi kode di segmen atau tumpukan data. Jadi kita harus secara eksplisit menentukan kompiler untuk menonaktifkan ini.
$ gcc -g -Wall -fno-stack-protector -z execstack launcher.c -o launcher
Sekarang launcher
dapat dipanggil untuk meluncurkan kode shell.
$ ./launcher
y0u sp34k 1337 ? $
Untuk kode shell yang lebih kompleks, akan ada rintangan lain. Kernel Linux modern memiliki ASLR atau Address Space Layout Randomization
Anda mungkin perlu menonaktifkan ini sebelum menyuntikkan kode shell, terutama saat melewati buffer overflows.
[email protected]:~# echo 0 > /proc/sys/kernel/randomize_va_space
Saat Anda menyuntikkan kode shell ini, Anda tidak tahu apa yang ada di message
:
mov ecx, message
dalam proses yang disuntikkan, itu bisa berupa apa saja tetapi tidak akan menjadi "Hello world!\r\n"
karena itu ada di bagian data saat Anda hanya membuang bagian teks. Anda dapat melihat bahwa shellcode Anda tidak memiliki "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";
Ini adalah masalah umum dalam pengembangan shellcode, cara mengatasinya adalah dengan cara ini:
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
Sekarang buang bagian teks:
$ 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 <-+
$
Baris yang saya tandai adalah "Hello, World!\r\n"
kita string:
$ printf "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0d\x0a"
Hello, World!
$
Jadi bungkus C kita akan menjadi:
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;
}
Mari kita coba, menggunakan -z execstack
untuk mengaktifkan read-implies-exec (di seluruh proses, meskipun namanya "tumpukan") sehingga kita dapat mengeksekusi kode di .data
atau .rodata
bagian:
$ gcc -m32 test.c -z execstack -o test
$ ./test
Hello wolrd!
Berhasil. (-m32
diperlukan juga, pada sistem 64-bit. int $0x80
ABI 32-bit tidak berfungsi dengan alamat 64-bit seperti .rodata
dalam PIE yang dapat dieksekusi. Juga, kode mesin dirakit untuk 32-bit. Kebetulan urutan byte yang sama akan mendekode ke instruksi yang setara dalam mode 64-bit tetapi tidak selalu demikian.)
GNU modern ld
menempatkan .rodata
di segmen terpisah dari .text
, sehingga tidak dapat dieksekusi. Dulu cukup menggunakan const char code[]
untuk meletakkan kode yang dapat dieksekusi di halaman data hanya-baca. Setidaknya untuk shellcode yang tidak ingin memodifikasi dirinya sendiri.