Мой агент передал бумажку с каким-то цифрами и больше не выходил на связь. Надеюсь его не схватили Арбалеты, но что делать с бумажкой я так и не понял. Разберись, а о награде договоримся.
My agent handed over a piece of paper with some numbers and never contacted me again. I hope the Arbalety didn’t grab him, but I still don’t understand what to do with the piece of paper. Figure it out, and we'll agree on a reward.
nc 0 13001
cd deploy/
docker compose -p more-heap up --build -d
Provide zip file public/more_heap.zip
- arbitrary write using 2free
- get heap, libc, stack leak
- rewrite
strchrnul
in .got toputs
, rewrite_IO_2_1_stdout_
to callread(0, &stack, 0x100)
on callingputs
, callfree
with invalid argument to callstrchrnul
- write rop to read flag
Сервис с заметками, в котором можно создать заметку, прочитать ее и удалить. Заметка всегда размера 0x10 байт, также при создании заметки проверяется, что чанк выделился не на стеке и не в бинаре. Если это так, то в чанк считывается 16 байт, иначе чанк освобождается.
-
При удалении заметки не зануляется указатель на нее, поэтому можно сначала удалить заметку, затем прочитать ее и ликнуть адрес кучи.
-
Для лика либсы можно освободить фейковый чанк большого размера (например, 0x460). Можно попытаться с помощью 2free выделить чанк так, чтобы перезаписать размер существующего чанка:
+0000 0x55e773b97290 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │........│!.......│ +0010 0x55e773b972a0 61 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │aa......│........│ <- 1st +0020 0x55e773b972b0 00 00 00 00 00 00 00 00 61 04 00 00 00 00 00 00 │........│a.......│ <- 2nd +0030 0x55e773b972c0 61 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │aa......│........│ <- 3rd +0040 0x55e773b972d0 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │........│!.......│
Проблема в том, что когда мы будем выделять фейковый чанк (который находится на
0x55e773b972b0
), первые 8 байт будут равны 0. tcache попробует расшифровать этот адрес, и получится следующее:tcachebins 0x20 [ 0]: 0x55e773b97
Из-за этого, если мы попробуем еще раз проэксплуатировать 2free, получится ошибка в тот момент, когда у нас не осталось чанков в tcache и мы попытаемся взять чанк из fastbin
tcachebins 0x20 [ 0]: 0x55e773b97 fastbins 0x20: 0x55e773b972b0 —▸ 0x55e773b972d0 ◂— 0x55e773b972b0
Чтобы избавиться от этой ошибки, можно сначала выделить чанк по адресу
heap+0x90
, где находится указатель на чанк из tcache, который вернется при следующемmalloc(0x10)
. Если записать туда 0, то 2free можно будет использовать еще раз.Также при следующем 2free можно будет записать по адресу
heap+0x88
0x21, чтобы получился валидный хедер. Тогда этот чанк можно будет освобождать, и мы получим arbitrary writedef write(what, where): add(1, b'aa') ; delete(1) # set tcache size to 1 delete(0) ; add(0, p64(where)) # 0 is chunk at `heap+0x90` add(1, what) delete(0) ; add(0, p64(0)) # fix tcache
-
Теперь можно перезаписать размер какого-либо чанка на 0x460, освободить его и ликнуть либсу
-
Создать чанк по адресу
libc.sym['environ']-0x10
и записать 0x10 ненулевых байт. При выводе заметки выводится все до первого 0-байта, поэтому так мы ликнем адрес стека. -
free
проверяет, что переданный ему указатель является валидным указателем на чанк. Если это не так, вызоветсяmalloc_printerr
и получится следующая цепочка вызовов:malloc_printerr (const char *str) __libc_message (do_abort, "%s\n", str) __strchrnul
__strchrnul
находится в .got либсы. Мы можем его заранее переписать на что-то другое, например, наputs
. -
puts
вызоветstdout->vtable[7]
. Предполагается, что так вызовется функция_IO_file_xsputn
. Но мы можем переписатьstdout->vtable
так, чтобы вместо_IO_file_xsputn
вызвался_IO_obstack_overflow
. Эта функция восприметstdout
как структуру_IO_obstack_file
, и еслиstdout->obstack->next_free + 1 > stdout->obstack->chunk_limit
, вызовется_obstack_newchunk
._obstack_newchunk
проверит, чтоstdout->obstack->use_extra_arg != 0
, и если это так, вызоветstdout->obstack->chunkfun(stdout->obstack->extra_arg, stdout->obstack->chunk_size)
. При этом в rdx будет лежатьstdout->obstack->alignment_mask
, то есть, переписавstdout->vtable
и переписавstdout+0xe0
(по этому оффсету должен лежать указатель наstruct obstack*
) на фейковую структуру, мы сможем вызватьread
и записать rop на стек.
-
We can add, show and delete note. Note size is always 0x10 bytes. Also after calling
malloc
we check that chunk hasn't been allocated on stack or on binary. If that's true, we read 0x10 bytes, otherwise we callfree
. -
To get libc leak we can free big fake chunk (for example of size 0x460). We can try exploit 2free to create chunk which will overwrite header of another chunk:
+0000 0x55e773b97290 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │........│!.......│ +0010 0x55e773b972a0 61 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │aa......│........│ <- 1st +0020 0x55e773b972b0 00 00 00 00 00 00 00 00 61 04 00 00 00 00 00 00 │........│a.......│ <- 2nd +0030 0x55e773b972c0 61 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │aa......│........│ <- 3rd +0040 0x55e773b972d0 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00 │........│!.......│
But when we will do
malloc
for fake chunk (in this example we want to allocate it at0x55e773b972b0
), first 8 bytes will be 0. So when tcache will try to decode it as address of next chunk, it will get the following:tcachebins 0x20 [ 0]: 0x55e773b97
Because of this if we try to do 2free again, we will get an error at the moment when tcache is empty and we try to get chunk from fastbin
tcachebins 0x20 [ 0]: 0x55e773b97 fastbins 0x20: 0x55e773b972b0 —▸ 0x55e773b972d0 ◂— 0x55e773b972b0
So at first we need to allocate chunk at
heap+0x90
, where tcache keeps pointer to last freed chunk of size 0x20. If we clear tcache and put here0
, we will be able to exploit 2free again.Also we should put 0x21 to
heap+0x88
to get valid chunk header. After that we can callfree
on this chunk, so we can do arbitrary write.def write(what, where): add(1, b'aa') ; delete(1) # set tcache size to 1 delete(0) ; add(0, p64(where)) # 0 is chunk at `heap+0x90` add(1, what) delete(0) ; add(0, p64(0)) # fix tcache
-
Now we can free fake chunk of size 0x460 and get libc leak.
-
To get stack leak we should create chunk at
libc.sym['environ']-0x10
and write some 0x10 bytes. On printing note program write to stdout everything until it gets null byte, so we will get stack leak. -
free
checks that passed argument is a valid pointer. It it's false, it will callmalloc_printerr
and we will get the following call chain:malloc_printerr (const char *str) __libc_message (do_abort, "%s\n", str) __strchrnul
__strchrnul
is a pointer from libc .got . We can rewrite it to something else, for example, toputs
. -
puts
will callstdout->vtable[7]
. Usually this calls_IO_file_xsputn
. But we can overwritestdout->vtable
to call_IO_obstack_overflow
instead of_IO_file_xsputn
. This function will interpretstdout
asstruct _IO_obstack_file
and call_obstack_newchunk
ifstdout->obstack->next_free + 1 > stdout->obstack->chunk_limit
._obstack_newchunk
will callstdout->obstack->chunkfun(stdout->obstack->extra_arg, stdout->obstack->chunk_size)
ifstdout->obstack->use_extra_arg != 0
. Also we will havestdout->obstack->alignment_mask
in rdx, so if we rewritestdout->vtable
and rewritestdout+0xe0
(this is an offset ofstruct obstack*
pointer) to fake struct, we will be able to call read and write rop on stack.