1 概述
相比Intel支持的快速系统调用指令sysenter/sysexit,AMD对应的是syscall/sysret,不过现在,Intel也兼容这两条指令。
测试环境:
Ubuntu 12.04
Ubuntu 16.04 64
只用于32位系统,64位系统上不起作用;
系统调用号和返回结果
EAX指定要调用的函数(系统调用号)
EBX传递函数的第一个参数
ECX传递函数的第二个参数
EDX传递函数的第三个参数
返回值EAX
示例
系统调用号:
sys/syscall.h
/usr/include/i386-linux-gnu/asm/unistd_32.h
如:
#define __NR_getuid 24
#define __NR_getuid32 199
参数:
EAX指定要调用的函数(系统调用号)
EBX传递函数的第一个参数
ECX传递函数的第二个参数
EDX传递函数的第三个参数
ESI
EDI
EBP
返回值EAX
静态链接时,采用"call *_dl_sysinfo"指令;
动态链接时,采用"call *%gs:0x10"指令;
最终调用的是VDSO(linux-gate.so.1)中的__kernel_vsyscall函数;
__kernel_vsyscall函数包含sysenter指令;
syscall()函数也是类似的,根据静态/动态链接的不同分别采用的不同的指令,最终调用__kernel_vsyscall函数;
millionsky@ubuntu-12:~/tmp$ gcc getuid_glibc.c -static millionsky@ubuntu-12:~/tmp$ gdb ./a.out (gdb) b main Breakpoint 1 at 0x8048ee3 (gdb) r Starting program: /home/millionsky/tmp/a.out
Breakpoint 1, 0x08048ee3 in main () (gdb) disass Dump of assembler code for function main: 0x08048ee0 <+0>: push %ebp 0x08048ee1 <+1>: mov %esp,%ebp => 0x08048ee3 <+3>: and $0xfffffff0,%esp 0x08048ee6 <+6>: sub $0x10,%esp 0x08048ee9 <+9>: call 0x8053c00 0x08048eee <+14>: mov $0x80c6088,%edx 0x08048ef3 <+19>: mov %eax,0x4(%esp) 0x08048ef7 <+23>: mov %edx,(%esp) 0x08048efa <+26>: call 0x8049980 0x08048eff <+31>: mov $0x0,%eax 0x08048f04 <+36>: leave 0x08048f05 <+37>: ret End of assembler dump. (gdb) disass 0x8053c00 Dump of assembler code for function getuid: 0x08053c00 <+0>: mov $0xc7,%eax //__NR_getuid32 0x08053c05 <+5>: call *0x80ef5a4 0x08053c0b <+11>: ret End of assembler dump. (gdb) x 0x80ef5a4 0x80ef5a4 <_dl_sysinfo>: 0xb7fff414 (gdb) disass 0xb7fff414 Dump of assembler code for function __kernel_vsyscall: 0xb7fff414 <+0>: push %ecx 0xb7fff415 <+1>: push %edx 0xb7fff416 <+2>: push %ebp 0xb7fff417 <+3>: mov %esp,%ebp 0xb7fff419 <+5>: sysenter 0xb7fff41b <+7>: nop 0xb7fff41c <+8>: nop 0xb7fff41d <+9>: nop 0xb7fff41e <+10>: nop 0xb7fff41f <+11>: nop 0xb7fff420 <+12>: nop 0xb7fff421 <+13>: nop 0xb7fff422 <+14>: int $0x80 0xb7fff424 <+16>: pop %ebp 0xb7fff425 <+17>: pop %edx 0xb7fff426 <+18>: pop %ecx 0xb7fff427 <+19>: ret End of assembler dump. |
millionsky@ubuntu-12:~/tmp$ gcc getuid_glibc.c millionsky@ubuntu-12:~/tmp$ gdb ./a.out (gdb) b main Breakpoint 1 at 0x8048417 (gdb) r Starting program: /home/millionsky/tmp/a.out
Breakpoint 1, 0x08048417 in main () (gdb) b getuid Breakpoint 2 at 0xb7ed9c30 (gdb) c Continuing.
Breakpoint 2, 0xb7ed9c30 in getuid () from /lib/i386-linux-gnu/libc.so.6 (gdb) disass Dump of assembler code for function getuid: => 0xb7ed9c30 <+0>: mov $0xc7,%eax 0xb7ed9c35 <+5>: call *%gs:0x10 0xb7ed9c3c <+12>: ret End of assembler dump. (gdb) si 0xb7ed9c35 in getuid () from /lib/i386-linux-gnu/libc.so.6 (gdb) 0xb7fdd414 in __kernel_vsyscall () (gdb) disass Dump of assembler code for function __kernel_vsyscall: => 0xb7fdd414 <+0>: push %ecx 0xb7fdd415 <+1>: push %edx 0xb7fdd416 <+2>: push %ebp 0xb7fdd417 <+3>: mov %esp,%ebp 0xb7fdd419 <+5>: sysenter 0xb7fdd41b <+7>: nop 0xb7fdd41c <+8>: nop 0xb7fdd41d <+9>: nop 0xb7fdd41e <+10>: nop 0xb7fdd41f <+11>: nop 0xb7fdd420 <+12>: nop 0xb7fdd421 <+13>: nop 0xb7fdd422 <+14>: int $0x80 0xb7fdd424 <+16>: pop %ebp 0xb7fdd425 <+17>: pop %edx 0xb7fdd426 <+18>: pop %ecx 0xb7fdd427 <+19>: ret End of assembler dump. |
直接执行sysenter指令,执行完成后,内核sysexit会跳转到__kernel_vsyscall的后半部分继续执行:
Dump of assembler code for function __kernel_vsyscall: 0xb7fff414 <+0>: push %ecx 0xb7fff415 <+1>: push %edx 0xb7fff416 <+2>: push %ebp 0xb7fff417 <+3>: mov %esp,%ebp 0xb7fff419 <+5>: sysenter 0xb7fff41b <+7>: nop 0xb7fff41c <+8>: nop 0xb7fff41d <+9>: nop 0xb7fff41e <+10>: nop 0xb7fff41f <+11>: nop 0xb7fff420 <+12>: nop 0xb7fff421 <+13>: nop 0xb7fff422 <+14>: int $0x80 0xb7fff424 <+16>: pop %ebp //跳转到这里 0xb7fff425 <+17>: pop %edx 0xb7fff426 <+18>: pop %ecx 0xb7fff427 <+19>: ret |
因此需要注意以下几点:
保存返回地址(sysenter指令的下一条指令)
保存ecx、edx寄存器(sysexit指令需要使用这两个寄存器);
asm("push %ecx\n");
asm("push %edx\n");
增加函数头
asm("push %ebp\n");
asm("mov %esp,%ebp\n");
系统调用号:
sys/syscall.h
/usr/include/x86_64-linux-gnu/asm/unistd_64.h
参数(man syscall):
参数寄存器:
静态链接时,直接调用syscall指令;
动态链接时,调用libc的系统调用代码,调用syscall指令;
syscall()函数也是类似的,最终调用syscall指令;
millionsky@ubuntu-16:~/tmp/VDSO$ cat getuid_glibc.c /** * filename: getuid_glibc.c */ #include #include #include
int main(int argc, char *argv[]) { printf("uid:%d\n", getuid()); return 0; } millionsky@ubuntu-16:~/tmp/VDSO$ gcc getuid_glibc.c -o getuid_glibc -static |
GDB调试
millionsky@ubuntu-16:~/tmp/VDSO$ gdb ./getuid_glibc -q Reading symbols from ./getuid_glibc...(no debugging symbols found)...done. (gdb) tb __getuid Temporary breakpoint 1 at 0x43e8e0 (gdb) r Starting program: /home/millionsky/tmp/VDSO/getuid_glibc
Temporary breakpoint 1, 0x000000000043e8e0 in getuid () (gdb) disass Dump of assembler code for function getuid: => 0x000000000043e8e0 <+0>: mov $0x66,%eax 0x000000000043e8e5 <+5>: syscall 0x000000000043e8e7 <+7>: retq End of assembler dump. |
值0x66是__NR_getuid在x64上的值,即把__NR_getuid放到eax寄存器后,不再是执行指令int 0x80,而是执行指令syscall。
millionsky@ubuntu-16:~/tmp/SROP$ gcc getuid_glibc.c millionsky@ubuntu-16:~/tmp/SROP$ gdb ./a.out (gdb) b main Breakpoint 1 at 0x40056a (gdb) r Starting program: /home/millionsky/tmp/SROP/a.out
Breakpoint 1, 0x000000000040056a in main () (gdb) b __getuid Breakpoint 2 at 0x7ffff7ada240: file ../sysdeps/unix/syscall-template.S, line 65. (gdb) c Continuing.
Breakpoint 2, getuid () at ../sysdeps/unix/syscall-template.S:65 65 ../sysdeps/unix/syscall-template.S: 没有那个文件或目录. (gdb) disass Dump of assembler code for function getuid: => 0x00007ffff7ada240 <+0>: mov $0x66,%eax 0x00007ffff7ada245 <+5>: syscall 0x00007ffff7ada247 <+7>: retq End of assembler dump. |
millionsky@ubuntu-16:~/tmp/VDSO$ cat getuid_syscall.c /** * filename: getuid_syscall.c */ #include #define _GNU_SOURCE #include #include
int main(int argc, char *argv[]) { printf("uid:%ld\n", syscall(__NR_getuid)); return 0; } millionsky@ubuntu-16:~/tmp/VDSO$ gcc getuid_syscall.c -o getuid_syscall -static |
GDB调试
millionsky@ubuntu-16:~/tmp/VDSO$ gdb ./getuid_syscall -q Reading symbols from ./getuid_syscall...(no debugging symbols found)...done. (gdb) b main Breakpoint 1 at 0x4009b2 (gdb) r Starting program: /home/millionsky/tmp/VDSO/getuid_syscall
Breakpoint 1, 0x00000000004009b2 in main () (gdb) disass Dump of assembler code for function main: ... 0x00000000004009bd <+15>: mov $0x66,%edi 0x00000000004009c2 <+20>: mov $0x0,%eax 0x00000000004009c7 <+25>: callq 0x43fc70 ... End of assembler dump. (gdb) disass 0x43fc70 Dump of assembler code for function syscall: 0x000000000043fc70 <+0>: mov %rdi,%rax 0x000000000043fc73 <+3>: mov %rsi,%rdi 0x000000000043fc76 <+6>: mov %rdx,%rsi 0x000000000043fc79 <+9>: mov %rcx,%rdx 0x000000000043fc7c <+12>: mov %r8,%r10 0x000000000043fc7f <+15>: mov %r9,%r8 0x000000000043fc82 <+18>: mov 0x8(%rsp),%r9 0x000000000043fc87 <+23>: syscall ... End of assembler dump. |
1. 系统调用指令:
传统的32位系统调用int 0x80
Intel的sysenter/sysexit
AMD的syscall/sysret
2. 传统Int 0x80系统调用
系统调用号:EAX
参数:EBX、ECX、EDX、ESI、EDI、EBP
返回值:EAX
3. 32位系统调用sysenter
系统调用号:EAX
参数:EBX、ECX、EDX、ESI、EDI、EBP
返回值:EAX
静态链接时,采用"call *_dl_sysinfo"指令;
动态链接时,采用"call *%gs:0x10"指令;
最终调用的是VDSO(linux-gate.so.1)中的__kernel_vsyscall函数;
__kernel_vsyscall函数包含sysenter指令;
syscall()函数也是类似的,根据静态/动态链接的不同分别采用的不同的指令,最终调用__kernel_vsyscall函数;
4. 64位系统调用syscall
系统调用号:RAX
参数:RDI、RSI、RDX、R10、R8、R9
返回值:RAX
静态链接时,直接调用syscall指令;
动态链接时,调用libc的系统调用代码,调用syscall指令;
syscall()函数也是类似的,最终调用syscall指令;
1. 64位Linux下的系统调用。http://www.lenky.info/archives/2013/02/2199。
2. Linux 2.6 对新型 CPU 快速系统调用的支持。https://www.ibm.com/developerworks/cn/linux/kernel/l-k26ncpu/index.html。