我的问候&尊重所有人。我有一个C程序,基本上是为了测试缓冲区溢出而编写的。
#include<stdio.h>
void display()
{
char buff[8];
gets(buff);
puts(buff);
}
main()
{
display();
return(0);
}
现在我使用GDB反汇编它的显示和主要部分。代码: -
转储函数main的汇编代码:
0x080484ae <+0>: push %ebp # saving ebp to stack
0x080484af <+1>: mov %esp,%ebp # saving esp in ebp
0x080484b1 <+3>: call 0x8048474 <display> # calling display function
0x080484b6 <+8>: mov $0x0,%eax # move 0 into eax , but WHY ????
0x080484bb <+13>: pop %ebp # remove ebp from stack
0x080484bc <+14>: ret # return
汇编程序转储结束。
转储函数显示的汇编程序代码:
0x08048474 <+0>: push %ebp #saves ebp to stack
0x08048475 <+1>: mov %esp,%ebp # saves esp to ebp
0x08048477 <+3>: sub $0x10,%esp # making 16 bytes space in stack
0x0804847a <+6>: mov %gs:0x14,%eax # what does it mean ????
0x08048480 <+12>: mov %eax,-0x4(%ebp) # move eax contents to 4 bytes lower in stack
0x08048483 <+15>: xor %eax,%eax # xor eax with itself (but WHY??)
0x08048485 <+17>: lea -0xc(%ebp),%eax #Load effective address of 12 bytes
lower placed value ( WHY???? )
0x08048488 <+20>: mov %eax,(%esp) #make esp point to the address inside of eax
0x0804848b <+23>: call 0x8048374 <gets@plt> # calling get, what is "@plt" ????
0x08048490 <+28>: lea -0xc(%ebp),%eax # LEA of 12 bytes lower to eax
0x08048493 <+31>: mov %eax,(%esp) # make esp point to eax contained address
0x08048496 <+34>: call 0x80483a4 <puts@plt> # again what is "@plt" ????
0x0804849b <+39>: mov -0x4(%ebp),%eax # move (ebp - 4) location's contents to eax
0x0804849e <+42>: xor %gs:0x14,%eax # # again what is this ????
0x080484a5 <+49>: je 0x80484ac <display+56> # Not known to me
0x080484a7 <+51>: call 0x8048394 <__stack_chk_fail@plt> # not known to me
0x080484ac <+56>: leave # a new instruction, not known to me
0x080484ad <+57>: ret # return to MAIN's next instruction
汇编程序转储结束。
所以大家,你应该考虑我的作业。休息所有代码都是我所知道的,除了几行。我加了一个大“为什么????”每行前面的评论中还有一些问题。对我来说,第一个障碍是“mov%gs:0x14,%eax”指令,我不能在此指令后制作流程图。有人解释一下,这几条指令是什么意思,并在程序中做什么?感谢...
答案 0 :(得分:10)
0x080484b6 <+8>: mov $0x0,%eax # move 0 into eax , but WHY ????
你有没有这个?:
return(0);
他们可能是相关的。 :)
0x0804847a <+6>: mov %gs:0x14,%eax # what does it mean ????
这意味着从地址gs:0x14的内存中读取4个字节到eax。 gs是一个段寄存器。很可能通过此寄存器引用了线程本地存储(AKA TLS
)。
0x08048483 <+15>: xor %eax,%eax # xor eax with itself (but WHY??)
不知道。可能与优化有关。
0x08048485 <+17>: lea -0xc(%ebp),%eax #Load effective address of 12 bytes
lower placed value ( WHY???? )
它使eax指向一个存在于堆栈中的局部变量。 sub $0x10,%esp
为他们分配了一些空间。
0x08048488 <+20>: mov %eax,(%esp) #make esp point to the address inside of eax
错误。它将eax写入堆栈,堆栈顶部。它将作为堆栈参数传递给被调用函数:
0x0804848b <+23>: call 0x8048374 <gets@plt> # calling get, what is "@plt" ????
我不知道。可能是一些名字错误。
到现在为止你应该猜到了那个局部变量。 buff
,还有什么呢?
0x080484ac <+56>: leave # a new instruction, not known to me
为什么不在CPU手册中查找?
现在,我可以解释一下gs / TLS的事情......
0x08048474 <+0>: push %ebp #saves ebp to stack
0x08048475 <+1>: mov %esp,%ebp # saves esp to ebp
0x08048477 <+3>: sub $0x10,%esp # making 16 bytes space in stack
0x0804847a <+6>: mov %gs:0x14,%eax # what does it mean ????
0x08048480 <+12>: mov %eax,-0x4(%ebp) # move eax contents to 4 bytes lower in stack
...
0x0804849b <+39>: mov -0x4(%ebp),%eax # move (ebp - 4) location's contents to eax
0x0804849e <+42>: xor %gs:0x14,%eax # # again what is this ????
0x080484a5 <+49>: je 0x80484ac <display+56> # Not known to me
0x080484a7 <+51>: call 0x8048394 <__stack_chk_fail@plt> # not known to me
0x080484ac <+56>
因此,此代码从TLS(在gs:0x14处)获取一个值,并将其存储在保存的ebp值(ebp-4)的正下方。然后是get()
和put()
的你的东西。然后,此代码检查来自TLS的值的副本是否未更改。 xor %gs:0x14,%eax
进行比较。
如果XORed值相同,则XOR的结果为0,flags.zf为1.否则,结果不为0且flags.zf为0.
如果flags.zf = 1,则 je 0x80484ac <display+56>
检查flags.zf并跳过call 0x8048394 <__stack_chk_fail@plt>
。如果TLS中的值副本未更改,则跳过此调用。
那是什么一回事?这是一种尝试捕获缓冲区溢出的方法。如果写入超出缓冲区末尾,则会覆盖从TLS复制到堆栈的值。
为什么我们从TLS中获取这个值,为什么不只是一个恒定的,硬编码的值?我们可能希望使用不同的非硬编码值来更频繁地捕获溢出(因此TLS中的值将从程序的运行更改为另一个运行程序,并且在程序的不同线程中它将有所不同)。如果每次程序运行时随机选择值,这也会降低攻击者成功利用缓冲区溢出的几率。
最后,如果发现由于缓冲区溢出而覆盖了值的副本,call 0x8048394 <__stack_chk_fail@plt>
将调用专用于执行任何必要操作的特殊功能,例如,报告问题并终止程序。
答案 1 :(得分:6)
0x0804849e <+42>: xor %gs:0x14,%eax # # again what is this ???? 0x080484a5 <+49>: je 0x80484ac <display+56> # Not known to me 0x080484a7 <+51>: call 0x8048394 <__stack_chk_fail@plt> # not known to me 0x080484ac <+56>: leave # a new instruction, not known to me 0x080484ad <+57>: ret # return to MAIN's next instruction
gs segment可用于thread local storage。例如。它用于errno
,因此多线程程序中的每个线程都有自己的errno变量。
上面的函数名称是一个很大的线索。这必须是stack canary。
(leave
是一些CISC指令,可以在实际返回之前执行您需要执行的所有操作。我不知道详细信息。)
答案 2 :(得分:3)
其他人已经解释了GS的事情(与线程有关)..
0x08048483 <+15>: xor %eax,%eax # xor eax with itself (but WHY??)
解释这需要一些X86架构的历史:
xor eax,eax指令清除寄存器eax中的所有位(加载零),但是你已经发现它似乎是不必要的,因为寄存器在下一条指令中加载了一个新值。 / p>
然而,xor eax,eax也在x86上做了其他事情。您可能知道您可以使用al,ah和ax访问寄存器eax的某些部分。从386开始就是这样,当时eax确实只是一个寄存器就可以了。
然而,现在已不复存在了。您在代码中看到并使用的寄存器只是占位符。 CPU内部使用更多内部寄存器和完全不同的指令集。您编写的指令将转换为此内部指令集。
例如,如果您使用AL,AH和EAX,则从CPU的角度来看,您使用的是三个不同的寄存器。
现在,如果在使用AL或AH后访问EAX,CPU必须合并这些不同的寄存器以构建有效的EAX值。
该行:
0x08048483 <+15>: xor %eax,%eax # xor eax with itself (but WHY??)
不仅清除寄存器eax。它还告诉CPU所有重命名的子寄存器:AL,AH和AX现在可以认为是无效的(设置为零),并且CPU不必进行任何子寄存器合并。
为什么编译器会发出这条指令?
因为编译器不知道将在哪个上下文中调用display()。您可以从使用AL和AH进行大量字节算术的代码中调用它。如果它不能通过XOR清除EAX寄存器,那么CPU将不得不进行昂贵的寄存器合并,这需要花费很多周期。
因此,在函数启动时执行此额外工作可提高性能。在您的情况下,这是不必要的,但由于编译器无法知道发出指令以确定。
答案 3 :(得分:1)
stack_check_fail是gcc缓冲区溢出检查的一部分。它使用libssp(stack-smash-protection),你在开始时的移动为堆栈设置了一个守卫,xor%gs:0x14 ...是一个检查守卫是否还可以。当它没问题时,它跳转到leave(检查汇编程序doc,它是堆栈处理的辅助指令)并跳过跳转到stack_chk_fail,这将中止程序并发出错误消息。
您可以使用gcc选项-fno-stack-protector
禁用此溢出检查的发出。
正如评论中已经提到的,xor x,x只是一个清除x的快速命令,最后一个mov 0,%eax是你的main的返回值。