首页
社区
课程
招聘
[原创]从0开始CTF-PWN(二)从PWN的HelloWorld-栈溢出开始
发表于: 2020-5-2 01:40 19507

[原创]从0开始CTF-PWN(二)从PWN的HelloWorld-栈溢出开始

2020-5-2 01:40
19507

作者: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)的状态。函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。
典型的函数调用栈结构如下:

2_1_stack_1

stack_2

stack_3

上面说的可能还是太抽象了,最好的办法就是自己动手调试跟踪一下程序运行过程中栈的变化情况,因为栈结构的理解在PWN中是基础的基础,非常重要,所以这里建议大家一定要自己调试一下去理解这一变化过程。

使用gdb开始调试

starti

使用disassemble查看函数汇编代码

Alt

在main函数调用func函数的地方下断点,观察此时的栈结构

Alt

左侧为eip指令的地址,我们给0x080491ef这条call func指令下断点,然后r运行,程序会到断点处停下。

此时查看栈信息,可以看到如之前描述,栈顶存放的是main函数中func(0xdeadbeef)的参数。

Alt

Alt

和main函数中进行对比,发现一致。

Alt

知识点-gdb调试工具常用指令

掌握了上面的函数调用过程,回到上面的问题上来,我们要怎么使得key == 0xcafebabe呢?

Alt

Alt

可以看到从地址0xffffd5e0开始填充a,计算0xffffd610 - 0xffffd5e0 = 48,即填充48位的padding后,即开始覆盖参数key的值。

Alt

将程序通过socat发布到5555端口

在虚拟机外执行payload测试

成功获得Shell

Alt

再次强调,官网由于开启了栈保护,相比本地环境没有开启,会在栈上额外插入4字节的保护内容(padding变为52位),但并不影响解题思路。

这里提供了pwntools攻击的实现,注释已有详细说明。

通过这样一个栈溢出程序练习,掌握了程序基本的栈调用结构,初步体会到了栈溢出的效果。那么我们继续思考,本题中是直接提供了system("/bin/sh")语句反弹shell,如果没有这句,我们又要怎么获得Shell呢?

# 注意,下面是临时修改方案,系统重启后会被重置为2
echo 0 > /proc/sys/kernel/randomize_va_space
# 注意,下面是临时修改方案,系统重启后会被重置为2
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 [可执行文件名] [源文件名]
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
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
# 调试的一开始输入(gdb8.0以上版本必须加,否则会导致断点设置失败)
(gdb) starti
gdb -q bof_32-gcc4.8
# 调试的一开始输入(gdb8.0以上版本必须加,否则会导致断点设置失败)
(gdb) starti
# 查看main函数
(gdb) disassemble main
# 查看func函数
(gdb) disassemble func
# 查看main函数
(gdb) disassemble main
# 查看func函数
(gdb) disassemble func
 
# 下断点
(gdb) b *0x080491ef
# 运行
(gdb) r
# 下断点
(gdb) b *0x080491ef
# 运行
(gdb) r
(gdb) stack
(gdb) stack
(gdb) stepi
(gdb) stepi
 
 
# 检查开启了哪些保护
(gdb) checksec
输出:
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
 
# 对进程调试
gdb attach [pid]
 
# 调试的一开始输入(gdb8.0以上版本必须加,否则会导致断点设置失败)
starti
# 查看main函数
(gdb) disassemble main
# 命令将函数代码和汇编指令映射起来
disassemble /m func
# 增加断点
(gdb) b *[address]
# 运行
(gdb) r
 
# 查看esp内容(以16进制查看$esp地址处2个单位的内容,每个单位4个字节)
(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
 
# 设置自动显示接下来的要执行的3条指令
(gdb) display /3i $eip
 
# 查询进程信息(一般运行一遍后会加载出libc)
(gdb) info proc mappings / (gdb) info proc map
——可以用于查找libc信息
 
# 从libc中查找关键字
(gdb) find "/bin/sh"
# 检查开启了哪些保护
(gdb) checksec
输出:
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial
 
# 对进程调试
gdb attach [pid]
 
# 调试的一开始输入(gdb8.0以上版本必须加,否则会导致断点设置失败)
starti
# 查看main函数
(gdb) disassemble main
# 命令将函数代码和汇编指令映射起来
disassemble /m func
# 增加断点
(gdb) b *[address]
# 运行
(gdb) r
 
# 查看esp内容(以16进制查看$esp地址处2个单位的内容,每个单位4个字节)
(gdb) x/2wx $esp
 
# 显示寄存器指向的地址
p $寄存器:显示寄存器指向的地址
 
# 显示寄存器内容
用x命令可以显示内容,“x/格式 地址”。
x $pc:显示程序指针内容
x/i $pc:显示程序指针汇编。
x/10i $pc:显示程序指针之后10条指令。
 
# 继续执行
(gdb) c
 
# 单步调试
(gdb) stepi
或 单步过

[注意]APP应用上架合规检测服务,协助应用顺利上架!

最后于 2020-11-29 22:05 被dxbaicai编辑 ,原因: 附件处理
上传的附件:
收藏
免费 6
支持
分享
最新回复 (12)
雪    币: 33
活跃值: (55)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
最后输入pwd,ls等命令没有反应,是怎么回事?谢谢
2020-5-5 22:28
0
雪    币: 2886
活跃值: (599)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
3
YiMianHS 最后输入pwd,ls等命令没有反应,是怎么回事?谢谢

大概率是编译环境、编译参数不正确,之前用gcc4.8+以上版本会有类似情况,注意下按第一篇环境准备里的配置。编译参数记得要关闭各种优化并设置-m32。另外payload中每个人的返回地址因为编译环境差异是可能不同的,要按方法自己确定,直接照搬是不行的。

最后于 2020-5-6 14:10 被dxbaicai编辑 ,原因: 修改回复
2020-5-6 14:08
0
雪    币: 33
活跃值: (55)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
谢谢,不是编译的问题。用python脚本就可以,用nc不行。怀疑是nc版本的问题,没有深究。。
2020-5-6 22:41
0
雪    币: 33
活跃值: (55)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
在macos上,brew安装的netcat,更夸张,命令行直接返回到下一个提示符,断开了。
2020-5-6 22:43
0
雪    币: 72
活跃值: (300)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
师傅,gdb用的什么版本的
2020-10-14 21:14
0
雪    币: 4934
活跃值: (1056)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
7
图好像崩了 麻烦 lz 补一下
2020-11-28 13:38
0
雪    币: 554
活跃值: (144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
图片崩了哦,大锅
2020-11-28 19:16
0
雪    币: 2886
活跃值: (599)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
9
明珉 图片崩了哦,大锅
不好意思之前用的图床。。图片现在应该都改到站内地址了。
2020-11-29 22:06
0
雪    币: 2886
活跃值: (599)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
10
mb_ndvyyoyx 师傅,gdb用的什么版本的
我用的是GNU gdb (Debian 9.1-3) 9.1
2020-11-29 22:07
0
雪    币: 2886
活跃值: (599)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
11
YiMianHS 最后输入pwd,ls等命令没有反应,是怎么回事?谢谢
可能是你编译环境和我有差别,特别是gcc版本和编译参数
2020-11-29 22:08
0
雪    币: 304
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12

师傅,我对填充48个A还是有疑惑,为什么计算func到gets的下一个条cmp的长度之后,在输入就能覆盖

最后于 2020-12-16 17:04 被web吴彦祖编辑 ,原因:
2020-12-16 14:37
0
雪    币: 58
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
师傅,为啥我用管道就segment fault用pwntools就可以呢
2021-1-1 18:54
0
游客
登录 | 注册 方可回帖
返回
//