那个解决方案是错误的。线路sub esp, 12
应该sub esp, 4
。它没有传递 0 作为退出状态;它传递了一个垃圾值。
Mac OS X 有 BSD 系统调用。例子很难找到,因为大多数 BSD 程序不进行直接系统调用;它们链接到 libc 并调用 libc 中包装系统调用的函数。但在汇编语言中,直接系统调用可能比 libc 调用更简单。
对于 32 位 Intel 代码,OS X 和 Linux 都会响应int 0x80
。他们都将系统调用号放入eax
,并且它们都返回结果eax
。主要区别如下:
- Linux 在寄存器中获取参数(
ebx
, ecx
, edx
),但 OS X 将参数放在堆栈上。您以相反的顺序压入参数,然后压入额外的 4 个字节。
- 当发生错误时,Linux 会在其中放入一个负数
eax
,但 OS X 在其中输入一个正数eax
。 OS X 还设置了一个条件代码,以便jb
or jnb
如果发生或未发生错误,则会跳转。
- 系统调用有不同的编号,有些有不同的参数。
重要文件:系统调用.master
BSD 系统使用名为 syscalls.master 的文件来定义系统调用。我已经链接到Mac OS X 10.4.11x86 的 syscalls.master。我们可以用它来查找每个系统调用的名称、参数和返回类型。例如:
4 PRE NONE ALL { user_ssize_t write(int fd, user_addr_t cbuf, user_size_t nbyte); }
write(2) 系统调用的编号为 4,因此我们加载eax
4.它有3个参数,所以我们以相反的顺序推送它们:我们推送缓冲区中的字节数,然后推送指向缓冲区的指针,然后推送文件描述符。推入参数后,我们推入 4 个额外字节,也许是sub esp, 4
。然后我们就做int 0x80
。那么我们可能想要add esp, 16
删除我们推送的内容。
大多数参数和返回值都是 4 字节整数,但是off_t
在 OS X 中始终是 8 字节整数。我们必须小心像 lseek(2) 这样的调用。
199 NONE NONE ALL { off_t lseek(int fd, off_t offset, int whence); }
offset 参数是一个 8 字节整数,因此我们将其作为一对 4 字节双字压入。 Intel 处理器是小端字节序,并且堆栈向下增长,因此我们在推送低位双字之前推送高位双字。 lseek(2) 的返回类型也是 off_t。它来自寄存器eax
and edx
,其中的低位词eax
和高词在edx
.
有些系统调用很奇怪。为了捕获信号,与 Linux 不同,OS X 没有对 signal(3) 的系统调用。我们必须使用 sigaction(2),但这很奇怪:
46 NONE KERN ALL { int sigaction(int signum, struct __sigaction *nsa, struct sigaction *osa); }
第二个参数不是常规的 sigaction 结构。这是一个更大的结构,包括一个额外的蹦床区域。如果我们不在 libc 中调用 sigaction(),那么我们必须提供我们自己的蹦床!它与 Linux 和其他 BSD 内核不同。