记录一下近期零散做到的题。
pwn_printf
程序分析
程序一开始使用 mmap
分配了一块内存,之后调用了 sprintf
进行了操作,最后有一个溢出。最开始分析了很久这个格式化字符串的作用和逻辑,但是后面发现最后执行溢出与这个逻辑不影响。所以直接就是一个简单的栈溢出。
利用分析
栈溢出,执行ROP
链泄露libc
地址。然后再通过 ROP
链 去 getshell
。
PS:这道题前面这么多的格式化字符串按理说一定不是出题人闲的随便写的。后面有大佬告诉我这是谷歌CTF里的一道题改编的,出题人把这道题的溢出条件改成了和格式化字符串没关系,但是原题是有关系的。有兴趣大家可以看看原题
EXP
1 | from pwn import * |
blend_pwn
程序分析
程序有几个明显的漏洞,一个是 ShowName
时有一个格式化字符串,可以泄露Libc
和栈地址。一个是 ShowNote
可以泄露 Heap
地址。
程序还存在一个 magic
函数,里面有一个溢出,能够覆盖 ebp
。当输入超过 16
字符后,会执行 _cxa_allocate_exception
和 _cxa_throw
两个异常处理函数。那么可以怀疑这个异常处理函数应该是存在漏洞点的。
利用分析
主要思路是参考这篇文章
C++
函数调用和放回
异常机制最重要三步是:throw, try, catch
。throw
抛出异常,try
包含异常模块,catch
捕捉抛出的异常。
异常抛出
编译器会将所有 throw 语句替换为其 C++
运行时库中的某一指定函数,这里我们叫它 __CxxRTThrowExp
。该函数接受一个编译器认可的内部结构,这个结构包含了待抛出异常的起始地址、用于销毁它的析构函数,以及他的 type_info
信息。对于没有启用 RTTI
机制的异常类层次结构,可能还要包含其所有基类的 type_info
信息。
__CxxRTThrowExp
首先接收(并保存)EXCEPTION
对象;然后从 TLS:Current ExpHdl 处找到与当前函数对应的 piHandler、nStep
等异常处理相关数据;并按照前文所述的机制完成异常捕获和栈回退。由此完成了包括“抛出”->“捕获”->“回退”等步骤的整套异常处理机制
异常捕获
异常被抛出时,会立即引发 C++
的异常捕获机制:根据 C++
标准,异常抛出后如果再当前函数内没有被捕获(catch
),他就要沿着函数的调用链继续往上抛,直到走完整个调用链,或者在某个函数中找到 catch
。如果走完调用链都没有找到相应的 catch
,那么std::terminate()
就会被调用,这个函数默认是把程序 abort
,而如果最后找到了相应的 catch
,就会进入该 catch
代码块,执行相应的操作。
从抛出异常到开始执行 catch
,包含两个过程:1. 从抛出异常的函数开始,对调用链上的函数逐个往前查找;2.如果没有找到 catch
则把程序 abort
,否则则记下 landing pad
的位置,再重新回到抛异常的函数开始清理调用链上的各个函数内部的局部变量,直到 landing pad
所在函数为止。
栈回退
回退时要来确保在异常被抛出、捕获并处理后,所有生命周期已结束的对象都会被正确的析构,他们所占用的空间会被正确的回收。
如下图所示,程序马上进入 _cxa_throw
函数,我们查看此时的ebp
,可以发现此时的 ebp
值为 main
函数的 ebp
的值。
我们能在 main
函数结尾发现 catch
处理的代码段
此时,我们跟进 _cxa_throw
函数中,在执行 Unwind_RaiseException
函数时,进行了栈回溯,回溯到 Main
函数时的 ebp
并找到了 catch
结构,此时会直接进入 catch
结构里。
思路
这题的思路就是在执行异常处理之前,将当前的ebp
的值指向我们的 ROP-8
的地址,当程序进入 异常处理之后,会向上遍历 查找 catch
,此时就会不断地向上栈回溯。由于main
函数中存在 catch
,所以异常处理在回溯到 main
函数地ebp
地值时会执行catch
函数。但是此时这个 ebp
已经被我们修改为了 执行 rop
地地址,所以就会返回到我们地 rop
处执行。
EXP
1 | from pwn import * |
Pwn3
程序分析
程序漏洞很明显,Add
函数,存在一个 off-by-one
漏洞。同时只使用了 realloc
函数。 realloc
函数具有如下性质:
ptr == 0
: malloc(size)ptr != 0 && size == 0
: free(ptr)ptr != 0 && size == old_size
: edit(ptr)ptr != 0 && size < old_size
: edit(ptr) and free(remainder)ptr != 0 && size > old_size
: new_ptr = malloc(size); strcpy(new_ptr, ptr); free(ptr); return new_ptr;
利用分析
这道题比赛的时候想的是 在 unsortedbin
中去堆合并做,因为 realloc
时如果其next chunk
其 next_size+old_siz = request_size
会直接进行堆合并。但是做了很久,失败了,主要原因是由于 堆合并时会进行 Unlink
操作,在 2.27
中 Unlink
一开始就会检查 当前需要unlink 的chunk的 next_chunk的 prev_size是否与当前chunk的size相等。而如果我们想用堆合并实现 对 next chunk
的覆盖时,这两个地址肯定不相等,所以失败了。
后面想到可以直接通过在 tache
里修改size来做。大概思路是:
- 构建 chunk extend
通过修改old_size
的 tcache
的为 new_size
,然后再申请 old_size 把他分配出来。然后再释放它,就会进入 new_size
的tcache链里,随后申请 new_size
的chunk 就能够实现堆溢出了。
我们首先 将 0x90 的 tcache布满,随后再申请释放一个 0x90 到 Unsortedbin,来泄露地址。
然后 通过上述方法,构建第一次 chunk extend,将 unsortedbin chunk
的 fd 改为 stdout
地址;
- 构建 tcache poisoning
然后再通过上述方法,实现第二次堆溢出。随后利用 tcache poisoning
,将 Unsortedbin chunk
链进 tcache 链。
- 泄露地址
然后将 tcache
中伪造的 stdout chunk
申请出来,需要一点步骤。思路还是,通过 修改 tcache
的size,将与 stdout chunk
之前的 chunk 释放到 其他 tcache 链中。最后就能将末尾的 stdout chunk
申请出来。
- Getshell
当泄露地址后,我们此时 的 Buf
就为 _IO_2_1_stdout_
地址,此时如果直接 再 realloc
会报错,因为 realloc
会释放 buf,而 _IO_2_1_stdout_
地址是非法的,free 会报错。
我们可以利用 exit
函数中 将 stdout
关闭和 将 Buf
清空,此后就能再次 realloc
了。
最后就是依然 是利用 堆溢出,将 free_hook
改为 system
,注意 不能直接 /bin/sh
。因为 exit
函数中将输出关闭,所以需要用 "cat flag 1>&2"
,将 flag 通过 stderr
输出。
EXP
1 | from pwn import * |
babyheap
程序分析
程序漏洞在 safe_read
里,存在一个任意地址写0漏洞,只能写入 0xf0
。并且程序只能申请 0xf8
大小的堆块。
利用分析
1 泄露Libc 和 Heap地址
首先这题有输出函数,可以直接泄露 Libc
和heap
地址。
2.构造堆合并
我们先将 0x100
的tcache
布满,随后申请3个chunk,chunk7,chunk8,chunk9
,我们依次释放 chunk7,chunk8,chunk9
,那么会在 chunk9
的 prev_size
留下 0x200
。然后我们再次申请该3个chunk
,利用任意地址写0,修改chunk9
的 Prev_Inuse
为0,释放 chunk7
到 Unsortedbin
,再释放 chunk9
,此时该三个块即会合并。我们再依次申请两个chunk,此时chunk8
存在两个指针,可以构造 uaf
。依次释放 新申请的 两个chunk
到tcache
,再利用原来的 chunk8修改 其 fd
指针指向 free_hook
来 getshell
。
EXP
1 | from pwn import * |
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2020/11/02/2020HXB/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!