Get-ADDnsRecords.ps1 (12486B)
1 <#
2 .SYNOPSIS
3 UDFX - Ultimate Domain Finder X
4 AD 통합 DNS 레코드를 LDAP으로 직접 조회하는 에어갭 환경용 도구
5 .DESCRIPTION
6 도메인 사용자 권한으로 Active Directory에 저장된 DNS 레코드를 조회합니다.
7 인터넷 연결 없이 에어갭 환경에서 사용 가능합니다.
8 .EXAMPLE
9 .\Get-ADDnsRecords.ps1 -Domain corp.example.com
10 .\Get-ADDnsRecords.ps1 -Domain corp.example.com -Output results
11 .NOTES
12 Version: 1.0.0
13 Author: UDFX Project
14 #>
15
16 param(
17 [Parameter(Mandatory=$true)]
18 [string]$Domain,
19
20 [string]$Output = "dns_records",
21
22 [switch]$ShowDebug
23 )
24
25 $banner = @"
26
27 __ ______ _________ __
28 / / / / __ \/ ____/ |/ /
29 / / / / / / / /_ | /
30 / /_/ / /_/ / __/ / |
31 \____/_____/_/ /_/|_|
32
33 ╔═══════════════════════════════════════════════════╗
34 ║ Ultimate Domain Finder X ║
35 ║ Air-Gap AD DNS Enumeration Tool v1.0 ║
36 ╠═══════════════════════════════════════════════════╣
37 ║ [*] LDAP-based AD DNS Record Extraction ║
38 ║ [*] No Internet Connection Required ║
39 ║ [*] Domain User Privileges Only ║
40 ╚═══════════════════════════════════════════════════╝
41
42 "@
43
44 Write-Host $banner -ForegroundColor Cyan
45
46 # 루트 도메인 추출 함수
47 function Get-RootDomain {
48 param([string]$domain)
49
50 $parts = $domain.Split(".")
51 if ($parts.Count -lt 2) { return $domain }
52
53 # .co.kr, .or.kr, .go.kr, .ac.kr 등 3단계 TLD 처리
54 if ($parts.Count -ge 3) {
55 $sld = $parts[-2]
56 $tld = $parts[-1]
57 if ($tld -eq "kr" -and $sld -match "^(co|or|go|ac|ne|re|pe|hs|ms|es|sc|kg)$") {
58 return "$($parts[-3]).$($parts[-2]).$($parts[-1])"
59 }
60 if ($tld -eq "uk" -and $sld -match "^(co|org|ac|gov)$") {
61 return "$($parts[-3]).$($parts[-2]).$($parts[-1])"
62 }
63 if ($tld -eq "jp" -and $sld -match "^(co|or|ac|go|ne)$") {
64 return "$($parts[-3]).$($parts[-2]).$($parts[-1])"
65 }
66 }
67
68 # 일반 도메인: example.com, example.io
69 return "$($parts[-2]).$($parts[-1])"
70 }
71
72 # DNS 이름 파싱 함수
73 function Get-DnsNameFromRecord {
74 param(
75 [byte[]]$data,
76 [int]$offset
77 )
78
79 $nameParts = @()
80 $pos = $offset
81
82 try {
83 while ($pos -lt $data.Length) {
84 $len = $data[$pos]
85 if ($len -eq 0) { break }
86 if ($len -gt 63) { break }
87 if (($pos + 1 + $len) -gt $data.Length) { break }
88
89 $label = [System.Text.Encoding]::ASCII.GetString($data, $pos + 1, $len)
90 $nameParts += $label
91 $pos += $len + 1
92 }
93 } catch {
94 return "unknown"
95 }
96
97 if ($nameParts.Count -eq 0) { return "" }
98 return ($nameParts -join ".")
99 }
100
101 # DNS 레코드 변환 함수
102 function Convert-DNSRecord {
103 param([byte[]]$RecordData)
104
105 if ($RecordData.Length -lt 24) { return $null }
106
107 $recordType = [BitConverter]::ToUInt16($RecordData, 2)
108
109 $typeName = switch ($recordType) {
110 1 { "A" }
111 2 { "NS" }
112 5 { "CNAME" }
113 6 { "SOA" }
114 12 { "PTR" }
115 15 { "MX" }
116 16 { "TXT" }
117 28 { "AAAA" }
118 33 { "SRV" }
119 default { "TYPE$recordType" }
120 }
121
122 $result = @{
123 Type = $typeName
124 TypeId = $recordType
125 Data = ""
126 }
127
128 try {
129 switch ($recordType) {
130 1 { # A Record
131 if ($RecordData.Length -ge 28) {
132 $ip = "{0}.{1}.{2}.{3}" -f $RecordData[24], $RecordData[25], $RecordData[26], $RecordData[27]
133 $result.Data = $ip
134 }
135 }
136 28 { # AAAA Record
137 if ($RecordData.Length -ge 40) {
138 $ipv6Bytes = $RecordData[24..39]
139 $ip = [System.Net.IPAddress]::new($ipv6Bytes).ToString()
140 $result.Data = $ip
141 }
142 }
143 2 { # NS Record
144 $result.Data = Get-DnsNameFromRecord $RecordData 24
145 }
146 5 { # CNAME Record
147 $result.Data = Get-DnsNameFromRecord $RecordData 24
148 }
149 12 { # PTR Record
150 $result.Data = Get-DnsNameFromRecord $RecordData 24
151 }
152 15 { # MX Record
153 if ($RecordData.Length -ge 28) {
154 $priority = [BitConverter]::ToUInt16($RecordData, 24)
155 $exchange = Get-DnsNameFromRecord $RecordData 26
156 $result.Data = "[$priority] $exchange"
157 }
158 }
159 6 { # SOA Record
160 $result.Data = Get-DnsNameFromRecord $RecordData 24
161 }
162 16 { # TXT Record
163 if ($RecordData.Length -gt 24) {
164 $txtLen = $RecordData[24]
165 if ($txtLen -gt 0 -and $RecordData.Length -ge (25 + $txtLen)) {
166 $result.Data = [System.Text.Encoding]::ASCII.GetString($RecordData, 25, $txtLen)
167 }
168 }
169 }
170 33 { # SRV Record
171 if ($RecordData.Length -ge 32) {
172 $priority = [BitConverter]::ToUInt16($RecordData, 24)
173 $weight = [BitConverter]::ToUInt16($RecordData, 26)
174 $port = [BitConverter]::ToUInt16($RecordData, 28)
175 $target = Get-DnsNameFromRecord $RecordData 30
176 $result.Data = "${target}:$port (pri:$priority wt:$weight)"
177 }
178 }
179 default {
180 $result.Data = "Binary (type:$recordType)"
181 }
182 }
183 } catch {
184 $result.Data = "Parse error"
185 }
186
187 return $result
188 }
189
190 # 메인 실행
191 Write-Host "[*] Target Domain: $Domain" -ForegroundColor White
192 Write-Host ""
193
194 # 도메인 DN 생성
195 $domainParts = $Domain.Split(".")
196 $domainDN = ($domainParts | ForEach-Object { "DC=$_" }) -join ","
197
198 Write-Host "[*] Searching for DNS Zones in Active Directory..." -ForegroundColor Cyan
199
200 # DNS 영역 검색
201 $zones = @()
202 $searchBase = "LDAP://DC=DomainDnsZones,$domainDN"
203
204 try {
205 $searcher = New-Object DirectoryServices.DirectorySearcher
206 $searcher.SearchRoot = [ADSI]$searchBase
207 $searcher.Filter = "(objectClass=dnsZone)"
208 $searcher.PageSize = 1000
209
210 $results = $searcher.FindAll()
211 foreach ($result in $results) {
212 $zoneName = $result.Properties["name"][0]
213 if ($zoneName -and $zoneName -ne "RootDNSServers") {
214 $zones += @{
215 Name = $zoneName
216 Path = $result.Path
217 }
218 }
219 }
220 } catch {
221 Write-Host "[!] Error accessing DNS zones: $_" -ForegroundColor Red
222 exit 1
223 }
224
225 if ($zones.Count -eq 0) {
226 Write-Host "[!] No DNS zones found." -ForegroundColor Red
227 exit 1
228 }
229
230 Write-Host "[+] Found $($zones.Count) DNS zone(s)" -ForegroundColor Green
231 Write-Host ""
232
233 # 모든 레코드 수집
234 $allRecords = @()
235
236 foreach ($zone in $zones) {
237 Write-Host "[*] Dumping: $($zone.Name)" -ForegroundColor Cyan
238
239 try {
240 $zoneSearcher = New-Object DirectoryServices.DirectorySearcher
241 $zoneSearcher.SearchRoot = [ADSI]$zone.Path
242 $zoneSearcher.Filter = "(objectClass=dnsNode)"
243 $zoneSearcher.PageSize = 1000
244 $zoneSearcher.PropertiesToLoad.AddRange(@("name", "dnsRecord"))
245
246 $nodeResults = $zoneSearcher.FindAll()
247 $zoneRecordCount = 0
248
249 foreach ($node in $nodeResults) {
250 $nodeName = $node.Properties["name"][0]
251 $dnsRecords = $node.Properties["dnsRecord"]
252
253 if ($nodeName -eq "@") {
254 $fqdn = $zone.Name
255 } else {
256 $fqdn = "$nodeName.$($zone.Name)"
257 }
258
259 foreach ($recordData in $dnsRecords) {
260 $parsed = Convert-DNSRecord -RecordData $recordData
261 if ($parsed -and $parsed.Data -and $parsed.Type -ne "SOA") {
262 # Zone 유형 분류
263 $zoneType = "External"
264 if ($zone.Name -eq $Domain) {
265 $zoneType = "Primary"
266 } elseif ($zone.Name.EndsWith(".$Domain")) {
267 $zoneType = "Subdomain"
268 }
269
270 # 루트 도메인 추출
271 $rootDomain = Get-RootDomain $zone.Name
272
273 $record = [PSCustomObject]@{
274 Domain = $fqdn
275 RecordType = $parsed.Type
276 Value = $parsed.Data
277 Zone = $zone.Name
278 ZoneType = $zoneType
279 RootDomain = $rootDomain
280 }
281 $allRecords += $record
282 $zoneRecordCount++
283
284 if ($ShowDebug) {
285 Write-Host " [$($parsed.Type)] $fqdn -> $($parsed.Data)" -ForegroundColor Gray
286 }
287 }
288 }
289 }
290
291 Write-Host " Found $zoneRecordCount records" -ForegroundColor Gray
292
293 } catch {
294 Write-Host " [!] Error: $_" -ForegroundColor Red
295 }
296 }
297
298 Write-Host ""
299 Write-Host "[*] Saving results..." -ForegroundColor Cyan
300
301 # JSON 출력
302 $jsonOutput = @{
303 Target = $Domain
304 ScanTime = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
305 TotalRecords = $allRecords.Count
306 ZoneCount = $zones.Count
307 Zones = $zones | ForEach-Object { $_.Name }
308 Records = $allRecords
309 } | ConvertTo-Json -Depth 10
310 $jsonOutput | Out-File -FilePath "$Output.json" -Encoding UTF8
311
312 # CSV 출력
313 $allRecords | Export-Csv -Path "$Output.csv" -NoTypeInformation -Encoding UTF8
314
315 # Root Domain CSV 출력 (중복 제거)
316 $uniqueRootDomains = $allRecords | Select-Object -ExpandProperty RootDomain -Unique | Sort-Object | ForEach-Object {
317 [PSCustomObject]@{
318 RootDomain = $_
319 }
320 }
321 $uniqueRootDomains | Export-Csv -Path "${Output}_root_domains.csv" -NoTypeInformation -Encoding UTF8
322
323 # TXT 출력
324 $txtContent = @"
325 # AD DNS Record Dump
326 # Target: $Domain
327 # Time: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
328 # Total Records: $($allRecords.Count)
329 # Zones: $($zones.Count)
330 #
331 # Format: RootDomain | ZoneType | Type | Domain | Value | Zone
332 # =============================================================
333
334 "@
335 foreach ($record in $allRecords) {
336 $txtContent += "$($record.RootDomain) | $($record.ZoneType) | $($record.RecordType) | $($record.Domain) | $($record.Value) | $($record.Zone)`n"
337 }
338 $txtContent | Out-File -FilePath "$Output.txt" -Encoding UTF8
339
340 Write-Host ""
341 Write-Host "========== SCAN COMPLETE ==========" -ForegroundColor Green
342 Write-Host "Total Zones: $($zones.Count)" -ForegroundColor White
343 Write-Host "Total Records: $($allRecords.Count)" -ForegroundColor White
344 Write-Host ""
345 Write-Host "Results saved to:" -ForegroundColor White
346 Write-Host " - $Output.json" -ForegroundColor Cyan
347 Write-Host " - $Output.csv" -ForegroundColor Cyan
348 Write-Host " - ${Output}_root_domains.csv (unique root domains)" -ForegroundColor Cyan
349 Write-Host " - $Output.txt" -ForegroundColor Cyan
350 Write-Host "===================================" -ForegroundColor Green
351
352 # 루트 도메인 목록
353 Write-Host ""
354 Write-Host "Root Domains Found:" -ForegroundColor White
355 $allRecords | Select-Object -ExpandProperty RootDomain -Unique | Sort-Object | ForEach-Object {
356 Write-Host " $_" -ForegroundColor Gray
357 }
358
359 # Zone 유형별 통계
360 Write-Host ""
361 Write-Host "Records by Zone Type:" -ForegroundColor White
362 $allRecords | Group-Object ZoneType | Sort-Object Count -Descending | ForEach-Object {
363 Write-Host " $($_.Name): $($_.Count)" -ForegroundColor Gray
364 }
365
366 # 레코드 타입별 통계
367 Write-Host ""
368 Write-Host "Records by Record Type:" -ForegroundColor White
369 $allRecords | Group-Object RecordType | Sort-Object Count -Descending | ForEach-Object {
370 Write-Host " $($_.Name): $($_.Count)" -ForegroundColor Gray
371 }
372
373 # 상위 도메인 출력
374 Write-Host ""
375 Write-Host "Sample Records (first 20):" -ForegroundColor White
376 $allRecords | Select-Object -First 20 | ForEach-Object {
377 Write-Host " [$($_.RecordType)] $($_.Domain) -> $($_.Value)" -ForegroundColor Gray
378 }