GOAD(Game of Active Directory) Lab 구축 - MiniLab
GOAD(Game of Active Directory) Lab 구축 - MiniLab
GOAD 미니랩을 노트북 한 대에 올리는 과정을 명령 단위로 정리한 문서다. 실제로 친 명령과 그 결과를 확인하는 절차를 순서대로 담았다. 환경은 AMD Ryzen 5 5600GT(6코어/12스레드), RAM 23.4 GB, Windows 11 Pro(build 26200), C 드라이브 여유 약 272 GB. 목표는 의도적으로 취약하게 구성한 Active Directory 실습 랩(GOAD MINILAB)을 이 노트북에 만들고, 따로 떨어진 Kali 데스크탑에서 Tailscale로 접속해 공격을 연습하는 것이다. (환경 구축 후 AI를 통해 정리된 글입니다.)
목차
- GOAD(Game of Active Directory) Lab 구축 - MiniLab
- 목차
- 1. 전체 그림 — 무엇을 만드는가
- 2. 최종 아키텍처 도식
- 3. 핵심 설계 개념 — 왜 WSL과 Windows로 역할을 나누는가
- Phase 0 — 환경 점검
- Phase 1 — WSL 의존성 준비
- Phase 2 — GOAD 저장소 클론
- Phase 3 — Vagrant + Vagrant VMware Utility 설치 (VMware 26 함정 2개 해결)
- Phase 4 — Vagrant 플러그인 설치
- Phase 5 — GOAD check 통과시키기
- Phase 6 — 랩 빌드 + 네트워크(VMnet2) 문제 해결
- Phase 7 — Tailscale subnet router 구성
- Phase 8 — 최종 도달성 검증
- Phase 9 — 재부팅 자동 복구 (예약 작업)
- 부록 A — VMware 26 호환성 문제 요약
- 부록 B — 명령어 용어집
- 부록 C — 처음부터 다시 할 때의 압축 체크리스트
1. 전체 그림 — 무엇을 만드는가
| 구성요소 | 값 |
|---|---|
| 랩 종류 | GOAD MINILAB (가장 가벼운 구성) |
| VM 1 | DC01 — Windows Server 2019, 도메인 컨트롤러, 192.168.56.30, 2 vCPU / 4 GB |
| VM 2 | WS01 — Windows 10 워크스테이션, 192.168.56.31, 2 vCPU / 4 GB |
| 도메인 | FQDN mini.lab (NetBIOS MINILAB) |
| 랩 네트워크 | 192.168.56.0/24 (VMware host-only, VMnet2) |
| 박스 출처 | mayfly/windows_server2019, mayfly/windows10 (Vagrant Cloud 사전빌드본 다운로드, packer 빌드 불필요) |
| 하이퍼바이저 | VMware Workstation 26.0.0 build-25388281 (2026 신버전, 64비트 전용 레이아웃) |
| 빌드 도구 | Vagrant 2.4.9 (Windows) + vagrant-vmware-utility 1.0.24 + WSL2 Ubuntu 24.04의 ansible-core 2.18 |
| 공격 머신 | Kali 데스크탑 (물리적으로 분리) — Tailscale로 접속 |
| 의도된 취약점(예) | MINILAB\alice / F0llOwTheWh1teR@bit 자격증명, Credential Manager 평문, 1분 주기 봇 스케줄 등 |
인스턴스 폴더명
2ec88e-minilab-vmware와 vmx의 UUID 경로(0b4a7020-...,1584ec8a-...)는 빌드할 때마다 새로 생성된다. 환경마다 값이 다르므로vmrun list나ls /mnt/c/GOAD/workspace로 실제 값을 그때그때 확인한다.
2. 최종 아키텍처 도식
┌──────────────────────────────────────────────────────────────────────────────┐
│ Kali 데스크탑 (공격 머신, 물리적으로 떨어진 별도 PC) │
│ tailnet 이름: kali-linux (100.113.199.118) │
│ $ sudo tailscale up --accept-routes ← 광고된 서브넷 라우트 수락 │
└───────────────────────────────┬──────────────────────────────────────────────┘
│ WireGuard 암호화 터널
│ 목적지 192.168.56.0/24
╔══════╩═══════╗
║ Tailnet ║ (코디네이션 서버가 '승인된'
║ (오버레이) ║ subnet route를 피어들에게 광고)
╚══════╦═══════╝
│
┌────────────────────────────────┼─────────────────────────────────────────────┐
│ 이 노트북 tailnet 이름: WinLabs (100.109.121.34) = Subnet Router │
│ Ryzen 5 5600GT / 23.4GB / Win11 Pro │
│ │ │
│ ┌────────────────────────┴────────────┐ │
│ │ Tailscale 어댑터 (ifIndex 6) │ Forwarding: Enabled │
│ └────────────────────────┬────────────┘ │
│ │ IP 포워딩(IPEnableRouter=1) + SNAT→.56.1 │
│ ┌────────────────────────┴────────────┐ │
│ │ VMware Network Adapter VMnet2 (idx39)│ ← 부팅 시 예약작업 │
│ │ 192.168.56.1/24 (host-only 게이트웨이)│ GOAD-VMnet2-IP 가 자동 보정 │
│ └──────┬───────────────────────┬───────┘ │
│ 192.168.56.0/24 (랩 내부망) │ │
│ ┌──────┴───────┐ ┌──────┴───────┐ ← VMware Workstation 26 │
│ │ DC01 │ │ WS01 │ (vmware-vmx 프로세스) │
│ │ WinServer2019 │ │ Windows 10 │ │
│ │ .56.30 (DC) │ │ .56.31 │ │
│ │ 도메인 MINILAB │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ NIC2: NAT(vmnet8 192.168.236.x) → 인터넷 │
│ └───────────┬────────────┘ │
│ ┌─────┴────┐ │
│ │ VMnet8 │ NAT → 외부 인터넷 (박스/업데이트 다운로드) │
│ └──────────┘ │
│ │
│ ┌────────────────────────── 빌드/관리 평면 (랩 트래픽과 분리) ───────────┐ │
│ │ Windows 네이티브 WSL2 (Ubuntu 24.04) │ │
│ │ • Vagrant 2.4.9 ───────(호출)──────► • goad.py (오케스트레이션) │ │
│ │ • vagrant-vmware-desktop 플러그인 └ vagrant.exe 심볼릭링크로 호출│ │
│ │ • VagrantVMware 유틸리티 서비스 • ansible-core 2.18 (프로비전)│ │
│ │ (127.0.0.1:9922 + vmrest) • venv: ~/.goad/.venv │ │
│ │ • 레지스트리 32비트 미러링 (호환 우회) • 작업트리: /mnt/c/GOAD (공유)│ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
데이터 흐름 요약
- Kali가
192.168.56.30(DC01)로 접속을 시도하면 패킷이 WireGuard 터널을 타고WinLabs(subnet router)에 도착한다. WinLabs가 이 패킷을 SNAT으로 출발지를192.168.56.1로 바꾼 뒤 VMnet2로 넘기면 DC01에 닿는다.- DC01의 응답은 같은 서브넷의
192.168.56.1로 돌아오고, router가 그걸 다시 터널로 Kali에 전달한다. 덕분에 VM 쪽에는 tailnet 라우트를 따로 넣을 필요가 없다(Tailscale의 기본 SNAT 동작).
3. 핵심 설계 개념 — 왜 WSL과 Windows로 역할을 나누는가
GOAD를 Windows에 VMware로 올릴 때 공식 구조는 역할을 둘로 나누는 교차 구성이다.
- VM을 만들고 구동하는 쪽(Vagrant + VMware)은 Windows 네이티브에서 돈다. VMware Workstation이 Windows 앱이고 vagrant-vmware-utility 서비스도 Windows에 붙는다.
- 오케스트레이션(goad.py)과 프로비저닝(Ansible)은 WSL2 리눅스에서 돈다. Ansible은 Windows 네이티브로 돌릴 수 없고 GOAD 관리 스크립트도 리눅스 기준이다.
- 두 평면이 같은 파일을 보게 하려고 GOAD는
/mnt/c/GOAD(=C:\GOAD)에 클론한다. WSL의 goad.py가vagrant.exe(Windows 바이너리)를 직접 부르는데, cwd가/mnt/c/...라 Windows에서는 같은 위치가C:\...로 보인다.
이 구분을 잡고 가면 뒤에 나오는 “WSL에서 vagrant.exe를 못 찾는 문제”나 “Ansible은 WSL인데 VM은 Windows” 같은 상황이 왜 생기는지 바로 이해된다.
셸 표기 — 명령 앞에 어떤 셸에서 치는지 프롬프트로 표시한다.
PS>= Windows 관리자 권한 PowerShell (시스템 변경은 대부분 관리자 권한이 필요하다)WSL$= WSL2 Ubuntu 일반 사용자 셸WSL#= WSL2 Ubuntu root (wsl -u root, 암호 불필요)kali$= Kali 데스크탑 터미널
Phase 0 — 환경 점검
설치를 시작하기 전에 RAM·CPU·디스크·가상화 상태와 이미 깔린 도구를 한 번에 확인해 둔다. 함정을 미리 걸러내는 단계다.
0-1. 하드웨어·OS 확인
PS> $cs=Get-CimInstance Win32_ComputerSystem; $os=Get-CimInstance Win32_OperatingSystem; $cpu=Get-CimInstance Win32_Processor
PS> [PSCustomObject]@{
TotalRAM_GB=[math]::Round($cs.TotalPhysicalMemory/1GB,1)
FreeRAM_GB =[math]::Round($os.FreePhysicalMemory/1MB,1)
CPU=$cpu.Name; Cores=$cpu.NumberOfCores; Logical=$cpu.NumberOfLogicalProcessors
OS=$os.Caption; Build=$os.BuildNumber
} | Format-List
Get-CimInstance Win32_*는 WMI/CIM에서 시스템 정보를 읽는 표준 cmdlet으로, 각각 컴퓨터·OS·CPU 정보를 가져온다.- 기대값은 RAM 약 23.4 GB, CPU는 Ryzen 5 5600GT. MINILAB은 VM 두 대를 합쳐도 8 GB 정도라 23.4 GB면 넉넉하다.
0-2. 디스크 여유 확인
PS> Get-PSDrive -PSProvider FileSystem | Select-Object Name,
@{N='Used_GB';E={[math]::Round($_.Used/1GB,1)}},
@{N='Free_GB';E={[math]::Round($_.Free/1GB,1)}}
- GOAD는 박스와 VM을 합쳐 115 GB까지도 쓸 수 있어 여유 공간은 150 GB 이상을 권장한다. 여기서는 C 드라이브에 272 GB가 남아 있어 문제없다.
0-3. WSL 상태 확인
PS> wsl --status
PS> wsl -l -v
wsl --status는 기본 배포판과 WSL 버전을 보여 준다.wsl -l -v는 설치된 배포판 목록과 각 버전(1/2)을 보여 준다. 기대값은 Ubuntu-24.04, VERSION 2다.
0-4. 가상화/하이퍼바이저 상태 (중요)
PS> (bcdedit /enum '{current}') | Select-String 'hypervisorlaunchtype'
PS> (Get-CimInstance Win32_Processor).VirtualizationFirmwareEnabled
PS> (Get-CimInstance Win32_ComputerSystem).HypervisorPresent
hypervisorlaunchtype = Auto에HypervisorPresent = True면 Hyper-V가 켜져 있다는 뜻이다. WSL2가 Hyper-V를 쓰기 때문이다.- 이 상태에서 VMware는 “Windows Hypervisor Platform” 호환 모드로 돈다. VMware Workstation 17.6 이상과 26은 Hyper-V와 공존할 수 있어 충돌이나 블루스크린이 없다. 구버전이라면 여기서 막혔을 부분이다.
0-5. 기존 설치 도구 + VMware 버전 + 가상 네트워크
PS> (Get-Item "C:\Program Files\VMware\VMware Workstation\vmware.exe").VersionInfo.ProductVersion
PS> Get-NetIPAddress -AddressFamily IPv4 | Where-Object {
(Get-NetAdapter -InterfaceIndex $_.InterfaceIndex -EA SilentlyContinue).InterfaceDescription -like "*VMware*"
} | Select-Object @{N='Adapter';E={(Get-NetAdapter -InterfaceIndex $_.InterfaceIndex).Name}}, IPAddress, PrefixLength
PS> tailscale version
PS> tailscale status
- VMware 버전은 26.0.0.25388281이다. 이 값이 뒤에서 호환성 우회가 필요해지는 핵심 단서가 된다.
- 처음에는 vmnet이 VMnet8(NAT, 192.168.236.0/24)과 VMnet1(host-only, 192.168.17.0/24) 둘뿐이다.
192.168.56.0/24는 GOAD 설치 중에 새로 생긴다. tailscale status로 tailnet 멤버를 확인한다. 이 노트북이WinLabs, 공격 머신이kali-linux(온라인)다.
Phase 1 — WSL 의존성 준비
WSL Ubuntu에 GOAD가 요구하는 git, python, venv를 설치하는 단계다.
1-1. sudo 동작 방식 확인
PS> wsl -d Ubuntu-24.04 -- bash -lc "whoami; sudo -n true 2>/dev/null && echo PASSWORDLESS || echo NEEDS_PASSWORD; python3 --version; git --version"
- 일반 사용자 sudo는 암호를 요구한다(NEEDS_PASSWORD). 그래서 이후 패키지 설치는 비대화형으로 돌리려고
wsl -u root를 쓴다. WSL은 root를 지정하면 리눅스 암호를 묻지 않는다. - python3 3.12.3과 git 2.43.0은 이미 깔려 있다.
1-2. 의존성 설치 (root로)
PS> wsl -d Ubuntu-24.04 -u root -- bash -lc 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y git python3 python3-pip python3-venv libpython3-dev'
wsl -u root는 그 명령을 WSL의 root로 실행해 암호 프롬프트를 건너뛴다.DEBIAN_FRONTEND=noninteractive는 apt가 설치 중에 대화형 질문을 띄우지 않게 막는다.git python3 python3-pip python3-venv libpython3-dev는 GOAD가 venv를 만들고 빌드할 때 쓰는 패키지다. 이 PC에서는 이미 모두 최신이었다.
Phase 2 — GOAD 저장소 클론
GOAD를 양쪽에서 공유하는 경로인
/mnt/c/GOAD에 클론한다.
PS> wsl -d Ubuntu-24.04 -- bash -lc 'cd /mnt/c && git clone https://github.com/Orange-Cyberdefense/GOAD.git GOAD && ls /mnt/c/GOAD'
- 굳이
/mnt/c아래에 클론하는 이유는 Windows의vagrant.exe와 WSL의ansible이 같은 파일을 봐야 하기 때문이다./mnt/c/GOAD와C:\GOAD는 양쪽에서 같은 경로로 접근된다. - 클론이 끝나면 최상위에
goad.py,goad.sh,ad/,ansible/,vagrant/,workspace/같은 것들이 보인다.
Phase 3 — Vagrant + Vagrant VMware Utility 설치 (VMware 26 함정 2개 해결)
Windows 네이티브에 Vagrant와 vagrant-vmware-utility를 설치하는 단계다. 여기서 VMware 26 호환성 문제 두 개가 터지는데, 둘 다 레지스트리 32비트/64비트 뷰 차이가 원인이다.
3-1. Vagrant 설치
PS> winget install --id Hashicorp.Vagrant -e --accept-package-agreements --accept-source-agreements --disable-interactivity
PS> & "C:\Program Files\Vagrant\bin\vagrant.exe" --version # Vagrant 2.4.9
winget install은 Windows 패키지 관리자로 설치한다.-e는 정확한 ID 매칭,--accept-*는 약관 자동 수락,--disable-interactivity는 프롬프트 억제다.- 설치 위치는
C:\Program Files\Vagrant\bin이고, 머신 PATH에 자동으로 등록된다.
3-2. VMware Utility MSI 다운로드 (winget에 없음)
PS> Invoke-WebRequest "https://releases.hashicorp.com/vagrant-vmware-utility/1.0.24/vagrant-vmware-utility_1.0.24_windows_amd64.msi" -OutFile "$env:TEMP\vvu_1.0.24.msi" -UseBasicParsing
- vagrant-vmware-utility는 winget에 없어서 HashiCorp 릴리스 페이지에서 MSI를 직접 받는다. 최신 버전은 1.0.24다.
3-3. (함정 ①) MSI 설치가 1603으로 실패 → 32비트 레지스트리 키 생성
첫 설치 시도:
PS> Start-Process msiexec.exe -ArgumentList "/i `"$env:TEMP\vvu_1.0.24.msi`" /qn /norestart /l*v `"$env:TEMP\vvu_install.log`"" -Wait -PassThru
설치는 exit 1603(치명적 실패)으로 끝난다. 로그($env:TEMP\vvu_install.log)에 남은 내용은 다음과 같다.
Vagrant VMware Utility requires a valid installation of VMware Workstation.
Note: 1: 1402 2: HKEY_LOCAL_MACHINE32\SOFTWARE\VMware, Inc.\VMware Workstation
- 원인은 이렇다. MSI의 설치 조건이 32비트 레지스트리 뷰(
WOW6432Node\VMware, Inc.\VMware Workstation)에서 VMware를 찾는데, VMware 26은 64비트 하위에만 키를 남기기 때문에 그 키를 찾지 못한다.
해결하려면 32비트 뷰에 키를 만든 뒤 다시 설치한다.
PS> $base='HKLM:\SOFTWARE\WOW6432Node\VMware, Inc.\VMware Workstation'
PS> New-Item -Path $base -Force | Out-Null
PS> New-ItemProperty -Path $base -Name 'InstallPath' -Value 'C:\Program Files\VMware\VMware Workstation\' -PropertyType String -Force | Out-Null
PS> New-ItemProperty -Path $base -Name 'InstallPath64' -Value 'C:\Program Files\VMware\VMware Workstation\x64\' -PropertyType String -Force | Out-Null
PS> New-ItemProperty -Path $base -Name 'ProductVersion' -Value '26.0.0.25388281' -PropertyType String -Force | Out-Null
PS> Start-Process msiexec.exe -ArgumentList "/i `"$env:TEMP\vvu_1.0.24.msi`" /qn /norestart /l*v `"$env:TEMP\vvu_install2.log`"" -Wait -PassThru # exit 0
msiexec /i ... /qn /norestart는 무인(quiet) 설치에 재부팅을 막고,/l*v로 상세 로그를 남긴다.WOW6432Node는 64비트 Windows에서 32비트 프로그램이 보는 레지스트리 뷰다. 32비트 MSI가SOFTWARE\VMware, Inc.를 읽으면 자동으로 이 경로로 리다이렉트된다.
3-4. (함정 ②) 유틸리티 서비스가 시작 실패 → VMware 레지스트리 트리 전체를 32비트로 미러링
PS> Start-Service VagrantVMware # 실패
PS> Get-Content "C:\ProgramData\hashicorp\vagrant-vmware-desktop\logs\utility.log" -Tail 5
# [ERROR] failed to generate VMware installation information: error="The system cannot find the file specified."
- 진단해 보면, 유틸리티 소스(
driver/base_windows.go의VmwareInfo())는 항상 32비트 레지스트리 뷰(WOW64_32KEY)로SOFTWARE\VMware, Inc.의Core값과 제품 키의ProductVersion을 읽는다. 32비트 뷰에Core값이 없어 에러 2가 난 것이다. 윈도우가 “파일을 찾을 수 없음”이라고 띄우지만 실제로는 레지스트리 키가 없는 상황이다.
VMware 레지스트리 트리 전체를 32비트 뷰로 복사하면 된다.
PS> reg delete "HKLM\SOFTWARE\WOW6432Node\VMware, Inc." /f
PS> reg copy "HKLM\SOFTWARE\VMware, Inc." "HKLM\SOFTWARE\WOW6432Node\VMware, Inc." /s /f
PS> Start-Service VagrantVMware
PS> Get-Service VagrantVMware | Select-Object Name,Status # Running
reg copy <src> <dst> /s /f는 키 트리 전체를 하위(/s)까지 강제(/f)로 복사한다. 이렇게 하면 유틸리티가 읽는 32비트 뷰에Core,ProductVersion, VMnetLib 네트워크 설정까지 다 채워진다.- 서비스가 Running 상태이고 로그에
vmrest나api service start: ... port=9922가 보이면 제대로 올라온 것이다.
이 두 함정(3-3, 3-4)은 VMware 26의 64비트 전용 레이아웃과, 구버전을 가정하고 만들어진 vagrant-vmware-utility가 만났을 때 생긴다. 부록 A에 정리해 뒀다.
Phase 4 — Vagrant 플러그인 설치
PS> & "C:\Program Files\Vagrant\bin\vagrant.exe" plugin install vagrant-reload vagrant-vmware-desktop winrm winrm-fs winrm-elevated
PS> & "C:\Program Files\Vagrant\bin\vagrant.exe" plugin list
# vagrant-reload (0.0.1, global)
# vagrant-vmware-desktop (3.0.5, global)
vagrant plugin install은 플러그인(루비 gem)을 설치한다.vagrant-vmware-desktop은 VMware Workstation provider로, 필수다.vagrant-reload는 프로비저닝 중 재부팅 단계를 지원한다.winrm,winrm-fs,winrm-elevated는 Windows 게스트와 통신하는 데 쓴다. Vagrant 2.4.9에 기본 번들로 들어 있어plugin list에는 안 보이지만 정상 동작한다.
Phase 5 — GOAD check 통과시키기
goad.sh -t check로 전제조건을 모두 검증하는 단계다. 여기서 경로 하드코딩 문제와 WSL PATH 문제 두 개를 잡는다.
5-1. (수정) GOAD의 WSL 체크 경로가 구버전 기준
C:\GOAD\goad\command\wsl.py가 VMware와 유틸리티를 엉뚱한 경로에서 찾고 있다. 이 PC의 실제 경로로 바꿔야 하는데, 고칠 두 항목은 다음과 같다.
| 항목 | 원래(구버전 가정) | 수정 후(이 PC 실제) |
|---|---|---|
check_vmware | /mnt/c/Program Files (x86)/VMware/VMware Workstation/vmrun.exe | /mnt/c/Program Files/VMware/VMware Workstation/vmrun.exe |
check_vmware_utility | /mnt/c/Program Files/VagrantVMwareUtility/vagrant-vmware-utility.exe | /mnt/c/Program Files/VagrantVMwareUtility/bin/vagrant-vmware-utility.exe |
(편집기로 wsl.py의 해당 두 줄을 고친다. 이 체크 함수들은 표시용이라 건드려도 안전하지만, 경로가 맞아야 check가 깔끔하게 통과한다.)
5-2. (함정 ③) WSL이 vagrant.exe를 PATH에서 못 찾음 → 심볼릭 링크
PS> wsl -d Ubuntu-24.04 -- bash -lc 'which vagrant.exe || echo NOTFOUND' # NOTFOUND
- 원인은 WSL이 시작 시점의 Windows PATH를 물려받는 데 있다. Vagrant를 설치하기 전부터 WSL과 부모 프로세스가 떠 있던 탓에 PATH 스냅샷에
C:\Program Files\Vagrant\bin이 빠져 있다. 부모 프로세스 환경이 오래된 경우엔wsl --shutdown으로 재시작해도 해결되지 않는다.
interop PATH에 기대지 않도록 영구 심볼릭 링크를 건다.
PS> wsl -d Ubuntu-24.04 -u root -- bash -lc 'ln -sf "/mnt/c/Program Files/Vagrant/bin/vagrant.exe" /usr/local/bin/vagrant.exe'
PS> wsl -d Ubuntu-24.04 -- bash -lc 'which vagrant.exe && vagrant.exe --version' # Vagrant 2.4.9
ln -sf <target> <link>는 심볼릭 링크를 만든다(-f는 기존 링크 덮어쓰기)./usr/local/bin은 PATH에 항상 들어 있어which vagrant.exe가 성공하고, WSL이 Windows 바이너리를 대신 실행해 준다.
5-3. check 실행 (첫 실행 시 venv 자동 빌드, 수 분)
PS> wsl -d Ubuntu-24.04 -- bash -lc 'cd /mnt/c/GOAD && bash goad.sh -t check -p vmware -l MINILAB -ip 192.168.56'
goad.sh는 처음 실행할 때~/.goad/.venv에 venv를 만들고pip install과ansible-galaxy install(컬렉션 다운로드)을 수행한다. 이때 ansible-core 2.18과 ansible.windows, community.general, community.windows 등이 깔린다.-t check의 작업 인자는-p vmware(프로바이더),-l MINILAB(랩),-ip 192.168.56(IP 대역 앞 3옥텟)이다.- 기대 결과는 다음과 같다. RAM 경고 한 줄을 빼면 전부
[+]다.[+] vagrant.exe found in PATH [+] ansible-playbook found in PATH [+] Ansible galaxy collection ansible.windows / community.general / community.windows is installed [+] vagrant plugin vagrant-reload / vagrant-vmware-desktop is installed [+] File .../VMware Workstation/vmrun.exe present [+] File .../VagrantVMwareUtility/bin/vagrant-vmware-utility.exe present [-] not enough ram on the system ← check_ram이 24GB 기준이라 23.4GB가 걸리는 것뿐이다. MINILAB(약 8GB)과는 무관하고 설치를 막지도 않는다.
Phase 6 — 랩 빌드 + 네트워크(VMnet2) 문제 해결
MINILAB을 실제로 빌드하는 단계다. 박스 다운로드,
vagrant up, ansible 프로비저닝이 차례로 돈다. 빌드 도중 호스트 전용 네트워크 IP가 적용되지 않는 문제(함정 ④)를 여기서 잡는다.
6-1. MINILAB 구성 확인 (어떤 박스/IP인지)
C:\GOAD\ad\MINILAB\providers\vmware\Vagrantfile:
boxes = [
{ :name => "DC01", :ip => "{{ip_range}}.30", :box => "mayfly/windows_server2019", :os => "windows", :cpus => 2, :mem => 4000},
{ :name => "WS01", :ip => "{{ip_range}}.31", :box => "mayfly/windows10", :os => "windows", :cpus => 2, :mem => 4000}
]
여기서 DC01은 .56.30, WS01은 .56.31을 쓰고, 박스는 mayfly의 사전 빌드본을 내려받는다.
6-2. 빌드 시작 (백그라운드 권장, 수 시간)
PS> wsl -d Ubuntu-24.04 -- bash -lc 'cd /mnt/c/GOAD && yes | bash goad.sh -t install -p vmware -l MINILAB -ip 192.168.56 > /mnt/c/GOAD/install.log 2>&1'
-t install은 인스턴스 생성부터vagrant up(박스 다운로드, VM 부팅, 네트워크 구성), ansible 프로비저닝까지 한 번에 돌린다.yes | ...는 goad가 띄우는Create lab with these settings? (y/N)확인 프롬프트에 자동으로y를 넣어 준다.> install.log 2>&1은 표준출력과 에러를 로그 파일로 보낸다(모니터링용). 이 로그는C:\GOAD\install.log에서도 열린다.
진행 모니터링 예:
PS> Get-Content "C:\GOAD\install.log" -Tail 20 # 단계 확인
PS> & "C:\Program Files\Vagrant\bin\vagrant.exe" box list # 받은 박스 확인
PS> (Get-Process vmware-vmx -EA SilentlyContinue).Count # 실행 중 VM 수
6-3. (함정 ④) VMnet2가 생겼지만 호스트 IP가 APIPA → 192.168.56.1 적용
빌드가 vagrant up을 지나 ansible “Gathering Facts”에서 멈춘다. 원인을 진단해 본다.
PS> Get-NetIPAddress -InterfaceAlias "*VMnet2*" -AddressFamily IPv4 | Select IPAddress,PrefixLength
# 169.254.20.47 / 16 ← APIPA. 192.168.56.1 이 아님!
PS> Get-Content "C:\GOAD\workspace\2ec88e-minilab-vmware\inventory" | Select-String 'ansible_host'
# dc01 ansible_host=192.168.56.30 / ws01 ansible_host=192.168.56.31 ← 호스트가 이 대역에 닿아야 함
PS> (Get-Item 'HKLM:\SOFTWARE\VMware, Inc.\VMnetLib\VMnetConfig\vmnet2').GetValue('IPSubnetAddress')
# 192.168.56.0 ← 64비트 레지스트리엔 서브넷이 올바르게 있음. 단지 어댑터에 적용이 안 됐을 뿐.
- 원인은 vagrant-vmware-desktop 플러그인이 VMnet2 어댑터는 만들었지만, VMware 26에서 호스트 전용 서브넷의 게이트웨이 IP(192.168.56.1)를 붙이는 단계가 빠진 데 있다. 그 탓에 어댑터가 APIPA(169.254)에 머물러 호스트와 WSL의 ansible이 VM에 닿지 못한다.
VMnetConfig를 32비트 뷰로 동기화한 다음, 어댑터에 IP를 직접 붙인다.
PS> reg copy "HKLM\SOFTWARE\VMware, Inc.\VMnetLib\VMnetConfig" "HKLM\SOFTWARE\WOW6432Node\VMware, Inc.\VMnetLib\VMnetConfig" /s /f
PS> $idx = (Get-NetAdapter -InterfaceAlias "*VMnet2*").ifIndex
PS> Get-NetIPAddress -InterfaceIndex $idx -AddressFamily IPv4 | Where-Object { $_.IPAddress -like '169.254.*' } | Remove-NetIPAddress -Confirm:$false
PS> New-NetIPAddress -InterfaceIndex $idx -IPAddress 192.168.56.1 -PrefixLength 24
New-NetIPAddress -InterfaceAlias "*VMnet2*" -IPAddress 192.168.56.1 -PrefixLength 24는 VMnet2 어댑터에 호스트 전용 게이트웨이 IP를 직접 붙인다. VM은 정적 IP(.30/.31)를 쓰므로 DHCP가 필요 없고, 호스트에.1만 있으면 L2로 통신이 된다.- IP를 붙이면 멈춰 있던 ansible이 알아서 다시 진행된다(WinRM 재시도가 성공한다). 프로세스를 죽이고 다시 시작할 필요는 없다.
검증(호스트→VM):
PS> "DC01:5985 = " + (Test-NetConnection 192.168.56.30 -Port 5985 -WarningAction SilentlyContinue).TcpTestSucceeded
PS> "WS01:5985 = " + (Test-NetConnection 192.168.56.31 -Port 5985 -WarningAction SilentlyContinue).TcpTestSucceeded
Test-NetConnection -Port 5985는 WinRM(HTTP) 포트 연결을 테스트한다.True면 도달에 성공한 것이다. ICMP/ping은 게스트 방화벽이 막아 실패하므로, 포트로 검증한다.
6-4. 빌드 완료 확인
빌드가 끝나면 로그 마지막에:
PLAY RECAP
dc01 : ok=15 changed=4 unreachable=0 failed=0 ...
ws01 : ok=6 changed=2 unreachable=0 failed=0 ...
[*] Lab successfully provisioned
failed=0에unreachable=0이면 성공이다. 프로비저닝 중에 GOAD가 취약점을 심어 둔다. 예를 들어MINILAB\alice자격증명을 만들고 WS01의 방화벽을 끄는데, 이때부터 WS01의 445/SMB도 열린다.
Phase 7 — Tailscale subnet router 구성
이 노트북을 subnet router로 만들어 랩망인
192.168.56.0/24를 tailnet에 광고하는 단계다. 그래야 따로 떨어진 Kali가 접속할 수 있다.
7-1. 랩 서브넷 광고 (이 노트북)
PS> tailscale set --advertise-routes=192.168.56.0/24
PS> tailscale debug prefs | Select-String 'AdvertiseRoutes' -Context 0,2 # 192.168.56.0/24 확인
tailscale set --advertise-routes=<CIDR>는 이 노드가 해당 서브넷으로 가는 라우트를 tailnet에 광고한다. 이게 subnet router 역할이다.
7-2. IP 포워딩 활성화 (재부팅 없이 즉시)
PS> Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' -Name IPEnableRouter -Value 1
PS> $ts = (Get-NetAdapter | Where-Object { $_.Name -like '*Tailscale*' }).ifIndex # 예: 6
PS> $vm2 = (Get-NetAdapter -InterfaceAlias '*VMnet2*').ifIndex # 예: 39
PS> Set-NetIPInterface -InterfaceIndex $ts -AddressFamily IPv4 -Forwarding Enabled
PS> Set-NetIPInterface -InterfaceIndex $vm2 -AddressFamily IPv4 -Forwarding Enabled
PS> Restart-Service Tailscale -Force
IPEnableRouter=1은 전역 IP 라우팅을 켠다(영구 설정이지만 전역 적용은 재부팅 후에 확실해진다).Set-NetIPInterface -Forwarding Enabled는 인터페이스별 포워딩을 재부팅 없이 즉시 켠다. Tailscale 어댑터로 들어온 192.168.56.x 패킷을 VMnet2로 넘기는 데 필요하다.Restart-Service Tailscale로 변경 사항을 반영한다.
7-3. 라우트 승인 (관리 콘솔 — 사용자 작업)
- https://login.tailscale.com/admin/machines 에서 머신
WinLabs를 열고, Subnet routes의192.168.56.0/24를 Approve한다(또는…→ Edit route settings → 체크 → Save). - 자기 라우트는 CLI로 승인할 수 없다. 컨트롤 플레인, 즉 콘솔에서 해야 하는 작업이다.
7-4. Kali에서 라우트 수락 (Kali 터미널 — 사용자 작업)
kali$ sudo tailscale set --accept-routes
- 리눅스 Tailscale은 광고된 서브넷을 기본적으로 받지 않으므로
--accept-routes가 필요하다. - 현행 공식 문서는
tailscale set --accept-routes를 권장한다. 구버전 형식인sudo tailscale up --accept-routes도 그대로 동작하며, 이번 구축에서 실제로 쓴 명령이 이쪽이다.
Phase 8 — 최종 도달성 검증
8-1. 호스트 기준 포트 검증
PS> foreach ($ip in '192.168.56.30','192.168.56.31') {
"{0} 445={1} 5985={2} 389={3}" -f $ip,
(Test-NetConnection $ip -Port 445 -WarningAction SilentlyContinue).TcpTestSucceeded,
(Test-NetConnection $ip -Port 5985 -WarningAction SilentlyContinue).TcpTestSucceeded,
(Test-NetConnection $ip -Port 389 -WarningAction SilentlyContinue).TcpTestSucceeded
}
# 192.168.56.30 445=True 5985=True 389=True ← DC01 (LDAP 열림 = DC)
# 192.168.56.31 445=True 5985=True 389=False ← WS01 (방화벽 비활성화 후 SMB 열림)
- 포트 의미는 445가 SMB, 5985가 WinRM, 389가 LDAP다. 389가 열려 있으면 도메인 컨트롤러라는 신호다.
8-2. Kali → 랩 (터널 end-to-end)
kali$ nc -zv 192.168.56.31 5985
# (UNKNOWN) [192.168.56.31] 5985 (?) open ← "open"이면 성공
# ※ "inverse host lookup failed: Unknown host"는 PTR(역방향 DNS) 없을 때 뜨는 무해한 경고
kali$ nxc smb 192.168.56.30 192.168.56.31 # NetExec로 도메인/호스트명/서명여부 확인
- 여기까지 되면 Kali → tailnet → WinLabs(subnet router) → VMnet2 → 랩 VM 경로가 끝에서 끝까지 살아 있는 것이다.
Phase 9 — 재부팅 자동 복구 (예약 작업)
재부팅하면 VMware 26이 VMnet2에
192.168.56.1을 다시 붙여 주지 않는 문제(함정 ④)를, 부팅할 때마다 자동으로 보정하는 단계다.
9-1. 부팅 스크립트
C:\ProgramData\GOAD\fix-vmnet2-ip.ps1은 VMnet2가 올라올 때까지 기다렸다가, 192.168.56.1이 없으면 붙이고 있으면 건너뛴다(멱등). 그다음 포워딩을 다시 적용하고 C:\ProgramData\GOAD\vmnet2-boot.log에 로그를 남긴다. 전체 내용은 해당 파일을 참조한다.
9-2. 예약 작업 등록 (시스템 시작 시, SYSTEM 권한)
PS> $action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\ProgramData\GOAD\fix-vmnet2-ip.ps1"'
PS> $trigger = New-ScheduledTaskTrigger -AtStartup; $trigger.Delay = 'PT30S'
PS> $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
PS> $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 5)
PS> Register-ScheduledTask -TaskName 'GOAD-VMnet2-IP' -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description 'Ensure VMnet2 has 192.168.56.1/24 for GOAD lab (VMware 26 workaround)' -Force
New-ScheduledTaskTrigger -AtStartup에Delay='PT30S'를 줘서 부팅 30초 뒤에 실행한다. VMware 네트워크가 올라올 시간을 벌어 두는 것이다.-UserId 'SYSTEM' -RunLevel Highest는 관리자 권한으로, 로그인하지 않아도 실행되게 한다.
9-3. 즉시 테스트 (멱등 확인)
PS> Start-ScheduledTask -TaskName 'GOAD-VMnet2-IP'
PS> Get-ScheduledTaskInfo -TaskName 'GOAD-VMnet2-IP' | Select LastRunTime, LastTaskResult # LastTaskResult = 0 (성공)
PS> Get-Content 'C:\ProgramData\GOAD\vmnet2-boot.log'
# ... 192.168.56.1 already present on ifIndex 39 / forwarding asserted; done
부록 A — VMware 26 호환성 문제 요약
VMware Workstation 26은 64비트 전용 레이아웃이라, 구버전(32비트 설치 시절)을 가정한 도구들과 네 군데서 충돌했다. 모두 해결한 내용을 정리하면 다음과 같다.
| # | 증상 | 근본 원인 | 해결 |
|---|---|---|---|
| ① | Utility MSI 설치 1603 실패 | MSI 설치조건이 32비트 레지스트리에서 VMware 탐색 | WOW6432Node\VMware, Inc.\VMware Workstation 키 생성 |
| ② | Utility 서비스 시작 실패 (“cannot find the file”) | 유틸리티가 32비트 뷰에서 Core/ProductVersion 읽기(키 없음) | reg copy 로 VMware 레지스트리 트리 전체를 32비트로 미러링 |
| ③ | WSL which vagrant.exe 실패 | 설치 전 PATH 스냅샷이 굳음 | /usr/local/bin/vagrant.exe 심볼릭 링크 |
| ④ | VMnet2 호스트 IP가 APIPA(169.254) | 호스트전용 서브넷 게이트웨이 IP 적용 누락 | New-NetIPAddress ... 192.168.56.1 + 부팅 예약작업 |
이와 별개로 GOAD wsl.py의 VMware/utility 체크 경로도 이 PC의 실제 경로로 고쳤다(표시용).
부록 B — 명령어 용어집
| 명령 | 뜻 |
|---|---|
wsl -d <distro> -- <cmd> | 특정 WSL 배포판에서 명령 실행 |
wsl -u root -- <cmd> | WSL을 root로 실행(리눅스 암호 불필요) |
wsl --shutdown | 모든 WSL 인스턴스 종료(다음 호출 시 재기동, PATH 재읽기) |
winget install --id <id> -e | Windows 패키지 관리자 설치(정확한 ID) |
msiexec /i <msi> /qn /norestart /l*v <log> | MSI 무인 설치 + 상세 로그 |
reg copy <src> <dst> /s /f | 레지스트리 키 트리 전체 강제 복사 |
New-Item / New-ItemProperty -Path HKLM:\... | 레지스트리 키/값 생성 |
Get-Service / Start-Service / Restart-Service | Windows 서비스 조회/시작/재시작 |
vagrant.exe plugin install/list | Vagrant 플러그인 관리 |
bash goad.sh -t <task> -p <provider> -l <lab> -ip <range> | GOAD 작업 실행(check/install/start/stop…) |
Get-NetAdapter / Get-NetIPAddress | 네트워크 어댑터/IP 조회 |
New-NetIPAddress -InterfaceAlias <a> -IPAddress <ip> -PrefixLength <n> | 어댑터에 정적 IP 부여 |
Set-NetIPInterface -Forwarding Enabled | 인터페이스 IP 포워딩 즉시 활성화 |
Test-NetConnection <ip> -Port <n> | TCP 포트 연결 테스트 |
tailscale set --advertise-routes=<CIDR> | subnet route 광고 |
tailscale up --accept-routes | (클라이언트) 광고된 라우트 수락 |
vmrun -T ws <cmd> <vmx> | VMware VM 제어 CLI |
Register-ScheduledTask | Windows 예약 작업 등록 |
부록 C — 처음부터 다시 할 때의 압축 체크리스트
[ ] 0. 환경 점검: RAM≥20GB, 디스크≥150GB, WSL2, VMware26, Hyper-V on 확인
[ ] 1. WSL: wsl -u root -- apt-get install -y git python3 python3-pip python3-venv libpython3-dev
[ ] 2. 클론: /mnt/c 에 git clone GOAD
[ ] 3a. winget install Hashicorp.Vagrant
[ ] 3b. vagrant-vmware-utility_1.0.24 MSI 다운로드
[ ] 3c. (VMware26) WOW6432Node\VMware, Inc.\VMware Workstation 키 생성 → MSI 설치
[ ] 3d. (VMware26) reg copy "HKLM\SOFTWARE\VMware, Inc." → WOW6432Node → Start-Service VagrantVMware
[ ] 4. vagrant plugin install vagrant-reload vagrant-vmware-desktop winrm winrm-fs winrm-elevated
[ ] 5a. wsl.py 경로 2곳 수정(Program Files, bin/)
[ ] 5b. ln -sf vagrant.exe → /usr/local/bin/vagrant.exe
[ ] 5c. goad.sh -t check -p vmware -l MINILAB -ip 192.168.56 → 전부 [+] (RAM 경고 무시)
[ ] 6a. yes | goad.sh -t install -p vmware -l MINILAB -ip 192.168.56 (백그라운드)
[ ] 6b. (VMware26) VMnet2가 169.254면 → reg copy VMnetConfig + New-NetIPAddress 192.168.56.1/24
[ ] 6c. PLAY RECAP failed=0 확인
[ ] 7a. tailscale set --advertise-routes=192.168.56.0/24
[ ] 7b. IPEnableRouter=1 + Set-NetIPInterface -Forwarding Enabled (Tailscale, VMnet2)
[ ] 7c. 관리 콘솔에서 라우트 승인
[ ] 7d. (Kali) sudo tailscale set --accept-routes (구버전: tailscale up --accept-routes 도 가능)
[ ] 8. 검증: Test-NetConnection / nc -zv 192.168.56.30 5985
[ ] 9. 예약작업 GOAD-VMnet2-IP 등록(부팅 자동 복구)
운영·관리 명령은 사용법 포스팅을 참조하자.
← ALL POSTS