写一道基础的栈溢出PWN题,打开二进制安全的门缝
第一步,准备一道pwn题的源码
示例.c
第二步就是gcc编译了,因为还要牵扯到很多的开关,所以这里一步步来
1、【关闭】EP/NX(堆栈不可执行)
2、【关掉】Stack Protector/Canary(栈保护)
3、【关掉】程序ASLR/PIE(程序随机化保护)
4、【关闭】整个linux系统的ASLR保护
5.「打开」整个linux系统的ASLR保护
6.64位linux下面的GCC编译出一个32位可执行程序
第三步,我们在做题目的时候经常碰到的就是将题目作为一个服务绑定在一个服务器的一个端口上,所以接下来可以用socat
最后一步就是访问题目了
程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,导致与其相邻的栈中的变量的值被改变。这种问题是一种特定的缓冲区溢出漏洞,类似的还有堆溢出,bss段溢出等溢出方式。
发生的前提条件
程序必须向栈上写入数据
写入的数据大小没有被良好地控制
GPT
这段代码存在严重的安全问题,主要是因为使用了不安全的 gets
函数,容易导致缓冲区溢出。具体问题如下:
使用 gets 函数: gets
函数没有提供缓冲区边界检查,因此可能导致缓冲区溢出,允许攻击者覆盖栈上的关键数据。
栈溢出风险: 函数 vulnerable
中的字符数组 s
只有 12 个字节,但用户输入没有受到限制。这可能允许攻击者输入超过 12 个字节的数据,导致栈溢出。
system 函数调用: 函数 func
中使用 system
函数执行用户输入的命令,这可能导致命令注入攻击,尤其是由于缺乏输入验证。
为了解决这些问题,应该使用安全的输入函数替代 gets
,例如 fgets
,并确保对用户输入进行适当的验证和边界检查。此外,对于执行用户输入的命令,应该谨慎处理,避免命令注入漏洞。
我们所要达成的目的是让程序执行func函数
首先关闭ASLR
echo 0 > /proc/sys/kernel/randomize_va_space
使用如下指令进行编译,编译32位不带stack保护和pie保护的二进制文件
gcc -m32 -fno-stack-protector -no-pie main.c -o main
得到一个main
二进制文件,使用checksec进行分析
gdb调试可看b站“国资社畜”的的pwn教学视频,讲得非常详细非常好。
国资社畜 你想有多PWN:https://www.bilibili.com/video/BV1mr4y1Y7fW
函数:vulnerable()
080484b9 83 ec 14 SUB ESP,0x14 //缓冲区的大小(EBP到ESP:0x14)
080484e0 e8 3b fe CALL <EXTERNAL>::gets //⚠️危险函数 char * gets(char * __s)
main二进制文件中main
函数反编译结果:
直接调用了vulnerable();和我们的源代码基本一致
主函数main
Ghidra反编译vulnerable
函数结果,以下为伪代码:
为什么在源代码中[12]而这里却是[16]?
源代码中定义了一个大小为 12 的字符数组 s
。然而,反编译的伪代码中,编译器可能进行了栈帧的调整和优化,将 s
的大小调整为 16 字节,这可能是为了实现更有效的栈内存布局或者对齐。
⚠️危险的system("/bin/sh");
至此,一道简单的pwn从写题到部署,再到寻找利用方式,最后到漏洞利用全部演示完毕
Pwn入门之基础栈溢出:https://zhuanlan.zhihu.com/p/587763752
c程序从编译开始到运行结束的过程:https://blog.csdn.net/tbsqigongzi/article/details/128137047
国资社畜 你想有多PWN:https://www.bilibili.com/video/BV1mr4y1Y7fW
int
main()
{
int
a
=
1
;
float
key
=
2018.81
,
input
;
if
(a
=
=
2
)
{
printf(
"input your key:\n"
);
scanf(
"%f"
,&
input
);
if
(
input
=
=
key)
printf(
"mctf{Hahahahaha_Y0u_C@n_use_GDB_N0W}"
);
}
return
0
;
}
int
main()
{
int
a
=
1
;
float
key
=
2018.81
,
input
;
if
(a
=
=
2
)
{
printf(
"input your key:\n"
);
scanf(
"%f"
,&
input
);
if
(
input
=
=
key)
printf(
"mctf{Hahahahaha_Y0u_C@n_use_GDB_N0W}"
);
}
return
0
;
}
gcc
-
z execstack
-
o 编译完的文件名 待编译的文件名
gcc
-
z execstack
-
o 编译完的文件名 待编译的文件名
gcc
-
fno
-
stack
-
protector
-
o 编译完的文件名 待编译的文件名
gcc
-
fno
-
stack
-
protector
-
o 编译完的文件名 待编译的文件名
gcc
-
no
-
pie 编译完的文件名 待编译的文件名
gcc
-
no
-
pie 编译完的文件名 待编译的文件名
sudo
-
s
echo
0
>
/
proc
/
sys
/
kernel
/
randomize_va_space
exit
sudo
-
s
echo
0
>
/
proc
/
sys
/
kernel
/
randomize_va_space
exit
sudo
-
s
echo
2
>
/
proc
/
sys
/
kernel
/
randomize_va_space
sudo
-
s
echo
2
>
/
proc
/
sys
/
kernel
/
randomize_va_space
gcc
-
m32
-
z execstack
-
fno
-
stack
-
protector
-
o 编译完的文件名 待编译的文件名
gcc
-
m32
-
z execstack
-
fno
-
stack
-
protector
-
o 编译完的文件名 待编译的文件名
socat tcp
-
l:端口号,fork
exec
:程序位置,reuseaddr
socat tcp
-
l:端口号,fork
exec
:程序位置,reuseaddr
nc IP地址 端口号
int
func(){
system(
"/bin/sh"
);
return
0
;
}
void vulnerable() {
char s[
12
];
puts(
"请输入:"
);
gets(s);
puts(s);
return
;
}
int
main(
int
argc, char
*
*
argv) {
vulnerable();
return
0
;
}
int
func(){
system(
"/bin/sh"
);
return
0
;
}
void vulnerable() {
char s[
12
];
puts(
"请输入:"
);
gets(s);
puts(s);
return
;
}
int
main(
int
argc, char
*
*
argv) {
vulnerable();
return
0
;
}
Arch: i386
-
32
-
little
/
/
x86架构下的
32
位小端程序
RELRO: Partial RELRO
/
/
Stack: No canary found
NX: NX enabled
PIE: No PIE (
0x8048000
)
Arch: i386
-
32
-
little
/
/
x86架构下的
32
位小端程序
RELRO: Partial RELRO
/
/
Stack: No canary found
NX: NX enabled
PIE: No PIE (
0x8048000
)
|
|
|
|
|
标题 |
RELRO |
STACK CANARY(堆栈保护) |
NX (不可执行内存) |
PIE |
示意 |
RELRO 是可执行文件中一个常见的保护特性。它的全称是 Relocation Read-Only(重定位只读),它的作用是防止针对 GOT 表的攻击。<br><br>RELRO 可以分为三种级别:<br><br>- Partial RELRO(部分 RELRO):在 program 的初始化阶段,只有 GOT 表中未被初始化的部分被保护起来。如果一个地址被初始化后,那么它就成为了漏洞的可能入口。<br> <br>- Full RELRO(完全 RELRO):在 program 的初始化阶段,GOT 表所有项都被标记为只读。这样一来,攻击者就无法改变 GOT 表中的地址来实现攻击。<br> <br>- Canary RELRO:这是 Full RELRO 的升级版,它还增加了内存区域的随机化,提高了安全性。 |
堆栈保护是一种通过检查函数返回地址是否被修改的技术。在执行函数时,函数返回地址会被压入栈中。堆栈保护插入了一个被称为“堆栈守卫”的随机值到返回地址之前,防止它被篡改。<br><br>如果堆栈保护是开启的,Checksec 将会显示“Canary found”,否则就是“No canary found”。 |
NX 是可执行文件中的一项重要安全特性,它通过将某些内存区域标记为不可执行来防止缓冲区溢出等攻击。<br><br>如果 NX 是开启的,Checksec 将会显示“NX enabled”,否则就是“NX disabled”。 |
PIE(Position Independent Executables)是在程序执行时将程序和依赖的库加载到内存中并且将其具体位位置放置在随机的内存地址中。这样,攻击者想要利用已知的内存位置来执行攻击时就很难了。<br><br>PIE 是增加安全性的有力手段。如果开启了 PIE,Checksec 将会显示“PIE enabled”,否则就是“No PIE”。 |
例子 |
Full RELRO |
Canary found |
NX enabled |
PIE enabled |
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
FUNCTION
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
undefined vulnerable()
undefined AL:
1
<RETURN>
undefined4 Stack[
-
0x8
]:
4
local_8 XREF[
1
]:
080484f8
(R)
undefined1 Stack[
-
0x18
]:
1
local_18 XREF[
2
]:
080484dc
(
*
),
080484eb
(
*
)
vulnerable XREF[
4
]: Entry Point(
*
), main:
08048518
(c),
080485f0
,
080486a8
(
*
)
080484b5
55
PUSH EBP
080484b6
89
e5 MOV EBP,ESP
080484b8
53
PUSH EBX
080484b9
83
ec
14
SUB ESP,
0x14
/
/
缓冲区的大小(EBP到ESP:
0x14
)
080484bc
e8 ff fe CALL __x86.get_pc_thunk.bx undefined __x86.get_pc_thunk.bx()
ff ff
080484c1
81
c3
3f
ADD EBX,
0x1b3f
1b
00
00
080484c7
83
ec
0c
SUB ESP,
0xc
080484ca
8d
83
b8 LEA EAX,[EBX
+
0xffffe5b8
]
=
>DAT_080485b8
=
E8h
e5 ff ff
080484d0
50
PUSH EAX
=
>DAT_080485b8
=
E8h
080484d1
e8
5a
fe CALL <EXTERNAL>::puts
int
puts(char
*
__s)
ff ff
080484d6
83
c4
10
ADD ESP,
0x10
080484d9
83
ec
0c
SUB ESP,
0xc
080484dc
8d
45
ec LEA EAX
=
>local_18,[EBP
+
-
0x14
]
080484df
50
PUSH EAX
080484e0
e8
3b
fe CALL <EXTERNAL>::gets
/
/
⚠️危险函数 char
*
gets(char
*
__s)
ff ff
080484e5
83
c4
10
ADD ESP,
0x10
080484e8
83
ec
0c
SUB ESP,
0xc
080484eb
8d
45
ec LEA EAX
=
>local_18,[EBP
+
-
0x14
]
080484ee
50
PUSH EAX
080484ef
e8
3c
fe CALL <EXTERNAL>::puts
int
puts(char
*
__s)
ff ff
080484f4
83
c4
10
ADD ESP,
0x10
080484f7
90
NOP
080484f8
8b
5d
fc MOV EBX,dword ptr [EBP
+
local_8]
080484fb
c9 LEAVE
080484fc
c3 RET
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
FUNCTION
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
undefined vulnerable()
undefined AL:
1
<RETURN>
undefined4 Stack[
-
0x8
]:
4
local_8 XREF[
1
]:
080484f8
(R)
undefined1 Stack[
-
0x18
]:
1
local_18 XREF[
2
]:
080484dc
(
*
),
080484eb
(
*
)
vulnerable XREF[
4
]: Entry Point(
*
), main:
08048518
(c),
080485f0
,
080486a8
(
*
)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)