浅入浅出Liunx Shellcode( 二 )


.globl _start
_start:
movl $4,陎 #define __NR_write 4
movl $1,離
movl $output,靫
movl $len,韝
int $0×80
movl $1,陎
movl $0,離
int $0×80
编译该程序,并查看运行结果:
pr0cess@pr0cess:~$ as -o syscall.o syscall.s
pr0cess@pr0cess:~$ ld -o syscall syscall.o
pr0cess@pr0cess:~$ ./syscall
hello,syscall!!!!
可以看到hello,syscall被写入到终端 。那么这个过程是怎么实现的呢?首先程序定义了一个字符串hello,syscall!!!!和字符串的长度len,接下来将write系统调用号写入到eax寄存器中,接着write系统调用的第一个参数需要一个文件描述符fd,linux包含3种文件描述符0[STDIN]:终端设备的标准输入;1[STDOUT]:终端设备的标准输出;2[STDERR]:终端设备的标准错误输出 。我们这里把fd的值设置为1,就是输入到屏幕上,因此把操作数1赋值给EBX寄存器 。write系统调用的第二个参数是要写入字符串的指针,这里需要一个内存地址,因此我们通过movl $output,靫把output指向的实际内存地址存放在 ECX寄存器中 。write系统调用的第三个参数是写入字符串的长度,按照顺序的参数传递方式,我们把len传递到EDX寄存器中,接着执行int $0×80软中断来执行write系统调用 。下一步执行了一个exit(0) 操作,将exit系统调用号1传递给EAX寄存器,将参数0传递给EBX寄存器,然后执行int $0×80来执行系统调用,实现程序的退出 。
为了更清晰的验证我们的系统调用确实被执行了,可以通过strace来查看二进制代码的运行情况,结果如下:
pr0cess@pr0cess:~$ strace ./syscall
execve(”./syscall”, ["./syscall"], [/* 34 vars */]) = 0
write(1, “hello,syscall!!!!/n”, 18hello,syscall!!!!
) = 18
_exit(0)
通过返回的结果我们可以清楚的看到刚才syscall程序都执行了哪些系统调用,以及每个系统调用都传递了什么参数进去 。
已经了解了系统调用的实现过程,让我们离shellcode更进一步吧 。

三:第一个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,陎
8048079: bb 01 00 00 00 mov $0×1,離
804807e: b9 98 90 04 08 mov $0×8049098,靫
8048083: ba 12 00 00 00 mov $0×12,韝
8048088: cd 80 int $0×80
804808a: b8 01 00 00 00 mov $0×1,陎
804808f: bb 00 00 00 00 mov $0×0,離
8048094: cd 80 int $0×80
pr0cess@pr0cess:~$
噢!!!这个SB的程序在
8048074: b8 04 00 00 00 mov $0×4,陎
这里就已经被00截断了,完全不能用于shellcode,只能作为一般的汇编程序运行 。现在来分析下为什么会出现这种情况 。现看这两段代码:
movl $4,陎
movl $1,離
这两条指令使用的是32位(4字节)的寄存器EAX和EBX,而我们却只分别赋值了1个字节到寄存器中,所以系统会用NULL字符(00)来填充剩下的字节空间,从而导致shellcode被截断 。知道了原因就可以找到很好的解决方法了,一个EAX寄存器是32位,32位寄存器也可以通过16位或者8位的名称引用,我们通过AX寄存器来访问第一个16位的区域(低16位),继续通过对AL的引用EAX寄存器的低8位被使用,AH使用AL后的高8位 。
EAX寄存器的构成如下:

推荐阅读