在雪豹中创建缓冲区溢出
作为大学计算机安全课程的一部分,我很快就会了解缓冲区溢出以及如何利用它们进行漏洞利用。我正在尝试使用以下代码执行一些简单的缓冲区溢出:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buffer_one[4], buffer_two[16];
strcpy(buffer_one, "one");
strcpy(buffer_two, "two");
strcpy(buffer_one, argv[1]);
printf("buffer_two is at %p and contains \'%s\'\n", buffer_two, buffer_two);
printf("buffer_one is at %p and contains \'%s\'\n", buffer_one, buffer_one);
}
如果我运行,我可以用空终止符覆盖 buffer_one 的内容
$./overflow 1234567890123456
buffer_two is at 0x7fff5fbff8d0 and contains '1234567890123456'
buffer_one is at 0x7fff5fbff8e0 and contains ''
但是如果我发送超过 16 个字符作为参数,程序会发送中止陷阱。我认为这是 Snow Leopard 上的某种缓冲保护(也许是 ASLR?)。如果如果使buffer_two的大小< 16,地址仍然相距16位
我正在运行gcc -o Overflow Overflow.c -fno-stack-protector
来删除堆栈保护
除了安装VM之外,还有其他解决方案吗?运行 Linux 发行版。?
As part of a course at university in computer security, I'm soon about to learn about buffer overflows and how to use them to as exploits. I'm trying to do some simple buffer overflow with the following code:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buffer_one[4], buffer_two[16];
strcpy(buffer_one, "one");
strcpy(buffer_two, "two");
strcpy(buffer_one, argv[1]);
printf("buffer_two is at %p and contains \'%s\'\n", buffer_two, buffer_two);
printf("buffer_one is at %p and contains \'%s\'\n", buffer_one, buffer_one);
}
I'm able to overwrite the content of buffer_one with the null terminator if i run
$./overflow 1234567890123456
buffer_two is at 0x7fff5fbff8d0 and contains '1234567890123456'
buffer_one is at 0x7fff5fbff8e0 and contains ''
But if i send more than 16 characters as argument, the program sends Abort trap. I supposed this is some sort of buffer protection on Snow Leopard (ASLR maybe?). If if make the size of buffer_two < 16, the adresse is still 16 bits apart
I'm running gcc -o overflow overflow.c -fno-stack-protector
to remove stack protection
Is there any solution to this problem, other than installing a VM running a linux dist.?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
发生这种情况的关键是
buffer_one
在内存中位于buffer_two
之后。这意味着当您溢出buffer_one
时,您不会溢出到buffer_two
中。相反,您会溢出到用于保存其他内容的堆栈内存,例如保存的 ebp 指针,以及最重要的返回地址。这正是您在尝试利用缓冲区溢出漏洞时希望发生的情况!当程序执行
strcpy(buffer_one, argv[1]);
时,argv[1]
中的前四个字节将进入为buffer_one
分配的内存中>。但接下来的 12 个开始溢出内存,用于其他用途,最终覆盖返回地址。如果没有看到机器代码,我无法确定到底哪些字节溢出了返回地址。但我猜测 SIGABRT 时 EIP 的值是 0x31323334 或类似的值(“1234”的十六进制表示)。关键是要认识到,通过覆盖返回地址,您可以控制 EIP。当您控制 EIP 时,您就控制了系统。 (有点夸张,但在大多数情况下相差不远)当您控制 EIP 时,您就可以控制处理器下一步将执行哪些指令(暂时抛开操作系统/内核实际上介于两者之间的事实)。现在,如果您准确地找到哪八个字节覆盖了返回地址,您可以将这些字节替换为缓冲区的地址 (
0x00007fff5fbff8e0
),而不是返回到原始调用者(在本例中为 libc),程序将开始执行您提供的指令(也称为 shellcode)。请注意,您必须在最重要的位置填写隐含的 0,并将地址提供为实际的不可打印 ASCII 字符(0x00 0x00 0x7f 0xff 0x5f 等),而不是实际的数字/字符
7ff5
等。如果使用 x86-64 架构,您还必须考虑小尾数并向后提供它 -0xe0 0xf8 0xbf
等。这些不可打印的字符最容易通过使用反引号和使用简短的 Python 或 Perl 脚本的命令替换来完成:(A 是填充以溢出缓冲区。)不幸的是,您将无法提供 2 个额外的
\x00<地址需要 /code>。
strcpy
将为您放置其中一个 NULL,但您必须幸运地获得最后一个 NULL,并希望您要覆盖的地址已经以0x00
开头>(实际上很有可能)。现在,当您使用正确数量的 A 执行此操作时,您可能仍然会遇到分段错误,甚至可能是非法指令,因为您现在将跳转到大写 A 并将它们作为实际机器指令执行(0x41< /code> =>
inc ecx
)。最后一部分是放入实际的 shellcode。鉴于缓冲区大小有限,很难仅在 12 个字节左右提供任何有用的内容。由于您正在编写这种情况下的代码,因此最简单的事情可能就是增大缓冲区。如果这不是一个选项,那么您可以 A) 使用
buffer_two
多 16 个字节,因为它出现在buffer_one
之前,或者 B) 在环境中提供 shellcode变量并跳转到该变量。如果您希望自己编写实际的 shellcode,则必须知道如何执行系统调用、调用约定是什么以及如何使用它们,以及如何避免 shellcode 中出现 NULL 字节。另一种选择是使用有效负载生成器,例如 Metasploit 中包含的有效负载生成器,这将使事情变得更加容易(尽管您不会学到那么多)。
从技术上讲,这些是您唯一需要的部分,特别是因为您很清楚地址是什么。然而,很多时候(特别是当 shellcode 地址未知时),所谓的 NOP sled 会被放置在 shellcode 前面,这样您就不必获得完全正确的地址。 NOP sled(无操作的缩写)只是数百到数千条 NOP 指令 (0x90),您可以跳到其中,然后在继续执行到 shellcode 之前不会产生任何效果。
如果您跟踪 GDB 中的所有内容并且执行正确跳转到 shellcode,但仍然遇到访问冲突,则可能是因为在堆栈页上设置了 NX 位,这意味着处理器将拒绝将堆栈中的数据作为指令执行。我不确定 OSX 中是否包含
execstack
,但如果包含,您可以将其用于测试目的以禁用 NX 位(execstack -s Overflow
) 。我对文字墙表示歉意,但我不确定您想研究缓冲区溢出到什么程度。您还可以查看其他指南,例如 Aleph One 的原型指南 “为了乐趣和利润而粉碎堆栈”。 Shellcoder 手册是一本值得查阅的好书也出来了,我相信其他人可以添加建议。
TL;DR: 简而言之,您正在溢出缓冲区并用垃圾覆盖已保存的指针和返回地址。
The key to why this is happening is the fact that
buffer_one
is located afterbuffer_two
in memory. This means that when you overflowbuffer_one
, you are not overflowing intobuffer_two
. Instead you are overflowing into stack memory being used to hold other things, such as the savedebp
pointer and most importantly, the return address.And this is exactly what you want to happen when attempting a buffer overflow exploit! When the program executes
strcpy(buffer_one, argv[1]);
the first four bytes fromargv[1]
go into the memory allocated forbuffer_one
. But then the next 12 start overflowing memory being used for other things, eventually overwriting the return address. Without seeing the machine code, I can't say for sure which bytes exactly are overflowing the return address. But I'm guessing the value of EIP at the time of the SIGABRT is 0x31323334 or something similar (hex representation of '1234'). The key is realizing that by being able to overwrite the return address, you control EIP. And when you control EIP, you control the system. (somewhat overexaggerated, but in most cases not far off) When you control EIP, you control which instructions the processor will execute next (putting aside for the moment the fact that the OS/kernel actually stand in between).Now if you find exactly which eight bytes overwrite the return address you can replace those bytes with the address of your buffer (
0x00007fff5fbff8e0
) and instead of returning to the original caller (libc in this case), the program will start executing the instructions you provided (AKA the shellcode). Note that you will have to fill in the implied 0s in the most significant places and provide the address as the actual nonprintable ASCII characters (0x00 0x00 0x7f 0xff 0x5f
and so on), not the actual digits/characters7ff5
etc. If using an x86-64 architecture, you'll also have to take the little-endianness into account and supply it backwards --0xe0 0xf8 0xbf
etc. Supplying these nonprintable characters is most easily accomplished using backticks and command substitution using a brief Python or Perl script:(The A's are padding to overflow the buffer.) Unfortunately, you won't be able to provide the 2 additional
\x00
needed for the address. One of these NULLs will be placed there for you bystrcpy
, but you'll have to get lucky with the last NULL and hope the address you're overwriting already started with0x00
(which is actually highly likely). Now when you execute this with the right number of A's, you'll probably still get a segmentation fault or even possibly illegal instruction, since you'll now jump to the capital A's and execute them as actual machine instructions (0x41
=>inc ecx
).Then finally the last piece is putting in the actual shellcode. Given your limited buffer sizes, it will be very hard to provide anything useful in only 12 bytes or so. Since you are writing the code in this case, the easiest thing will probably be to make your buffer bigger. If this weren't an option, then you could either A) use
buffer_two
as well for 16 more bytes since it comes beforebuffer_one
or B) provide the shellcode in an environment variable and jump to that instead.If you wish to write the actual shellcode yourself, you'll have to know how to perform syscalls and what calling conventions are and how to use them, as well as how to avoid NULL bytes in the shellcode. The other alternative is to use a payload generator such as the one included with Metasploit which will make it a lot easier (although you won't learn nearly as much).
These are technically the only pieces you need, especially since you have a good idea of what the address will be. However, many times (especially when the shellcode address is not known) a so-called NOP sled will be placed in front of the shellcode so that you don't have to get the address exactly right. A NOP sled (short for No Operation) is simply hundreds to thousands of NOP instructions (0x90) that you can jump into the middle of and then have no effect until execution continues into the shellcode.
If you trace everything in GDB and execution jumps to the shellcode correctly but you still get access violations, it's likely because the NX bit is set on the stack page, meaning that the processor will refuse to execute data on from the stack as instructions. I'm not sure if
execstack
is included with OSX or not, but if so, you can use it for testing purposes to disable the NX bit (execstack -s overflow
).I apologize for the wall of text, but I wasn't sure how far you wanted to go studying buffer overflows. There's other guides you can check out as well, such as Aleph One's archetypal guide, "Smashing the Stack for Fun and Profit". The Shellcoder's Handbook is a good book to check out as well, and I'm sure others can add recommendations.
TL;DR: In short, you are overflowing your buffer and overwriting saved pointers and return addresses with garbage.
如果您正在学习漏洞利用,那么您需要真正深入研究细节。
来吧,阅读机器代码!您也许能够找到如何绕过 Snow Leopard 使用的任何检查方法的溢出。
问题可能也比这更简单。没有规定编译器必须将 buffer_one 和 buffer_two 以任何特定顺序放入堆栈,甚至根本不将它们放入堆栈。请注意,
buffer_one
实际上适合寄存器。当然,这里的情况并非如此,但我看到 buffer_two 放置在 buffer_one 之前。这意味着将溢出写入
buffer_one
永远不会写入buffer_two
。我无法解释为什么它最终会包含''
,但f8d0
在内存中绝对先于f8e0
。If you are learning about exploits then you'll need to really dig into details.
Go ahead, read the machine code! You might be able to find out how to slip the overflow past whatever check method Snow Leopard is using.
The problem may be simpler than that too. There's no rule that the compiler has to put
buffer_one
andbuffer_two
in any particular order on the stack or even put them on the stack at all. Notice thatbuffer_one
would actually fit into a register.That isn't the case here of course, but I see that buffer_two is placed before buffer_one. That means that writing an overflow into
buffer_one
will never write intobuffer_two
. I can't explain why it ends up containing''
, butf8d0
is definitely beforef8e0
in memory.x86 上堆栈上的数据是 4 字节对齐的。如果
buffer_two
长度不是 4 字节的倍数,则在buffer_two
和buffer_one
之间放置填充。将其更改为 12 或更少,它们应该相距 12 个字节,等等。[更新] 我忽略了地址大小。您在 64 位系统上,您的堆栈是 8 字节对齐的。在缓冲区大小改变至少 8 个字节之前,地址差异不会改变。
此行是否正确:
输出看起来像是您将
argv[1]
复制到buffer_two
中。鉴于这种情况,当它崩溃时你复制了多少? 17字节? 18?如果它超过 24,您将开始以导致中止的方式破坏堆栈。
请注意,
"1234567890123456"
实际上复制了 17 个字节,其中包括截断buffer_one
的空终止符。Data on the stack on x86 is 4-byte aligned. There is padding placed between
buffer_two
andbuffer_one
ifbuffer_two
length is not a multiple of 4 bytes. change it to 12 or less and they should be 12 bytes apart, etc.[Update] I overlooked the address size. You are on a 64-bit system, your stack is 8-byte aligned. The address differences won't change until your buffer size changes by at least 8 bytes.
Is this line correct:
The output looks like you are copying
argv[1]
intobuffer_two
.Given that case, how much are you copying when it crashes? 17 bytes? 18? If it is more than 24 you will start clobbering the stack in ways which would lead to the abort.
Note that
"1234567890123456"
is actually copying 17 bytes which includes the null terminator truncatingbuffer_one
.您是否尝试在编译时禁用 FORTIFY_SOURCE ?
-D_FORTIFY_SOURCE=0
Have you tried to disable FORTIFY_SOURCE when compiling?
-D_FORTIFY_SOURCE=0