▌ TRANSMISSION · [CONCEPT]

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를 통해 정리된 글입니다.)


목차


1. 전체 그림 — 무엇을 만드는가

구성요소
랩 종류GOAD MINILAB (가장 가벼운 구성)
VM 1DC01 — Windows Server 2019, 도메인 컨트롤러, 192.168.56.30, 2 vCPU / 4 GB
VM 2WS01 — 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 listls /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 (공유)│  │
│  └────────────────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────────────────┘

데이터 흐름 요약

  1. Kali가 192.168.56.30(DC01)로 접속을 시도하면 패킷이 WireGuard 터널을 타고 WinLabs(subnet router)에 도착한다.
  2. WinLabs가 이 패킷을 SNAT으로 출발지를 192.168.56.1로 바꾼 뒤 VMnet2로 넘기면 DC01에 닿는다.
  3. 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 = AutoHypervisorPresent = 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/GOADC:\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.goVmwareInfo())는 항상 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 상태이고 로그에 vmrestapi 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 installansible-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=0unreachable=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 -AtStartupDelay='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> -eWindows 패키지 관리자 설치(정확한 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-ServiceWindows 서비스 조회/시작/재시작
vagrant.exe plugin install/listVagrant 플러그인 관리
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-ScheduledTaskWindows 예약 작업 등록

부록 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