Step 262. Active Directory 2 — Kerberoasting과 AS-REP Roasting

Step 262. Active Directory 2 — Kerberoasting과 AS-REP Roasting

Level 3 — CTF 실전과 공격 스킬 심화 | 난이도 ★★★★☆ | 예상 소요 시간 4시간

전제: Step 261의 도메인 구조(Kerberos 다섯 단계, SPN)를 이해했다. Step 123~124의 오프라인 크래킹(hashcat)을 경험했다.

  • 준비물: THM/HTB의 AD 램(도메인 계정 하나 필요). 이 환경에는 AD 도메인이 없으므로 공격 장면은 전부 출력 예시로 제시합니다 — 해시 형식과 명령의 구조를 익히는 것이 오늘의 실습입니다.
  • ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.

Step 261에서 두 문장을 남겼습니다 — "서비스 티켓은 서비스 계정의 비밀번호로 암호화돼 온다", "도메인 사용자는 누구나 서비스 티켓을 요청할 수 있다". 오늘은 이 두 문장을 맞물려 AD의 대표 공격 Kerberoasting을 조립합니다. 티켓을 정상 경로로 받아 와서, 집에서 조용히 까는 것 — 네트워크에는 아무 이상 징후도 남지 않는다는 점이 이 공격이 무서운 이유입니다. 그리고 사촌 격 공격인 AS-REP Roasting, 그리고 "그래서 어떻게 막는가"까지 오늘 한 세트입니다.


1. 학습 목표

이 챕터를 끝내면 다음을 할 수 있습니다:

  • Kerberoasting이 왜 "정상 기능의 악용"인지 Kerberos 흐름 위에서 설명한다
  • SPN 계정을 열거하고 TGS 티켓을 크래킹용 형식으로 추출하는 절차를 안다
  • $krb5tgs$ 해시 형식의 구조를 읽는다
  • AS-REP Roasting의 전제(사전 인증 해제)와 Kerberoasting과의 차이를 안다
  • hashcat 모드 13100/18200으로 오프라인 크래킹하는 흐름을 설명한다
  • 방어 수단(긴 서비스 비밀번호, gMSA, 사전 인증 강제)을 기법별로 연결한다

2. 배경 지식 — 오늘의 도구와 개념

오늘의 도구 한눈에 보기

구분 내용
언어·환경 Kali(Impacket) + hashcat — 전부 랩 기준 출력 예시
오늘의 도구 GetUserSPNs.py(Impacket), GetNPUsers.py, hashcat -m 13100 / -m 18200
필요한 개념 SPN, TGS 티켓의 암호화 구조, 사전 인증(pre-authentication), 오프라인 크래킹
오늘의 산출물 Kerberoasting/AS-REP 공격 흐름도 1장 + 방어 대응표

2-1. Kerberoasting의 조립 — 두 문장을 맞물리다

Step 261의 Kerberos 그림을 펼치세요. TGS-REP(서비스 티켓 발급) 화살표에 공격 화살표가 얹히는 지점이 보입니다:

  1. 도메인 사용자는 누구나 "저 SPN의 서비스 티켓 주세요"(TGS-REQ)를 할 수 있다 — 정상 기능
  2. KDC는 그 티켓을 서비스 계정의 비밀번호 유래 키로 암호화해 준다 — 정상 기능
  3. 공격자는 받은 티켓을 네트워크 밖으로 가져간다 — 여기서부터 공격
  4. 집에서 비밀번호 후보를 하나씩 대입해 "이 키로 티켓이 풀리나?"를 시험한다 — 오프라인 크래킹(Step 123의 그 방식 그대로)
  5. 풀리는 순간, 그 비밀번호 = 서비스 계정 탈취

범인이 금고를 부수는 게 아니라, 금고 열쇠의 시험용 사본을 정당하게 받아 와서 집에서 열쇠를 맞춰 보는 구조입니다. DC 입장에서는 "티켓을 발급했을 뿐" — 로그에 남는 것도 정상 티켓 요청뿐입니다. 이것이 "오프라인"이라는 단어의 무게입니다.

2-2. 사냥감의 조건 — 어떤 계정이 털리는가

모든 계정이 Kerberoasting에 걸리는 것은 아닙니다. 조건은 세 개:

  • SPN이 붙어 있다 — 티켓 주문 주소가 있어야 주문이 되니까요. 보통 svc_sql 같은 서비스 계정입니다 (Step 261의 setspn -Q */*이 사냥 목록).
  • 사람이 정한 비밀번호를 쓴다 — 크래킹은 사전·규칙 공격이므로 Summer2026! 같은 "사람다운" 비밀번호가 걸립니다. 반대로 25자 이상 무작위 비밀번호는 사실상 안 깨집니다 — 이것이 방어의 핵심이 됩니다.
  • 컴퓨터 계정(이름 끝이 $)은 SPN이 있어도 비밀번호가 시스템 생성 128자 무작위라 사실상 면역 — 사냥 목록에서 제외하고 보면 됩니다.

2-3. AS-REP Roasting — 팔찌 발급 창구의 허점

Kerberos에는 원래 안전장치가 있습니다 — TGT(팔찌)를 주기 전에 "먼저 네 비밀번호로 암호화된 시간표를 풀어 봐라"고 시험하는 사전 인증(pre-authentication)입니다. 그런데 계정 설정에서 이것이 꺼져 있으면(Do not require Kerberos preauthentication 체크 — 레거시 호환용으로 남아 있는 함정), 비밀번호 없이도 "저 계정의 AS-REP 주세요" 요청이 통합니다.

돌아오는 AS-REP 응답에는 그 계정 비밀번호 유래 키로 암호화된 부분이 있습니다 — 역시 가져다 오프라인 크래킹할 수 있습니다. 차이를 정리하면:

  • Kerberoasting: 도메인 계정이 필요(티켓 요청은 인증된 사용자만) / 사냥감 = SPN 붙은 계정 / 크래킹 대상 = TGS 티켓
  • AS-REP Roasting: 도메인 계정이 없어도 됨(사전 인증 꺼진 계정 이름만 알면) / 사냥감 = 사전 인증 해제 계정 / 크래킹 대상 = AS-REP 응답

둘 다 결국 도착지는 같습니다 — "암호문을 가져다 집에서 깐다."

2-4. 해시 형식 읽기 — $krb5tgs$의 해부

크래킹 도구(hashcat)에 먹이려면 티켓을 약속된 문자열 형식으로 변환합니다. 형식의 생김새를 미리 봐 두세요 (구조 예시):

$krb5tgs$23$*alice$CORP.LOCAL$MSSQLSvc/db01.corp.local:1433*$<체크섬16글자>$<암호화된티켓본문수백글자...>

$로 나뉜 칸막이를 읽으면: krb5tgs = Kerberos TGS 티켓 / 23 = 암호화 방식(RC4-HMAC 계열) / alice = 티켓을 요청한 사용자 / CORP.LOCAL = 영역(도메인) / MSSQLSvc/... = SPN / 뒤의 긴 덩어리 = 실제 크래킹 대상인 암호문. 이 한 줄이 "파일로 저장 가능한 티켓"이고, hashcat은 이 형식을 모드 번호로 구분합니다 — TGS 크래킹은 -m 13100, AS-REP은 -m 18200입니다.

2-5. 방어는 왜 작동하는가

오늘의 공격 둘은 모두 크래킹 가능성에 기대므로, 방어도 크래킹을 무산시키는 쪽으로 모입니다:

  • 서비스 계정 비밀번호 25자 이상 무작위 — 사전·규칙 공격이 절대 못 닿는 길이
  • gMSA(그룹 관리 서비스 계정) — 비밀번호를 AD가 128자 무작위로 자동 생성·주기적 교체. 사람이 정하지 않으니 크래킹이 성립하지 않음
  • 사전 인증 강제 — 도메인 전체 계정에서 "Do not require preauthentication" 체크를 감사하고 끄기. AS-REP Roasting의 전제를 없앤다
  • 탐지 — 단시간에 여러 SPN의 티켓을 요청하는 행위는 이상 징후 (이벤트 ID 4769의 급증)

3. 따라 하기

이 환경에는 AD 도메인·Impacket·hashcat이 없으므로 (2026-09-09 실측 확인: hashcat, impacket-GetUserSPNs 모두 미설치), 오늘의 "따라 하기"는 출력 예시를 읽고 손으로 흐름을 재현하는 훈련입니다. 랩에 들어갔을 때 그대로 쓸 수 있도록 명령은 실제 형식으로 적습니다.

3-1. 전제 확인 — 도메인 계정 하나가 시작점

모든 것의 시작은 "도메인 계정 하나"입니다. 피싱이든, 웹 취약점이든, 스프레이 공격(Step 165)이든 — 방법은 달라도 AD 공격의 개막은 같습니다. 랩에서는 보통 corp.local\alice:Password123 같은 계정이 주어집니다.

흐름 확인: Step 261의 그림 위에 오늘의 공격 화살표를 그려 보세요 — "alice(도메인 사용자)가 svc_sql의 SPN으로 TGS-REQ → 티켓 수령 → 네트워크 밖 반출 → 오프라인 크래킹" 네 화살표입니다.

3-2. SPN 사냥 목록 만들기 — 출력 예시

Impacket의 GetUserSPNs.py가 SPN 열거와 티켓 요청을 한 번에 해 줍니다:

GetUserSPNs.py corp.local/alice:Password123 -dc-ip 10.10.10.10

출력 예시:

ServicePrincipalName            Name       MemberOf  PasswordLastSet             LastLogon
------------------------------  ---------  --------  --------------------------  --------------------------
MSSQLSvc/db01.corp.local:1433   svc_sql              2026-01-15 09:12:33         2026-09-01 14:02:11
HTTP/backup.corp.local          svc_backup           2025-11-02 10:41:07         <never>

읽는 법: 이 표가 사냥 목록입니다. 보는 순서 — ① Name: SPN이 붙은 계정 이름(svc_sql, svc_backup). ② PasswordLastSet: 비밀번호를 마지막으로 바꾼 날 — 오래될수록 "사람이 예전에 정한 약한 비밀번호"일 확률이 올라갑니다. 2025년 11월에 멈춘 svc_backup이 유망해 보이지 않나요? ③ MemberOf가 비어 있으면(여기처럼) 특권 그룹이 아니라는 뜻 — 랩에서는 여기에 Domain Admins가 붙은 서비스 계정이 대박 사냥감입니다.

3-3. 티켓 요청과 추출 — 출력 예시

-request 옵션이 "실제로 티켓을 주문해 크래킹 형식으로 출력"까지 해 줍니다:

GetUserSPNs.py corp.local/alice:Password123 -dc-ip 10.10.10.10 -request

출력 예시 (발췌 — 형식의 구조에 주목):

ServicePrincipalName            Name      ...
------------------------------  --------  ...
...
$krb5tgs$23$*alice$CORP.LOCAL$MSSQLSvc/db01.corp.local:1433*$a1b2c3d4e5f60718$9f8e7d6c...(수백 글자)

읽는 법: 2-4에서 해부한 $krb5tgs$ 형식 그대로입니다. 이 긴 줄 전체를 파일로 저장하면 크래킹 재료 완성입니다:

GetUserSPNs.py corp.local/alice:Password123 -dc-ip 10.10.10.10 -request > tgs.txt

여기까지 DC에 남은 흔적은 "정상 티켓 발급" 로그뿐입니다. 공격의 온라인 파트는 이 한 번으로 끝 — 이후는 전부 오프라인입니다.

3-4. 오프라인 크래킹 — 출력 예시

Step 123~124에서 배운 hashcat 그대로입니다. 모드 번호만 새 것:

hashcat -m 13100 tgs.txt /usr/share/wordlists/rockyou.txt

출력 예시:

$krb5tgs$23$*alice$CORP.LOCAL$MSSQLSvc/...:Summer2026!

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 13100 (Kerberos 5, etype 23, TGS-REP)

읽는 법: Status: Cracked와 함께 티켓 뒤에 붙은 Summer2026!이 복구된 비밀번호입니다. 서비스 계정 svc_sql의 비밀번호가 털렸습니다. 이후 전개 — 이 계정으로 DB 서버에 로그인하고, 그 계정의 권한(때로는 도메인 관리자급)으로 다음을 노립니다. 반대로 여기서 Exhausted(사전 소진, 미크랙)가 뜨면 그것도 결과입니다 — "방어가 작동한 것"이 보고서의 한 줄이 됩니다.

3-5. AS-REP Roasting — 출력 예시

사전 인증이 꺼진 계정 사냥입니다. 계정 목록(users.txt)만 있으면 비밀번호 없이 시도합니다:

GetNPUsers.py corp.local/ -dc-ip 10.10.10.10 -no-pass -usersfile users.txt

출력 예시:

$krb5asrep$23$bob@CORP.LOCAL:c4d3e2f1...(수백 글자)

읽는 법: 형식이 $krb5asrep$으로 바뀐 것만 다릅니다 — AS-REP 응답의 암호화 부분입니다. bob 계정이 사전 인증 해제 상태였던 것입니다. 크래킹은 모드만 바꿔서:

hashcat -m 18200 asrep.txt rockyou.txt

흐름 정리: Kerberoasting이 "입장 후 이용권 수집"이라면, AS-REP Roasting은 "입장 전에 팔찌 응답 수집"입니다. 전제 조건(도메인 계정 필요 여부)만 다르고, 마무리(오프라인 크래킹)는 같습니다.

3-6. 방어 대응표 완성하기

공격 네 단계를 방어와 짝지어 종이에 정리해 보세요 — 이 표가 오늘의 산출물의 절반입니다:

공격 단계 방어 수단 작동 원리
SPN 열거 (막기 어려움 — 정상 기능)
티켓 수령 이상 징후 탐지 (4769 급증) 단시간 다수 SPN 요청은 비정상
오프라인 크래킹 25자+ 무작위 비밀번호 / gMSA 크래킹 자체를 무산
(AS-REP) 사전 인증 우회 사전 인증 강제 감사 전제 조건 제거

4. 미션과 연습문제

미션 — Kerberoasting 흐름도와 크래킹 재현

  1. Step 261의 Kerberos 그림에 오늘의 공격 화살표 네 개(TGS-REQ → 수령 → 반출 → 크래킹)를 얹어 "공격 흐름도"를 완성합니다
  2. $krb5tgs$ 형식 문자열(3-3 출력 예시)을 칸별로 해부해 각 부분의 의미를 적습니다
  3. (랩이 있다면) THM/HTB AD 룸에서 GetUserSPNs.py -request로 티켓을 추출하고 hashcat -m 13100으로 크래킹해 Status: Cracked 화면을 증거로 저장합니다
  4. (랩에서) GetNPUsers.py로 사전 인증 해제 계정을 찾아 -m 18200 크래킹을 시도합니다
  5. 크래킹이 안 된 계정이 있었다면, 그 사례로 "방어가 작동했다"는 보고 문장을 한 줄 씁니다

연습문제

문제 1. Kerberoasting에서 DC의 로그에 "공격"이 아니라 "정상 티켓 발급"만 남는 이유를 설명해 보세요.

문제 2. Kerberoasting과 AS-REP Roasting의 차이를 전제 조건·사냥감·크래킹 대상·hashcat 모드의 네 항목으로 비교해 보세요.

문제 3. 서비스 계정 비밀번호를 25자 이상 무작위로 바꾸는 것이 왜 Kerberoasting의 완전한 방어가 되는지, 크래킹의 작동 방식 관점에서 설명해 보세요.

문제 4. gMSA가 "사람이 정한 비밀번호" 계정보다 근본적으로 안전한 이유를 두 가지 말해 보세요.


5. 모범 답안과 완료 기준

미션 모범 답안

흐름도 검증법: Step 261 그림의 TGS-REQ/TGS-REP 구간에 ① alice가 svc_sql의 SPN으로 주문, ② 암호화된 티켓 수령, ③ 파일(tgs.txt)로 반출, ④ hashcat이 rockyou.txt를 대입 — 네 화살표가 얹혔는지 봅니다. ④ 옆에 "오프라인 — DC는 모른다" 주석이 있으면 만점입니다.

형식 해부의 정답: $krb5tgs$ / 23(etype, RC4-HMAC) / 요청 사용자 / 영역 / SPN / 체크섬 / 암호문 본문 — 2-4의 표와 대조합니다.

크래킹 성공의 증거: hashcat 출력의 Status...: Cracked와 복구된 비밀번호가 저장돼 있어야 합니다. 미크랙(Exhausted)도 증거입니다 — 그때는 "사전·규칙이 이 비밀번호 길이·조합에 못 닿음 = 방어 작동"이라는 해석 문장이 붙어야 미션 완료입니다.

연습문제 해답

문제 1 해답. 공격자는 Kerberos의 정상 절차 — 인증된 도메인 사용자가 SPN을 지정해 서비스 티켓을 요청하는 것 — 를 그대로 밟을 뿐입니다. 티켓을 받은 뒤 벌어지는 크래킹은 공격자의 컴퓨터 안에서 일어나므로 DC와 무관합니다. 프로토콜 위반도, 이상 패킷도 없어서 시그니처 탐지가 어렵고, 그래서 탐지는 "행위 패턴"(단시간 다수 SPN 요청)으로 우회해야 합니다.

문제 2 해답. 전제 조건: Kerberoasting은 도메인 계정 필요, AS-REP은 불필요(계정 이름만). 사냥감: SPN 붙은 계정 / 사전 인증 해제된 계정. 크래킹 대상: TGS 티켓 / AS-REP 응답. hashcat 모드: -m 13100 / -m 18200. 공통점은 "암호문을 가져다 오프라인에서 깐다"는 구조입니다.

문제 3 해답. 오프라인 크래킹은 사전 단어와 변형 규칙을 순서대로 대입하는 시도입니다. 성공 여부는 "비밀번호가 그 후보 집합 안에 있는가"에 달렸는데, 25자 이상 무작위 문자열은 어떤 사전·규칙 조합의 도달 범위도 벗어나고, 전수 대입은 조합 수가 천문학적이라 현실 시간 안에 끝나지 않습니다. 티켓을 받아 가도 까지 못하게 만드는 것 — 공격의 최종 단계를 무산시키는 방어입니다.

문제 4 해답. 첫째, 비밀번호가 AD가 생성한 128자 무작위라 크래킹 후보 집합에 원천적으로 존재하지 않습니다. 둘째, AD가 주기적으로 자동 교체하므로 설령 과거 티켓이 유출돼도 유효 기간이 짧고, 사람이 "기억하기 쉬운 값"으로 되돌릴 여지 자체가 없습니다. 사람의 기억 가능성이 곧 취약성이라는 구조를 설계로 제거한 사례입니다.

완료 기준 체크리스트

  • [ ] Kerberoasting의 다섯 단계(요청→수령→반출→크래킹→탈취)를 순서대로 말할 수 있다
  • [ ] $krb5tgs$ 형식의 칸별 의미를 읽을 수 있다
  • [ ] -m 13100-m 18200의 대상 차이를 안다
  • [ ] AS-REP Roasting의 전제(사전 인증 해제)를 설명할 수 있다
  • [ ] PasswordLastSet이 오래된 계정이 유망한 이유를 안다
  • [ ] 방어 4종(긴 비밀번호·gMSA·사전 인증·4769 탐지)을 공격 단계와 연결할 수 있다
  • [ ] 미션: 공격 흐름도 + 형식 해부 + (랩) 크래킹 증거를 완성했다

6. 흔한 실수와 해결

벽 1. GetUserSPNs.py가 인증 오류를 낸다

증상 (출력 예시): KDC_ERR_PREAUTH_FAILED 또는 SessionError: ....

원인: 계정명·비밀번호·도메인 표기가 틀렸습니다. 가장 흔한 것은 도메인 표기 — corp.local/aliceCORP.LOCAL/alice, 그리고 alice가 실제로 다른 도메인 소속인 경우.

해결: 순서대로 점검 — ① 도메인/사용자:비번 표기 ② -dc-ip가 실제 DC의 IP인지 ③ 랩에서 준 계정 문자열을 그대로 다시 베끼기. 인증 오류 단계에서 막히면 열에 아홉은 철자입니다.

벽 2. 티켓은 받았는데 hashcat이 형식을 못 읽는다

증상 (출력 예시): No hashes loaded. 또는 Token length exception.

원인: 파일에 티켓 외의 잡동사니(표 헤더, 줄바꿈 깨짐)가 섞였거나, 한 줄짜리 티켓이 여러 줄로 끊겨 저장됐습니다.

해결: tgs.txt를 열어 $krb5tgs$로 시작하는 한 줄 통째만 남기세요. 티켓 문자열은 수백 글자의 한 줄입니다 — 중간에 개행이 들어가면 죽은 해시입니다. 모드 번호(-m 13100)가 형식($krb5tgs$)과 맞는지도 함께 확인하세요 — AS-REP을 13100으로 돌리면 같은 증상이 납니다.

벽 3. 크래킹이 영원히 안 끝난다

증상: hashcat이 몇 시간째 Running입니다.

원인 후보: ① 사전이 작거나 규칙이 없음 — 반대로 ② 비밀번호가 정말 강해서 안 깨짐.

해결: 랩의 비밀번호는 대부분 rockyou.txt 안에 있습니다 — 먼저 사전과 모드가 맞는지 확인하세요. 그래도 안 되면 그것이 결론입니다 — 2-5에서 배운 대로 "안 깨지는 비밀번호"는 방어가 작동한 것이고, Exhausted도 보고서의 유효한 결과입니다. 무한정 돌리지 말고 다른 경로(다른 계정, 다른 기법)로 이동하는 판단도 실력입니다.

벽 4. SPN이 하나도 안 나온다

증상: GetUserSPNs.py가 빈 표를 반환합니다.

원인 후보: ① 그 도메인에 서비스 계정이 정말 없음(작은 랩에서는 있음) ② -request 없이 목록 조회 단계에서 무언가 잘못됨.

해결: 다른 각도에서 교차 확인하세요 — 랩 머신 안에서 setspn -Q */*(Step 261)로 같은 목록이 나오는지. 둘 다 비어 있으면 이 경로는 무효로 기록하고, AS-REP Roasting(사전 인증 해제 계정)이나 다른 챕터의 경로로 이동합니다. "없음"의 확인도 열거의 결과입니다.

벽 5. 공격은 됐는데 "왜 됐는지" 설명을 못 한다

증상: 명령은 돌았고 비밀번호도 나왔는데, 원리를 물으면 막힙니다.

원인: 명령을 따라 쳤을 뿐 Kerberos 그림 위에 얹지 않은 것입니다.

해결: Step 261의 다섯 화살표 그림을 다시 꺼내서, 여러분이 방금 한 일이 어느 화살표를 악용한 것인지 손가락으로 짚어 보세요 — TGS-REP입니다. "정상 발급 화살표를 정상으로 받아 와서 오프라인에서 깠다"는 한 문장이 나오면 설명이 된 것입니다. 도구가 하는 일을 그림 위에서 지목할 수 없으면 아직 그 공격을 안다고 할 수 없습니다.


7. 정리

오늘의 개념

개념 한 줄 설명
Kerberoasting SPN 계정의 TGS 티켓을 받아 오프라인 크래킹 — 정상 기능의 악용
오프라인 크래킹 암호문을 네트워크 밖으로 가져가 깨는 것 — 피해자가 모름
AS-REP Roasting 사전 인증 꺼진 계정의 AS-REP 응답 크래킹 — 계정 없이 가능
$krb5tgs$ / $krb5asrep$ 크래킹용 티켓 문자열 형식 — 모드 13100 / 18200
gMSA AD 관리 128자 자동 비밀번호 — 크래킹의 구조적 무력화
이벤트 4769 티켓 발급 로그 — 단시간 급증이 탐지 신호

오늘의 명령어 (전부 출력 예시)

명령 하는 일
GetUserSPNs.py 도메인/사용자:비번 -dc-ip DC_IP SPN 사냥 목록
... -request > tgs.txt 티켓 주문 + 크래킹 형식 저장
hashcat -m 13100 tgs.txt rockyou.txt TGS 티켓 크래킹
GetNPUsers.py 도메인/ -dc-ip DC_IP -no-pass -usersfile users.txt 사전 인증 해제 계정 수확
hashcat -m 18200 asrep.txt rockyou.txt AS-REP 크래킹

명령어보다 중요한 감각

오늘 공격의 본질은 기술이 아니라 자리입니다 — 암호문이 "공격자가 가져갈 수 있는 곳"에 있다는 것. Kerberos는 훌륭한 설계지만, "티켓을 받은 자는 그것을 집에 가져갈 수 있다"는 성질까지는 막지 못합니다. 그래서 방어의 결론이 "요청을 막기"가 아니라 "까도 소용없게 만들기"(25자 무작위, gMSA)로 모이는 것입니다. 공격을 배우면 방어의 설계 근거가 보이고, 방어를 보면 공격의 한계가 보입니다 — 이 양방향으로 읽는 습관이 이 과 전체의 복근입니다.


전부 체크되면 Step 262 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.