Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
This pwn exists off-by-one:
When pwner wants to get a new chunk, the program will malloc() two same size chunk. The pwner’s input will be put into the first chunk, and then the program uses strcpy() without input size to do the memory copy. The first chunk will be free() very soon.
if ( size > 0x7F && size <= 0x100 ) { fake_new_chunk = malloc(size); new_chunk = malloc(size); memset(new_chunk, 0, size); memset(fake_new_chunk, 0, size); puts("input your data"); read(0, fake_new_chunk, (unsignedint)size); strcpy((char *)new_chunk, (constchar *)fake_new_chunk); ++total; for ( i = 0; i < total; ++i ) { if ( !heap_form[i] ) { heap_form[i] = (char *)new_chunk; break; } } if ( i == total ) heap_form[i] = (char *)new_chunk; free(fake_new_chunk); }
If pwner doesn’t enter \x00 to end the string, the next chunk’s size will be regarded as a part of the string. Here exists off-by-one.
We first malloc() 5 chunks, chunk 1-4 will reuse the first chunk’s fake_new_chunk to do the string copy. Chunk 1 will be used to get shell by system().
When we free() the chunk 4, it will merge the fake chunk 3 of size 0x80 instead of 0x90. The unlink attack detail:
1 2 3 4 5
FD = 0x6020c0; BK = 0x6020c8; // will pass the security check FD->bk = BK; // *(0x6020d8) = 0x6020c8 BK->fd = FD; // *(0x6020d8) = 0x6020c0
After unlink, the chunk_list[3] will point to the chunk_list[0]’s address and we will have the ability to write and read arbitrarily. Then leak the libc address and get the shell by overwrite the free@got.