Pwn学习笔记(二)-栈溢出(持续学习更新)
大佬课程链接:https://www.bilibili.com/video/BV1mr4y1Y7fW/?spm_id_from=333.337.search-card.all.click
第二章-栈溢出
1.pwn工具
1.pwndbg,ida
pwndbg与插件
一些pwndbg命令可以去https://github.com/Gallopsled/pwntools/tree/dev/pwnlib/commandline中查询
pwndbg常用指令
finish 执行完当前函数并返回 |
ida
f5->反编译 |
2.pwntools写脚本
用pwn包写脚本
q1
from pwn import * |
q3
from pwn import * |
pwntools提供用gdb调试的程序
from pwn import * |
2.程序的内存布局以及函数调用过程
1.程序内存分布
1.32位主机内存4g是因为总线长度为32,每个地址四个字节
256T地址内存并不是64位程序的真正的总长度, 64位程序只用后12个
2.程序尽量往前放,从上往下增长,如下图黄色。
程序下面是堆空间,如下图绿色。
堆空间下面是别人写好的内容的空间,如下图紫色。
栈(变量)空间往后放,从下往上增长,如下图蓝色
2.栈操作与函数调用过程
1.栈操作
1.栈操作
esp存放当前栈顶地址,ebp存放当前栈底地址
push ebp
先将 esp-4.再把ebp的值放到esp所指的内存中,
pop ebp
把esp指向的地址的值赋给ebp,再将esp+4
leave :move esp,ebp; pop ebp
call : push eip;jump func
当程序执行一条语句时,对应的eip是该语句下一行的地址
ret : pop eip
2.如果想给eip进行赋值
jmp cs[0x11111111] = move eip,0x11111111
ret = pop eip(没有Pop eip指令)
注:64位程序
(2^8)^8 一个字节8个位,64位一个字是8个字节
2^64
16*(10K,10M,10G,10T,10P,10Z)
256(10K,10M,10G,10T)
rbp下一行还是返回地址
2.函数调用过程
1.进入到dofunc中,先push ebp和mov ebp,esp 与最后的leave相反。
2.执行push后,esp-4由bc变为b8, 值为ebp地址
3.执行move后
4.继续执行到leave
5.执行leave,esp变为原ebp:d4b8,将esp即d4b8地址的值d4c8给ebp,esp+4变为d4bc
6.最后ret,对应call
3.Question4
|
read函数
read(0,buf,0x1c)第一个参数必须为0,第二个参数是写入的地址,第三个参数是写入的长度 |
gdb调试时步入dofunc中,执行两步后
可以看到dofunc进入时的eip放在ebp的下一块内存中,最终返回时会返回到0x8049278
如果将其改为func的地址,则dofunc运行完后会返回到func中
通过python脚本攻击
当进入dofunc后存在read函数,输入足量的内容后会覆盖后续栈空间
成功覆盖ebp下一条地址的值,及返回函数地址
python写脚本,func地址0x80491d6
from pwn import * |
成功拿到shell
程序ida反汇编后进入stack可以看到
s -> ebp |
当esp寻址时,只有eip有用,ebp与esp和普通寄存器相同
写exp时也是按照查看返回函数的溢出值位置来写。
3.ret2text
1.函数调用约定
1.__cdecl: C/C++默认方式,参数从右向左入栈,主调函数负责栈平衡。
2.__stdcall: windows API默认方式,参数从右向左入栈,被调函数负责栈平衡。
3.__fastcall: 快速调用方式。所谓快速,这种方式选择将参数优先从寄存器传入(ECX和EDX),剩下的参数再从右向左从栈传入。因为栈是位于内存的区域,而寄存器位于CPU内,故存取方式快于内存,故其名曰\“__fastcall”。
一般默认是第一种
|
add_test(arg1,arg2,arg3);//从arg3->arg2->arg1从右向左压入栈中
在进入函数前,栈中前几位依次是函数从左到右的参数
cdecl
x86: 使用栈来传递参数,使用eax存放返回值
amd64:前6个参数依次存放于rdi、rsi、rdx、rcx、r8、r9寄存器中,第七个以后的参数存放于栈中
2.Question_2(传参)
|
编译指令
gcc q4_2.c -m32 -fno-stack-protector -no-pie -o q4_2_x86 |
1.手工改内存
进入dofunc后,ebp下一行是返回函数地址,可以将其改为func地址从而进入func执行
ret时pop eip将func地址传给eip
进入func中,ebp下一行是返回地址
执行完move ebp,esp 后ebp下一行为返回地址,返回地址下一行是参数地址
继续执行,拿到shell
2.缓冲区溢出覆盖栈
到read时输入足够量的字符导致溢出
找到ebp下一行位置和ebp下三行位置,分别存入func地址和/bin/sh地址
由于没有call的过程所以没有将返回地址push到栈里,所以ebp下二行变为返回地址,下三行变为参数
cyclic -l faaa //20 |
payload
from pwn import * |