CTF-特训营-Notes
第11章 PWN 基础
gcc 的 -z execstack
可开启栈可执行功能.
修改 /proc/sys/kernel/randomize_va_space
里的值用于控制系统级的 ASLR.
gcc 添加 -fno-stack-protector
可以关闭程序的 stack canary 栈保护.
11.3 PWN 类型
- 栈漏洞
- 堆漏洞
- 格式化字符串漏洞
- 整形漏洞
- 逻辑漏洞
11.4 常见的利用方法
shellcode 指用于获取 shell 的代码.
11.5 程序内存布局
Data 段用来存放可执行文件中已经初始化的变量, 包括静态分配的变量和全局变量.
BSS 段主要包含程序中未初始化的全局变量, 在内存中 BSS 段全部置零.
栈 (stack) 存放程序临时创建的局部变量, 包括函数内部的临时变量和调用函数时压入的参数.
对于一些题, 需要找到 buffer 的大小, 以及读取 buffer 的长度.
第12章 栈相关漏洞
12.1 栈介绍
不同的函数栈之间是相互隔离的.
函数栈上存储的信息一般包括:
- 临时变量 (包括栈保护哨 canary)
- 函数的返回栈基址 (bp)
- 函数的返回地址 (ip)
示意图如:
12.1.1 函数栈的调用机制
函数栈用于实现状态的隔离, 当前函数栈的边界就是:
- 栈顶指针 (sp)
- 栈底指针 (bp)
压入过程, 函数调用时:
- 首先将参数入栈
- 压入返回地址 (通过 call 实现)
- 压入栈底指针寄存器
bp
函数结束时:
sp
重新指向bp
的位置- 弹出
bp
(通过leave
或pop rbp
) - 弹出返回地址
ip
函数栈示意图:
12.1.2 函数参数传递
函数的穿参规则受函数调用协议影响, 包括:
__stdcall
__cdecl
__fastcall
影响函数参数的入栈方式 (从左向右还是从右向左), 栈平衡的修复方式, 编译器函数名的修饰规则等.
Linux 程序通常采用 __cdecl
的调用方式.
规则如:
尝试后的输出:
注意这里 <main>
之下的第一条指令为什么就是 push %rbp
(为什么不是先 push 参数和返回地址?):
这里就是先调用了
main
函数, 因此之前已经有了call main
, 也就是说, push 参数和返回地址的指令已经运行了
12.2 栈溢出
12.2.1 基本概念
栈溢出是指栈上的缓冲区被填入了过多的数据, 超出了边界, 从而导致栈上原有的数据被覆盖.
12.2.2 覆盖栈缓冲区的具体用途
一般覆盖的方面包括:
- 数据不可执行及栈保护哨
- 覆盖当前栈中函数的返回地址 (当前函数或之前的函数)
- 覆盖栈中所存储的临时变量
- 覆盖栈底寄存器
bp
- 关注的敏感函数
利用 NX 保护
若开启了 NX 保护, 则:
1 |
|
会报错.
未开启, 则能 get shell.
关于 canary
canary 是程序在每次进入函数前会被赋予的值, 在函数返回前检查该值是否发生了改变, 若检测出改变则报异常并退出. (此时加大了覆盖 bp
和 ip
的难度)
虽然 canary 在程序中每次运行时都是随机的, 但是就程序的一次运行来说, 大部分情况下, 不同函数中的 canary 值是固定的.
只要泄漏出 canary, 就可以绕过 canary 保护机制.
容易发生栈覆盖的函数
常见有:
gets(buff)
scanf("%s", buff)
潜在的覆盖函数:
read
strcpy
memcpy
12.3 栈的特殊利用
libc
信息泄漏
main
函数的栈底存放的是 __libc_start_main_ret
可以利用 libc.so
文件或使用 libc_database
来计算 libc
的基址以及 system
地址.
- 环境变量修改, 环境变量指针参数会压在栈上, 修改可以达到特殊目的
在进入程序主逻辑之前, 环境变量已经压入栈底, 如 LD_PRELOAD
, LIBC_FATAL_STDERR_
等