栈溢出
stack frame pointer:上一个栈帧(父函数)的栈底的值。
函数调用栈是指程序运行时一段连续的区域,用来保存函数运行时的状态信息,包括函数参数与局部变量等。
发生函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(callee)的状态被压入调用栈的栈顶。函数调用结束时,栈顶的函数(callee)状态被弹出,栈顶恢复到调用函数(caller)的状态。
函数调用栈在内存中从高地址到低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。
栈帧中子函数所用的形参保存在父函数栈帧中arguments。
esp指向当前所调用函数所属的栈帧。
将被调用函数的参数压入栈内时是倒序压入。
栈顶永远指向当前正在执行的指令所属的函数对应的栈帧。
子函数完成指定功能后要释放掉所调用的局部变量,只需要将esp推到ebp,就可以释放掉;然后ebp所指向的地址中保存着父函数栈底的地址会传送至ebp寄存器内,ebp就会回到父函数栈底的位置;esp自动减去一个字长并指向return address;
字长:32位字长就是32bit,64位字长就是64bit。
pop,push的工作单位为一字长。
x86架构用eax存放返回值。
汇编指令中call指令自带保存返回地址的操作。
leave=mov(将esp移到ebp位置)+pop(ebp返回到父函数的栈底位置),若esp已经指向ebp,则可以直接pop。
gdb升级:peda gef pwndbg
python3 脚本:运行此脚本
cat 文件名 :捕捉此文件
ida :shift+fn+f12 :将程序转换为字符串形式
寻找main函数:
1.首先执行该文件
2.其次 shift+fn+f12将程序中可转为ASCLL码的内容转换为字符串,找到该字符串
3.双击进入后
4.左下角位置的DATA中存放了 引用这段数据的代码位置,双击进入
5.找到了此数据在程序中的位置,这是程序员自己写的,大概率是我们所要找的位置。f5反编译一下就可以找到main函数
下载pwntools(python2和python2):pip 2 install pwntools(pip 3 install pwntools)
导入python3: python3
攻击:1. from pwn import *(导入pwntools所有模块)
建议先用本地连接攻打获取shell然后在转换为远程连接
2.打开远程或本地连接
(1) io(自定义名字) = process("./本地程序") 打开本地连接
(2) io =remote ("IP",本地端口) 打开远程连接 IP要以字符串的的形式输入,本地端口应提前打开
此时io变成进程管道
3.接受内容
(1)io.recvline() (显示收到的内容(一行))
(2)io.recv () (所有)
4.编写payload
5.发送payload
(1)io.send(b"字符串") 加b是为了将字符串以一字节形式发送,
(2)io.send(p64(64位的数字)) ()里应为字节流 p64()将数据打包为64位宽度的字节流数据,p32()即为32位
(3)io.sendline () 一直读取,直到遇到换行符或字符串结束符。而sendline会自动在输入的数据末尾加上换行符 等效于io.send(b" \n")
6.io.interactive() 进入交互模式
checksec : 查看有哪些保护措施
gdb 调试 :b 后跟要打断点的位置 (可以b *地址,也可以b 一个函数,比如b main)
r命令 :运行程序
n命令逐个执行语句
s命令 进入函数
stack 24 :查看24项的stack
ret2text
1.file 文件 查看多少位的ELF文件,再用IDA打开
nc IP 端口 :访问这个端口
2.用python3 recv一下
除了刚刚收到的字符串,这里还多出了一串字符串。末尾的\r为特殊的文本控制字符,\r会清空前面的内容,也就是所多出来的内容。此段数据发了过来,但是由于\r的存在使得此数据无法显示出来。
3.用echo函数解码此数据 解码: echo 内容 | base64 -d , -d指decode , | 为管道符
篡改栈帧上的返回地址为程序中已有的后门函数
phone_book(找假的flag)
1.file 文件 查看多少位的ELF文件,再用IDA打开
2.(1)第一种方法 查看程序执行流,因为假的flag(fleg)只有可能是出题人写的。首先f5反编译为c语言找到main函数,其次在其中找到非库函数即出题人自己编写的函数,在此题目中record即为所找函数
双击进入此函数,即可找到fleg
(2)第二中方法 shift+fn+f12 ,若fleg是以完整的方式存放即可使用此方法
pwn题目的基本步骤
ret2text
1.首先checksec 文件名 查看文件的保护措施
2.用IDA打开文件 ,f5反编译为c语言,寻找存在漏洞的函数
3.进入此函数,发现gets函数存在漏洞
4.对文件进行动态调试 gdb 文件名,gdb内容较多,建议用全屏然后缩小字体
5.打断点 b main 在main函数中打断点(也可以b *地址)
run(简写r)
REGISTERS为寄存器,DISASM即所执行指令周围的指令进行反汇编的结果
STACK 栈,BACKTRACE 函数调用栈的关系
6.next(简写为n),直到遇到漏洞函数
7.s 进入此函数
n 步过,执行此函数
8.stack 24 看长度为24项stack栈里的内容
通过输入字符串覆盖掉eax与ebp之间的区域,使其执行完vulnerable后无法return到main函数,而到get_shell函数。quit停止动态调试
9.找到后门函数
system一个字符串与在shell中执行此字符串是一个作用,就可以控制此对方服务器
10.eap与ebp之间有16个字节,ebp也需要4个字节覆盖掉,(如果是64位的架构rbp则需要8个字节来覆盖)要想将所想返回的地址覆盖程序中原有的地址就要输入20个字节的恶意数据加上所需要的地址(返回地址)。
找到get_shell的返回地址
编写脚本准备攻击
io.interactive()即进入交互模式,此时已经获得shell,获取了本机的控制权
再将本地连接转换为远程连接
ctrl+d 退出python交互环境
ret2text题目一般程序中有后门函数即有systerm
ret2shellcode题目篡改栈帧上的返回地址为攻击着手动传入的shellcode所在的缓冲区地址,由于the NX bits 保护措施的开启,栈缓冲区不可执行,故当下的常用手段变为向bss缓冲区写入shellcode或向堆缓冲区写入shellcode并使用mprotect赋予其可执行权限
ASLR保护措施(地址随机化)
ASLR关闭时多次查看栈的地址,地址不变,没有被随机化
ASLR打开时多次查看栈的地址,地址发生改变
PIE保护措施 打开后bss,data,text也会地址随机化
canary保护措施
在形成一个新的栈帧的时候,会在previous的低地址出形成一个canary,里面是一个随机值,因此如果要进行栈溢出,就一定会遇到canary并覆盖它,销毁栈帧的时候会检测canary是否发生了变化,如果发生了变化,程序会直接崩溃退出,并弹出stack check file,即栈检查失败,程序强制退出。
获取shellcode:
pwntools中有此工具,在python3中
输入shellcraft.sh()即可获取关于shell的一个shellcode,这样输出的并不美观可以输入print(shellcraft.sh())即可将换行符隐藏 为汇编代码
pwntools中 有工具(asm)可以将汇编代码转换为机器码如下
默认把二进制串当成可显示字符转化为ASCLL码,如果不可显示即原样输出
此命令默认输出的都是攻击32位的shellcode,如果是攻击64位需print(shellcraft.amd64.sh())。由于默认的攻击环境为32位,所以在脚本中应添加 context.arch = "amd64"
pwndbg中vmmap显示虚拟内存的空间分布
ret2shellcode(将shellcode写入bss段中)
1.IDA打开
找到可以函数gets
输入的数据存入局部变量s中,s的前0x64u个字节复制到buf2中,推测出buf2为未初始化的全局变量,其存放在bss段,所以可以将shellcode写入bss段。可以将shellcode写入s,然后将buf2的地址当作return 的地址写入攻击脚本中。
2.gdb动态调试
需要112个字节的恶意数据与目标地址
3.编写脚本
payload = asm(shellcraft.sh().ljust(112,b'A')) 作用为向得到的shellcode后加A直到字节长度为112个字节,asm是将汇编代码转换位机器码
本地连接打通后转换为远程连接
ret2shellcode : 106.54.129.202 10003
进入交互模式后,cat flag.txt 获取其flag
ret2shellcode(将shellcode写入栈中)
首先sudo su 变为超级用户,然后echo 0 > /proc/sys/kernel/randomize_va_space 将ASLR保护措施关闭
gcc -fno-stack-protector -z execstack -no-pie -g -o ret2stack ret2stack.c 其写入脚本之中
-fno-stack-protector 关闭canary保护措施
-z execstack 打开栈的可执行权限
-no-pie 关闭PIE保护措施
-g 将调制信息带上,可以看到C语言代码,前提是 ret2stack.c 存在
-o 输出目标文件的名称
写脚本的时候表头应写上 #!/bin/sh
写完之后给脚本可执行权限
执行完后就得到一个关闭有关栈保护措施的ret2stack
虚拟机中ctrl+c是强行终止所在执行的进程,ctrl+shift+c是复制粘贴
对ret2stack进行gdb调试
需要112个字节覆盖掉空间以及8个字节的数据覆盖rbp的内容,末尾再加上所要求返回到的返回地址
因为要将shellcode写入栈中,所以返回地址应为栈的地址 即 0x7fffffffe060
进入python3编写脚本
攻击失败,原因为gdb里看到的地址很可能是错误的
在ret2stack.c原代码中使用print函数输出栈的地址 为0x7fffffffe0e0 与gdb动态调试得出的不同
重新编写脚本 ret2stack.c
执行此脚本时无法执行,原因是没有消除缓冲区,print函数输出的数据会先到缓冲区里,由于缓冲区没有满所以不会输出,执行时就会卡在io.recv()
重新整理ret2stack.c
结果发现gdb中与程序的print 函数输出 的rsp地址是不同的。原因是gdb在调试此程序时建立了一个新的沙盒空间,gdb中默认是关闭了ALSR的(即使物理机中是关闭了ASLR的),与宿主机的ALSR是无关的,而程序中的。gdb调试时地址偏移量是不会错的,但是地址是不一定一模一样的。环境的不同也可能会影响pwn的过程,比如有的题目远程连接可以打通,本地连接却可以打通。
由于此程序是自己编写的,没有考虑额外的因素所以导致出现了问题。
第一个函数的栈帧是main函数的,在main函数之前是没有栈帧的