一、下载 NASM
macOS 自带 NASM 编译器,但是版本比较低,只能编译 32 位的程序,最好是下载新版的编译器。
官网地址:http://www.nasm.us
我下载的是最新版 nasm-2.13.02rc2 版本。
二、设置环境变量
下载完成之后解压,然后设置一下环境变量,使用 echo $PATH 查看当前环境变量
1 2 |
$ echo $PATH /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Applications/Wireshark.app/Contents/MacOS |
修改 ~/.bash_profile,如果不存在该文件则添加
1 |
sudo vi ~/.bash_profile |
在 .bash_profile 文件里输入以下语句,这样相当于每次打开终端会执行这个命令,实现了自动设置环境变量。
1 |
export PATH=/Users/exchen/dev/tools/nasm/nasm-2.13.02rc2:$PATH |
1 |
source ~/.bash_profile |
三、编写 HelloWorld
编写代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
SECTION .data msg: db "HelloWorld!", 0x0a len: equ $-msg SECTION .text global _main _main: mov rax,0x2000004 ;0x2000004 表示 syscall 调用号 write mov rdi,1 ;表示控制台输出 mov rsi,msg ;syscall 调用会到 rsi 来获取字符 mov rdx,len ;字符串长度 syscall mov rax,0x2000001 ;0x2000001 表示退出 syscall mov rdi,0 syscall |
编译:
1 |
nasm -f macho64 HelloWorld.asm -o HelloWorld.o |
链接:
1 |
ld -o HelloWorld -e _main HelloWorld.o |
运行结果:
1 2 |
$ ./HelloWorld HelloWorld! |
SECTION .data 表示数据段。
SECTION .text 表示代码段。
global 表示是对外公开函数的名称,这样 ld 时就可以指定它为入口函数。
syscall 是内核调用函数,在 syscall.h 中可以找到调用的具体作用,在 macOS 下调用号需要 0×2000000 + unix syscall #
1 2 3 4 5 6 7 8 9 10 11 |
#include <sys/appleapiopts.h> #ifdef __APPLE_API_PRIVATE #define SYS_syscall 0 #define SYS_exit 1 #define SYS_fork 2 #define SYS_read 3 #define SYS_write 4 #define SYS_open 5 #define SYS_close 6 #define SYS_wait4 7 ............ |
看下反汇编后的效果:
四、函数调用的参数传递
上面我们试了通过 syscall 输出 Helloworld,这节我们来了解一下在 x64 汇编里函数怎么传递参数的。Windows 平台的 64 位汇编函数传递参数和 macOS 是不一样的,在 Windows 平台 64 位汇编函数传递参数按照从右到左的顺序,顺序是 RCX、RDX、R8、R9,如果还有更多的参数,就通过椎栈传递,而 macOS 64 位汇编里函数传递参数按照从左到右的顺序,依次通过寄存器 RDI、RSI、RDX、RCX、R8、R9 来传递,如果参数个数超过了这么多,那么多余的参数通过栈来传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
extern _printf SECTION .data textmsg: db "Hello! asm x64 for macOS", 0x0a,0 SECTION .text global _main _main: push rbp mov rbp, rsp mov rdi,textmsg ;第一个参数 rdi call _printf leave ret |
由于使用了 printf 函数,所以链接的时候,记得要把 /usr/lib/libSystem.B.dylib 放进去
1 2 |
nasm -f macho64 HelloWorld.asm -o HelloWorld.o gcc /usr/lib/libSystem.B.dylib -o HelloWorld HelloWorld.o |
输出的结果:
1 2 |
./HelloWorld Hello! asm x64 for macOS |
再来看一段代码,该段代码演示函数的六个参数传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
extern _printf SECTION .data strformat: db "num %d %d %d %d %d", 0x0a,0 str1: db "exchen", 0x0a,0 SECTION .text global _main _main: push rbp ; push rbp mov rbp,rsp 相当于 enter 指令 mov rbp, rsp mov rdi,strformat ;第一个参数 mov rsi,1 ;第二个参数 mov rdx,2 ;第三个参数 mov rcx,3 ;第四个参数 mov r8,4 ;第五个参数 mov r9,5 ;第六个参数 call _printf leave ;leave 指令相当于 mov rsp,rbp pop ebp ret |
输出结果:
1 2 |
./HelloWorld num 1 2 3 4 5 |
当需要传递第七个以及七个以上的参数,就要使用栈来传递,以下代码演示了通过栈传递十个参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
extern _printf SECTION .data strformat: db "name %d %d %d %d %d %d %d %d %d", 0x0a,0 SECTION .text global _main _main: push rbp mov rbp, rsp sub rsp,100h mov rdi,strformat ;第一个参数 mov rsi,1 ;第二个参数 mov rdx,2 ;第三个参数 mov rcx,3 ;第四个参数 mov r8,4 ;第五个参数 mov r9,5 ;第六个参数 mov dword [rsp],6 ;第七个参数 mov dword [rsp+8],7 ;第八个参数 mov dword [rsp+16],8 ;第九个参数 mov dword [rsp+24],9 ;第十个参数 call _printf add rsp,100h leave ret |
五、局部变量的使用
全局变量都是定义在数据段里,在编译文件的时候直接就编译到文件体里了,而局部变量是定义在栈中,在使用的时候才会在栈中分配,所以在编写 ShellCode 的时候只能使用局部变量,不要使用全局变量。以下代码演示如何使用局部变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
extern _printf SECTION .data strformat: db "name %d %d %d %d %d %d %d %d %d", 0x0a,0 SECTION .text global _main _main: push rbp mov rbp, rsp sub rsp,100h mov byte [rsp],61h ;字符 a mov byte [rsp+1],62h ;字符 b mov byte [rsp+2],'c' ;字符 c mov byte [rsp+3],'d' ;字符 d mov rdi,rsp ;第一个参数 call _printf add rsp,100h leave ret |
输出结果:
1 2 |
$ ./HelloWorld abcd |
六、编写 ShellCode
通过上面的了解,我们尝试一下编写一段 ShellCode,然后将 ShellCode 放到其他程序中去执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
extern _printf SECTION .data strformat: db "name %d %d %d %d %d %d %d %d %d", 0x0a,0 SECTION .text global _main _main: push rbp mov rbp, rsp sub rsp,100h mov byte [rsp],'a' mov byte [rsp+1],'b' mov byte [rsp+2],'c' mov rax,0x2000004 ;0x2000004 表示 syscall 调用号 write mov rdi,1 ;表示控制台输出 mov rsi,rsp ;syscall 调用会到 rsi 来获取字符 mov rdx,3 ;字符串长度 syscall mov rax,0x2000001 ;0x2000001 表示退出 syscall mov rdi,0 syscall add rsp,100h leave ret |
输出结果:
1 2 |
$ ./HelloWorld abc |
将汇编对应的机器码复制就是一段 ShellCode,将这段数据复制到其他程序中,调用之后就可以得到执行。
1 2 3 4 5 |
5548 89e5 4881 ec00 0100 00c6 0424 61c6 4424 0162 c644 2402 63b8 0400 0002 bf01 0000 0048 89e6 ba03 0000 000f 05b8 0100 0002 bf00 0000 000f 0548 81c4 0001 0000 c9c3 |
转载请注明:exchen's blog » 使用 NASM 在 macOS 下编写汇编