我试图弄清楚藏匿粉碎是如何逐步进行的。我已经使用谷歌无济于事,我仍然不知道为什么我的EIP没有被覆盖。我有这个示例程序:
1 #include <stdio.h>
2 #include <string.h>
3
4 int main(int argc, char *argv[])
5 {
6 char buf[10];
7
8 strcpy(buf, argv[1]);
9 printf("Done.\n");
10 return 0;
11
12 }
用
编译gcc -g -o prog main.c
当我放入很多AAAAAA时,我得到了SEGV和寄存器EBP(以及argc和argv地址被覆盖:
Program received signal SIGSEGV, Segmentation fault.
0x08048472 in main (argc=<error reading variable: Cannot access memory at address 0x41414141>, argv=<error reading variable: Cannot access memory at address 0x41414145>)
at main.c:12
12 }
(gdb) info reg
eax 0x0 0
ecx 0x41414141 1094795585
edx 0xb7fbb878 -1208240008
ebx 0xb7fba000 -1208246272
esp 0x4141413d 0x4141413d
ebp 0x41414141 0x41414141
esi 0x0 0
edi 0x0 0
eip 0x8048472 0x8048472 <main+71>
eflags 0x10282 [ SF IF RF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
我认为EIP正好在EBP之下,但它仍然具有主要功能的地址。这是主要的反汇编:
(gdb) disass main
Dump of assembler code for function main:
0x0804842b <+0>: lea 0x4(%esp),%ecx
0x0804842f <+4>: and $0xfffffff0,%esp
0x08048432 <+7>: pushl -0x4(%ecx)
0x08048435 <+10>: push %ebp
0x08048436 <+11>: mov %esp,%ebp
0x08048438 <+13>: push %ecx
0x08048439 <+14>: sub $0x14,%esp
0x0804843c <+17>: mov %ecx,%eax
0x0804843e <+19>: mov 0x4(%eax),%eax
0x08048441 <+22>: add $0x4,%eax
0x08048444 <+25>: mov (%eax),%eax
0x08048446 <+27>: sub $0x8,%esp
0x08048449 <+30>: push %eax
0x0804844a <+31>: lea -0x12(%ebp),%eax
0x0804844d <+34>: push %eax
0x0804844e <+35>: call 0x80482f0 <strcpy@plt>
0x08048453 <+40>: add $0x10,%esp
0x08048456 <+43>: sub $0xc,%esp
0x08048459 <+46>: push $0x8048510
0x0804845e <+51>: call 0x8048300 <puts@plt>
0x08048463 <+56>: add $0x10,%esp
0x08048466 <+59>: mov $0x0,%eax
0x0804846b <+64>: mov -0x4(%ebp),%ecx
0x0804846e <+67>: leave
0x0804846f <+68>: lea -0x4(%ecx),%esp
=> 0x08048472 <+71>: ret
End of assembler dump.
现在我正逐步找出汇编程序指令,但是我没有看到EIP在strcpy
之后加载了堆栈返回地址的时刻。饰面。我试过了-fno-stack-protector
,但它并没有改变一件事。这可能是什么原因?
编辑:
好的,我会尝试一步一步地检查它,请在我错误的地方纠正我
# Just below the sp are argc and argv and the sp points to the address
# where RET will be stored
# This one moves the address of argc (which is on the stack) to $ecx
0x0804842b <+0>: lea 0x4(%esp),%ecx
# Move stack pointer down for alignment
0x0804842f <+4>: and $0xfffffff0,%esp
# Push the value to which $sp pointed to before alignment
# It is never used - correct me if I'm wrong
0x08048432 <+7>: pushl -0x4(%ecx)
# Push last used base pointer value (and start creating another frame)
0x08048435 <+10>: push %ebp
# Set current position sp as bp - I think here the main body starts
0x08048436 <+11>: mov %esp,%ebp
# Push the address of argc - it's later used for calculating
# the address of argv[1].
0x08048438 <+13>: push %ecx
# Make some space on the stack (20 bytes - 5 words - first two I'm
# sure for what (alignment and not used here return value?)
# another 3 for buffer[10]
0x08048439 <+14>: sub $0x14,%esp
# Move argc address to $eax
0x0804843c <+17>: mov %ecx,%eax
# Move argv address to $eax
0x0804843e <+19>: mov 0x4(%eax),%eax
# Move past argv - $eax should now point to pointer to first
# argument string
0x08048441 <+22>: add $0x4,%eax
# Move the address of the parameter string to $eax
0x08048444 <+25>: mov (%eax),%eax
# Make space for 2 words
# (probably alignment and return value from strcpy)
0x08048446 <+27>: sub $0x8,%esp
# Push the parameter address
0x08048449 <+30>: push %eax
# Get the address of the local buffer
0x0804844a <+31>: lea -0x12(%ebp),%eax
# Push it
0x0804844d <+34>: push %eax
# Call strcpy
0x0804844e <+35>: call 0x80482f0 <strcpy@plt>
# Remove 4 words - 2 for arguments and 2 for return + alignment
0x08048453 <+40>: add $0x10,%esp
# Make space for 3 words - alignment + return value
0x08048456 <+43>: sub $0xc,%esp
# Push the printf argument address (the string address)
0x08048459 <+46>: push $0x8048510
# Call printf
0x0804845e <+51>: call 0x8048300 <puts@plt>
# Remove 4 words - 1 for parameter and previous 3
0x08048463 <+56>: add $0x10,%esp
# Reset 0x0 just because
0x08048466 <+59>: mov $0x0,%eax
# Load previously saved address of argc
0x0804846b <+64>: mov -0x4(%ebp),%ecx
# not sure about that leave...
0x0804846e <+67>: leave
# Reload $esp starting value
0x0804846f <+68>: lea -0x4(%ecx),%esp
# Pop the RET address - this one should be changed to
# pointer to malicious code
=> 0x08048472 <+71>: ret
答案 0 :(得分:6)
免责声明:我在安装了gnuwin32的Windows 7系统上使用gcc-4.8.3。 Windows默认情况下似乎没有启用ASLR,因此当我运行此程序时,我可以获得可重现的内存地址,这使生活更容易。此外,如果你遵循这个,你得到的记忆地址很可能是不同的。
现在考虑一下这个程序:
#include <string.h>
void copyinput(char* input)
{
char buf[10];
strcpy(buf, input);
}
int main(int argc, char** argv)
{
int a = 5;
copyinput(argv[1]);
a = 7;
return 0;
}
我们可以用这个命令行编译:
gcc -g -ansi -pedantic -Wall overflow2.c -o overflow
然后在gdb下运行该程序。
我们在'main&#39;处设置一个断点。并将命令行参数设置为&#34; AAAAAAAAAABBBBBBBBBBCCCCCCCCCC&#34;并注意以下内容:
首先注意main的反汇编:
0x0040157a <+0>: push %ebp
0x0040157b <+1>: mov %esp,%ebp
=> 0x0040157d <+3>: and $0xfffffff0,%esp
0x00401580 <+6>: sub $0x20,%esp
0x00401583 <+9>: call 0x401fd0 <__main>
0x00401588 <+14>: movl $0x5,0x1c(%esp)
0x00401590 <+22>: mov 0xc(%ebp),%eax
0x00401593 <+25>: add $0x4,%eax
0x00401596 <+28>: mov (%eax),%eax
0x00401598 <+30>: mov %eax,(%esp)
0x0040159b <+33>: call 0x401560 <copyinput>
0x004015a0 <+38>: movl $0x7,0x1c(%esp)
0x004015a8 <+46>: mov $0x0,%eax
0x004015ad <+51>: leave
0x004015ae <+52>: ret
0x004015af <+53>: nop
我们感兴趣的是下一个的地址
我们致电copyinput
后的指示。这将是价值
eip 在传递控制流时被压入堆栈
copyinput
。
让我们看一下寄存器:
(gdb) info reg
eax 0x1 1
ecx 0x752c1162 1965822306
edx 0xa02080 10494080
ebx 0x2 2
esp 0x28fea0 0x28fea0
ebp 0x28fec8 0x28fec8
esi 0xa01858 10491992
edi 0x1f 31
eip 0x401590 0x401590 <main+22>
eflags 0x202 [ IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x53 83
gs 0x2b 43
我们对上面的 esp 和 ebp 感兴趣。请记住 ebp
在函数调用期间也应该被压入堆栈
copyinput
。
单步调用copyinput
然后进入该步骤
功能。此时,查看寄存器(在调用之前)
strcpy
)再次:
(gdb) info reg
eax 0x9218b0 9574576
ecx 0x752c1162 1965822306
edx 0x922080 9576576
ebx 0x2 2
esp 0x28fe70 0x28fe70
ebp 0x28fe98 0x28fe98
esi 0x921858 9574488
edi 0x1f 31
eip 0x401566 0x401566 <copyinput+6>
eflags 0x202 [ IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x53 83
gs 0x2b 43
我们在这里看到的是copyinput
的堆栈帧来自
0x28fe70到0x28fe98,并且回到第(2)点我们可以看到
main
的堆栈帧基于0x28fec8。
我们可以检查从0x28fe70到0x28fec8的堆栈(总共88个 字节)像这样:
(gdb) x/88xb 0x28fe70
0x28fe70: 0x50 0x15 0x40 0x00 0xdc 0x00 0x00 0x00
0x28fe78: 0xff 0xff 0xff 0xff 0x30 0x60 0x44 0x00
0x28fe80: 0x03 0x00 0x00 0x00 0x8c 0xfe 0x28 0x00
0x28fe88: 0x00 0x00 0x00 0x00 0x8f 0x17 0x40 0x00
0x28fe90: 0x50 0x1f 0x40 0x00 0x1c 0x50 0x40 0x00
0x28fe98: 0xc8 0xfe 0x28 0x00 0xa0 0x15 0x40 0x00
0x28fea0: 0xb0 0x18 0x92 0x00 0x00 0x50 0x40 0x00
0x28fea8: 0x88 0xff 0x28 0x00 0xae 0x1f 0x40 0x00
0x28feb0: 0x50 0x1f 0x40 0x00 0x60 0x00 0x00 0x40
0x28feb8: 0x1f 0x00 0x00 0x00 0x05 0x00 0x00 0x00
0x28fec0: 0x58 0x17 0x92 0x00 0x1f 0x00 0x00 0x00
原始内存转储不是很容易阅读,所以让我们崩溃 将字节转换为单词,并将字节顺序转换为big-endian,我们 可以看到某些值所在的位置:
0x28fe70: 0x00401550 <- esp for `copyinput`
0x000000dc
0x28fe78: 0xffffffff
0x00446030
0x28fe80: 0x00000003
0x0028fe8c
0x28fe88: 0x00000000
0x0040178f
0x28fe90: 0x00401f50
0x0040501c
0x28fe98: 0x0028fec8 <- stored *ebp* for `main``s stack frame
0x004015a0 <- stored *eip*,
0x28fea0: 0x009218b0 <- esp for `main``s stack frame
0x00405000
因此我们可以看到存储的 eip 位于 堆栈在地址0x28fe9C。从中您可以看到 eip 首先被压入堆栈,然后 ebp 被推入堆栈。
现在单步执行直到调用字符串复制和检查 记忆再次显示:
(gdb) x/88xb 0x28fe70
0x28fe70: 0x86 0xfe 0x28 0x00 0xb0 0x18 0x92 0x00
0x28fe78: 0xff 0xff 0xff 0xff 0x30 0x60 0x44 0x00
0x28fe80: 0x03 0x00 0x00 0x00 0x8c 0xfe 0x41 0x41
0x28fe88: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x28fe90: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0x28fe98: 0x42 0x42 0x43 0x43 0x43 0x43 0x43 0x43
0x28fea0: 0x43 0x43 0x43 0x43 0x00 0x50 0x40 0x00
0x28fea8: 0x88 0xff 0x28 0x00 0xae 0x1f 0x40 0x00
0x28feb0: 0x50 0x1f 0x40 0x00 0x60 0x00 0x00 0x40
0x28feb8: 0x1f 0x00 0x00 0x00 0x05 0x00 0x00 0x00
0x28fec0: 0x58 0x17 0x92 0x00 0x1f 0x00 0x00 0x00
我们可以看到 ebp 和 eip 的存储值都已存在
被摧毁在堆栈上。现在当我们从copyinput
返回时
将弹出 eip (现在为0x43434343)和 ebp (其中的值
现在是0x43434242)离开堆栈并尝试执行
指令0x43434343;这显然会产生一个
异常。
像这样的堆栈攻击的主要目的是安排它,以便我们用我们选择的有效值覆盖 eip 。例如,请考虑以下程序:
#include <stdio.h>
#include <string.h>
void copyinput(char* input)
{
char buf[10];
strcpy(buf, input);
}
void testinput()
{
printf("we should never see this\n");
}
int main(int argc, char** argv)
{
int a = 5;
copyinput(argv[1]);
a = 7;
return 0;
}
永远不会调用函数testinput
。但是,如果我们可以使用值0x0040157a(我的机器上copyinput
的位置)覆盖testinput
中的返回地址,我们就能够执行该函数。
=============================================== ================================== 对评论中提出的问题的答案:
不确定您使用的操作系统/编译器。我在Windows 7机器上使用gcc-4.8.3对你的示例程序进行了编译。我对main的反汇编看起来像这样:
(gdb) disass main
Dump of assembler code for function main:
0x00401560 <+0>: push %ebp
0x00401561 <+1>: mov %esp,%ebp
0x00401563 <+3>: and $0xfffffff0,%esp
0x00401566 <+6>: sub $0x20,%esp
0x00401569 <+9>: call 0x401fc0 <__main>
这是main的前导码,我们正在为main设置堆栈帧。我们推送前一个堆栈帧的基指针(来自运行时库提供的某些函数),然后将基指针移动到堆栈点所在的位置。接下来我们调整 esp 使其可以被16整除,然后我们从 esp 中减去32个字节(0x20)(请记住堆栈增长了,所以我们现在有了一些主要使用的空间。
push %ebp
,mov %esp, %ebp
和sub xxx, %esp
的常见模式是函数的常见前导码。
让我们试着找出事物在记忆中的位置。在gdb中,我们可以执行以下操作:
(gdb) x/16xb &argv[0]
0xa31830: 0x58 0x18 0xa3 0x00 0x98 0x18 0xa3 0x00
0xa31838: 0x00 0x00 0x00 0x00 0xab 0xab 0xab 0xab
这是我们所期望的,两个32位指针后跟一个空终止符。所以argv [0]位于0x00a31858,而argv 1位于0x00a31898;通过检查这两个位置的记忆可以看出:
(gdb) x/20cb 0x00a31858
0xa31858: 100 'd' 58 ':' 92 '\\' 117 'u' 115 's' 101 'e' 114 'r' 115 's'
0xa31860: 92 '\\' 103 'g' 104 'h' 117 'u' 98 'b' 101 'e' 114 'r' 92 '\\'
0xa31868: 71 'G' 78 'N' 85 'U' 72 'H'
(gdb) x/20xb 0x00a31898
0xa31898: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xa318a0: 0x41 0x41 0x00 0xab 0xab 0xab 0xab 0xab
0xa318a8: 0xab 0xab 0xab 0xfe
我们可以找到缓冲区的位置,但在GDB中执行以下操作:
(gdb) print $esp
$4 = (void *) 0x28fea0
(gdb) print $ebp
$5 = (void *) 0x28fec8
(gdb) x/40xb $esp
0x28fea0: 0xb6 0xfe 0x28 0x00 0x98 0x18 0xa3 0x00
0x28fea8: 0x88 0xff 0x28 0x00 0x9e 0x1f 0x40 0x00
0x28feb0: 0x40 0x1f 0x40 0x00 0x60 0x00 0x41 0x41
0x28feb8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0x28fec0: 0x00 0x17 0xa3 0x00 0x0b 0x00 0x00 0x00
所以我们可以看到我们的缓冲区从0x28feb6开始
现在我们已经解决了这个问题,让我们看一下代码的下一部分,它应该设置为调用strcpy
:
0x0040156e <+14>: mov 0xc(%ebp),%eax
0x00401571 <+17>: add $0x4,%eax
0x00401574 <+20>: mov (%eax),%eax
0x00401576 <+22>: mov %eax,0x4(%esp)
0x0040157a <+26>: lea 0x16(%esp),%eax
0x0040157e <+30>: mov %eax,(%esp)
0x00401581 <+33>: call 0x402748 <strcpy>
提醒一下,在AT&amp; T汇编语法中,地址操作数如下所示:
displacement(base register, offset register, scalar multiplier)
相当于intel语法:
[base register + displacement + offset register * scalar multiplier]
因此,
0x0040156e <+14>: mov 0xc(%ebp),%eax
0x00401571 <+17>: add $0x4,%eax
0x00401574 <+20>: mov (%eax),%eax
0x00401576 <+22>: mov %eax,0x4(%esp)
我们将0x0C添加到当前的基指针,其值为0x28FED4,然后我们将该内存地址中包含的内容复制到 eax 。通过使用GDB,我们可以发现位于0x08FEC4
的四个字节是0x00a31830
,它是argv [0]的地址。向 eax 添加四个会导致 eax 现在指向argv 1。接下来的两条指令有效地将argv 1的地址移动到 esp 之上的四个字节。
0x0040157a <+26>: lea 0x16(%esp),%eax
0x0040157e <+30>: mov %eax,(%esp)
继续,我们将 esp 增加0x16(这给我们0x28FEB6,我们之前已经确定它是buf[10]
所在的位置。然后我们将此值移动到的位置esp 就在。目前,我们的堆栈现在看起来像:
~ ~
| |
+------------+
0x28fea4 | 0x00a31898 | remember that this is the address of argv[1][0]
+------------+
0x28fea0 | 0x0028feb6 | remember that this is the address of buf[0]
+------------+
鉴于strcpy
的函数原型是:
char* strcpy(char* dst, const char* src);
通常情况下,参数会从右向左推入堆栈,因此我们希望src
先被推送,然后dst
会被推到第二位。因此,编译器不是仅仅将参数推送到堆栈中,而是留出足够的空间,以便它可以在正确的位置加载所需的值。所以一切都已到位,我们现在可以致电strcpy
。
接下来的几条指令只是设置了对printf
(实际上是puts
)的调用,我们需要移动字符串的地址&#34;完成。\ n&#34;到堆栈然后调用puts
:
0x00401586 <+38>: movl $0x404024,(%esp)
0x0040158d <+45>: call 0x402750 <puts>
最后,我们将返回值移动到 eax (这是通常包含函数返回值的寄存器),然后我们退出main
。
0x00401592 <+50>: mov $0x0,%eax
0x00401597 <+55>: leave
0x00401598 <+56>: ret
不确定我是否回答了你的所有问题,但我想我做到了。另外,我希望我没有过多地搞砸分析,我通常不会对汇编进行深入分析,也不会使用AT&amp; T语法。
=============== edit2 =============================== ====
其余三个问题:
+7行中的值是否不必要?我没有看到任何用途,所以 为什么要存储?
我们正在推动 esp 的原始未对齐值的分析显示正确。我的预感是,在你拆卸的前几行我们 正在寻找主要的特殊启动代码。请记住,在为main创建堆栈帧之前,堆栈上有堆栈帧。您可能需要查看this link以了解Linux下程序的正常启动顺序。
我的预感是我们需要保留 esp 的未修改值,以便我们可以将早期的堆栈帧恢复到正确的位置。
在某些地方sp移动得比它更多 - 是否由于对齐? (例如第+ 14行)
我会分析这些行是我们实际为main设置堆栈帧的地方。在main+14
中,我们从 esp 中减去20个字节,因此我们分配20个字节供主函数使用。我们可以争辩说这些字节中有12个是由我们的缓冲区使用的(请记住,在缓冲区的末尾可能会有两个填充字节,因此存储在堆栈中的下一个值将位于32位字边界)。
0x08048435 <+10>: push %ebp
0x08048436 <+11>: mov %esp,%ebp
0x08048438 <+13>: push %ecx
0x08048439 <+14>: sub $0x14,%esp
所以,我会声称main+10
到main+14
是正常的功能序言
我对第+71行的结论是否正确?
是。此时我们需要覆盖堆栈中存储的 eip ,这将导致RET指令读取我们的值。以下对RET指令的描述取自from here(实际上这个页面有很多关于汇编的信息,值得一读。唯一的缺点是这个页面使用的是Intel语法并且你一直在展示AT&amp; T语法。)
call,ret - 子程序调用并返回
这些指令实现子程序调用并返回。电话 指令首先将当前代码位置推送到 内存中的硬件支持堆栈(参见推送指令) 详细信息),然后执行无条件跳转到代码位置 标签操作数表示。与简单的跳转指令不同, 调用指令保存返回的位置 子程序完成。
ret指令实现子程序返回机制。该指令&gt;首先从支持的硬件弹出代码位置 内存堆栈(有关详细信息,请参阅pop指令)。然后呢 执行无条件跳转到检索到的代码位置。
Syntax call <label> ret
有关LEAVE指令的附加信息(在main+67
使用)是(摘自here):
释放先前的ENTER指令设置的堆栈帧。该 LEAVE指令将帧指针(在EBP寄存器中)复制到 堆栈指针寄存器(ESP),释放堆栈空间 分配给堆栈帧。旧帧指针(帧指针 对于由ENTER指令保存的调用过程) 然后从堆栈弹出到EBP寄存器,恢复 调用程序的堆栈帧。
RET指令通常在LEAVE指令之后执行 将程序控制返回给调用过程。
参见&#34;过程调用块结构化语言&#34;在第6章 IA-32英特尔架构软件开发人员手册,第1卷, 有关使用ENTER和LEAVE的详细信息 指令。
N.B。可以改变GDB发出的反汇编的味道 使用以下命令:
set disassembly-flavor att
set disassembly-flavor intel
show disassembly-flavor
第三个命令显示当前的味道。
PS 我的第二个Jesters在下面的回答中发表评论。将实际易受攻击的代码移到函数而不是main中会使分析变得更容易,因为您不必处理main和唯一的prolog和epilog对齐的奇怪性。一旦你掌握了这种类型的堆栈利用,你就可以回过头来处理漏洞主要存在的例子。
在Linux系统上工作的PPS 您也可能遇到ASLR问题,因为每次运行程序时都会有不同的内存位置,因此堆栈帧和堆栈帧位置之间的偏移将会更改。您可以使用以下简短程序(摘自The Shellcoder的手册:发现和利用Chris Anley的安全漏洞, et.al )来查看ASLR是否存在问题
#include <stdio.h>
unsigned long find_start(void)
{
__asm__("movl %esp, %eax");
}
int main()
{
printf("0x%x\n", find_start());
return (0);
}
多次运行程序,如果输出不同,则运行某个版本的ASLR。它会让你的生活变得更加艰难,但并非不可克服
答案 1 :(得分:3)
您首先覆盖堆栈上的本地化,其中包括编译器用于记住堆栈指针的ecx
的保存副本。因此,当代码到达0x0804846b
时,堆栈上的值被破坏,因此ecx
加载了错误的值。您可以在注册转储中看到它0x41414141
。接下来,esp
基于ecx
加载,因此esp
也会得到错误的值。最后,ret
尝试从堆栈中弹出返回地址,当然使用esp
,但是我们已经看到了它的价值很低。因此,崩溃。
请注意,此代码通常仅为main
生成以用于对齐目的,因此如果您将代码插入到仅从main
调用的单独函数中,则可能会更好运。