Repository: tothi/ad-honeypot-autodeploy Branch: master Commit: ec48267abddf Files: 34 Total size: 78.7 KB Directory structure: gitextract_tyoks_c1/ ├── README.md ├── ansible/ │ ├── dashboard.json │ ├── gen_users.py │ ├── graylog_config.sh │ ├── hosts │ ├── id.pub │ ├── nxlog.conf │ ├── rdp_public.sh │ ├── requirements.txt │ ├── setup-domain.yml │ └── wordlist.txt ├── init-passwords.sh ├── packer/ │ ├── .ssh/ │ │ ├── id_ed25519 │ │ └── id_ed25519.pub │ ├── answer_files/ │ │ ├── graylog/ │ │ │ ├── preseed.cfg │ │ │ └── preseed.cfg~ │ │ ├── win10/ │ │ │ └── Autounattend.xml │ │ ├── win2012r2/ │ │ │ └── Autounattend.xml │ │ └── win2016/ │ │ └── Autounattend.xml │ ├── get-virtio.sh │ ├── graylog.json │ ├── packer-build-all.sh │ ├── private.json │ ├── scripts/ │ │ ├── bootstrap.ps1 │ │ ├── graylog.sh │ │ ├── setupcomplete.ps1 │ │ ├── shutdown.ps1 │ │ └── win2012r2-dotnet-fix.ps1 │ ├── win10.json │ ├── win2012r2.json │ └── win2016.json └── terraform/ ├── main.tf ├── network-dhcp-lease.xsl └── timer-patch.xsl ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ # ad-honeypot-autodeploy Deploy a small, intentionally insecure, vulnerable Windows Domain for RDP Honeypot fully automatically. Runs on self-hosted virtualization using [libvirt](https://libvirt.org) with [QEMU](https://www.qemu.org/)/[KVM](https://www.linux-kvm.org/page/Main_Page) (but it can be customized easily for cloud-based solutions). Used for painlessly set up a small Windows Domain from scratch automatically (without user interaction) for the purpose of RDP Honeypot testing. Features a Domain Controller, a Desktop Computer and a configured Graylog server for logging the actions of the bad guys. ## Automatic deployment phases 1. [Packer](https://www.packer.io/): download the necessary install media and setup the automated base virtual machine images unattended. 2. [Terraform](https://www.terraform.io/): provision the libvirt virtualization infrastructure (network + virtual machines) using the packer-prepared virtual machine images. 3. [Ansible](https://www.ansible.com/): Configure the infrastructure (DC, Desktop, Graylog) automatically, without user interaction. After going through the Packer+Terraform+Ansible pipeline, the configured Windows Domain should be up and running, you could attach the RDP service of the Desktop to the public internet, and let's monitor the events through the Graylog. ## Features Features of the running system are: * a Windows Server 2016 as a Domain Controller * a Windows 10 Desktop (version 21H2) as a Domain Computer * a [Graylog](https://www.graylog.org/) 3.3 (Open Source edition) running as a Log Collector on [Ubuntu](https://ubuntu.com/) 18.04 LTS * Using [VirtIO](https://wiki.libvirt.org/page/Virtio) drivers for best performance * Enabled RDP and WinRM Services * Populated Windows Active Directory with random users * [Sysmon](https://docs.microsoft.com/en-us/sysinternals/downloads/sysmon) (from [Windows Sysinternals](https://docs.microsoft.com/en-us/sysinternals/)) installed and running on Domain Computers * [NXLog](https://nxlog.co/) Collector running a Domain Computers and forwarding logs to Graylog * Configured Graylog GeoIP lookup table and pipeline for IP addresses (useful for showing a map of invalid RDP login attempts) * Graylog World Map of RDP attacks * an extra [Kali](https://www.kali.org/) VM attached to the Windows subnet for playing with attack techniques ## Host System Requirements Virtualization needs some power of your host system: * ~100 GB disk space for the base images and the sparse images of the guest computers. * at least 4 x 4 GB memory for the guest machines (may run with less than 16 GB because of overcommitment) * installed up-to-date libvirt with QEMU/KVM (official current packages in Ubuntu 18.04 LTS should work) * Python 3 (preferably with venv) for Ansible Tested on Ubuntu 18.04 LTS host. ## Installation and Usage First, clone the repo: ``` git clone https://github.com/tothi/ad-honeypot-autodeploy cd ad-honeypot-autodeploy ``` Before starting with Packer, set up the intial passwords (watch for complexity requirements): ``` ./init_passwords.sh ``` ### Packer Now build the initial images. ``` cd packer ``` Windows Server 2016 and Ubuntu installation media should be downloaded by the Packer script. VirtIO needs to be downloaded by the attached get-virtio.sh script: ``` ./get-virtio.sh ``` Windows 10 should be downloaded manually by getting a temporary download link and save it to the ISO folder. The download link could be obtained from [here](https://www.microsoft.com/hu-hu/software-download/windows10ISO). Select the English (International), 64-bit version and save the ISO to `ISO/Win10_21H2_EnglishInternational_x64.iso`. For mapping IP locations on a World Map in Graylog, the MaxMind GeoIP database is needed. Unfortunately due to licensing terms it cannot be redistributed, so you have to download it manually (after registering) from the [MaxMind site](https://www.maxmind.com). The free GeoLite2 version should work, get the "GeoLite2 City" Database in MMDB format (download the GZIP and untar) and put it at `resources/GeoLite2-City.mmdb`. If you do not have Packer, get the latest version from the packer.io site ([download the pre-compiled binary](https://www.packer.io/downloads.html)) or try to [add the Hashicorp repository](https://learn.hashicorp.com/tutorials/terraform/install-cli) to your packaging system (useful for Terrafrom also). If you are rebuilding the images, do not forget to clean up previous builds: ``` rm -fr output_* ``` If you want to re-download the images, remove packer_cache: ``` rm -fr packer_cache ``` After these preparing steps, run the Packer builds in parallel: ``` ./packer-build-all.sh ``` ![Packer in action](./packer.png) The images should be ready in a reasonable time (~20-30 mins depending on your host hardware power). ### Terraform Now the infrastructure can be deployed using Terraform. Get Terraform (>=0.13) if you do not have it (look at the install methods at Packer, above). [Terraform provider for libvirt](https://github.com/dmacvicar/terraform-provider-libvirt) should be automatically downloaded from the [Terraform Registry](https://registry.terraform.io/) during the apply phase. Enter Terraform folder: ``` cd ../terraform ``` Initialize the working directory (only needed for first time use): ``` terraform init ``` Build and launch the infrastructure ("apply the changes"): ``` terraform apply ``` Note, that if the user running `terraform apply` is not root, sudo privileges for running `/usr/sbin/iptables` is needed (without password). ![Terraform in action](./terraform.png) After a short time (~2-3 mins), the network and virtual machines are up and running. If there are any failures, `terraform destroy` might not be enough, manual undefining resources may be necessary. > WARNING: You should take care of protecting your private > network. The terraform config (main.tf) provided here just contains > a custom firewall rule for my own testing environment > (blocking 192.168.0.0/16 destination traffic from the > 192.168.3.0/24 honeypot network). Next is the configuration phase. ### Ansible Get into the ansible folder: ``` cd ../ansible ``` Recommended installation method is installing the latest Ansible with some required additional dependencies in a Python venv virtualized environment: ``` python3 -m venv venv . ./venv/bin/activate pip3 install -r requirements.txt ``` For later use just activate the venv by ``` . ./venv/bin/activate ``` And just `deactivate` if it is not needed anymore in your current session. You should put an SSH public key with filename `id.pub` (use `ssh-keygen`) into the ansible folder for accessing the Ubuntu Graylog machine with the ubuntu user (ansible will add it to `~ubuntu/.ssh/authorized_keys`). The `wordlist.txt` file contains some (intentionally weak) passwords for the populated domain users which can be customized. Run the configuration phase: ``` ansible-playbook -i hosts setup-domain.yml -v ``` ![Ansible in action](./ansible.png) After 20-25 mins everything is ready. ## The deployed system | hostname | ip address | operating system | role | | --------- | ------------- | ------------------------- | ------------------------- | | dc1 | 192.168.3.100 | Windows Server 2016 | Domain Controller | | desktop12 | 192.168.3.112 | Windows 10 (version 2004) | Domain Member Workstation | | graylog | 192.168.3.191 | Ubuntu 18.04 LTS | Graylog Server | | kali | 192.168.3.192 | Kali Rolling (2022.3) | Offensive Operations | According to the libvirt network configuration (NAT), the hosts can access the public internet (if your host system allows it). Accessing the hosts is possible through the host system. Practically using an SSH socks tunnel and proxychains for RDP or WinRM access is very comfortable. For example, if your libvirt host IP is 192.168.0.10, create a socks tunnel listening on `localhost:5000` by ``` ssh 192.168.0.10 -D5000 -NTv ``` And access the Windows 10 desktop (using an appropriate `/etc/proxychains.conf` configured for the :5000 tunnel): ``` proxychains xfreerdp /v:192.168.3.112 /u:administrator ``` Or, access the Graylog web interface listening on :9000 locally on the Graylog Ubuntu server by SSH ProxyJump and custom forward tunnel: ``` ssh -J 192.168.0.10 ubuntu@192.168.3.191 -NTv -L9000:127.0.0.1:9000 ``` Then open URL `http://localhost:9000` and you reach the Graylog web interface. For activating the RDP honeypot, just allow public access to 192.168.3.112:3389 (for example with some port forwarding configuration on your router and iptables rules on the host machine; my helper script is [rdp_public.sh](ansible/rdp_public.sh)) and keep watching the Graylog. ;) ================================================ FILE: ansible/dashboard.json ================================================ { "v": 1, "id": "2397d589-a1fd-4ad8-b271-e72f44b4611f", "rev": 1, "name": "RDP Attack Dashboard", "summary": "Monitoring Dashboard for RDP Attacks", "description": "", "vendor": "an0n", "url": "", "parameters": [], "entities": [ { "v": "1", "type": { "name": "dashboard", "version": "2" }, "id": "e42b37b9-3e53-4e0e-ab66-064d57feacab", "data": { "summary": { "@type": "string", "@value": "Monitor RDP Attacks" }, "search": { "queries": [ { "id": "703206bc-2209-44c6-9027-148d841210ba", "timerange": { "type": "relative", "range": 300 }, "query": { "type": "elasticsearch", "query_string": "" }, "search_types": [ { "query": { "type": "elasticsearch", "query_string": "EventID: 4625" }, "name": "chart", "timerange": { "type": "relative", "range": 0 }, "streams": [], "series": [ { "type": "count", "id": "Message Count", "field": null } ], "filter": null, "rollup": true, "row_groups": [], "type": "pivot", "id": "30b34c81-72ee-411e-a9e6-8bfc6d13fdce", "column_groups": [], "sort": [] }, { "query": { "type": "elasticsearch", "query_string": "EventID: 4625" }, "name": "chart", "timerange": { "type": "relative", "range": 0 }, "streams": [], "series": [ { "type": "count", "id": "count()", "field": null } ], "filter": null, "rollup": true, "row_groups": [ { "type": "values", "field": "IpAddress_geo_city", "limit": 15 } ], "type": "pivot", "id": "56cd0b71-feac-4765-9750-7fc3f20486c2", "column_groups": [], "sort": [] }, { "query": { "type": "elasticsearch", "query_string": "EventID: 4625" }, "name": "chart", "timerange": { "type": "relative", "range": 0 }, "streams": [], "series": [ { "type": "count", "id": "count()", "field": null } ], "filter": null, "rollup": true, "row_groups": [ { "type": "values", "field": "IpAddress_geo_location", "limit": 15 } ], "type": "pivot", "id": "1f393b80-ef25-40d4-b04d-0d4a9d8156f1", "column_groups": [], "sort": [] }, { "query": { "type": "elasticsearch", "query_string": "EventID: 4624 AND LogonType: 10" }, "name": "chart", "timerange": { "type": "relative", "range": 0 }, "streams": [], "series": [ { "type": "count", "id": "Message Count", "field": null } ], "filter": null, "rollup": true, "row_groups": [], "type": "pivot", "id": "3963de17-64fb-4178-8aec-11b4951cbb2e", "column_groups": [], "sort": [] } ] } ], "parameters": [], "requires": {}, "owner": "admin", "created_at": "2020-10-05T23:20:44.620Z" }, "created_at": "2020-10-05T22:34:45.717Z", "requires": {}, "state": { "703206bc-2209-44c6-9027-148d841210ba": { "selected_fields": null, "static_message_list_id": null, "titles": { "widget": { "3c978baf-6e85-431a-b4c1-6d82c3ddddc4": "RDP Attack Origin World Map", "4db2a07e-8b77-4716-b659-029e2750dbf8": "Failed Login Attempts", "c1e773ab-706a-4935-9c9c-7e23c02e6820": "RDP Attack Origin", "496f078b-9707-47c2-8829-094b35d61004": "Successful RDP Logins" } }, "widgets": [ { "id": "3c978baf-6e85-431a-b4c1-6d82c3ddddc4", "type": "aggregation", "filter": null, "timerange": { "type": "relative", "range": 0 }, "query": { "type": "elasticsearch", "query_string": "EventID: 4625" }, "streams": [], "config": { "visualization": "map", "event_annotation": false, "row_pivots": [ { "field": "IpAddress_geo_location", "type": "values", "config": { "limit": 15 } } ], "series": [ { "config": { "name": null }, "function": "count()" } ], "rollup": true, "column_pivots": [], "visualization_config": { "viewport": { "zoom": 1, "center_x": 51.83577752045248, "center_y": 23.203125000000004 } }, "formatting_settings": null, "sort": [] } }, { "id": "4db2a07e-8b77-4716-b659-029e2750dbf8", "type": "aggregation", "filter": null, "timerange": { "type": "relative", "range": 0 }, "query": { "type": "elasticsearch", "query_string": "EventID: 4625" }, "streams": [], "config": { "visualization": "numeric", "event_annotation": false, "row_pivots": [], "series": [ { "config": { "name": "Message Count" }, "function": "count()" } ], "rollup": true, "column_pivots": [], "visualization_config": null, "formatting_settings": null, "sort": [] } }, { "id": "496f078b-9707-47c2-8829-094b35d61004", "type": "aggregation", "filter": null, "timerange": { "type": "relative", "range": 0 }, "query": { "type": "elasticsearch", "query_string": "EventID: 4624 AND LogonType: 10" }, "streams": [], "config": { "visualization": "numeric", "event_annotation": false, "row_pivots": [], "series": [ { "config": { "name": "Message Count" }, "function": "count()" } ], "rollup": true, "column_pivots": [], "visualization_config": null, "formatting_settings": null, "sort": [] } }, { "id": "c1e773ab-706a-4935-9c9c-7e23c02e6820", "type": "aggregation", "filter": null, "timerange": { "type": "relative", "range": 0 }, "query": { "type": "elasticsearch", "query_string": "EventID: 4625" }, "streams": [], "config": { "visualization": "table", "event_annotation": false, "row_pivots": [ { "field": "IpAddress_geo_city", "type": "values", "config": { "limit": 15 } } ], "series": [ { "config": { "name": null }, "function": "count()" } ], "rollup": true, "column_pivots": [], "visualization_config": null, "formatting_settings": null, "sort": [] } } ], "widget_mapping": { "4db2a07e-8b77-4716-b659-029e2750dbf8": [ "30b34c81-72ee-411e-a9e6-8bfc6d13fdce" ], "496f078b-9707-47c2-8829-094b35d61004": [ "3963de17-64fb-4178-8aec-11b4951cbb2e" ], "c1e773ab-706a-4935-9c9c-7e23c02e6820": [ "56cd0b71-feac-4765-9750-7fc3f20486c2" ], "3c978baf-6e85-431a-b4c1-6d82c3ddddc4": [ "1f393b80-ef25-40d4-b04d-0d4a9d8156f1" ] }, "positions": { "4db2a07e-8b77-4716-b659-029e2750dbf8": { "col": 9, "row": 1, "height": 2, "width": 2 }, "c1e773ab-706a-4935-9c9c-7e23c02e6820": { "col": 7, "row": 1, "height": 4, "width": 2 }, "3c978baf-6e85-431a-b4c1-6d82c3ddddc4": { "col": 1, "row": 1, "height": 4, "width": 6 }, "496f078b-9707-47c2-8829-094b35d61004": { "col": 9, "row": 3, "height": 2, "width": 2 } }, "formatting": { "highlighting": [] }, "display_mode_settings": { "positions": {} } } }, "properties": [], "owner": "admin", "title": { "@type": "string", "@value": "RDP Attacks" }, "type": "DASHBOARD", "description": { "@type": "string", "@value": "Basic Monitoring Dashboard" } }, "constraints": [ { "type": "server-version", "version": ">=3.3.6+92fb41e" } ] } ] } ================================================ FILE: ansible/gen_users.py ================================================ #!/usr/bin/env python3 # # generate fake users for ad: # * importable by ansible win_domain_user module users.yml var file (slow) # * ps1 script format (fast) # from faker import Factory import random NUM_OF_USERS = 1000 OUT_YML = "users.yml" OUT_PS1 = "users.ps1" OU = "Staff" fake = Factory.create('en-GB') group_chance = {"Domain Admins": 0.05, "RDP All": 0.8} wordlist = list(map(lambda x: x.rstrip(), open("wordlist.txt", "r").readlines())) def grouplist(): res = [] for g in group_chance: if random.random() < group_chance[g]: res.append(g) return res yml = open(OUT_YML, "w") ps1 = open(OUT_PS1, "w") ps1.write('$d = (Get-ADDomain).DistinguishedName\r\n') ps1.write('If (Get-ADOrganizationalUnit -Filter "distinguishedName -eq \'OU={},$d\'") {{ Remove-ADOrganizationalUnit -Identity "OU={},$d" -Confirm:$False }}\r\n'.format(OU, OU)) ps1.write('New-ADOrganizationalUnit -Name "{}" -Path $d -ProtectedFromAccidentalDeletion $false\r\n'.format(OU)) yml.write("users:\n") groupdb = {} samdb = [] for i in range(NUM_OF_USERS): fn = fake.first_name() ln = fake.last_name() pw = random.choice(wordlist) samname_base = "{}.{}".format(fn.lower(), ln.lower()) samname = samname_base idx = 0 while samname in samdb: idx += 1 samname = "{}.{}".format(samname_base, idx) samdb.append(samname) if idx > 0: cn = "{} {} {}".format(fn, ln, idx) else: cn = "{} {}".format(fn, ln) yml.write(" - name: {}\n".format(samname)) yml.write(" firstname: {}\n".format(fn)) yml.write(" surname: {}\n".format(ln)) yml.write(" password: {}\n".format(pw)) yml.write(" state: present\n") yml.write(" groups:\n") ps1.write('New-ADUser -Enabled $true -AccountPassword (ConvertTo-SecureString -AsPlainText "{}" -Force) -Name "{}" -GivenName "{}" -Surname "{}" -SamAccountName "{}" -Path "OU={},$d"\r\n'.format(pw, cn, fn, ln, samname, OU)) for g in grouplist(): yml.write(" - {}\n".format(g)) if g not in groupdb: groupdb[g] = [] groupdb[g].append(samname) for g in groupdb: ps1.write('If (-Not (Get-ADGroup -Filter "Name -eq \'{}\'")) {{ New-ADGroup -Name "{}" -GroupScope Global }}'.format(g, g)) ps1.write('Add-ADGroupMember -Identity "{}" -Members "{}"\r\n'.format(g, '","'.join(groupdb[g]))) yml.close() ps1.close() ================================================ FILE: ansible/graylog_config.sh ================================================ #!/bin/bash # # create Graylog lookup table (using previously created adapter + caches) by API calls # USER="$1" PASS="$2" ADAPTER=`/usr/bin/curl -s -H 'Content-Type: application/json' -H 'X-Requested-By: cli' http://${USER}:${PASS}@127.0.0.1:9000/api/system/lookup/adapters | /usr/bin/jq '.data_adapters[] | select(.name=="geoip")' | /usr/bin/jq -r '.id'` CACHE=`/usr/bin/curl -s -H 'Content-Type: application/json' -H 'X-Requested-By: cli' http://${USER}:${PASS}@127.0.0.1:9000/api/system/lookup/caches | /usr/bin/jq '.caches[] | select(.name=="geoip")' | /usr/bin/jq -r '.id'` /usr/bin/curl -s -H 'Content-Type: application/json' -H 'X-Requested-By: cli' "http://${USER}:${PASS}@127.0.0.1:9000/api/system/lookup/tables" -X POST --data "{\"title\":\"GeoIP\",\"description\":\"GeoIP Lookup Table\",\"name\":\"geoip\",\"cache_id\":\"${CACHE}\",\"data_adapter_id\":\"${ADAPTER}\",\"content_pack\":null,\"default_single_value\":\"\",\"default_single_value_type\":\"NULL\",\"default_multi_value\":\"\",\"default_multi_value_type\":\"NULL\"}}" /usr/bin/curl -s -H 'Content-Type: application/json' -H 'X-Requested-By: cli' "http://${USER}:${PASS}@127.0.0.1:9000/api/system/pipelines/rule" -X POST --data '{"title":"GeoIP lookup: IpAddress","description":"","source":"rule \"GeoIP lookup: IpAddress\"\nwhen\n has_field(\"IpAddress\")\nthen\nlet geo = lookup(\"geoip\", to_string($message.IpAddress));\nset_field(\"IpAddress_geo_location\", geo[\"coordinates\"]);\nset_field(\"IpAddress_geo_country\", geo[\"country\"].iso_code);\nset_field(\"IpAddress_geo_city\", geo[\"city\"].names.en);\nend\n"}' /usr/bin/curl -s -H 'Content-Type: application/json' -H 'X-Requested-By: cli' "http://${USER}:${PASS}@127.0.0.1:9000/api/system/pipelines/pipeline" -X POST --data '{"title":"GeoIP lookup","description":"","source":"pipeline \"GeoIP lookup\"\nstage 0 match either\nrule \"GeoIP lookup: IpAddress\"\nend","stages":[{"stage":0,"match_all":false,"rules":["GeoIP lookup: IpAddress"]}]}' /usr/bin/curl -s -H 'Content-Type: application/json' -H 'X-Requested-By: cli' "http://${USER}:${PASS}@127.0.0.1:9000/api/system/pipelines/pipeline" PIPELINE=`/usr/bin/curl -s -H 'Content-Type: application/json' -H 'X-Requested-By: cli' "http://${USER}:${PASS}@127.0.0.1:9000/api/system/pipelines/pipeline" | /usr/bin/jq '.[] | select(.title=="GeoIP lookup")' | /usr/bin/jq -r '.id'` STREAM="000000000000000000000001" /usr/bin/curl -s -H 'Content-Type: application/json' -H 'X-Requested-By: cli' "http://${USER}:${PASS}@127.0.0.1:9000/api/system/pipelines/connections/to_stream" -X POST --data "{\"stream_id\":\"${STREAM}\",\"pipeline_ids\":[\"${PIPELINE}\"]}" /usr/bin/curl -s -H 'Content-Type: application/json' -H 'X-Requested-By: cli' "http://${USER}:${PASS}@127.0.0.1:9000/api/system/content_packs" -X POST -d @/home/ubuntu/dashboard.json /usr/bin/curl -s -H 'Content-Type: application/json' -H 'X-Requested-By: cli' "http://${USER}:${PASS}@127.0.0.1:9000/api/system/content_packs/2397d589-a1fd-4ad8-b271-e72f44b4611f/1/installations" -X POST -d '{"parameters": {}, "comment": ""}' ================================================ FILE: ansible/hosts ================================================ [domain] dc1 ansible_host=192.168.3.100 ansible_user=Administrator ansible_password='{{ domain_admin_password }}' desktop12 ansible_host=192.168.3.112 ansible_user=Administrator ansible_password='{{ default_password }}' [domain:vars] ansible_connection=winrm ansible_winrm_transport=ntlm ansible_port=5985 dns_name=ecorp.local default_password= domain_admin_password= dsrm_password= [monitor] graylog ansible_host=192.168.3.191 ansible_connection=ssh ansible_user=ubuntu ansible_ssh_private_key_file=../packer/.ssh/id_ed25519 [monitor:vars] ubuntu_password= graylog_admin=admin graylog_pwd= ================================================ FILE: ansible/id.pub ================================================ ================================================ FILE: ansible/nxlog.conf ================================================ define ROOT C:\Program Files (x86)\nxlog Moduledir %ROOT%\modules Module xm_gelf Module im_msvistalog Module om_udp Host 192.168.3.191 Port 12201 OutputType GELF Path eventlog => graylog ================================================ FILE: ansible/rdp_public.sh ================================================ #!/bin/bash # help () { echo "$0 [on/off]" } if [ $# -ne 1 ]; then help exit 0 fi if [ "$1" == "on" ]; then iptables -I FORWARD -p tcp -d 192.168.3.112 --dport 3389 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT iptables -t nat -A PREROUTING -p tcp -i br0 --dport 14999 -j DNAT --to-destination 192.168.3.112:3389 echo "Public RDP access enabled" exit 0 elif [ "$1" == "off" ]; then iptables -D FORWARD -p tcp -d 192.168.3.112 --dport 3389 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT iptables -t nat -D PREROUTING -p tcp -i br0 --dport 14999 -j DNAT --to-destination 192.168.3.112:3389 echo "Public RDP access disabled" exit 0 fi help exit 0 ================================================ FILE: ansible/requirements.txt ================================================ wheel ansible pywinrm faker ================================================ FILE: ansible/setup-domain.yml ================================================ - name: Configure DC1 hosts: dc1 gather_facts: false # vars_files: # - users.yml tasks: - name: Using the default admin password set_fact: ansible_password='{{ default_password }}' - name: Waiting for WinRM... wait_for_connection: - name: Install AD Features win_feature: name: AD-Domain-Services include_management_tools: yes include_sub_features: yes state: present register: ad_features - name: Set Hostname win_hostname: name: '{{ inventory_hostname }}' register: dc_hostname - name: Reboot Server win_reboot: msg: "Installing AD. Rebooting..." pre_reboot_delay: 15 when: ad_features.reboot_required or dc_hostname.reboot_required - name: Install Domain win_domain: dns_domain_name: '{{ dns_name }}' safe_mode_password: '{{ dsrm_password }}' register: ad - name: Reboot Server win_reboot: msg: "Installing AD. Rebooting..." pre_reboot_delay: 15 when: ad.reboot_required - name: Waiting for Active Directory Web Services... win_wait_for: port: 9389 - name: Weaken Security Policy win_shell: Set-ADDefaultDomainPasswordPolicy -Identity {{ dns_name }} -ComplexityEnabled 0 -LockoutThreshold 0 retries: 10 delay: 30 register: result until: result.rc == 0 - name: Change Administrator password in domain win_domain_user: name: Administrator state: present password: '{{ domain_admin_password }}' - name: Using the new domain Administrator password set_fact: ansible_password='{{ domain_admin_password }}' - name: Generate ADUser PS script command: ./gen_users.py delegate_to: 127.0.0.1 - name: Populate domain users script: users.ps1 # - name: Populate domain users # win_domain_user: # name: '{{ item.name }}' # state: present # password: '{{ item.password }}' # groups: '{{ item.groups }}' # with_items: '{{ users }}' # async: 600 # poll: 0 # register: create_users # # - name: Check users # async_status: # jid: "{{ create_users.ansible_job_id }}" # register: job_result # until: job_result.finished # retries: 30 - name: Configure Graylog hosts: graylog gather_facts: false tasks: - name: Write new host keys to known hosts shell: '/usr/bin/ssh-keygen -R {{ ansible_host }}; /usr/bin/ssh-keyscan -H {{ ansible_host }} | /bin/grep -v "^#" >> ~/.ssh/known_hosts' delegate_to: 127.0.0.1 - name: Configure GELF UDP Collection uri: url: http://127.0.0.1:9000/api/system/inputs method: POST user: "{{ graylog_admin }}" password: "{{ graylog_pwd }}" body: '{"title":"nxlog_udp","type":"org.graylog2.inputs.gelf.udp.GELFUDPInput","configuration":{"bind_address":"0.0.0.0","port":12201,"recv_buffer_size":262144,"override_source":null,"decompress_size_limit":8388608},"global":true}' force_basic_auth: yes status_code: 201 body_format: json headers: X-Requested-By: cli - name: Add Graylog Adapter for GeoIP Lookup uri: url: http://127.0.0.1:9000/api/system/lookup/adapters method: POST user: "{{ graylog_admin }}" password: "{{ graylog_pwd }}" body: '{"title":"GeoIP","description":"GeoIP Adapter","name":"geoip","custom_error_ttl_enabled":false,"custom_error_ttl":null,"custom_error_ttl_unit":null,"content_pack":null,"config":{"type":"maxmind_geoip","type":"maxmind_geoip","path":"/etc/graylog/server/GeoLite2-City.mmdb","database_type":"MAXMIND_CITY","check_interval":1,"check_interval_unit":"HOURS"}}' force_basic_auth: yes status_code: 200 body_format: json headers: X-Requested-By: cli - name: Add Graylog Cache for GeoIP Lookup uri: url: http://127.0.0.1:9000/api/system/lookup/caches method: POST user: "{{ graylog_admin }}" password: "{{ graylog_pwd }}" body: '{"config":{"type":"guava_cache","type":"guava_cache","max_size":1000,"expire_after_access":1,"expire_after_access_unit":"HOURS","expire_after_write":0,"expire_after_write_unit":null},"title":"GeoIP","description":"GeoIP Cache","name":"geoip","content_pack":null}' force_basic_auth: yes status_code: 200 body_format: json headers: X-Requested-By: cli - name: Copy dashboard.json copy: src: dashboard.json dest: '/home/ubuntu/dashboard.json' - name: More Configuration for Graylog... script: graylog_config.sh '{{ graylog_admin }}' '{{ graylog_pwd }}' - name: Disable SSH Password Auth shell: "echo {{ ubuntu_password }} | /usr/bin/sudo -S /bin/sh -c '/bin/sed -i \"/PasswordAuthentication/cPasswordAuthentication no\" -i /etc/ssh/sshd_config; /bin/systemctl reload sshd'" - name: Add extra SSH key lineinfile: path: /home/ubuntu/.ssh/authorized_keys insertafter: EOF line: "{{ lookup('file', 'id.pub') }}" - name: Join Desktops hosts: domain gather_facts: true tasks: - name: Sync NTP Time win_command: w32tm /resync - name: Join Desktops win_domain_membership: dns_domain_name: '{{ dns_name }}' hostname: '{{ inventory_hostname }}' domain_admin_user: Administrator@{{dns_name}} domain_admin_password: '{{ domain_admin_password }}' state: domain register: domain_state - name: Reboot Server win_reboot: msg: 'Joined Domain. Rebooting...' when: domain_state.reboot_required - name: Install Softwares on Domain hosts: domain gather_facts: false tasks: - name: Install Firefox win_chocolatey: name: firefox state: present - name: Install NXLog Collector win_chocolatey: name: nxlog state: present - name: Install Sysmon win_chocolatey: name: sysmon ignore_checksums: true state: present - name: Configure Logging hosts: domain gather_facts: false tasks: - name: Configure NXLog win_copy: src: nxlog.conf dest: 'c:\Program Files (x86)\nxlog\conf\nxlog.conf' - name: Fetch Sysmon config win_get_url: url: https://raw.githubusercontent.com/SwiftOnSecurity/sysmon-config/master/sysmonconfig-export.xml dest: 'c:\windows\sysmonconfig-export.xml' - name: Launch Sysmon Service win_command: 'c:\ProgramData\chocolatey\bin\Sysmon64.exe -accepteula -i c:\windows\sysmonconfig-export.xml' - name: Restart NXLog win_service: name: nxlog state: restarted ================================================ FILE: ansible/wordlist.txt ================================================ Summer2020 Spring2020 Spring2019 Winter2019 Autumn2019 Password123 ================================================ FILE: init-passwords.sh ================================================ #!/bin/bash # echo "[*] Setting initial passwords." echo -n "[?] Enter default Windows local Administrator password: " read -s adminpass echo echo -n "[?] Enter Windows Domain Admin password for user ECORP\\Administrator: " read -s domainadminpass echo echo -n "[?] Enter DSRM password for Windows Domain: " read -s dsrmpass echo echo -n "[?] Enter password for sudo user 'ubuntu' on Ubuntu (Graylog) system: " read -s ubuntupass echo echo -n "[?] Enter Graylog password for root user 'admin': " read -s graylogpass echo echo -n "[?] Enter password for sudo user 'kali' on Kali system: " read -s kalipass echo echo "[*] Setting Windows local Administrator password, Ubuntu user password and Kali password in packer/private.json" adminpass_esc=$(printf '%s\n' "${adminpass}" | sed -e 's/[\/&]/\\&/g') ubuntupass_esc=$(printf '%s\n' "${ubuntupass}" | sed -e 's/[\/&]/\\&/g') kalipass_esc=$(printf '%s\n' "${kalipass}" | sed -e 's/[\/&]/\\&/g') sed -i packer/private.json -e "s/\"administrator_password\": \".*\"/\"administrator_password\": \"${adminpass_esc}\"/" \ -e "s/\"ubuntu_password\": \".*\"/\"ubuntu_password\": \"${ubuntupass_esc}\"/" \ -e "s/\"kali_password\": \".*\"/\"kali_password\": \"${kalipass_esc}\"/" p1=`echo -n "${adminpass}Password" | iconv -tutf-16le | base64 -w0` p2=`echo -n "${adminpass}AdministratorPassword" | iconv -tutf-16le | base64 -w0` for w in win2016 win10 win2012r2; do a="packer/answer_files/${w}/Autounattend.xml" echo "[*] Setting Windows local Administrator password in ${a} for UserAccounts and AutoLogon" sed -i "$a" -e "//,/<\/Password>/ s/.*<\/Value>/${p1}<\/Value>/" \ -e "//,/<\/AdministratorPassword>/ s/.*<\/Value>/${p2}<\/Value>/" done echo "[*] Creating SSH key for Ubuntu (Graylog) and Kali access..." rm -fr packer/.ssh mkdir packer/.ssh ssh-keygen -t ed25519 -f packer/.ssh/id_ed25519 -N "" -C supervisor@infra SSH_PUBKEY=`cat packer/.ssh/id_ed25519.pub | tr -d '\n'` echo "[*] Setting Ubuntu password and SSH key in packer/answer_files/graylog/preseed.cfg" ubuntupasscrypt=`mkpasswd -m sha-512 -S $(pwgen -ns 16 1) ${ubuntupass}` sed -i packer/answer_files/graylog/preseed.cfg -e "s#d-i passwd/user-password-crypted password .*#d-i passwd/user-password-crypted password ${ubuntupasscrypt}#" \ -e "s#echo ssh-ed25519 AAA.* supervisor@infra#echo ${SSH_PUBKEY}#" echo "[*] Setting Graylog password in packer/scripts/graylog.sh" graylogsha2=`echo -n "${graylogpass}" | sha256sum | cut -d' ' -f1` sed -i packer/scripts/graylog.sh -e "s/GRAYLOG_SHA2=\".*\"/GRAYLOG_SHA2=\"${graylogsha2}\"/" echo "[*] Setting Kali password and SSH key in packer/answer_files/kali/preseed.cfg" kalipasscrypt=`mkpasswd -m sha-512 -S $(pwgen -ns 16 1) ${kalipass}` sed -i packer/answer_files/kali/preseed.cfg -e "s#d-i passwd/user-password-crypted password .*#d-i passwd/user-password-crypted password ${kalipasscrypt}#" \ -e "s#echo ssh-ed25519 AAA.* supervisor@infra#echo ${SSH_PUBKEY}#" echo "[*] Updating passwords in ansible/hosts" domainadminpass_esc=$(printf '%s\n' "${domainadminpass}" | sed -e 's/[\/&]/\\&/g') dsrmpass_esc=$(printf '%s\n' "${dsrmpass}" | sed -e 's/[\/&]/\\&/g') graylogpass_esc=$(printf '%s\n' "${graylogpass}" | sed -e 's/[\/&]/\\&/g') sed -i ansible/hosts -e "s/^default_password=.*/default_password=\"${adminpass_esc}\"/" \ -e "s/^domain_admin_password=.*/domain_admin_password=\"${domainadminpass_esc}\"/" \ -e "s/^dsrm_password=.*/dsrm_password=\"${dsrmpass_esc}\"/" \ -e "s/^ubuntu_password=.*/ubuntu_password=\"${ubuntupass_esc}\"/" \ -e "s/^graylog_pwd=.*/graylog_pwd=\"${graylogpass_esc}\"/" echo "[+] Done. Deploy with packer+terraform+ansible." ================================================ FILE: packer/.ssh/id_ed25519 ================================================ -----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW QyNTUxOQAAACCGBoPvk7BWDdlq9umPbz3LbaDEV8egoPlg++gzywglxwAAAJhCYCqIQmAq iAAAAAtzc2gtZWQyNTUxOQAAACCGBoPvk7BWDdlq9umPbz3LbaDEV8egoPlg++gzywglxw AAAEA7owk2wVvt21vApPlle6zQ8IaQpi/LiTh2aab5jFiwh4YGg++TsFYN2Wr26Y9vPctt oMRXx6Cg+WD76DPLCCXHAAAAEnVidW50dUBwYWNrZXItaG9zdAECAw== -----END OPENSSH PRIVATE KEY----- ================================================ FILE: packer/.ssh/id_ed25519.pub ================================================ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIYGg++TsFYN2Wr26Y9vPcttoMRXx6Cg+WD76DPLCCXH ubuntu@packer-host ================================================ FILE: packer/answer_files/graylog/preseed.cfg ================================================ #d-i debconf/priority string critical #d-i auto-install/enable boolean true # localization d-i debian-installer/locale string en_US # keyboard d-i console-setup/ask_detect boolean false d-i keyboard-configuration/xkb-keymap select us # use dhcp network configuration d-i netcfg/choose_interface select auto # user setup d-i passwd/user-fullname string ubuntu d-i passwd/username string ubuntu # mkpasswd -m sha-512 -S $(pwgen -ns 16 1) mypassword d-i passwd/user-password-crypted password $6$r7ItP8TFvsgaLKsa$MvlIgvX/wpjITq/74dPLebOfoS9CoEA9NWuFPKfVonmZKiPQGYI6f6wflHPgOEBGGRAHRDd9vDM7Ox9TbPrOh1 # clock & timezone d-i clock-setup/utc boolean true d-i time/zone string Europe/Budapest # auto-partition, all files in one partition d-i partman-auto/method string regular d-i partman-auto/choose_recipe select atomic d-i partman/choose_partition select finish d-i partman/confirm_nooverwrite boolean true d-i partman/confirm boolean true # packages d-i pkgsel/include string openssh-server d-i pkgsel/upgrade select full-upgrade d-i pkgsel/update-policy select none # reboot at the end d-i finish-install/reboot_in_progress note d-i preseed/late_command string \ in-target sh -c "mkdir -m 700 /home/ubuntu/.ssh ; echo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIYGg++TsFYN2Wr26Y9vPcttoMRXx6Cg+WD76DPLCCXH ubuntu@packer-host > /home/ubuntu/.ssh/authorized_keys; chmod 600 /home/ubuntu/.ssh/authorized_keys; chown -R ubuntu:ubuntu /home/ubuntu/.ssh" ================================================ FILE: packer/answer_files/graylog/preseed.cfg~ ================================================ #d-i debconf/priority string critical #d-i auto-install/enable boolean true # localization d-i debian-installer/locale string en_US # keyboard d-i console-setup/ask_detect boolean false d-i keyboard-configuration/xkb-keymap select us # use dhcp network configuration d-i netcfg/choose_interface select auto # user setup d-i passwd/user-fullname string ubuntu d-i passwd/username string ubuntu # mkpasswd -m sha-512 -S $(pwgen -ns 16 1) mypassword d-i passwd/user-password-crypted password $6$WwICQQbv2lPNRZLh$4oivwzgiU/ydX4NcljluqtJfRKmJO.ktaj/fDCiv.bcqzxeQiEfDwcK8mKMteNHKzYtapG6znOhNTFpDIeuFI. # clock & timezone d-i clock-setup/utc boolean true d-i time/zone string Europe/Budapest # auto-partition, all files in one partition d-i partman-auto/method string regular d-i partman-auto/choose_recipe select atomic d-i partman/choose_partition select finish d-i partman/confirm_nooverwrite boolean true d-i partman/confirm boolean true # packages d-i pkgsel/include string openssh-server d-i pkgsel/upgrade select full-upgrade d-i pkgsel/update-policy select none # reboot at the end d-i finish-install/reboot_in_progress note d-i preseed/late_command string \ in-target sh -c "mkdir -m 700 /home/ubuntu/.ssh ; echo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMR2FQi9N1SCsnrwpAuyiGo4e/rpZ665q28wu3QPvXBh istvan@archive01-host > /home/ubuntu/.ssh/authorized_keys; chmod 600 /home/ubuntu/.ssh/authorized_keys; chown -R ubuntu:ubuntu /home/ubuntu/.ssh" ================================================ FILE: packer/answer_files/win10/Autounattend.xml ================================================ en-US 0409:00000409 en-GB en-GB en-GB a:\ 1 350 Primary 2 true Primary true false NTFS 1 1 false false NTFS C 2 2 0 true /IMAGE/NAME Windows 10 Pro 0 2 false OnError VK7JG-NPHTM-C97JM-9MPGT-3V66T OnError true UABhAHMAcwB3AG8AcgBkAA== false</PlainText> </Password> <Enabled>true</Enabled> <LogonCount>1</LogonCount> <Username>administrator</Username> </AutoLogon> <FirstLogonCommands> <SynchronousCommand wcm:action="add"> <CommandLine>reg add &quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon&quot; /v AutoLogonCount /t REG_DWORD /d 0 /f</CommandLine> <Description>Disable AutoLogon (LogonCount issue fix)</Description> <Order>1</Order> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>2</Order> <Description>Bootstrap Script</Description> <CommandLine>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ep bypass a:\bootstrap.ps1</CommandLine> </SynchronousCommand> </FirstLogonCommands> <OOBE> <HideEULAPage>true</HideEULAPage> <HideLocalAccountScreen>true</HideLocalAccountScreen> <HideOnlineAccountScreens>true</HideOnlineAccountScreens> <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> <ProtectYourPC>3</ProtectYourPC> </OOBE> <UserAccounts> <AdministratorPassword> <Value>QQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBQAGEAcwBzAHcAbwByAGQA</Value> <PlainText>false</PlainText> </AdministratorPassword> </UserAccounts> <TimeZone>Central Europe Standard Time</TimeZone> </component> <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <InputLocale>0409:00000409</InputLocale> <UserLocale>en-GB</UserLocale> <UILanguage>en-GB</UILanguage> <SystemLocale>en-GB</SystemLocale> </component> </settings> <cpi:offlineImage cpi:source="wim://vboxsvr/_shared/win10_rw/install.wim#Windows 10 Pro" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> </unattend> ================================================ FILE: packer/answer_files/win2012r2/Autounattend.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <unattend xmlns="urn:schemas-microsoft-com:unattend"> <settings pass="windowsPE"> <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DiskConfiguration> <Disk wcm:action="add"> <CreatePartitions> <CreatePartition wcm:action="add"> <Order>1</Order> <Size>350</Size> <Type>Primary</Type> </CreatePartition> <CreatePartition wcm:action="add"> <Extend>true</Extend> <Order>2</Order> <Type>Primary</Type> </CreatePartition> </CreatePartitions> <ModifyPartitions> <ModifyPartition wcm:action="add"> <Active>true</Active> <Extend>false</Extend> <Format>NTFS</Format> <Label>Boot</Label> <Order>1</Order> <PartitionID>1</PartitionID> </ModifyPartition> <ModifyPartition wcm:action="add"> <Active>false</Active> <Extend>false</Extend> <Format>NTFS</Format> <Label>OS</Label> <Letter>C</Letter> <Order>2</Order> <PartitionID>2</PartitionID> </ModifyPartition> </ModifyPartitions> <DiskID>0</DiskID> <WillWipeDisk>true</WillWipeDisk> </Disk> </DiskConfiguration> <ImageInstall> <OSImage> <InstallFrom> <MetaData wcm:action="add"> <Key>/IMAGE/NAME</Key> <Value>Windows Server 2012 R2 SERVERSTANDARD</Value> </MetaData> </InstallFrom> <InstallTo> <DiskID>0</DiskID> <PartitionID>2</PartitionID> </InstallTo> <InstallToAvailablePartition>false</InstallToAvailablePartition> <WillShowUI>OnError</WillShowUI> </OSImage> </ImageInstall> <UserData> <ProductKey> <WillShowUI>OnError</WillShowUI> </ProductKey> <AcceptEula>true</AcceptEula> </UserData> </component> <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SetupUILanguage> <UILanguage>en-US</UILanguage> </SetupUILanguage> <InputLocale>en-US</InputLocale> <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> </component> <component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DriverPaths> <PathAndCredentials wcm:action="add" wcm:keyValue="1"> <Path>a:\</Path> </PathAndCredentials> </DriverPaths> </component> </settings> <settings pass="oobeSystem"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <UserAccounts> <AdministratorPassword> <Value>QQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBQAGEAcwBzAHcAbwByAGQA</Value> <PlainText>false</PlainText> </AdministratorPassword> </UserAccounts> <AutoLogon> <Password> <Value>UABhAHMAcwB3AG8AcgBkAA==</Value> <PlainText>false</PlainText> </Password> <Enabled>true</Enabled> <Username>administrator</Username> <LogonCount>1</LogonCount> </AutoLogon> <FirstLogonCommands> <SynchronousCommand wcm:action="add"> <Order>2</Order> <Description>Bootstrap Script</Description> <CommandLine>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ep bypass a:\bootstrap.ps1</CommandLine> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <CommandLine>reg add &quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon&quot; /v AutoLogonCount /t REG_DWORD /d 0 /f</CommandLine> <Description>Disable AutoLogon (LogonCount issue fix)</Description> <Order>1</Order> </SynchronousCommand> </FirstLogonCommands> <TimeZone>Central Europe Standard Time</TimeZone> </component> </settings> <cpi:offlineImage cpi:source="wim://vboxsvr/shared/win2012r2_rw/install.wim#Windows Server 2012 R2 SERVERSTANDARD" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> </unattend> ================================================ FILE: packer/answer_files/win2016/Autounattend.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <unattend xmlns="urn:schemas-microsoft-com:unattend"> <settings pass="windowsPE"> <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SetupUILanguage> <UILanguage>en-US</UILanguage> </SetupUILanguage> <InputLocale>en-US</InputLocale> <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> </component> <component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DriverPaths> <PathAndCredentials wcm:action="add" wcm:keyValue="1"> <Path>a:\</Path> </PathAndCredentials> </DriverPaths> </component> <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <DiskConfiguration> <Disk wcm:action="add"> <CreatePartitions> <CreatePartition wcm:action="add"> <Order>1</Order> <Size>350</Size> <Type>Primary</Type> </CreatePartition> <CreatePartition wcm:action="add"> <Order>2</Order> <Extend>true</Extend> <Type>Primary</Type> </CreatePartition> </CreatePartitions> <ModifyPartitions> <ModifyPartition wcm:action="add"> <Active>true</Active> <Label>Boot</Label> <Order>1</Order> <PartitionID>1</PartitionID> <Format>NTFS</Format> <Extend>false</Extend> </ModifyPartition> <ModifyPartition wcm:action="add"> <Active>false</Active> <Extend>false</Extend> <Format>NTFS</Format> <Label>OS</Label> <Letter>C</Letter> <Order>2</Order> <PartitionID>2</PartitionID> </ModifyPartition> </ModifyPartitions> <DiskID>0</DiskID> <WillWipeDisk>true</WillWipeDisk> </Disk> </DiskConfiguration> <ImageInstall> <OSImage> <InstallFrom> <MetaData wcm:action="add"> <Key>/IMAGE/NAME</Key> <Value>Windows Server 2016 SERVERSTANDARD</Value> </MetaData> </InstallFrom> <InstallTo> <DiskID>0</DiskID> <PartitionID>2</PartitionID> </InstallTo> <InstallToAvailablePartition>false</InstallToAvailablePartition> <WillShowUI>OnError</WillShowUI> </OSImage> </ImageInstall> <UserData> <ProductKey> <WillShowUI>OnError</WillShowUI> </ProductKey> <AcceptEula>true</AcceptEula> </UserData> </component> </settings> <settings pass="oobeSystem"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <AutoLogon> <Password> <Value>UABhAHMAcwB3AG8AcgBkAA==</Value> <PlainText>false</PlainText> </Password> <Enabled>true</Enabled> <LogonCount>1</LogonCount> <Username>administrator</Username> </AutoLogon> <FirstLogonCommands> <SynchronousCommand wcm:action="add"> <Order>1</Order> <Description>Disable AutoLogon (LogonCount issue fix)</Description> <CommandLine>reg add &quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon&quot; /v AutoLogonCount /t REG_DWORD /d 0 /f</CommandLine> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>2</Order> <Description>Bootstrap Script</Description> <CommandLine>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ep bypass a:\bootstrap.ps1</CommandLine> </SynchronousCommand> </FirstLogonCommands> <UserAccounts> <AdministratorPassword> <Value>QQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBQAGEAcwBzAHcAbwByAGQA</Value> <PlainText>false</PlainText> </AdministratorPassword> </UserAccounts> <TimeZone>Central Europe Standard Time</TimeZone> </component> </settings> <cpi:offlineImage cpi:source="wim://vboxsvr/shared/win2016_rw/install.wim#Windows Server 2016 SERVERSTANDARD" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> </unattend> ================================================ FILE: packer/get-virtio.sh ================================================ #!/bin/bash # echo "[*] Cleaning up & init virtio folder..." rm -fr virtio mkdir virtio if ! cd virtio; then echo "[!] Problem creating virtio folder" exit 0 fi echo "[*] Downloading stable virtio-win.iso..." wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso echo "[*] Extracting iso..." mkdir virtio-win 7z x -ovirtio-win virtio-win.iso echo "[*] Arranging drivers..." shopt -s nullglob for winver in w10 w8.1 2k16; do mkdir -p ${winver}/core mkdir -p ${winver}/extra for driver in NetKVM viostor; do for f in virtio-win/${driver}/${winver}/amd64/*.{inf,cat,sys,dll}; do mv $f ${winver}/core done done for driver in Balloon viorng vioserial qxldod; do for f in virtio-win/${driver}/${winver}/amd64/*.{inf,cat,sys,dll}; do mv $f ${winver}/extra done done done shopt -u nullglob echo "[*] Cleaning up..." rm -fr virtio-win ================================================ FILE: packer/graylog.json ================================================ { "builders": [ { "type": "qemu", "name": "qemu-graylog", "iso_url": "http://cdimage.ubuntu.com/releases/18.04/release/ubuntu-18.04.6-server-amd64.iso", "iso_checksum": "sha256:f5cbb8104348f0097a8e513b10173a07dbc6684595e331cb06f93f385d0aecf6", "output_directory": "output_graylog", "disk_size": "20480M", "format": "qcow2", "accelerator": "kvm", "cpus": "2", "memory": "4096", "vm_name": "graylog", "net_device": "virtio-net", "disk_interface": "virtio", "http_directory": "answer_files/graylog", "communicator": "ssh", "ssh_username": "ubuntu", "ssh_private_key_file": ".ssh/id_ed25519", "ssh_timeout": "20m", "headless": true, "boot_wait": "10s", "boot_command": [ "<esc><wait>", "<esc><wait>", "<enter><wait>", "/install/vmlinuz<wait>", " initrd=/install/initrd.gz", " auto-install/enable=true", " debconf/priority=critical", " preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg<wait>", " -- <wait>", "<enter><wait>" ], "shutdown_command": "echo '{{user `ubuntu_password`}}' | sudo -S shutdown -P now" } ], "provisioners": [ { "type": "file", "source": "resources/GeoLite2-City.mmdb", "destination": "GeoLite2-City.mmdb" }, { "type": "shell", "script": "scripts/graylog.sh", "execute_command": "echo '{{user `ubuntu_password`}}' | sudo -S bash {{.Path}}" } ] } ================================================ FILE: packer/packer-build-all.sh ================================================ #!/bin/bash # echo "[*] Running packers..." packer build -timestamp-ui -var-file private.json win2016.json & packer build -timestamp-ui -var-file private.json win10.json & packer build -timestamp-ui -var-file private.json graylog.json & packer build -timestamp-ui -var-file private.json kali.json & wait echo "[+] All of the builds have been completed." ================================================ FILE: packer/private.json ================================================ { "administrator_password": "", "ubuntu_password": "", "kali_password": "" } ================================================ FILE: packer/scripts/bootstrap.ps1 ================================================ # bootstrap script for win2012r2 and win2016 packer image New-Item -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff" -Force Write-Output "[*] New Network Window Popup -> OFF" $ifaceinfo = Get-NetConnectionProfile Set-NetConnectionProfile -InterfaceIndex $ifaceinfo.InterfaceIndex -NetworkCategory Private Write-Output "[*] NetConnectionProfile -> Private" Set-WSManQuickConfig -Force Set-Item WSMan:\localhost\Service\AllowUnencrypted $true Write-Output "[!] INSECURE!!! WARNING!!! AllowUnencrypted WSMan over HTTP" ================================================ FILE: packer/scripts/graylog.sh ================================================ #!/bin/bash # echo "=== Setup Graylog ===" GRAYLOG_TIMEZONE="Europe/Budapest" GRAYLOG_SHA2="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" # Gr@yl0g_Rul3z DEBIAN_FRONTEND=noninteractive echo "[*] Upgrade base" apt-get install -y software-properties-common add-apt-repository universe apt-get update && apt-get upgrade apt-get install -y apt-transport-https openjdk-8-jre-headless uuid-runtime pwgen gnupg libterm-readline-gnu-perl curl jq echo "[*] Installing MongoDB" apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4 echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.0.list apt-get update apt-get install -y mongodb-org echo "[*] Enabling mongodb" systemctl daemon-reload systemctl enable mongod.service systemctl restart mongod.service systemctl --type=service --state=active | grep mongod echo "[*] Installing Elasticsearch" wget -q https://artifacts.elastic.co/GPG-KEY-elasticsearch -O myKey apt-key add myKey rm myKey echo "deb https://artifacts.elastic.co/packages/oss-6.x/apt stable main" | tee -a /etc/apt/sources.list.d/elastic-6.x.list apt-get update && apt-get install -y elasticsearch-oss echo "[*] Configuring Elasticsearch" sed -i /etc/elasticsearch/elasticsearch.yml \ -e '/cluster\.name:/c\cluster.name: graylog' \ -e '$ a action.auto_create_index: false' echo "[*] Enabling Elasticsearch" systemctl daemon-reload systemctl enable elasticsearch.service systemctl restart elasticsearch.service systemctl --type=service --state=active | grep elasticsearch echo "[*] Installing Graylog" wget https://packages.graylog2.org/repo/packages/graylog-3.3-repository_latest.deb dpkg -i graylog-3.3-repository_latest.deb rm graylog-3.3-repository_latest.deb apt-get update && apt-get install -y graylog-server graylog-enterprise-plugins graylog-integrations-plugins graylog-enterprise-integrations-plugins echo "[*] Installing Slack plugin" wget https://github.com/graylog-labs/graylog-plugin-slack/releases/download/3.1.0/graylog-plugin-slack-3.1.0.deb dpkg -i graylog-plugin-slack-3.1.0.deb rm graylog-plugin-slack-3.1.0.deb echo "[*] Configuring Graylog" SECRET=`pwgen -N 1 -s 96` sed -i /etc/graylog/server/server.conf \ -e "/^password_secret/c\\password_secret = ${SECRET}" \ -e "/^root_password_sha2/c\\root_password_sha2 = ${GRAYLOG_SHA2}" \ -e "/root_timezone/c\\root_timezone = ${GRAYLOG_TIMEZONE}" echo "[*] Enabling Graylog" systemctl daemon-reload systemctl enable graylog-server.service systemctl start graylog-server.service systemctl --type=service --state=active | grep graylog echo "[*] Copying GeoLite2-City.mmdb to /etc/graylog/server/" cp ~ubuntu/GeoLite2-City.mmdb /etc/graylog/server/ echo "=== Setup Graylog Done ===" ================================================ FILE: packer/scripts/setupcomplete.ps1 ================================================ Write-Output "[*] Installing extra VirtIO drivers..." <# this was fixed in new VirtIO release, no need to install custom cert $driverFile = "c:\windows\temp\extra\balloon.sys" $certFile = "c:\windows\temp\extra\redhat.cer" $exportType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert $cert = (Get-AuthenticodeSignature $driverFile).SignerCertificate; [System.IO.File]::WriteAllBytes($certFile, $cert.Export($exportType)); Import-Certificate -FilePath $certFile -CertStoreLocation Cert:\LocalMachine\TrustedPublisher #> pnputil -i -a c:\windows\temp\extra\balloon.inf pnputil -i -a c:\windows\temp\extra\qxldod.inf pnputil -i -a c:\windows\temp\extra\viorng.inf pnputil -i -a c:\windows\temp\extra\vioser.inf Write-Output "[*] Disabling Auto-Hibernate..." powercfg -hibernate OFF Write-Output "[*] Enabling Windows Time Service" Set-Service -Name w32time -StartupType Automatic sc.exe triggerinfo w32time delete Write-Output "[*] Checking for Windows 10..." If ([Environment]::OSVersion.Version -ge (new-object 'Version' 10,0)) { Write-Output "[+] Validated Windows 10" Write-Output "[*] Disabling Windows AutoUpdate" New-Item HKLM:\SOFTWARE\Policies\Microsoft\Windows -Name WindowsUpdate New-Item HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate -Name AU New-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name NoAutoUpdate -Value 1 Write-Output "[*] Disabling Windows Defender" Set-MpPreference -DisableIntrusionPreventionSystem $true ` -DisableIOAVProtection $true ` -DisableRealtimeMonitoring $true ` -DisableScriptScanning $true ` -EnableControlledFolderAccess Disabled ` -EnableNetworkProtection AuditMode ` -Force -MAPSReporting Disabled ` -SubmitSamplesConsent NeverSend } Else { Write-Output "[!] Older Windows detected" } Write-Output "[*] Allowing incoming WinRM on Any Profile in Firewall..." New-NetFirewallRule -DisplayName "Allow WinRM" -Direction Inbound -LocalPort 5985 -Protocol TCP -Action Allow -Profile Any Write-Output "[*] Enabling RDP..." Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -name "fDenyTSConnections" -value 0 Enable-NetFirewallRule -DisplayGroup "Remote Desktop" Write-Output "[+] Setup complete. Cleaning up files..." Remove-Item -Recurse -Force -Path c:/windows/temp/extra ================================================ FILE: packer/scripts/shutdown.ps1 ================================================ Set-Item WSMan:\localhost\Service\AllowUnencrypted $false Write-Output "[+] Disabled Unencrypted WSMan over HTTP" shutdown /s /t 5 /f /d p:4:1 /c "Packer Shutdown" ================================================ FILE: packer/scripts/win2012r2-dotnet-fix.ps1 ================================================ Write-Host "[*] Fixing CPU spiking caused by .NET Runtime Optimization Service" Get-ChildItem $env:SystemRoot/Microsoft.net/NGen.exe -recurse | %{ & $_ executeQueuedItems } ================================================ FILE: packer/win10.json ================================================ { "builders": [ { "type": "qemu", "name": "qemu-win10", "iso_url": "ISO/Win10_21H2_EnglishInternational_x64.iso", "iso_checksum": "sha256:06fd4a512c5f3e8d16f77ca909c4f20110329b8cdd5ad101e2afc0d58b06d416", "output_directory": "output_win10", "disk_size": "40960M", "format": "qcow2", "accelerator": "kvm", "cpus": "2", "memory": "4096", "vm_name": "win10", "net_device": "virtio-net", "disk_interface": "virtio", "floppy_files": [ "answer_files/win10/Autounattend.xml", "virtio/w10/core/*", "scripts/bootstrap.ps1" ], "communicator": "winrm", "winrm_username": "administrator", "winrm_password": "{{user `administrator_password`}}", "winrm_use_ntlm": true, "shutdown_command": "powershell -ep bypass c:\\windows\\temp\\shutdown.ps1", "headless": true } ], "provisioners": [ { "type": "file", "source": "scripts/shutdown.ps1", "destination": "c:/windows/temp/shutdown.ps1" }, { "type": "file", "source": "virtio/w10/extra", "destination": "c:/windows/temp/" }, { "type": "powershell", "script": "scripts/setupcomplete.ps1" } ] } ================================================ FILE: packer/win2012r2.json ================================================ { "builders": [ { "type": "qemu", "name": "qemu-win2012r2", "iso_url": "http://download.microsoft.com/download/6/2/A/62A76ABB-9990-4EFC-A4FE-C7D698DAEB96/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_SERVER_EVAL_EN-US-IR3_SSS_X64FREE_EN-US_DV9.ISO", "iso_checksum": "sha256:6612b5b1f53e845aacdf96e974bb119a3d9b4dcb5b82e65804ab7e534dc7b4d5", "output_directory": "output_win2012r2", "disk_size": "40960M", "format": "qcow2", "accelerator": "kvm", "cpus": "2", "memory": "4096", "vm_name": "win2012r2", "net_device": "virtio-net", "disk_interface": "virtio", "floppy_files": [ "answer_files/win2012r2/Autounattend.xml", "virtio/w8.1/core/*", "scripts/bootstrap.ps1" ], "communicator": "winrm", "winrm_username": "administrator", "winrm_password": "{{user `administrator_password`}}", "winrm_use_ntlm": true, "shutdown_command": "powershell -ep bypass c:\\windows\\temp\\shutdown.ps1", "headless": true } ], "provisioners": [ { "type": "file", "source": "scripts/shutdown.ps1", "destination": "c:/windows/temp/shutdown.ps1" }, { "type": "file", "source": "virtio/w8.1/extra", "destination": "c:/windows/temp/" }, { "type": "powershell", "script": "scripts/setupcomplete.ps1" }, { "type": "powershell", "script": "scripts/win2012r2-dotnet-fix.ps1" } ] } ================================================ FILE: packer/win2016.json ================================================ { "builders": [ { "type": "qemu", "name": "qemu-win2016", "iso_url": "https://software-download.microsoft.com/download/pr/Windows_Server_2016_Datacenter_EVAL_en-us_14393_refresh.ISO", "iso_checksum": "sha256:1ce702a578a3cb1ac3d14873980838590f06d5b7101c5daaccbac9d73f1fb50f", "output_directory": "output_win2016", "disk_size": "40960M", "format": "qcow2", "accelerator": "kvm", "cpus": "2", "memory": "4096", "vm_name": "win2016", "net_device": "virtio-net", "disk_interface": "virtio", "floppy_files": [ "answer_files/win2016/Autounattend.xml", "virtio/2k16/core/*", "scripts/bootstrap.ps1" ], "communicator": "winrm", "winrm_username": "administrator", "winrm_password": "{{user `administrator_password`}}", "winrm_use_ntlm": true, "shutdown_command": "powershell -ep bypass c:\\windows\\temp\\shutdown.ps1", "headless": true } ], "provisioners": [ { "type": "file", "source": "scripts/shutdown.ps1", "destination": "c:/windows/temp/shutdown.ps1" }, { "type": "file", "source": "virtio/2k16/extra", "destination": "c:/windows/temp/" }, { "type": "powershell", "script": "scripts/setupcomplete.ps1" } ] } ================================================ FILE: terraform/main.tf ================================================ provider "libvirt" { uri = "qemu:///system" } locals { mac_dc1 = "50:73:0F:31:81:E1" mac_desktop12 = "50:73:0F:31:81:E2" mac_graylog = "50:73:0F:31:81:F1" mac_kali = "50:73:0F:31:81:F2" } resource "libvirt_network" "honeypot" { name = "honeypot" mode = "nat" bridge = "honeybr0" addresses = ["192.168.3.0/24"] dhcp { enabled = true } dns { enabled = true forwarders { address = "192.168.3.100" domain = "local" } } xml { xslt = file("network-dhcp-lease.xsl") } provisioner "local-exec" { command = "/usr/bin/sudo /usr/sbin/iptables -I FORWARD -j DROP -i honeybr0 -d 192.168.0.0/16; /usr/bin/sudo /usr/sbin/iptables -I FORWARD -j ACCEPT -i honeybr0 -o honeybr0" } provisioner "local-exec" { command = "/usr/bin/sudo /usr/sbin/iptables -D FORWARD -j DROP -i honeybr0 -d 192.168.0.0/16; /usr/bin/sudo /usr/sbin/iptables -D FORWARD -j ACCEPT -i honeybr0 -o honeybr0" when = destroy } } resource "libvirt_pool" "honeypot" { name = "honeypot-pool" type = "dir" path = "/mnt/archive01/vm/honeypot-pool" } resource "libvirt_volume" "dc1-vol" { pool = libvirt_pool.honeypot.name name = "dc1-vol" source = "../packer/output_win2016/win2016" } resource "libvirt_volume" "desktop12-vol" { pool = libvirt_pool.honeypot.name name = "desktop12-vol" source = "../packer/output_win10/win10" } resource "libvirt_volume" "graylog-vol" { pool = libvirt_pool.honeypot.name name = "graylog-vol" source = "../packer/output_graylog/graylog" } resource "libvirt_volume" "kali-vol" { pool = libvirt_pool.honeypot.name name = "kali-vol" source = "../packer/output_kali/kali" } resource "libvirt_domain" "dc1-dom" { provider = libvirt name = "h-dc1" memory = "4096" vcpu = 4 disk { volume_id = libvirt_volume.dc1-vol.id } network_interface { network_id = libvirt_network.honeypot.id hostname = "dc1" mac = local.mac_dc1 } xml { xslt = file("timer-patch.xsl") } } resource "libvirt_domain" "desktop12-dom" { provider = libvirt name = "h-desktop12" memory = "4096" vcpu = 4 disk { volume_id = libvirt_volume.desktop12-vol.id } network_interface { network_id = libvirt_network.honeypot.id hostname = "desktop12" mac = local.mac_desktop12 } xml { xslt = file("timer-patch.xsl") } } resource "libvirt_domain" "graylog-dom" { provider = libvirt name = "h-graylog" memory = "4096" vcpu = 4 disk { volume_id = libvirt_volume.graylog-vol.id } network_interface { network_id = libvirt_network.honeypot.id hostname = "graylog" mac = local.mac_graylog } xml { xslt = file("timer-patch.xsl") } } resource "libvirt_domain" "kali-dom" { provider = libvirt name = "h-kali" memory = "4096" vcpu = 4 disk { volume_id = libvirt_volume.kali-vol.id } network_interface { network_id = libvirt_network.honeypot.id hostname = "kali" mac = local.mac_kali } xml { xslt = file("timer-patch.xsl") } } terraform { required_version = ">= 0.13" required_providers { libvirt = { source = "dmacvicar/libvirt" version = "0.6.14" } } } ================================================ FILE: terraform/network-dhcp-lease.xsl ================================================ <?xml version="1.0" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="/network/ip/dhcp"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:copy-of select="node()"/> <host mac='50:73:0F:31:81:E1' ip='192.168.3.100'/> <host mac='50:73:0F:31:81:E2' ip='192.168.3.112'/> <host mac='50:73:0F:31:81:F1' ip='192.168.3.191'/> <host mac='50:73:0F:31:81:F2' ip='192.168.3.192'/> </xsl:copy> </xsl:template> </xsl:stylesheet> ================================================ FILE: terraform/timer-patch.xsl ================================================ <?xml version="1.0" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="/domain/clock"> <xsl:copy> <xsl:copy-of select="@*"/> <xsl:copy-of select="node()"/> <timer name='hpet' present='yes'/> <timer name='hypervclock' present='yes'/> </xsl:copy> </xsl:template> </xsl:stylesheet>