off by one漏洞原理
1 | 严格来说 off-by-one 漏洞是一种特殊的溢出漏洞,off-by-one 指程序向缓冲区中写入时,写入的字节数超过了这个缓冲区本身所申请的字节数并且只越界了一个字节。 |
off by one利用思路
1 | 1.溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠从而泄露其他块数据,或是覆盖其他块数据。也可使用NULL字节溢出的方法 |
实例1 asis ctf 2016 b00ks
1 | 题目介绍 |
1 | 严格来说 off-by-one 漏洞是一种特殊的溢出漏洞,off-by-one 指程序向缓冲区中写入时,写入的字节数超过了这个缓冲区本身所申请的字节数并且只越界了一个字节。 |
1 | 1.溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠从而泄露其他块数据,或是覆盖其他块数据。也可使用NULL字节溢出的方法 |
1 | 题目介绍 |
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
1 | 由于echo拷贝时,会被\x00截断,所以不能连续覆盖多个地址来rop, |
1 | from pwn import * |
1 | 分析能够发现程序有两处漏洞,一处是栈溢出,一处是格式化字符串漏洞 |
1 | 程序有Canary保护,这个保护就是在栈上面抱一个标志canary,在程序执行结束后,会检测这个标志是否发生变化,若发生变化则程序报错,这个canary通常是在ebp的上面,同一个程序在每一次执行时这个标志canary是不同的,但是在执行过程中这个标志是不会发生变化的 |
1 | 首先计算一下格式化字符串的偏移,偏移为5,由于程序是64位的,所以%x不可以使用,改用%llx |
1 | 由于buf的大小为0x90,并且标志canary是在ebp的上面,也就是buf的最低处,所以标志canary的偏移就是23 |
1 | from pwn import *context.arch = "amd64" |
1 | 在前面几篇文章中,为了降低难度,很多通过调用库函数system的题目我们实际上都故意留了后门或者提供了目标系统的libc版本。我们知道,不同版本的libc,函数首地址相对于文件开头的偏移和函数间的偏移不一定一致。所以如果题目不提供libc,通过泄露任意一个库函数地址计算出system函数地址的方法就不好使了。这就要求我们想办法获取目标系统的libc。 |
1 | 我们先来看比较简单的write函数。write函数的特点在于其输出完全由其参数size决定,只要目标地址可读,size填多少就输出多少,不会受到诸如‘\0’, ‘\n’之类的字符影响。因此leak函数中对数据的读取和处理较为简单。 |
1 | 这种情况下我们就可以使用DynELF来leaklibc,进而获取system函数在内存中的地址。 |
1 | 现在我们需要让这个payload可以被重复使用。首先我们需要改掉write函数返回的地址,以免执行完write之后程序崩溃。那么改成什么好呢?继续改成write是不行的,因为参数显然没办法继续传递。如果使用pop清除栈又会导致栈顶下降,多执行几次就会耗尽栈空间。这里我们可以把返回地址改成start段的地址 |
1 | 这段代码是编译器添加的,用于初始化程序的运行环境后,执行完相应的代码后会跳转到程序的入口函数main运行程序代码。因此,在执行完write函数泄露数据后,我们可以返回到这里刷新一遍程序的环境,相当于是重新执行了一遍程序。现在的payload封装成leak函数如下 |
1 | 我们可以利用这个DynELF类的实例泄露read函数的真正内存地址,用于读取”/bin/sh”字符串到内存中,以便于执行system(“/bin/sh”)。最终脚本如下: |
1 | 除了“好说话”的write函数之外,一些专门由于处理字符串输出的函数也经常出现在各类CTF pwn题目中,比如printf, puts等。这类函数的特点是会被特殊字符影响,因此存在输出长度不固定的问题。针对这种函数,我们可以参考这篇博文:https://www.anquanke.com/post/id/85129 对leak函数的接收输出部分进行调整。我们看一下例子~/LCTF 2016-pwn100/pwn100,其漏洞出现在sub_40068E()中。 |
1 | 很明显的栈溢出漏洞。 |
1 | 由于实际上栈溢出漏洞需要执行完puts(“bye~”)之后才会被触发,输出对应地址的数据,因此我们需要去掉前面的字符,所以可以写leak函数如下: |
1 | 通过查看输出的leak结果我们可以发现有大量的地址输出处理之后都是0x0a,即一个回车符。从Traceback上看,最根本原因是读取数据错误。这是因为puts()的输出是不受控的,作为一个字符串输出函数,它默认把字符’\x00’作为字符串结尾,从而截断了输出。因此,我们可以根据上述博文修改leak函数: |
1 | int sub_40068E() |
1 | 无libc 无system 无bin/sh |
1 | #!usr/bin/python |
最近省赛要开始了,把去年的题目重新详细的做一遍 较为简单
借鉴了csdn大佬的博客
1 | 以一个简单的函数为例 |
1 | 在调用函数的时候,要传入的参数会放在RET之下,也就是主函数的栈空间的顶端。并且参数是根据调用的顺序依次向下排序的,也就是越早用到越上面。 |
1 | 就一个NX(栈不可执行),另外还给了libc.so.6,应该是用ret2libc |
1 | 溢出点很明显因为是32位的程序,payload填充84+4个字节就能完成溢出 |
1 | from pwn import * |
1 | 最基本的格式化字符串溢出 |
1 | 1、利用格式化漏洞覆盖任意地址的值,这里我们需要覆盖secret的值,所以先要找到secret的地址,在IDA中,可以看到secret在bss段: |
1 | 2、利用%k$n(k$用于获取格式化字符串中的指定参数)对指定地址进行覆盖 |
1 | 然后运行,提示输出后,我就随便输入一个 %d%d |
1 | 观察栈中的信息 |
1 | 可以看出,printf的第一个参数(format)地址为:0xffffd920 |
1 | from pwn import * |
1 | ARP欺骗的运作原理是由攻击者发送假的ARP数据包到网上,尤其是送到网关上。其目的是要让送至特定的IP地址的流量被错误送到攻击者所取代的地方。因此攻击者可将这些流量另行转送到真正的网关(被动式数据包嗅探,passive sniffing)或是篡改后再转送(中间人攻击,man-in-the-middle attack)。攻击者亦可将ARP数据包导到不存在的MAC地址以达到阻断服务攻击的效果,例如netcut软件。 |
1 |
1 | #include <stdio.h> |
1 | Frees a fake fastbin chunk to get malloc to return a nearly-arbitrary pointer. |
1 | RELRO STACK CANARY NX PIE |
1 | 编写dynelf查看内存结构 |
1 | from pwn import * |