作者:dxbaicai
上一篇中我们介绍了PWN的基础环境:
从0开始CTF-PWN(一)——基础环境准备
本篇会从PWN的HelloWorld——栈溢出漏洞开始,进行新手教程,由于我也是刚学习,所以理解上也是从新手的角度出发的,一些理解不到位的地方,恳请各位大神指正,我会及时修订。
为了降低入门难度,会关闭操作系统的地址空间随机化(ASLR),这是针对栈溢出漏洞被操作系统广泛采用的防御措施。
在实验环境创建.c源代码文件,使用如下命令进行编译。
知识点-编译参数说明
我们来看这样一段程序,来自PWN著名站点pwnable的经典程序bof,下载下来代码如下:
我们先简单读一下这段代码,main函数中调用了func函数,但参数为0xdeadbeef,func函数中定义了一个char overflowme[32]的32位字符数组,调用gets函数获取用户的输入,如果key参数是0xcafebabe,则返回shell。可以看到我们的目的就是想办法使得参数key等于0xcafebabe,这样就能get shell,但是key在main函数调用时写死了,有什么办法可以改动吗?
注意这句gets(overflowme);,会将我们输入的内容填入overflowme字符数组,如果填入超过32位的定义长度,就会发生栈溢出。
函数调用栈
函数调用栈是指程序运行时内存一段连续的区域,用来保存函数运行时的状态信息,包括函数参数与局部变量等。称之为“栈”是因为发生函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(callee)的状态被压入调用栈的栈顶;在函数调用结束时,栈顶的函数(callee)状态被弹出,栈顶恢复到调用函数(caller)的状态。函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。
典型的函数调用栈结构如下:
上面说的可能还是太抽象了,最好的办法就是自己动手调试跟踪一下程序运行过程中栈的变化情况,因为栈结构的理解在PWN中是基础的基础,非常重要,所以这里建议大家一定要自己调试一下去理解这一变化过程。
使用gdb开始调试
使用disassemble查看函数汇编代码
在main函数调用func函数的地方下断点,观察此时的栈结构
左侧为eip指令的地址,我们给0x080491ef这条call func指令下断点,然后r运行,程序会到断点处停下。
此时查看栈信息,可以看到如之前描述,栈顶存放的是main函数中func(0xdeadbeef)的参数。
和main函数中进行对比,发现一致。
知识点-gdb调试工具常用指令
掌握了上面的函数调用过程,回到上面的问题上来,我们要怎么使得key == 0xcafebabe呢?
可以看到从地址0xffffd5e0开始填充a,计算0xffffd610 - 0xffffd5e0 = 48,即填充48位的padding后,即开始覆盖参数key的值。
将程序通过socat发布到5555端口
在虚拟机外执行payload测试
成功获得Shell
再次强调,官网由于开启了栈保护,相比本地环境没有开启,会在栈上额外插入4字节的保护内容(padding变为52位),但并不影响解题思路。
这里提供了pwntools攻击的实现,注释已有详细说明。
通过这样一个栈溢出程序练习,掌握了程序基本的栈调用结构,初步体会到了栈溢出的效果。那么我们继续思考,本题中是直接提供了system("/bin/sh")语句反弹shell,如果没有这句,我们又要怎么获得Shell呢?
echo
0
>
/
proc
/
sys
/
kernel
/
randomize_va_space
echo
0
>
/
proc
/
sys
/
kernel
/
randomize_va_space
gcc
-
4.8
-
g
-
m32
-
O0
-
fno
-
stack
-
protector
-
z execstack
-
o [可执行文件名] [源文件名]
gcc
-
4.8
-
g
-
m32
-
O0
-
fno
-
stack
-
protector
-
z execstack
-
o [可执行文件名] [源文件名]
void func(
int
key){
char overflowme[
32
];
printf(
"overflow me : "
);
gets(overflowme);
/
/
smash me!
if
(key
=
=
0xcafebabe
){
system(
"/bin/sh"
);
}
else
{
printf(
"Nah..\n"
);
}
}
int
main(
int
argc, char
*
argv[]){
func(
0xdeadbeef
);
return
0
;
}
void func(
int
key){
char overflowme[
32
];
printf(
"overflow me : "
);
gets(overflowme);
/
/
smash me!
if
(key
=
=
0xcafebabe
){
system(
"/bin/sh"
);
}
else
{
printf(
"Nah..\n"
);
}
}
int
main(
int
argc, char
*
argv[]){
func(
0xdeadbeef
);
return
0
;
}
gcc
-
4.8
-
g
-
m32
-
O0
-
fno
-
stack
-
protector
-
z execstack
-
o bof_32
-
gcc4.
8
bof.c
gcc
-
4.8
-
g
-
m32
-
O0
-
fno
-
stack
-
protector
-
z execstack
-
o bof_32
-
gcc4.
8
bof.c
1.
随着函数执行,地址从高地址向低地址增长。
2.
esp寄存器指向栈顶。
3.
按先进后出原则,调用函数(caller)先入栈,被调用函数(callee)后入栈。
1.
随着函数执行,地址从高地址向低地址增长。
2.
esp寄存器指向栈顶。
3.
按先进后出原则,调用函数(caller)先入栈,被调用函数(callee)后入栈。
gdb
-
q bof_32
-
gcc4.
8
(gdb) starti
gdb
-
q bof_32
-
gcc4.
8
(gdb) starti
(gdb) disassemble main
(gdb) disassemble func
(gdb) disassemble main
(gdb) disassemble func
(gdb) b
*
0x080491ef
(gdb) r
(gdb) b
*
0x080491ef
(gdb) r
(gdb) stack
(gdb) stepi
(gdb) checksec
输出:
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
gdb attach [pid]
starti
(gdb) disassemble main
disassemble
/
m func
(gdb) b
*
[address]
(gdb) r
(gdb) x
/
2wx
$esp
p $寄存器:显示寄存器指向的地址
用x命令可以显示内容,“x
/
格式 地址”。
x $pc:显示程序指针内容
x
/
i $pc:显示程序指针汇编。
x
/
10i
$pc:显示程序指针之后
10
条指令。
(gdb) c
(gdb) stepi
或 单步过
(gdb) ni
或 单步入
(gdb) si
(gdb) i r
(gdb) display
/
3i
$eip
(gdb) info proc mappings
/
(gdb) info proc
map
——可以用于查找libc信息
(gdb) find
"/bin/sh"
(gdb) checksec
输出:
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
gdb attach [pid]
starti
(gdb) disassemble main
disassemble
/
m func
(gdb) b
*
[address]
(gdb) r
(gdb) x
/
2wx
$esp
p $寄存器:显示寄存器指向的地址
用x命令可以显示内容,“x
/
格式 地址”。
x $pc:显示程序指针内容
x
/
i $pc:显示程序指针汇编。
x
/
10i
$pc:显示程序指针之后
10
条指令。
(gdb) c
(gdb) stepi
或 单步过
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-11-29 22:05
被dxbaicai编辑
,原因: 附件处理