Skip to content

Instantly share code, notes, and snippets.

@vgmoose
Last active March 25, 2024 22:04
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save vgmoose/125271f1d9e4a1269454a64095b9e4a1 to your computer and use it in GitHub Desktop.
Save vgmoose/125271f1d9e4a1269454a64095b9e4a1 to your computer and use it in GitHub Desktop.

The below instructions describe the process for MITM'ing a target device over HTTPS using nginx. It tries to go over every aspect of intercepting traffic, including hosting a Wifi access point.

Overview

The goal is to get a target device (such as an iPhone, Wii U, or another computer) to trust our local nginx server instead of the remote trusted server. This is going to be done by importing a custom CA root certificate on the target that corresponds with the nginx server's certificate.

Client (Trusted Device) <--> MITM Server (nginx) <--> Remote (Trusted) Server

Requirements

These instructions are being performed on a PureOS machine, which is Debian based. They should also work in other environments with slight modifications

  • openssl - to generate certificates as needed for our server and the target device
  • nginx - to host our mitm server and connect to the trusted remote pretending to be the client
  • dnsmasq - to host a dns server to redirect the target device to our server instead of the trusted remote
  • hostapd - to host an access point for the target device to connect to
  • wireshark - to inspect the traffic that occurs between the cilent and remote

It should also be noted that you will also need another way to get online besides your Wifi connection (such as Ethernet, USB, or another Wifi card), since most Wifi cards cannot host an access point while they're connected to another one.

Generate Root CA Certificate

The custom Root CA is what will be installed on the target device to allow it to trust our MITM server. The target device ships with a bunch of root CA's in order to know which notable signed certificates to trust on the Internet.

We will generate it using openssl. The below command:

openssl req --nodes -new -x509 -days 3650 -extensions v3_ca -keyout cacert.private.pem -out cacert.pem -config /etc/ssl/openssl.cnf

Will prompt you to fill out the fields of the certificate. None of them matter, and they can all be filled out to anything you want or left blank, except you should fill out "Common Name" with something unique.

After the command runs, you will be left with two files in your current directory, cacert.pem and cacert.private.pem. The .pem file is the Root CA, which you will have to install on the target device. The .private.pem is going to be used in the next step to sign a certificate with this CA.

Generate Signed Certificate

Now that we generated the Root CA, we need to generate a certificate that is signed by it. The target device will be able to use the Root CA to cryptographically verifiy that a presented certificate is trustworthy.

The below command generates a self-signed certificate, which is not signed by any certificate authority. Once it's created, it will be signed by later steps.

openssl req -x509 -newkey rsa:4096 -keyout mycert.private.pem -out mycert.pem -days 365 --nodes

It will also prompt you for fields similar to when we created the Root CA. Again, they can all be anything, except the "Common Name (e.g. server FQDN or YOUR name)" must be set to apple.com, or the target server that you're intercepting. Use a full domain here, for subdomains or additional domains, we will use a subject alternative name file in the next few steps.

Next, we're going to move around the files a bit into a structure that plays nicer with openssl's ca command. There's probably a better way to do this, but after seeing this excerpt from the ca man page: "The ca command is quirky and at times downright unfriendly.", I'm giving up on that, and going with their structure.

First, create a file named alt.txt in the current directory, containing the following:

[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = apple.com
DNS.2 = *.apple.com

Where the DNS.1, DNS.2, and so on values are replaced with whatever domain/wildcard domains you want to spoof with your HTTPS certificate. Then run the following commands to actually generate the certificate:

mkdir -p demoCA/newcerts
touch demoCA/index.txt
echo '01' > demoCA/serial
openssl ca -policy policy_anything -keyfile cacert.private.pem -cert cacert.pem -ss_cert mycert.pem -out mycert.signed.pem -extensions v3_req -extfile alt.txt

When it asks you to sign the certificate and commit the change, say yes and yes. The end result will be mycert.signed.pem in the current directory. This is the next certificate in the chain of trust, and it should be presented to the client by our nginx server alongside a copy of the Root CA.

To merge this newly signed certificate with the root ca, cat the two files together into a new one:

cat cacert.pem mycert.signed.pem > certchain.pem

For this guide, we'll copy these files (private and signed chain) into a new folder, /etc/nginx/mitm:

mkdir /etc/nginx/mitm
cp mycert.private.pem certchain.pem /etc/nginx/mitm

Server Setup

The server certificate is what our MITM server will present to the client instead of the remote server's. It must be signed by the above generated root ca, so that when the client sees it, it wil trust it from having the root CA installed.

Open the /etc/nginx/nginx.conf file, and put the following contents inside of the http { ... } block:

server {
    listen 443 ssl http2;

    server_name apple.com;
    ssl_certificate /etc/nginx/mitm/certchain.pem;
    ssl_certificate_key /etc/nginx/mitm/mycert.private.pem;
    location / {
        proxy_pass https://apple.com;
    }
}

In this case, the server_name we are pretending to be is apple.com, and we are going to handle all requests by passing them along to the real https://apple.com. In this case, we are passing it along to the real server, but the location block could also be used to proxy_pass it to a local server such as http://localhost:8080.

Fake DNS Server

When the client device is going to issue a request for the trusted domain, they are going to ask the DNS server to resolve the domain name into an IP address. Our fake DNS server will tell the client that the IP that corresponds to the domain they're asking for is our own machine.

dnsmasq is going to run our DNS server on port 53. To configure it, create a new file at /etc/dnsmasq.d/apple.com and fill it without the following contents:

address=/apple.com/10.42.0.1
address=/captive.apple.com/10.42.0.1

Replace apple.com with the domain that you want to redirect, and 10.42.0.1 with your machine's local IP address. You can add more lines for any other domains or subdomains that you want to send to your machine as well.

You should also check to make sure that dnsmasq is aware to look in the /etc/dnsmasq.d directory. Check the file at /etc/dnsmasq.conf and make sure it has the following line in it (uncommented):

conf-dir=/etc/dnsmasq.d

Sanity Check

Feel free to skip this section, we will be trying to verify the setup so far from your local computer.

Before we continue, we're going to be testing our setup for spoofing apple.com, so we won't be able to continue on to the true apple.com since we'll be under our own fake dns server. So you should use http://localhost:8080 in the nginx.conf file, as mentioned in the Server Setup section. Then, you should host something on port 8080 (for instance, python -m SimpleHTTPServer 8080) that we will try to "fake" as the real Apple.

Up until this point, we've created a custom root ca certificate, created the nginx configuration file that uses our custom certificate to pass along the traffic, and created a local DNS server that forwards requests for the target domain to us. To verifiy everything is working, let's bring up the nginx server:

service nginx start

Hopefully it starts without any errors. If it does encounter errors, use service nginx status to see what they are. It would likely be a syntax issue.

If it starts successfully, it's time to bring up the dns server. Similarly:

service dnsmasq start

We can test the DNS server right away, using the host command:

host apple.com 127.0.0.1

Should report: "apple.com has address 192.168.0.127", which demonstrates it's resolving to our IP rather than Apple's.

Now, if you set your computer's DNS server to 127.0.0.1 in your network settings, and try to visit https://apple.com in the browser, you should be presented with a security warning: security warning

The target device will reject this as it doesn't recognize the certificate that is being presented by our nginx server for apple.com (as it shouldn't). If you import the generated cacert.pem into your browser, however, then the browser will be able to see the presented certificate as trusted.

To dismiss this warning, and test our MITM setup, import the Certificate Authority into your Browser or Operating System. This is the cacert.pem file. For example, in Chromium on Linux, this is managed in settings.

If you were successful in importing your custom CA, signing your custom Apple.com certificate, setting up the DNS server to reroute apple.com to your machine, and setting up nginx to redirect apple.com requests to a locally running python server, you should see something like the following when you try to visit https://apple.com in your browser: success!

If you get the green lock (or equivalent in other browsers) then you're all set! If this is working, you should edit your nginx.conf's proxy_pass line as necessary. Using this, you can make any website into any another website. Nginx will handle a lot of edge cases, so the website can be http or https, or even be a full URL such as https://www.microsoft.com/en-us/ (don't tell Tim Cook):

Before continuing on, make sure that you have proxy_pass set to your desired target (probably just the same target as you're spoofing, if you're intending to do MITM). Also ensure that your computer is no longer using your fake DNS, it should be using a real DNS server internally so that it can resolve the spoof'd website whose HTTPS connection you want to snoop on.

Hosting an Access Point

This guide is focused on intercepting devices over Wifi, so we will be hosting a wireless access point for the target device to connect to. When they are connected to this access point, their traffic will head to our Nginx server, where it can be routed somewhere else, or wireshark can snoop on it.

Before you do this, you will need an alternate method of connecting to the Internet, such as Ethernet or USB.

If you're running network-manager (runs by default in Gnome) you may have to explicitly kill your wifi connection and release it before continuing:

nmcli radio wifi off
rfkill unblock wlan
rfkill unblock wifi
ifconfig wlo1 10.42.0.1/24 up

Edit /etc/hostapd/hostapd.conf to contain the following contents:

interface=/var/run/hostapd

# Some usable default settings...
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0

# Uncomment these for base WPA & WPA2 support with a pre-shared key
wpa=3
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP

# DO NOT FORGET TO SET A WPA PASSPHRASE!!
wpa_passphrase=SuPeRSeCrEtPaSs123

# Most modern wireless drivers in the kernel need driver=nl80211
driver=nl80211

# Customize these for your local configuration...
interface=wlo1
hw_mode=g
channel=11
ssid=connectme

I am including the full file, as with my hostapd install the file wasn't included, but if it was included in yours, then you can just pay attention to the bottom part of the file.

At the bottom, you should ensure that the interface line matches your wireless device's, and that the ssid and password are to your liking.

Then, bring up the service:

service hostapd start

If all went well, you should see your SSID name being hosted from your trusted device. If you try to connect directly to it, however, it won't go through, since there's no dhcp server running on your computer. You can try to specify a manual static IP if you're lazy, but dnsmasq can also be a dhcp server for us. Notably, the values in dhcp-range should match the ones in the aboveifconfig ... up command.

Append the following to /etc/dnsmasq/dnsmasq.conf:

interface=wlo1
dhcp-range=10.42.0.3,10.42.0.20,12h

And restart dnsmasq:

service dnsmasq restart

With this done, you should now finally be able to connect to your access point from your trusted device. You should find that you are able to browse the full Internet, with the exception of your spoof'd domain. The spoof'd server should still display a certificate warning since we haven't installed the Root CA into the trusted device yet, but that's all good then!

Troubleshooting

  • Make sure that your computer's IP is in /etc/dnsmasq.d/apple.com in the Fake DNS Server section. This is especially notable as your IP may change, and it can't be a loopback address like 127.0.0.1.
  • Ensure the certificate generation properly contains the domains that you wish to spoof
  • Ensure that Nginx config properly contains the domains you want to spoof, and pass traffic through on
  • Make sure if you did the Sanity Check that you undid some of the local changes made to your computer before continuing
  • You can add multiple server { ... } blocks to nginx.conf for multiple subdomains, to route them to different local servers. You can even share the same certificate, or sign more certificates with the same CA in order to minimize the amount of modifying you have to perform on the target device.

Wii U Specific Stuff

The following sections detail stuff that's specific to the Wii U while performing this

Updating Blocking

You'll want to block updates, as by default dnsmasq will pass the traffic through. You can do this by editing /etc/hosts and then restarting dnsmasq.

Add the following to your /etc/hosts:

255.255.255.255	nus.cdn.c.shop.nintendowifi.net
255.255.255.255	nus.cdn.shop.wii.com
255.255.255.255	nus.cdn.wup.shop.nintendo.net
255.255.255.255	nus.wup.shop.nintendo.net
255.255.255.255	nus.c.shop.nintendowifi.net
255.255.255.255	c.shop.nintendowifi.net
255.255.255.255	cbvc.cdn.nintendo.net
255.255.255.255	cbvc.nintendo.net

Here 255.255.255.255 is used rather than 127.0.0.1, since on 127.0.0.1 we are hosting the nginx server, which might cause unwanted side-effects, since we may be passing that traffic through to the real nintendo servers.

Replacing Nintendo's CA

We will want to replace Nintendo's CA with the CA that we generated in order to snoop on Ninty traffic. To do this, the CA must be in .DER format. The below command can convert our .pem certificate into .DER needed:

openssl x509 -in cacert.pem -out cacert.der -outform DER 

Once the .DER file. is created, you can run the w.up command with it instead of FiddlerRoot in these instructions.

Spoofing Client Certificate

Normally when the Wii U connects to Nintendo, it authenticates itself using a client certificate that is stored on the Wii U. This file can be dumped via wupserver, and the file provided to Nginx. Normally the Wii U would present this to talk to Nintendo, so as a middle-man we may still need it to talk to Nintendo as well.

TODO: is this needed?

notes

If you want to pass through all traffic, and only intercfept some subdomains (via DNS) with nginx, then you shuld use the following:

iptables -t nat -A POSTROUTING -o enp0s25  -j MASQUERADE

where enp0s25 is the device name that has an internet connection. This allows the traffic to be available to hostapd when it's hosting an access point.

When downloading the Common and Account certificates, they can be converted to .CER format with the following:

openssl pkcs12 -in input.p12 -out mycerts.crt -nokeys -clcerts
openssl pkcs12 -in WIIU_COMMON_1_CERT.p12 -out mycerts.crt -nodes -nocerts -clcerts 

Then they can be given to nginx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment