Off-by-one overflow
Explanation on how off-by-one vulnerability on the heap can lead to RCE
off-by-one vulnerability
Programming mistakes that cause off-by-one
Incorrect Bounds Checking.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>
#define SIZE 1024
int main(void) {
char *a = (char*)malloc(SIZE);
if (!a) exit(0);
for (int i = 0; i <= SIZE; i++) {
char c = fgetc(stdin);
if (c == EOF) a[i] = '\0';
else a[i] = c;
}
}
If i reaches SIZE, then the loop continues, but a[SIZE] is out of bounds. The valid index ranges from 0 to SIZE - 1.
String Operations.
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>
#define SIZE 1024
int main(int argc, char **argv) {
if (argc == 2 && strlen(argv[1] > SIZE)) exit(0);
char *chunk1 = malloc(SIZE);
if (!chunk1) exit(0);
if (strlen(argv[1] == SIZE))
strcpy(chunk1, argv[1]);
}
This is caused by the functionality of strlen() because when the function calculates the string length, it does not count \x00, which then causes strcpy() to copy SIZE+1 bytes into the chunk1.
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <string.h>
#define SIZE 1024
int main(int argc, char **argv) {
if (argc == 2 && strlen(argv[1] > SIZE)) exit(0);
char *a = malloc(SIZE);
if (!a) exit(0);
int bytesRead = read(0, a, SIZE);
a[bytesRead] = '\0';
}
Types of off-by-one
There are 2 types of off by one vulnerabilities:
- Arbitrary byte: Allows to overwrite with any value.
- Poison null byte (off-by-null): Allows to overwrite with only null byte (
0x00).
Heap Review
In 64 bit systems, the memory is aligned with 16 bytes ( 8 bytes for 32 bit systems). For some other allocations whose size isn’t aligned with 16 bytes (e.g. 0x38) the last 8 bytes of a chunk will overlap with the prev_size field of the next chunk, and because of this a off-by-one vulnerability can overwrite the size field.
Exploitation
Off-by-one / poison null bytes on the heap can lead to code execution by corrupting heap metadata, particularly the prev_inuse flag and the previous size field, to create overlapping chunks by forcing the malloc() to consolidate memory blocks incorrectly .So that we can change the contents of other chunks to then further use other heap exploitation techniques like tcache poisoning.
General off-by-one attack. (arbitrary byte)
- Allocate 4 chunks.
- Chunk
A,B, andCof any size. - Chunk
Dto prevent consolidation.
- Chunk
- Free chunk
C- It will go to one of the bins based on the size.
- Use chunk
Ato overflow to the size field of chunkB- Modify the size field of chunk
Band make sure the size overlaps with chunkC
- Modify the size field of chunk
- Now chunk
Bwill contain the free chunkCor its metadata. - Free chunk
Band allocate the same size again.- Lets say chunk
Bwas0x20and we modified it to be0x40. -
Now free chunk Band allocate a chunk of size0x40.pass 0x30tomalloc()
- Lets say chunk
- Now the
fd/nextpointer of chunkCcan be modified or viewed.- Can modify the
fd/nextpointer to performtcache poisoningor any other bin attack. - If chunk
Cis in unsorted bin, you can view thefd/bkand getlibcleak.
- Can modify the
General poison null byte.
The poison null byte is used to clear the prev_inuse flag for chunks that are greater than `0x100.
Here is why.
- Lets say that we have a chunk of size
0x20. - when we try to clear the
prev_inuseflag we will overwrite the size field from0x21to0x00. - Now the chunk will have size
0x00which is not a valid size field, and the
Chunk C is our target.
Steps
- Allocate 4 chunks.
- Chunk
A’s size must not be aligned with 16 bytes and must be large enough to containlibcaddresses (unsorted bin). - Chunk
Bis used to trigger the overflow. - Chunk
Cis our target. - Chunk
Dto prevent consolidation.
- Chunk
- Corrupt metadata.
- Free chunk
Afirst to make it a valid free chunk. - Use off-by-null vulnerability in chunk
Bto set a fakeprev_sizeand clear theprev_inuseflag in chunkC. - Fake
prev_sizecombined size ofA+Be.g. Lets say the size of chunkA=0x40and size of chunkB=0x20, then setprev_size=0x60.
- Free chunk
- Force Consolidation.
- Free chunk
C. because theprev_sizefield is set to fake size (A + B) and theprev_inuseflag is not set,libcwill start the consolidation process. - A overlapping chunk will be created: All the chunks will be combined (
A+B+C) into a single large free chunk. - The large chunk now overlaps with chunk
B, which is NOT free (currently in use).
- Free chunk
- Allocate new chunk.
- Lets call it chunk
E. - This chunk will be carved out from the large consolidated free chunk.
- chunk
Eis allocated to adjust thefdandbkpointers. - The size must be the same as chunk
A
- Lets call it chunk
how2heap
- allocate a large padding.
- so that the fake chunk’s addresses lowest 2nd byte is 0x00
- allocate 3 chunks
- chunk 3 to prevent consolidation.
- link chunk 1 into largebin.
- fd_nextsize and bk_nextsize = fd and bk of the fake chunk.
- Allocate new chunk (a) with a little bit smaller size then chunk1, and then a small chunik to prevent cosolidation
- allocate another new chunk (b) with a little bit larger size than chunk1 and a small chunk for colidation.
- free chunk a, b, and then 1.
- allocate a huge chunk to enable sorting. e.g. 0x1000
- now a, b, and 1 will be in largebin.
- allocate new chunk with size 1.
Double free
Requirements
- Use-after-free.
-
Poison null byte.
- Allocate chunk
Aand then free it. - Allocate chunk
Bof different size than chunkAthen free it. - Allocate chunk
Cof the same size as chunkA.
- This will return the same chunk as chunk
A - Use off-by-one to overwrite the
prev_inuseflag of chunkB- Free chunk
B
- Free chunk
- Now you have a double free.
- Allocate chunk
You can now use double free for other attacks like tcache poisoning or fastbin dup.
- Allocate 3 adjacent chunks.
a, b, c- poison null byte at
b
- poison null byte at
- Allocate another chunk to prevent consolidation
- Free
c.bandastays in use.
- Set fake metadata in chunk
a.prev_size= 0size=a + b - 0x10
- use off-by-one metadata
- Set prev_size of
btoa + b - 0x10 - perform off-by-null byte to unset the
prev_inusein chunkb
- Set prev_size of
- free
b- This will force backward consolidation
A + B. - Allocate overlapping chunks with of sizeA + B
- This will force backward consolidation
Resources
- https://devel0pment.de/?p=688
- https://github.com/shinmao/WhyNot-HEAP-Exploitation/blob/master/Off-By-One/README.md
- https://www.youtube.com/watch?v=nNObrLQZlMw