---
title: 'CTF 리버스 엔지니어링: DownUnderCTF 플래그 검사기 — REA'
url: https://rea.tools/ko/showcase/ctf/
---

[홈](https://rea.tools/ko/) / [사례 소개](https://rea.tools/ko/showcase/) / CTF

사례 연구 · DownUnderCTF 2023

# CTF 바이너리에서 플래그를 찾으세요

REA로 작은 플래그 검사기를 분석하고 규칙을 방정식으로 바꿔, 원본 프로그램이 `Correct!`을 출력하는 입력을 찾아봅니다.

masked squares 플래그 검사기 · 쉬움 · Linux x86-64

[원본 문제 보기](https://github.com/DownUnderCTF/Challenges_2023_Public/tree/b48f83db2984bc7fa2ec7879a32d7efb1be8d333/rev/masked-squares-flag-checker)

바이너리에서 검증된 답까지

1. 01 · 검사 코드 찾기

   **문자열 → 검사 함수**

   REA는 프롬프트와 그 참조, 입력을 허용하거나 거부하는 코드를 찾습니다.
2. 02 · 규칙 추출

   **문자 36개 · 합 26개**

   REA는 각 검사에 쓰이는 명령어와 데이터를 반환합니다. 에이전트는 이를 방정식으로 바꿉니다.
3. 03 · 풀이와 실행

   **복원한 플래그 → Correct!**

   Python 스크립트가 방정식을 풉니다. REA는 원본 프로그램이 결과를 받아들이는 모습을 캡처합니다.

joseph가 만든 문제이며 DownUnderCTF 공식 저장소에 공개돼 있습니다.

## 검사 코드 찾기

제공 파일은 15 KB 실행 파일입니다. 플래그를 입력받고 `Correct!` 또는 `Incorrect!`을 출력합니다. 에이전트에게 간단한 요청을 해보세요.

코딩 에이전트

REA로 ms_flag_checker를 분석하고 플래그를 찾으세요. 검사 동작을 설명한 뒤 원래 프로그램으로 답을 검증하세요.

문제 파일을 로컬에 다운로드하고 네이티브 분석 도구를 연결한 상태에서 사용하는 예제 프롬프트입니다.

1. #### 입력 프롬프트에서 해당 함수로 추적

   에이전트 → REA

   ```
   open_binary
   {"path": "ms_flag_checker"}

   search_strings
   {"pattern": "flag|correct|wrong|input",
    "mode": "regex", "case_sensitive": false}

   xrefs
   {"address": "0x102004"}

   resolve_containing_procedure
   {"address": "0x10126d"}
   ```

   REA → 에이전트 · 결과 발췌

   ```
   0x102004  "What is the flag? "
   0x10201c  "Incorrect!"
   0x102027  "Correct!"

   Prompt referenced at: 0x10126d
   Containing function: 0x10124d
   ```

   원래 함수 이름이 제거된 실행 파일에서도 이 참조를 통해 조사를 시작할 위치를 찾을 수 있습니다.
2. #### 검사기와 보조 함수 두 개 읽기

   에이전트 → REA

   ```
   analyze_function
   {"procedure": "0x10124d"}

   analyze_function
   {"procedure": "0x101189"}

   analyze_function
   {"procedure": "0x101217"}
   ```

   REA → 에이전트 · 선택한 명령어

   ```
   0x1012c1: CMP EDI, 0x24
   0x1012c6: LEA RBX, [0x1040e0]
   0x1012cd: LEA RBP, [0x104060]
   0x1012d4: LEA R13, [RBX + 0x270]
   0x1012e1: CALL 0x00101189
   0x1012ec: CALL 0x00101217
   0x1012f1: CMP dword ptr [RBP], EAX
   0x1012f4: JNZ 0x00101335
   0x1012f6: ADD RBX, 0x18
   ```

   첫 루프는 문자 코드 36개를 복사합니다. 검사 루프는 마스크를 해석해 선택한 코드를 더하고 저장된 수와 비교합니다. `0x270`바이트 범위를 `0x18`바이트씩 이동하므로 마스크는 26개입니다.

새로 수행한 REA 분석에서 일부 결과를 골랐습니다. 로컬 대상 경로는 표시용으로 줄였고, 주소는 REA가 가져온 이미지를 기준으로 합니다.

## 하나의 검사로 문자 하나를 알아냅니다

검사기는 입력을 6×6 격자로 배치합니다. 마스크는 합산할 칸을 선택합니다. 압축된 인코딩에서 음수는 칸 건너뛰기, 양수는 칸 선택을 뜻합니다.

![일곱 번째 마스크는 21칸을 건너뛰고 위치 21을 선택한 뒤 14칸을 건너뜁니다. 이 칸만 합계에 포함됩니다. 필요한 문자 코드는 55이므로 문자는 7입니다.](https://rea.tools/assets/localized/ko/figures/ctf-mask-equation.svg)

일곱 번째 검사는 한 위치만 선택합니다. [그림 열기](https://rea.tools/assets/localized/ko/figures/ctf-mask-equation.svg).

### 바이너리에서 마스크와 목표값 읽기

에이전트 → REA · read_bytes

```
{"address": "0x1040e0", "length": 624}
{"address": "0x104060", "length": 144}
```

REA → 에이전트 · 일곱 번째 항목

```
Mask at 0x104170:
eb 01 f2 00  →  -21, +1, -14, stop

Target at 0x104078:
37 00 00 00  →  55
```

명령어 `MOVSX`은 마스크 바이트를 부호 있는 값으로 읽습니다. 그래서 `eb`은 −21을 뜻합니다. 목표값은 리틀 엔디언 정수입니다. 이 바이트들로 `code[21] = 55`임을 알 수 있으므로 해당 문자는 `7`입니다.

## 명령어에서 읽기 쉬운 검사 코드로

단계를 선택해 합산 보조 함수와 호출 함수가 C 코드의 같은 규칙에 어떻게 대응하는지 살펴보세요.

REA · 원본 명령어 발췌

```
0x10121c: MOV ECX, 0x0

0x10122c: CMP dword ptr [RSI + RAX*0x1], 0x0
0x101230: JZ 0x00101223

0x101232: ADD ECX, dword ptr [RDI + RAX*0x1]

0x101223: ADD RAX, 0x4

0x1012f1: CMP dword ptr [RBP], EAX
0x1012f4: JNZ 0x00101335
```

읽기 쉬운 C 코드 · 검사 하나의 요약

```
int check_one_mask(const int codes[36],
                   const int mask[36],
                   int target) {
    int total = 0;
    for (int i = 0; i < 36; ++i) {
        if (mask[i]) {
            total += codes[i];
        }
    }
    return total == target;
}
```

**01 · 0에서 시작합니다.** `ECX`에는 누적 합이 저장됩니다.

**02 · 마스크 셀을 확인합니다.** 0 값은 문자를 건너뛰고 0 이 아닌 값은 문자를 포함합니다.

**03 · 문자 코드를 추가합니다.** 같은 오프셋에 있는 입력의 정수가 합에 더해집니다.

**04 · 다음 셀로 이동합니다.** 원래 루프는 정수당 4 바이트를 앞당기고 6 개의 셀의 6 개의 행을 방문합니다.

**05 · 저장된 목표값과 비교합니다.** 호출 함수는 `EAX`로 합을 전달받습니다. 값이 일치하지 않으면 `Incorrect!`로 분기합니다.

명령어는 합산 보조 함수와 호출 함수에서 가져왔습니다. C 코드는 설명용 요약으로, 의미 있는 이름과 36개 칸을 한 번에 순회하는 루프를 사용했습니다.

## 방정식 26개 풀기

대부분의 마스크는 여러 칸을 선택합니다. 첫 검사는 문자 코드 16개를 더해 합이 1441인지 확인합니다. 선택 영역들이 겹치므로 작은 Python 스크립트로 함께 풀 수 있는 연립방정식이 됩니다.

파이썬 · 솔버 발췌

```
from z3 import Int, Or, Solver, Sum, sat

codes = [Int(f"code_{i}") for i in range(36)]
solver = Solver()
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}"
for code in codes:
    solver.add(Or(*[code == ord(c) for c in alphabet]))
for code, char in zip(codes, "DUCTF{"):
    solver.add(code == ord(char))
solver.add(codes[-1] == ord("}"))

for selected, target in checks:
    solver.add(Sum([codes[i] for i in selected]) == target)

assert solver.check() == sat
model = solver.model()
print("".join(chr(model.eval(code).as_long()) for code in codes))
```

Z3는 이 방정식들을 만족하는 값을 찾는 솔버입니다. 발췌 코드는 추출한 검사를 사용하며, 영문자·숫자·밑줄·중괄호로 이루어진 `DUCTF{…}` 플래그를 가정합니다. 전체 다운로드에는 마스크 26개와 목표값이 모두 들어 있습니다.

[전체 풀이 코드 다운로드](https://rea.tools/showcase/ctf/solve.py)

## 원본 프로그램으로 정답 검증

REA의 프로세스 캡처로 원본 실행 파일이 구한 플래그를 받아들이는 것을 기록했습니다. 선택된 문자 하나를 `z`에서 `y`로 바꾸면 첫 합계가 1441에서 1440이 되어 프로그램이 거부합니다.

| 입력 | 관찰된 출력 | 종료 코드 |
| --- | --- | --- |
| 솔버가 구한 플래그 | `Correct!` | 0 |
| 한 문자 변경 | `Incorrect!` | 255 |

복구된 플래그 및 캡처된 출력 표시

REA · 원본 프로그램 출력

```
What is the flag? DUCTF{ezzzpzzz_07bcda7bfe81faf43caa}
Correct!
```

복구한 값은 주최 측이 공개한 문제 메타데이터의 플래그와도 일치합니다. 이 대조는 바이너리를 푼 뒤에 수행했습니다.


출처, 대상 식별 정보, 분석 세부 사항

2026년 10월 8일 Linux x64에서 REA 4.1.0과 Ghidra 12.1.4를 사용해 새로 분석하고 프로세스 실행을 두 차례 캡처했습니다. 바이너리는 심볼 정보가 제거된 x86-64 ELF이며, 크기는 15,248바이트입니다.

원본 실행 파일 · SHA-256

```
dcd3bec4f608e11f3a12ce461100aaf5f621851ee63a1f870d7c90d4e3dd51ae
```

주최 측의 소스나 풀이를 읽기 전에 문자열 참조, 함수, 상수를 분석했습니다. Python 풀이 코드와 그림은 이 REA 결과로 만들었습니다.

[주최 측 소스와 비교](https://github.com/DownUnderCTF/Challenges_2023_Public/blob/b48f83db2984bc7fa2ec7879a32d7efb1be8d333/rev/masked-squares-flag-checker/src/ms_flag_checker.c) · [문제 메타데이터와 플래그](https://github.com/DownUnderCTF/Challenges_2023_Public/blob/b48f83db2984bc7fa2ec7879a32d7efb1be8d333/rev/masked-squares-flag-checker/ctfcli.yaml) · [REA 증거 기록](https://github.com/morluto/rea/blob/main/website/evidence/ctf-masked-squares.md)

## 직접 해보기

원본 문제와 풀이 코드를 한 폴더에 다운로드하세요. 위 프롬프트로 에이전트와 함께 조사하거나, 제공된 스크립트를 실행하세요.

[원본 문제 다운로드](https://raw.githubusercontent.com/DownUnderCTF/Challenges_2023_Public/b48f83db2984bc7fa2ec7879a32d7efb1be8d333/rev/masked-squares-flag-checker/publish/ms_flag_checker)[풀이 코드 다운로드](https://rea.tools/showcase/ctf/solve.py)

터미널 · 해결 및 실행

```
python3 -m venv .venv
.venv/bin/python -m pip install z3-solver
.venv/bin/python solve.py

chmod +x ms_flag_checker
./ms_flag_checker
```

스크립트가 출력하는 후보 플래그를 프로그램 프롬프트에 붙여넣으세요. 풀이에는 Python과 `z3-solver`가 필요합니다. 원본 실행 파일은 Linux x86-64에서 실행해야 합니다.

[코딩 에이전트와 REA 설정하기](https://rea.tools/get-started/)
