对Solar Designer's Non-executable stack一点补充 by alert7 最近很多人问我这样的问题: 对于在内核中模拟信号返回的一段汇编指令理解有点困难,代码如下 + * Call sys_sigreturn() to restore the context. It would definitely be better + * to convert sys_sigreturn() into an inline function accepting a pointer to + * pt_regs, making this faster... + */ + regs->esp += 8; 退掉用户堆栈的返回地址与参数。 + __asm__("movl %3,%%esi;" //把regs地址放到esi中 + "subl %1,%%esp;" //esp减小regs的大小,其实也就是为 //sys_sigreturn分配了一快regs + "movl %2,%%ecx;" // + "movl %%esp,%%edi;" //把刚分配到的regs的地址放到edi + "cld; rep; movsl;" //进行拷贝 + "call sys_sigreturn;" + "leal %3,%%edi;" 以下做相反操作 + "addl %1,%%edi;" + "movl %%esp,%%esi;" + "movl (%%edi),%%edi;" + "movl %2,%%ecx;" + "cld; rep; movsl;" + "movl %%esi,%%esp" + : +/* %eax is returned separately */ + "=a" (regs->eax) + : + "i" (sizeof(*regs)), + "i" (sizeof(*regs) >> 2), + "m" (regs) + : + "cx", "dx", "si", "di", "cc", "memory"); 以上一段汇编代码模拟返回时的正常状态,并替用户执行信号返回 + return; + } 其中对下面: leal %3, %%edi; addl %1, %%edi movl (%%edi),%%edi; 这两句真是无法理解,为什么要把regs这个指针的地址放到edi中,而不是movl %3,%%edi??? 一开始一个叫zrzeng的朋友问我这个问题我没有注意到,后来一个叫cathycai的朋友又问我同样的问题, (在这里我要感谢他们)就开始觉得奇怪了。分析了以上这段汇编代码,发现还真有点东东,还是学习态度不 够严谨啊,一开始在整理《Solar Designer's Non-executable stack的实现机理分析》的时候没有 注意到这个精华部分。 假如按照一般的计算方法的话 leal %3, %%edi;取的是regs的地址 然后再addl %1, %%edi也就是regs的地址+sizeof(*regs),这个值可以分析得到的地址是 regs->esp的地址,可见图一。 然后在取出esp的值放入edi中 如果这样计算的话,那么就是说要把sys_sigreturn的那个regs拷贝到应用程序空间的esp上 如果坚信上面计算正确的话还真是想破脑袋都想不明白: 1:把regs拷贝到应用程序空间的esp上有何作用 2:那么do_general_protection函数又如何能正确返回,即使返回了还是那条ret指令 从esp上弹出一个值作为EIP那么还是产生通用保护错误,还会触发 do_general_protection函数。 其实我们我们注意到一个事实,内核编译是带-fomit-frame-pointer编译选项的,也就是忽略帧指针, 这样在程序就不需要保存,安装,和恢复ebp了,更重要的是函数的参数开始用esp做为寻址寄存器了, 这一点很重要很重要。 以上汇编代码会产生类试如下真实的汇编代码: 0x8048420 : mov 0x10(%esp,1),%esi 0x8048424 : sub $0x8,%esp 0x8048427 : mov $0x2,%ecx 0x804842c : mov %esp,%edi 0x804842e : lea 0x10(%esp,1),%edi 0x8048432 : add $0x8,%edi 0x8048435 : mov %esp,%esi 0x8048437 : mov (%edi),%edi 带上-fomit-frame-pointer编译后,函数的参数开始用esp做为寻址寄存器。那是静态产生 的汇编指令。他们不知道ESP是否被改变,当我们的指令改变了ESP的话,那么下面的一些参数 引用都会出现错误。基于这个事实,我们再来计算下。 leal %3, %%edi;汇编代码类试如下lea 0x10(%esp,1),%edi 由于上面subl %1,%%esp;减小了ESP,所以在这里EDI得到的是(regs的地址-sizeof(*regs))的值 然后用addl %1, %%edi指令给edi补上sizeof(*regs),这样edi的值才是regs的地址。 然后movl (%%edi),%%edi;也就得到了regs的指向地址,这样才把sys_sigreturn的regs 拷贝到了do_general_protection的regs中去了,这样返回也就对了。 上面的问题中为什么不用movl %3,%%edi指令也就显而易见了。 也可以拿以下的应用程序代码试试,增加下理解。 -----> cut here <-------------- #include struct regs { int eax; int ff; }; int test(struct regs * regs) { printf("regs addr %p point %p \n",®s,regs); __asm__("movl %3,%%esi;" "subl %1,%%esp;" "movl %2,%%ecx;" "movl %%esp,%%edi;" "leal %3,%%edi;" "addl %1,%%edi;" "movl %%esp,%%esi;" "movl (%%edi),%%edi;" "movl %2,%%ecx;" "movl %%esi,%%esp" : /* %eax is returned separately */ "=a" (regs->eax) : "i" (sizeof(*regs)), "i" (sizeof(*regs) >> 2), "m" (regs) : "cx", "dx", "si", "di", "cc", "memory"); return 0; } int main(int argc, char *argv[]) { struct regs tt; test(&tt); } ---------------------- #gcc -o t t.c -fomit-frame-pointer [alert7@redhat73 alert7]# gdb -q t (gdb) disass test Dump of assembler code for function test: 0x8048400 : push %edi 0x8048401 : push %esi 0x8048402 : sub $0x4,%esp 0x8048405 : sub $0x4,%esp 0x8048408 : pushl 0x14(%esp,1) 0x804840c : lea 0x18(%esp,1),%edx 0x8048410 : mov %edx,%eax 0x8048412 : push %eax 0x8048413 : push $0x80484d8 0x8048418 : call 0x80482f0 0x804841d : add $0x10,%esp 0x8048420 : mov 0x10(%esp,1),%esi 0x8048424 : sub $0x8,%esp 0x8048427 : mov $0x2,%ecx 0x804842c : mov %esp,%edi 0x804842e : lea 0x10(%esp,1),%edi 0x8048432 : add $0x8,%edi 0x8048435 : mov %esp,%esi 0x8048437 : mov (%edi),%edi 0x8048439 : mov $0x2,%ecx 0x804843e : mov %esi,%esp 0x8048440 : mov 0x10(%esp,1),%edx 0x8048444 : mov %eax,%eax 0x8048446 : mov %eax,(%edx) 0x8048448 : mov $0x0,%eax 0x804844d : add $0x4,%esp 0x8048450 : pop %esi 0x8048451 : pop %edi 0x8048452 : ret 0x8048453 : nop End of assembler dump. ********************************************************************************** 图一 (内存高址) +--------------------------------------+ | ss | +--------------------------------------+ | esp | +--------------------------------------+ | eflags | +--------------------------------------+ | cs | +--------------------------------------+ | eip | +--------------------------------------+ | error no错误代码(orig_eax) | +--------------------------------------+ |do_general_protection地址(es) | +--------------------------------------+ | ds | +--------------------------------------+ | eax | +--------------------------------------+ | ebp | +--------------------------------------+ | edi | +--------------------------------------+ | esi | +--------------------------------------+ | edx | +--------------------------------------+ | ecx | +--------------------------------------+ | ebx | +--------------------------------------+ <----- pt_regs开始地址 pt_regs pointer | error no错误代码 | do_general_protection的参数error_code +--------------------------------------+ | pt_regs pointer | do_general_protection的参数regs指针 +--------------------------------------+ |do_general_protection的返回地址 | +--------------------------------------+ |....................... | +--------------------------------------+ | | + + |分配了一个pt_regs大小空间 | +--------------------------------------+ <----- esp (内存低址) ********************************************************************************** -------------------the end -----------