正如BSH所提到的,你的shellcode中不包含消息字节。在定义msg
字节之前跳转到MESSAGE
标签并调用GOBACK
例程是一个好方法,因为msg的地址将作为返回地址位于堆栈顶部,可以弹出到存储msg地址的ecx
寄存器中。
但是你和BSH的代码都有一个小问题。它们包含NULL字节(\x00)
,当函数指针引用时会被视为字符串结束符。
有一种聪明的方法可以解决这个问题。你存储到eax、ebx和edx
中的值足够小,可以通过直接访问al、bl和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
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二进制文件中提取shellcode:
$ 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
来启动shellcode。
$ ./launcher
y0u sp34k 1337 ? $
对于更复杂的shellcode,还会有另一个障碍。现代Linux内核有ASLR或地址空间布局随机化
,您可能需要在注入shellcode之前禁用它,特别是当通过缓冲区溢出时。
root@localhost:~# echo 0 > /proc/sys/kernel/randomize_va_space
strcpy
等字符串函数,那么空字节就会成为问题,它不会读取整个shellcode字符串。否则的话就没问题了。 - user1129665