声明:主要内容来自《The Shellcoder's Handbook》,摘录重点作为笔记并加上个人的一些理解,如有错,请务必指出。
系统调用
Shellcode是一组可注入的指令,可在被攻击的程序内运行。由于shellcode要直接操作寄存器,通常用汇编语言编写并翻译成十六进制操作码。我们想让目标程序以不同与设计折预期的方式运行,操纵程序的方法之一是强制它产生系统调用。
在Linux里有两种方法来执行系统调用。间接的方法是libc,直接的方法是用汇编指令调用软中断执行系统调用。在Linux里,程序通过int 0x80软中断来执行系统调用,调用过程如下:
1、把系统调用编号载入EAX;
2、把系统调用的参数压入其他寄存器;最多支持6个参数,分别保存在EBX、ECX、EDX、ESI、EDI和EBP里;
3、执行int 0x80指令;
4、CPU切换到内核模式;
5、执行系统函数。
如何得到一个shellcode
注意两点:
1、shellcode应该尽量紧凑,这样才能注入更小的缓冲区;
2、shellcode应该是可注入的;当攻击时,最有可能用来保存shellcode的内存区域是为了保存用户的输入而开辟的字符数组缓冲区。因此shellcode不应包含空值(/x00),在字符数组里,空值是用来终止字符串的,空值的存在使得把shellcode复制到缓冲区时会出现异常。
下面以exit()系统调用为例写一个shellcode。exit()的系统编号为1。用汇编指令实现:
Section .text
global _start
_start:
mov ebx,0
mov eax,1
int 0x80
用nasm编译生成目标文件,然后用GNU链接器链接目标文件,最后用objdump显示相应的操作码(下图的标号为_start部分):
sep@debian66:~/shellcode$ nasm -f elf shellcode.asm
sep@debian66:~/shellcode$ ld -o shellcode shellcode.o
sep@debian66:~/shellcode$ objdump -d shellcode
shellcode: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: bb 00 00 00 00 mov $0x0,%ebx
8048085: b8 01 00 00 00 mov $0x1,%eax
804808a: cd 80 int $0x80
sep@debian66:~/shellcode$
shellcode[] = {"/xbb/x00/x00/x00/x00/xb8/x01/x00/x00/x00/xcd/x80"},可以发现里面出现很多空值,基于shellcode的可注入性,我们需要找出把空值转换成非空操作码的方法。有两种方法:
1、直接用其他具有相同功能的指令替换那些产生空值的指令;
2、在运行时用指令加上空值。
第2个方法比较复杂,暂时先讨论第1个方法。根据objdump的结果,发现mov ebx,0和mov eax,1均产生空值。了解汇编语言的话,可修改为以下代码:
Section .text
global _start
_start:
xor ebx,ebx
mov al,1
int 0x80
编译、链接、反汇编.text段:
sep@debian66:~/shellcode$ nasm -f elf shellcode.asm
sep@debian66:~/shellcode$ ld -o shellcode shellcode.o
sep@debian66:~/shellcode$ objdump -d shellcode
shellcode: file format elf32-i386
Disassembly of section .text:
08048080 <_start>:
8048080: 31 db xor %ebx,%ebx
8048082: b0 01 mov $0x1,%al
8048084: cd 80 int $0x80
sep@debian66:~/shellcode$
可以看到shellcode的空值消失了,长度也减少了。该shellcode是可注入的。
派生shell
如何写一个派生shell的shellcode,参考原书P39,这里不详述。我现在只需对shellcode的获取有个大致的了解,实现时再看细节。