我试图模拟 syscall 指令在 Windows 7 X64 (SP1) 上的工作方式,因此我使用 MinGW64 编写了一个 64 位 GCC 示例。据我所知,对于 Windows,所有系统调用入口点都在 ntdll.dll 或 ntdll32.dll 中(在这种情况下,我们只关心 ntdll.dll)。
Status = NtCreateFile(&FileHandle, // returned file handle
(GENERIC_WRITE | SYNCHRONIZE), // desired access
&ObjectAttributes, // ptr to object attributes
&Iosb, // ptr to I/O status block
0, // allocation size
FILE_ATTRIBUTE_NORMAL, // file attributes
0, // share access
FILE_SUPERSEDE, // create disposition
FILE_SYNCHRONOUS_IO_NONALERT, // create options
NULL, // ptr to extended attributes
0); // length of ea buffer
这是源代码的原始部分用C写的,然后我用gas重写
asm volatile
(
"leaq %4, %%r9\n\t"
"leaq %3, %%r8\n\t"
"movq %2, %%rdx\n\t"
"leaq %1, %%rcx\n\t"
"movq %11,0x50(%%rsp)\n\t"
"movq %10,0x48(%%rsp)\n\t"
"movq %9, 0x40(%%rsp)\n\t"
"movq %8, 0x38(%%rsp)\n\t"
"movq %7, 0x30(%%rsp)\n\t"
"movq %6, 0x28(%%rsp)\n\t"
"movq %5, 0x20(%%rsp)\n\t"
"movq %%r9, 0x18(%%rsp)\n\t"
"movq %%r8, 0x10(%%rsp)\n\t"
"movq %%rdx, 0x8(%%rsp)\n\t"
"movq %%rcx, (%%rsp)\n\t"
"movq __imp_NtCreateFile(%%rip), %%rax\n\t"
"call *%%rax\n\t"
: "=a"(Status)
: "m"(FileHandle), "g"(GENERIC_WRITE | SYNCHRONIZE),"m"(ObjectAttributes),"m"(Iosb),"g"(0),"g"(FILE_ATTRIBUTE_NORMAL),"g"(0),"g"(FILE_SUPERSEDE),"g"(FILE_SYNCHRONOUS_IO_NONALERT),"g"(NULL),"g"(0)
: "%rcx", "%rdx", "%r8", "%r9", "%r10","%r11"
);
到目前为止,该程序按预期工作:它创建了一个文本文件并在文件中写入了一些内容。
我用windbg反汇编了ntdll!NtCreateFile,只看到(重写为GAS AT&T格式)
"movq $0x52, %%rax\n\t"
"movq %%rcx, %%r10\n\t"
"syscall\n\t"
"ret\n\t"
我在我的程序中添加了这部分代码作为
asm volatile
(
"leaq %4, %%r9\n\t"
"leaq %3, %%r8\n\t"
"movq %2, %%rdx\n\t"
"leaq %1, %%rcx\n\t"
"movq %11,0x50(%%rsp)\n\t"
"movq %10,0x48(%%rsp)\n\t"
"movq %9, 0x40(%%rsp)\n\t"
"movq %8, 0x38(%%rsp)\n\t"
"movq %7, 0x30(%%rsp)\n\t"
"movq %6, 0x28(%%rsp)\n\t"
"movq %5, 0x20(%%rsp)\n\t"
"movq %%r9, 0x18(%%rsp)\n\t"
"movq %%r8, 0x10(%%rsp)\n\t"
"movq %%rdx, 0x8(%%rsp)\n\t"
"movq %%rcx, (%%rsp)\n\t"
"movq $0x52, %%rax\n\t"
"movq %%rcx, %%r10\n\t"
"syscall\n\t"
: "=a"(Status)
: "m"(FileHandle), "g"(GENERIC_WRITE | SYNCHRONIZE),"m"(ObjectAttributes),"m"(Iosb),"g"(0),"g"(FILE_ATTRIBUTE_NORMAL),"g"(0),"g"(FILE_SUPERSEDE),"g"(FILE_SYNCHRONOUS_IO_NONALERT),"g"(NULL),"g"(0)
: "%rcx", "%rdx", "%r8", "%r9", "%r10","%r11"
);
现在 Status 总是返回值“0xc000000d”,程序失败。现在我有几个困惑的问题:
保存在用户态栈中的参数如何传递到内核态?因为我看到 NtDll!NtCreateFile 中没有执行任何操作。
如何将正确的返回值赋值回%%rax?这部分在 disassmebler 中也丢失了。
如何使我的代码在执行直接系统调用时按预期工作?
非常感谢您的大力帮助。
好的,这里显示工作代码
asm volatile
(
"leaq %4, %%r9\n\t"
"leaq %3, %%r8\n\t"
"movq %2, %%rdx\n\t"
"leaq %1, %%rcx\n\t"
"movq %11,0x50(%%rsp)\n\t"
"movq %10,0x48(%%rsp)\n\t"
"movq %9, 0x40(%%rsp)\n\t"
"movq %8, 0x38(%%rsp)\n\t"
"movq %7, 0x30(%%rsp)\n\t"
"movq %6, 0x28(%%rsp)\n\t"
"movq %5, 0x20(%%rsp)\n\t"
"push $_end \n\t"
"movq %%rcx,%%r10\n\t"
"movq $0x52,%%rax\n\t"
"syscall\n\t"
"ret\n\t"
"_end:\n\t"
: "=a"(Status)
: "m"(FileHandle), "g"(GENERIC_WRITE | SYNCHRONIZE),"m"(ObjectAttributes),"m"(Iosb),"g"(0),"g"(FILE_ATTRIBUTE_NORMAL),"g"(0),"g"(FILE_SUPERSEDE),"g"(FILE_SYNCHRONOUS_IO_NONALERT),"g"(NULL),"g"(0)
: "%rcx", "%rdx", "%r8", "%r9", "%r10","%r11"
);
模拟 call/ret 并不是很痛苦。在这里,我使用了 Linus 在他的 Linux 0.11 中使用过的解决方法。
最佳答案
我认为你对堆栈深度的看法是错误的。 许多参数是通过堆栈传递的。如果库调用介于两者之间,则系统调用期望它们准确位于它们所在的位置。
如果您跳过库调用并自己执行系统调用(您只应该为实验而不是生产性东西做的事情!),则堆栈中缺少一项。
因此,要么将虚拟值压入堆栈,要么调整偏移量。
详细来说,原始代码中发生了以下情况:
- 您将参数放入堆栈(直到
movq %%rcx, (%%rsp)
)。 - 您对
__imp_NtCreateFile
执行调用
。 这会将返回地址放入堆栈,并将%tip
传输到库函数。 - 然后,库函数实质上执行系统调用。
- 然后内核期望数据远离堆栈顶部一项,因为所述调用添加了一项。
如果您自己执行系统调用,则必须放入另一个项以补偿此返回地址,这会移动内核的堆栈 View 。
关于c - 如何在 Windows 7 X64 SP1(x64 模式)下执行直接系统调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18350185/