x64汇编之从程序编辑到系统调用

张开发
2026/4/21 3:37:58 15 分钟阅读

分享文章

x64汇编之从程序编辑到系统调用
大家好你们可以叫我凌是个16岁的网络安全学习者。今天我们来学习x64汇编的调试、结构以及系统调用内容重要且基础。那我们就直接开始吧注本教程采用使用范围更广的NASM语法如需学习其他语法自行上网查找两种语法的区别即可。只因语法不同因此花几十分钟即可快速掌握。你的首个asm程序按照各个语言的传统我们先来简单写段“hello word”的程序该文章也会从这个程序下手进行讲解以帮助读者更好地了解。打开终端输入以下命令以此来创建并编写汇编程序如不理解或许应该去补充下Linux的知识vim hello.asmsection .data msg db hello, world,0 section .bss section .text global main main: mov rax, 1 ; 1表示写入 mov rdi, 1 ; 1表示标准输出 mov rsi, msg ; 需要显示的是字符串存放在rsi中 mov rdx, 12 ; 字符串的长度不包括0 syscall ; 显示字符串 mov rax, 60 ; 60表示退出 mov rdi, 0 ; 0是成功退出代码 syscall ; 退出随后点击Esc输入:wq保存并退出编辑模式这就是串简单的汇编代码接下来我们要对其进行汇编和使用gcc链接达到运行的效果汇编汇编就是生成目标文件使用以下命令来执行各个部分解释如下nasm调用 NASMNetwide Assembler汇编器。-f elf64指定输出文件的格式为elf64即 64 位的 ELFExecutable and Linkable Format。这是 Linux 系统上常用的目标文件格式用于后续链接生成可执行文件。hello.asm输入的汇编源文件通常包含 64 位 x86 汇编代码。-o hello.o指定输出的目标文件名为hello.o。如果不加-o默认输出文件名会是hello.o这样 home目录下就会多出相关的文件刚刚生成的使用gcc链接这是是在Linux下使用GCC将之前生成的hello.o目标文件链接成最终的可执行文件hello这里要注意-no-pie选项这样就可以生成可执行文件了以下为各部分解释gcc调用 GCC 编译器驱动这里主要用于链接阶段因为输入是.o目标文件。-no-pie现代 Linux 默认生成PIE位置无关可执行文件程序加载地址随机化安全性更高。加-no-pie后程序会被链接到固定基址如0x400000代码中可以直接使用绝对地址。hello.o输入的目标文件由之前的nasm -f elf64 hello.asm -o hello.o生成。-o hello指定输出的可执行文件名为hello默认是a.out。注是 GCC 链接时的一个选项作用是生成传统、非位置无关的可执行文件。当手写汇编代码如 NASM没有使用RIP 相对寻址而是直接写了绝对地址或依赖固定内存布局时用默认 PIE 模式链接会出错此时加上-no-pie可以解决兼容性问题。这里进行了解即可运行代码接下来我们使用以下命令即可运行我们刚刚创建的可执行文件汇编程序的结构刚刚程序我们已经见识的汇编程序的模样以下为汇编程序的主要组成部分.data段 .bss段 .text段接下来我们将逐一讲解各个部分.data段.data段(section)就是准备好要处理的数据使用以下格式声明和定义初始化数据变量名称 类型 值如该段包含变量当源代码被汇编并链接到可执行文件时将为该变量分配内存。变量名称为符号名称对内存位置和变量的引用可以占用一个或多个内存位置。变量名称是指变量在内存中的起始位置。在上面程序中.data段包含变量msg这是个符号名称指向内存地址h为字符串“hello word”的第一个字节。字符串是内存中字符的“列表”或“数组”。事实上内存中的任何连续列表都可以视为字符串另外我们发现这后面有个0。你可以忽略或不写他但是带来的后果得自负。这里的0并不是ASCII里面的0而是单纯的数字0添加一个空字节并且它的位置包含一个字节此外该段还可以包含常量学过其他语言的应该知道即无法更改的值下面为声明格式常量名称 equ 值例如abc equ 12345.bss段.bss段就是写出处理数据的步骤。用于存放未初始化的量未初始化变量的空间在此段声明变量名称 类型 数字以下为bss段的数据类型例如以下内容声明个包含20个双字的数组dArray resd 20该段的变量不包含任何值这些值将在执行中分配。内存位置不是在编译时保留的而是在执行时保留的。当程序开始执行时程序会从操作系统中请求所需的内存分配给.bss段中变量并初始化为零。如果执行时没有足够的内存可用于.bss变量程序将崩溃.txt段.txt段是所有操作所在的位置global main main:main:称为标签后面最好跟上冒号“:”避免引起不必要的麻烦此外我们还会发现有许多数字这称为系统调用码不同数字在不同寄存器里面有不同的意思。比如下面这串代码就是写入的意思(有关寄存器的内容见x64汇编内存工作的基本原理之后会出更详细的寄存器文章)mov rax,1以下为mov的指令用法极为重要mov 寄存器,即时值mov 寄存器,内存mov 内存,寄存器注mov 内存,内存 为不合法的系统调用什么是系统调用我们的程序运行在“用户态”不能直接操作硬件如屏幕、键盘。需要操作系统内核帮忙时通过“系统调用”请求内核执行。syscall指令就是触发这个请求的开关。系统调用的三个步骤1.rax中放服务号告诉内核做什么2.rdi, rsi, rdx, r10, r8, r9中放参数告诉内核具体怎么做最多6个3. 执行syscall常用服务号rax服务号名称功能1sys_write写入数据如输出到屏幕60sys_exit退出程序参数寄存器参数位置寄存器第1个rdi第2个rsi第3个rdx第4个r10第5个r8第6个r9注超过6个参数时通过栈传递但入门极少遇到。易混淆点澄清1.rax1是“写入”服务rdi1在sys_write中是“屏幕”在sys_exit中是“退出码1错误”。同一个数字在不同寄存器/不同调用中含义不同。2.只有sys_exit时rdi才叫“退出码”在sys_write中叫“文件描述符”

更多文章