使用自定义binfmt处理程序加载的程序中存在分段错误;ELF格式的相同程序无缝运行



我一直在为Ubuntu Linux 12.04、Intel x86_64架构开发一个自定义PE binfmt处理程序(如果这听起来很熟悉,我已经发布了一些与该项目相关的问题)。如果我提供的信息太多,我会提前道歉。

binfmt处理程序是相当标准的;我读取PE头和节,然后将这些节写入用户空间内存中节表中指定的地址。然后,当一切就绪时,我打电话给

start_thread(regs, entry_addr, current->mm->start_stack);

就像内置的Linux处理程序一样;在我的情况下,regs = 0xcf7dffb4entry_addr = 0x401000start_stack = 0xbffff59b

我有以下代码,在英特尔x86汇编:

push ebp
mov ebp, esp
mov eax, 4
add eax, 5
pop ebp
ret

我用fasm将这个程序编译成PE格式的可执行文件(math1.exe),并用insmod安装我的binfmt处理程序。如果我在gdb中调试这个程序,我会看到:

(gdb) set disassembly-flavor intel
(gdb) x/6i 0x401000
    0x401000:   push   ebp
    0x401001:   mov    ebp,esp
    0x401003:   mov    eax,0x4
    0x401008:   add    eax,0x5
    0x40100b:   pop    ebp
    0x40100c:   ret

所以我知道代码加载到了正确的地址。然后:

(gdb) run
Starting program: /media/sf_Sandbox/math1.exe 
Program received signal SIGSEGV, Segmentation fault.
0x0040100c in ?? ()

当我进行寄存器转储时:

(gdb) info registers
eax            0x9  9
ecx            0x81394e8    135501032
edx            0x8137808    135493640
ebx            0x8139548    135501128
esp            0xbfffe59b   0xbfffe59b
ebp            0x0  0x0
esi            0x81394e8    135501032
edi            0x2f7ff4 3112948
eip            0x40100c 0x40100c
...other registers...

您可以看到,代码确实是因为eax = 0x9而执行的,这是应该的。不过,从表面上看,我找不到任何理由让ret语句出错。检查dmesg,我发现

math1.exe[1864] general protection ip:40100c sp:bffff5bd error:0

但我发现很少有关于可能导致这种情况的文件。我知道问题不在于代码本身,因为用相同的汇编程序编译成ELF格式的相同代码运行起来没有任何问题。

我目前关于这个问题的理论是:

  1. 我并没有真正弄乱处理程序中的堆栈指针。内置的Linux处理程序(ELF、a.out和平面格式,仅举三个例子)有一个函数create_*_tables(),用于处理argcargvenvp参数。一开始我没有包含这个函数,因为测试程序不接受任何输入,但实现create_flat_tables()函数(来自平面处理程序)到目前为止还不能解决问题。(我知道盲目粘贴和调用其他模块的函数是一个糟糕的想法,但该函数的a.out和flat版本本质上是相同的,所以它似乎不太依赖于可执行文件的格式;我想我应该试一试。)
  2. 我发现了这篇关于在执行main()之前和之后发生的函数调用链的文章。math1.exe的objdump只包含上面给出的汇编代码,但同一程序的objdump在汇编成ELF格式(生成一个*.o文件)并与gcc链接(获得一个ELF二进制文件)后,包含了文章中提到的其他函数(_start()__libc_start_main()等)。也许这些函数在Linux平台上比我以前认为的更强制性

我正在寻找我可以采取的任何解释/建议/进一步的故障排除步骤。提前感谢!

您确实需要实现_start__libc_start_main序列的至少一个方面:调用_exit syscall。您不能仅仅从execve创建的框架中执行一个"ret",并期望它会导致进程干净地终止。从main返回以退出进程是C特性,而您的程序不是C。

我的记忆在系统调用接口上有点模糊,但我相信它是这样的:

  1. 将%eax设置为系统调用编号(_NR_exit
  2. 将%ebx设置为第一个参数(退出代码)
  3. int$0x80

%ecx、%edx的附加参数。。。我不确定订单。但是_exit()只需要一个arg,我很确定它在%ebx中。

相关内容

  • 没有找到相关文章

最新更新