1. int 0x80简介
先看看下面通过系统调用实现的hello world代码:
系统调用是通过int 0x80来实现的,eax寄存器中为调用的功能号,ebx、ecx、edx、esi等等寄存器则依次为参数,从 /usr/include/asm/unistd.h中可以看到exit的功能号_NR_exit为1,write(_NR_write)功能号为4,因此第一个int $0x80调用之前eax寄存器值为4,ebx为文件描述符,stdout的文件描述符为1,ecx则为buffer的内存地址,edx为buffer长度。第二个int $0x80之前eax为1表示调用exit,ebx为0表示返回0。
2. 系统调用功能号
这部分可以参考System Call Number Definition
以及http://asm.sourceforge.net/syscall.html#2
其实调用功能号放在了/usr/include/asm/unistd.h之中,打开文件发现:
cat /usr/include/asm/unistd.h | less
|
总共有383条,就不细细讲述了
3. 系统调用及参数传递过程
这部分具体可以参考:
系统调用及参数传递过程
深入理解Linux的系统调用
我们以x86为例说明:
由于陷入指令是一条特殊指令,而且依赖与操作系统实现的平台,如在x86中,这条指令是int 0x80,这显然不是用户在编程时应该使用的语句,因为这将使得用户程序难于移植。所以在操作系统的上层需要实现一个对应的系统调用库,每个系统调用都在该库中包含了一个入口点(如我们看到的fork, open, close等等),这些函数对程序员是可见的,而这些库函数的工作是以对应系统调用号作为参数,执行陷入指令int 0x80,以陷入核心执行真正的系统调用处理函数。当一个进程调用一个特定的系统调用库的入口点,正如同它调用任何函数一样,对于库函数也要创建一个栈帧。而当进程执行陷入指令时,它将处理机状态转换到核心态,并且在核心栈执行核心代码。
这里给出一个示例(linux/include/asm/unistd.h):
在执行一个系统调用库中定义的系统调用入口函数时,实际执行的是类似如上的一段代码。这里牵涉到一些gcc的嵌入式汇编语言,不做详细的介绍,只简单说明其意义:
其中\_\NR\##name是系统调用号,如name == ioctl,则为\_\_NR\_ioctl,它将被放在寄存器eax中作为参数传递给中断0x80的处理函数。而系统调用的其它参数arg1, arg2, …则依次被放入ebx, ecx, . . .等通用寄存器中,并作为系统调用处理函数的参数,这些参数是怎样传入核心的将会在后面介绍。
4. 实例介绍
4.1 sys_execve(x86)
从系统调用约定 一文中我们可以找到sys_execve系统调用的内容
eax | ebx | ecx | edx | esi | edi | |||
---|---|---|---|---|---|---|---|---|
11 | sys_execve | 0x0b | char __user * | char user *user * | char user *user * | struct pt_regs * | - | arch/alpha/kernel/entry.S:925 |
从此处可以知道sys_execve的系统调用约定
从Linux中pt_regs结构体 可以得到pt_regs的结构介绍
在http://man7.org/linux/man-pages/man2/execve.2.html 中有给出execve的定义
|
|
ebx是执行文件路径,ecx是命令行参数,edx是环境变量
很多时候ecx=0,edx=0。但是有时候若有参数,可以设置ecx
范例shellcode:
|
|