У меня есть следующий код 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". У меня также есть следующая C-оболочка, которая содержит предыдущий объектный код NASM:
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)();
}
Однако, когда я запускаю код, похоже, что код ассемблера не выполняется, но программа выходит из строя. Есть идеи?
благодаря
Ответ 1
Когда вы вводите этот шеллкод, вы не знаете, что есть в 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!
$
оно работает.
Ответ 2
Как упоминалось в BSH, ваш шеллкод не содержит байтов сообщений. Переход к метке MESSAGE
и вызов процедуры GOBACK
непосредственно перед определением байта msg
был хорошим шагом, так как адрес msg был бы в верхней части стека в качестве адреса возврата, который можно было бы выскочить в ecx
, где хранится адрес msg,
Но и ваш, и код BSH имеют небольшое ограничение. Он содержит NULL bytes ( \x00 )
которые будут рассматриваться как конец строки при разыменовании указателем функции.
У этого есть разумный путь. Значения, которые вы храните в eax, ebx and edx
, достаточно малы, чтобы напрямую записываться в нижние полубайты соответствующих регистров за один проход, обращаясь к al, bl and dl
соответственно. Верхний полубайт может содержать значение нежелательной почты, чтобы его можно было хранить.
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
Теперь у нас может быть наша программа для запуска shellcode.
#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