一.实验环境准备
安装工具:
sudo apt install build-essential sudo apt install qemu # install QEMU sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
下载内核源码:
sudo apt install axel axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/ linux-5.4.34.tar.xz xz -d linux-5.4.34.tar.xz tar -xvf linux-5.4.34.tar cd linux-5.4.34:
制作根文件系统
# pwd = ~
axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2
tar -jxvf busybox-1.31.1.tar.bz2
cd busybox-1.31.1
make menuconfig
#记得要编译成静态链接,不⽤动态链接库。
Settings --->
[*] Build static binary (no shared libs)
#然后编译安装,默认会安装到源码⽬录下的 _install ⽬录中。
make -j$(nproc) && make install
#pwd = ~ mkdir rootfs cd rootfs
cp ../busybox-1.31.1/_install/* ./ -rf
mkdir dev proc sys home
sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
二.系统调用
1.选择自己的系统调用,本人学号091;所以选择91号;
fchmod函数说明:
头文件:#include
定义函数:int fchmod(int fildes, mode_t mode);
函数说明:fchmod()会依参数mode 权限来更改参数fildes 所指文件的权限。参数fildes 为已打开文件的文件描述词。参数mode 请参考chmod ()。
返回值:权限改变成功则返回0, 失败返回-1, 错误原因存于errno.
mode | 说明 |
S_ISUID S_ISGID S_ISVTX |
执行时设置用户ID 执行时设置组ID 保存正文(粘住位) |
S_IRWXU S_IRUSR S_IWUSR S_IXUSR |
用户(所有者)读、写和执行 用户(所有者)读 用户(所有者)写 用户(所有者)执行 |
S_IRWXG S_IRGRP S_IWGRP S_IXGRP |
组读、写和执行 组读 组写 组执行 |
S_IRWXO S_IROTH S_IWOTH S_IXOTH |
其他读、写和执行 其他读 其他写 其他执行 |
汇编改写手动触发系统调用
新建文件 fchmod-test.c
#include#include int main() { int fd; fd = open ("/etc/passwd",O_RDONLY); fchmod(fd,S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); close(fd);
return 0; }
运行fchmod-test.c 更改后的权限:
更改fchmod-test.c
#include#include int main() { int fd; fd = open ("/etc/passwd",O_RDONLY); asm volatile( "movq %1, %%rsi\n\t" // 参数2 "movq $0x1, %%rdi\n\t" // 参数1 "movl $0x58,%%eax\n\t" // 传递系统调用号 "syscall\n\t" // 系统调用 "movq %%rax,%0\n\t" // 结果存到%0 就是str_len中 :"=m"(str_len) // 输出 :"g"(vec) // 输入 ); close(fd); return 0; }
gdb跟踪:
qemu-system-i386 -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -S -s -append nokaslr
file vmlinux
cd linux-5.4.34 # 启动
gdb vmlinux
target remote:1234 # 在semctl调用处打断点
b __x64_sys_semctl
函数的调用顺序为entry_SYSCALL_64 () —> do_syscall_64() —> __x64_sys_gettimeofday()
(gdb) n do_syscall_64 (nr=140730116323824, regs=0xffffc900001b7f58) at arch/x86/entry/common.c:300 300 syscall_return_slowpath(regs); (gdb) n 301 } (gdb) n entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:184 184 movq RCX(%rsp), %rcx (gdb) n 185 movq RIP(%rsp), %r11 (gdb) n 187 cmpq %rcx, %r11 /* SYSRET requires RCX == RIP */ (gdb) n 188 jne swapgs_restore_regs_and_return_to_usermode (gdb) n 205 shl $(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx (gdb) n 206 sar $(64 - (__VIRTUAL_MASK_SHIFT+1)), %rcx (gdb) n 210 cmpq %rcx, %r11 (gdb) n 211 jne swapgs_restore_regs_and_return_to_usermode (gdb) n 213 cmpq $__USER_CS, CS(%rsp) /* CS must match SYSRET */ (gdb) n 214 jne swapgs_restore_regs_and_return_to_usermode (gdb) n 216 movq R11(%rsp), %r11 (gdb) n 217 cmpq %r11, EFLAGS(%rsp) /* R11 == RFLAGS */ (gdb) n 218 jne swapgs_restore_regs_and_return_to_usermode (gdb) n 238 testq $(X86_EFLAGS_RF|X86_EFLAGS_TF), %r11 (gdb) n 239 jnz swapgs_restore_regs_and_return_to_usermode (gdb) n 243 cmpq $__USER_DS, SS(%rsp) /* SS must match SYSRET */ (gdb) n 244 jne swapgs_restore_regs_and_return_to_usermode (gdb) n 253 POP_REGS pop_rdi=0 skip_r11rcx=1 (gdb) n entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:259 259 movq %rsp, %rdi (gdb) n 260 movq PER_CPU_VAR(cpu_tss_rw + TSS_sp0), %rsp (gdb) n entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:262 262 pushq RSP-RDI(%rdi) /* RSP */ (gdb) n entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:263 263 pushq (%rdi) /* RDI */ (gdb) n entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:271 271 SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi (gdb) n 273 popq %rdi (gdb) n entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:274 274 popq %rsp (gdb) n entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:275 275 USERGS_SYSRET64 (gdb) n 0x0000000000448b97 in ?? () (gdb) c Continuing. (gdb)
实验总结:
Linux内核中设置了一组用于实现各种系统功能的子程序,称为系统调用。用户可以通过系统调用命令在自己的应用程序中调用它们。从某种角度来看,系统调用和普通的函数调用非常相似。区别仅仅在于,系统调用由操作系统核心提供,运行于核心态;而普通的函数调用由函数库或用户自己提供,运行于用户态。
系统调用是怎么工作的?
其原理是进程先用适当的值填充寄存器,然后调用一个特殊的指令,这个指令会跳到一个事先定义的内核中的一个位置。在Intel CPU中,这个由中断0x80实现。硬件知道一旦你跳到这个位置,你就不是在限制模式下运行的用户,而是作为操作系统的内核--由用户态转为内核态。
进程可以跳转到的内核位置叫做sysem_call。这个过程检查系统调用号,这个号码告诉内核进程请求哪种服务。然后,它查看系统调用表(sys_call_table)找到所调用的内核函数入口地址。接着,就调用函数,等返回后,做一些系统检查,最后返回到进程(或到其他进程,如果这个进程时间用尽)。
进程号是由eax寄存器存储的,参数一般是由ebx、ecx、edx、esl、edl、ebp来存储的。