[🥉 Bronze 3] base64 안에 숨겨진 ASCII 배열 — DreamHack 64se64 풀이

2026-05-26·1분 읽기·

[🥉 Bronze 3] base64 안에 숨겨진 ASCII 배열 — DreamHack 64se64 풀이

zip 안 index.html의 hidden input에 박힌 base64 문자열을 디코딩하면 ASCII 코드 배열을 chr()로 합치는 파이썬 스크립트가 나온다. 그대로 실행하면 플래그가 출력된다.

문제: DreamHack — 64se64

분류: Misc (정적 분석 / 인코딩)

난이도: 🥉 Bronze 3

FLAG: DH{be046b7522aa2e28f276e0c910521f23a0587005a8377370a11e5e4dc15fb2ab}


문제 개요

항목내용
문제명64se64
난이도🥉 Bronze 3
분류Misc
제공 파일index.html (zip 내부)
서버host8.dreamhack.games:21995 (정적 HTML)
핵심 기법base64 디코딩 → ASCII 코드 → 문자열 변환

문제명 64se64는 곧 "base64 in base64" 의 말장난이다.

제공된 zip에는 단 한 개의 HTML 파일이 들어 있고, 그 안 hidden form input에 박힌 긴 base64 문자열이 곧 플래그를 출력하는 파이썬 스크립트다.

서버에 접속할 필요도 없이 정적 분석만으로 끝나는 입문용 문제.



🔬 분석

Step 1 — zip 해제 & 파일 확인

먼저 다운로드한 zip을 풀고 어떤 파일이 들어있는지 본다.

$ unzip 62e3ec4c-1259-4d31-8620-75a83fd9bd98.zip
  inflating: index.html
 
$ file index.html
index.html: HTML document, ASCII text

서버 측 동작이 없는 순수 정적 HTML 한 장짜리 문제다.

Step 2 — index.html 분석

파일을 열어 보면 친근한 인사말과 함께 form 하나가 있는데, 사용자가 채울 입력 필드는 없고 type="hidden" 으로 박힌 긴 문자열이 보인다.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Welcome</title>
</head>
 
<body>
  <h1>Welcome! 👋</h1>
  <form method="POST">
    <input type="hidden" name="64se64_encoding"
           value="IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwphc2M9WzY4LCA3MiwgMTIzLCA..." >
  </form>
</body>
</html>

값 끝이 = 로 끝나면서 A-Z a-z 0-9 + / 만 사용된 전형적인 base64 인코딩 문자열이다.

name까지 친절하게 64se64_encoding 이라고 알려주고 있으니 그대로 디코딩만 해주면 된다.



💣 핵심 기법

Layer 1 — base64 → Python 스크립트

base64 문자열을 그대로 base64 -d 해보면 파이썬 소스가 튀어나온다.

#!/usr/bin/env python3
asc = [68, 72, 123, 98, 101, 48, 52, 54, 98, 55, 53, 50, 50, 97, 97, 50,
       101, 50, 56, 102, 50, 55, 54, 101, 48, 99, 57, 49, 48, 53, 50, 49,
       102, 50, 51, 97, 48, 53, 56, 55, 48, 48, 53, 97, 56, 51, 55, 55,
       51, 55, 48, 97, 49, 49, 101, 53, 101, 52, 100, 99, 49, 53, 102,
       98, 50, 97, 98, 125]
arr = [0 for i in range(68)]
for i in range(0, 68):
    arr[i] = chr(asc[i])
flag = ''.join(arr)
print(flag)

Layer 2 — ASCII 코드 배열 → 문자열

asc 리스트는 길이 68의 ASCII 코드값 배열이다. 첫 세 값만 미리 풀어 봐도 이미 답이 보인다.

indexcodechr
068D
172H
2123{
.........
67125}

D, H, { 로 시작하고 } 로 끝나니, 그대로 합치면 DH{...} 포맷의 플래그가 나온다는 것이 보장된다.


🎯 풀이 / Payload 설계

두 단계가 전부다.

  1. hidden input 의 value 값을 base64 디코드해서 파이썬 스크립트를 얻는다.
  2. 그 스크립트를 그대로 실행 — 또는 ASCII 배열만 직접 chr() 로 합쳐도 동일하다.

스크립트 자체가 곧 풀이라서 별도 역산은 필요 없다.


🚀 Full Exploit

solve.py — base64 디코드 후 그대로 exec() 해서 플래그를 뽑는다.

#!/usr/bin/env python3
"""
64se64 — DreamHack Misc Bronze 3
풀이: hidden input의 base64를 디코드하면 Python 스크립트가 나오고,
그 안의 ASCII 코드 배열을 chr()로 변환하면 플래그가 나온다.
"""
import base64
 
b64 = ("IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwphc2M9WzY4LCA3MiwgMTIzLCA5OCwgMTAx"
       "LCA0OCwgNTIsIDU0LCA5OCwgNTUsIDUzLCA1MCwgNTAsIDk3LCA5NywgNTAsIDEw"
       "MSwgNTAsIDU2LCAxMDIsIDUwLCA1NSwgNTQsIDEwMSwgNDgsIDk5LCA1NywgNDks"
       "IDQ4LCA1MywgNTAsIDQ5LCAxMDIsIDUwLCA1MSwgOTcsIDQ4LCA1MywgNTYsIDU3"
       "LCA0OCwgNDgsIDUzLCA5NywgNTYsIDUxLCA1NSwgNTUsIDUxLCA1NSwgNDgsIDk3"
       "LCA0OSwgNDksIDEwMSwgNTMsIDEwMSwgNTIsIDEwMCwgOTksIDQ5LCA1MywgMTAy"
       "LCA5OCwgNTAsIDk3LCA5OCwgMTI1XQphcnI9WzAgZm9yIGkgaW4gcmFuZ2UoNjgp"
       "XQpmb3IgaSBpbiByYW5nZSgwLDY4KToKICAgIGFycltpXT1jaHIoYXNjW2ldKQpm"
       "bGFnPScnLmpvaW4oYXJyKQpwcmludChmbGFnKQ==")
 
script = base64.b64decode(b64).decode()
print("[+] Decoded Python script:")
print(script)
print("[+] Executing...")
exec(script)

실행 결과는 다음과 같다.

$ python3 solve.py
[+] Decoded Python script:
#!/usr/bin/env python3
asc=[68, 72, 123, 98, 101, 48, 52, 54, ...]
arr=[0 for i in range(68)]
for i in range(0,68):
    arr[i]=chr(asc[i])
flag=''.join(arr)
print(flag)
[+] Executing...
DH{be046b7522aa2e28f276e0c910521f23a0587005a8377370a11e5e4dc15fb2ab}
solve.py 실행 결과 — 디코드된 스크립트와 최종 플래그
solve.py 실행 결과 — 디코드된 스크립트와 최종 플래그


📝 결론

문제명에 답이 있다.

64se64 는 곧 base64 안의 base64, 그러니까 한 꺼풀만 더 벗기면 끝이라는 힌트다. 입문자가 base64 인코딩이 무엇인지 직관적으로 체감하게 만든 좋은 문제 설계.

진짜 인코딩은 두 번이 아니라 한 번이었다.

겉보기는 "base64 안에 base64"라는 이름값을 하지만, 실제로는 base64 한 번 + ASCII 코드 변환 한 번이다. ASCII 배열을 그대로 본문에 박아둔 것도 일종의 "사람이 알아보기 어려운 인코딩"인 셈.

이 글이 도움이 됐나요?

Comments

댓글

0개

댓글을 남기려면 로그인이 필요해요. (네이버 · 구글 계정)

댓글 불러오는 중…

Related

관련 글

3개
[🥉 Bronze 1] 31줄 발췌본이 base64 인코더라는 걸 증명하기 — DreamHack [CodeEngn] Malware L05 풀이
blog

[🥉 Bronze 1] 31줄 발췌본이 base64 인코더라는 걸 증명하기 — DreamHack [CodeEngn] Malware L05 풀이

악성코드 소스 31줄만 주고 "무엇을 하는 함수인가"를 묻는다. 표준 알파벳 64자와 6비트 마스크가 보이니 base64 같은데, 눈으로 찍는 대신 가려진 두 줄을 채워 컴파일하고 입력 304종을 표준과 바이트 단위로 맞춰 봤다. 덤으로 이 구현이 3의 배수 아닌 길이에서 터지고 76열 규격도 어긴다는 것까지 실측했다.
#dreamhack#ctf#misc+6
2026-08-19#dreamhack +5
[🥉 Bronze 2] 뒤집힌 base64 속에 숨은 JSFuck — DreamHack reverse_decoding 풀이
blog

[🥉 Bronze 2] 뒤집힌 base64 속에 숨은 JSFuck — DreamHack reverse_decoding 풀이

이메일로 받은 JPG가 열리지 않는다. 앞부분의 널 바이트를 걷어 내면 파일이 통째로 뒤집혀 있고, 머리에 붙은 base64는 padding이 앞에 온 역순 문자열이다. 뒤집어 디코드하면 JSFuck이 나오고, 이를 실행하는 대신 eval을 가로채면 플래그가 그대로 드러난다.
#dreamhack#ctf#misc+3
2026-07-15#dreamhack +4
[🥈 Bronze 2] 함수 주소일까, 호출 지점일까 — DreamHack [CodeEngn] Malware L08 풀이
blog

[🥈 Bronze 2] 함수 주소일까, 호출 지점일까 — DreamHack [CodeEngn] Malware L08 풀이

다른 악성코드를 실행해 추가 감염을 일으키는 함수의 주소를 찾으라는 문제. 그래프 전체가 ShellExecuteExA로 대상 파일을 실행하는 하나의 함수인데, 그 함수 자체의 시작 주소를 냈다가 오답을 받았다. 함수 안에 ShellExecuteExA 호출이 두 곳이라는 걸 알고도 어느 쪽인지 헷갈렸는데, 결국 "함수의 주소"가 가리키는 건 실행 API를 호출하는 그 지점이었다.
#dreamhack#ctf#reversing+5
2026-07-19#dreamhack +4