WPICTF - rsa-machine

description: nc rsamachine.wpictf.xyz 31337 (or 31338 or 31339)

category: Crypto - 250

Once again this a RSA challenge, you can connect to the service rsamachine.wpictf.xyz, it will display the public key sign anything except getflag

nc rsamachine.wpictf.xyz 31337 
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm6U3u7cIcAvZpLoNnC7+
Ed6AtPq8q2G+P0h9SFfF4dY10Y6qC0Q76M6YjsHnxJVjB1hHpMRRZ1xEBETDDAEd
E0Q7fkE4nmo1zIlZ+GDc1Wh8yjFP5dgUUsV+gmpHpiV8bGf7fjbOUQQsr4Tdj36r
k/MECUJ1KirI6hxwjGbiv91QOtaWxHowklEew/qjcluvRXWQSny4QbBnEiDDlcc7
LsJNWC9vSrVSdJYzOq1AHg8OAj/9LwZBP4LKv5R1KeHmbHcA9YH7209KPRuoUq+8
yZT5dwcDOj7CIAi5ZZTNB+8JFye9P5Io7KLtjNfc2LuWFcmnD1L2Jkq1uaAKlDhW
xwIDAQAB
-----END PUBLIC KEY-----
sign something 
263143455915553814887752724380314073403898730047303403988419060491103813517683987149316936899917770854377052259279067668404252951681718316256673764873508758275796657584169673472618879593959868838554701949970086430857326767140906350258609046836933983182628811986955853432603640580199244364940762358967783650993395295484998136760749744014952178178481654620475181643286301429842971867932606572045849027337514537764615811040636337471022302207829370474793300200243702914208594827859646506339198480880761272715718921034602009369230231577795574179605634300263686633900298593950959943424333149187160061409252478269885226682

The author provided the challenge source code, we have to send getflag followed by its signature which will be verified in if privkey.verify(b'getflag', (signature,)):.

#!/usr/bin/python3

import sys

from Crypto.PublicKey import RSA

def run(inFile, outFile):
    privkey = RSA.generate(2048)
    pubkey = privkey.publickey().exportKey()

    outFile.write(pubkey + b'\n')

    while not outFile.closed:
        outFile.flush()

        line = inFile.readline()
        if not line: #EOF
            break

        line = line.rstrip(b'\n')
        if not line:
            continue

        try:
            [cmd, param] = line.split(maxsplit=1)
        except ValueError:
            outFile.write(b'Parameter required!\n')
            continue

        if cmd == b'sign':
            if param == b'getflag':
                outFile.write(b'No cheating!\n')
            else:
                (signature,) = privkey.sign(param, None)
                outFile.write(str(signature).encode() + b'\n')

        elif cmd == b'getflag':
            signature = int(param)
            if privkey.verify(b'getflag', (signature,)):
                outFile.write(b'[REDACTED]')
                break
            else:
                outFile.write(b'Bad signature!\n')

        else:
            outFile.write(b'Bad command ' + cmd + b'\n')

if __name__ == '__main__':
    run(sys.stdin.buffer, sys.stdout.buffer)

This is a classic RSA blinding attack, the full details about the attack are available at http://the2702.com/2015/09/07/RSA-Blinding-Attack.html. I made a python script based on the pwn lib to interact with the service.

import re
import codecs
import random
from Crypto.PublicKey import RSA
from binascii import unhexlify, hexlify
from pwn import *

srv = remote('rsamachine.wpictf.xyz', 31337)
pubkey = srv.recv().strip()
key = RSA.importKey(pubkey)
print("[!] Got public key")

M = int(hexlify("getflag"), 16)
N = key.n
e = key.e
r = 3 # random.randint(2, N-1)
mbis = (M * pow(r,e))  % N
messagebis = ("%2X" % mbis).decode('hex')

srv.send("sign " + messagebis + "\n")

sbis = srv.recv()
sbis = long(sbis.strip())  #sbis = pow(mbis, d) % N  
print("[!] Got S' : {}".format(sbis))

s = (sbis/r) % N 

srv.send("getflag {}\n".format(s))
print(srv.recv())

We got the flag after launching the script 3 times.

╰─$ python2 rsablind.py
[+] Opening connection to rsamachine.wpictf.xyz on port 31337: Done
[!] Got public key
[!] Got S' : 8055189041943281850539614428987268833243977988231476308451969394241131492275713532521566856414705140678584275459454061478877916901106021929082261596304029000380356771010090392077825985126904892021860846170710444878788617553685554227251040415596010342529263252780579779283805197748539892098561765320527416126709326474068550668984482014647358415646453014451009099645828063210244579362249356356992100432879012681260104886379237896331092235634504293978157618536973885986939804073798159721968127183030650941348417673375708632952880399373025493539732962654422773982210391960030492635331940490213507770535612609263131485740
WPI{m411e4b1e_cipher5_4re_d4ngerou5}
[*] Closed connection to rsamachine.wpictf.xyz port 31337

The flag was WPI{m411e4b1e_cipher5_4re_d4ngerou5}

WPICTF - Remy's Epic Adventure

description: A tribute to the former God Emperor of CSC and Mankind, Ultimate Protector of the CS Department, and Executor of Lord Craig Shue’s Divine will.

category: Miscellaneous - 150

wpi_adventure.png

You want some game play ?

wpi_adventure_vid.gif

Yeah I know I’m pretty good but not enough to win the game tho.

As you can see, it looks like the boss has infinity health (that’s not true, it’s a huge number…).

Okay, let’s launch Cheat Engine.exe

What are we looking for ? anything displayed on the screen that can make me win:

wpi_adventure_data.png

- `HP` ? I can put it to 200 and never lose life but that won't make me kill the boss faster.
- `Death` ? Hum I can identify the `die` function, so i won't die or maybe i can force the boss to die ?
- `level` ? Why not try to find the level in memory and change it to the level after the boss (boss should be level 6 if the level on the image is truly 1).

Modifying the level seems interesting. This is how I find the correct value:

wpi_adventure_data.png wpi_adventure_data.png wpi_adventure_data.png wpi_adventure_data.png

So the level is stored at 0x6ff82c.

Let’s see what is accessing this value: right click -> Find out what accesses the address then finish the level to see what code is writing the next level:

The instruction is at 0xb50201.

cmon do it IDA (in debug mode of course):

.text:00B501E5 loc_B501E5:
.text:00B501E5 call    sub_B717B0
.text:00B501EA mov     edx, dword_C13BE4
.text:00B501F0 movzx   ecx, di
.text:00B501F3 shl     ecx, 10h
.text:00B501F6 movzx   eax, si
.text:00B501F9 or      ecx, eax
.text:00B501FB mov     eax, [edx+1F0h]
.text:00B50201 mov     [ebp-4], eax
.text:00B50204 mov     eax, [edx+0D8h]
.text:00B5020A mov     [ebp-8], ecx
.text:00B5020D test    eax, eax
.text:00B5020F jz      short loc_B5024E

The level is stored in [edx+1F0h]. all we have to do is to set this value to 7 (one more gif =) ):

wpi_adventure_final.gif

Well, that was fast ! I recorded it with vlc, so I can go frame by frame:

wpi_adventure_flag.png

The flag is WPI{j0in_th3_illumin@ti_w1th_m3}

PS1: I’m good @ gaming right ? You can find me on Twitch here =)

PS2: Best WPI challenge in my opinion, well done to the author ;)

WPICTF - jocipher

description: Decrypt PIY{zsxh-sqrvufwh-nfgl} to get the flag!

category: Crypto - 100

The code was provided as a .pyc file (available at https://ctf.wpictf.xyz/files/d9543469d876cffa70bb84667ed5d369/jocipher.pyc). It’s a compiled python file which means we can get the code back with uncompyle.

pacaur -S python-uncompyle6
uncompyle6 jocipher.pyc jocipher.py

And we get the following python code.

# uncompyle6 version 3.2.6
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.7.3 (default, Mar 26 2019, 21:43:19) 
# [GCC 8.2.1 20181127]
# Embedded file name: ./jocipher.py
# Compiled at: 2019-03-01 18:41:21
import argparse, re
num = ''
first = ''
second = ''
third = ''

def setup():
    global first
    global num
    global second
    global third
    num += '1'
    num += '2'
    num += '3'
    num += '4'
    num += '5'
    num += '6'
    num += '7'
    num += '8'
    num += '9'
    num += '0'
    first += 'q'
    first += 'w'
    first += 'e'
    first += 'r'
    first += 't'
    first += 'y'
    first += 'u'
    first += 'i'
    first += 'o'
    first += 'p'
    second += 'a'
    second += 's'
    second += 'd'
    second += 'f'
    second += 'g'
    second += 'h'
    second += 'j'
    second += 'k'
    second += 'l'
    third += 'z'
    third += 'x'
    third += 'c'
    third += 'v'
    third += 'b'
    third += 'n'
    third += 'm'


def encode(string, shift):
    result = ''
    for i in range(len(string)):
        char = string.lower()[i]
        if char in num:
            new_char = num[(num.index(char) + shift) % len(num)]
            result += new_char
        elif char in first:
            new_char = first[(first.index(char) + shift) % len(first)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        elif char in second:
            new_char = second[(second.index(char) + shift) % len(second)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        elif char in third:
            new_char = third[(third.index(char) + shift) % len(third)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        else:
            result += char

    print result
    return 0


def decode(string, shift):
    result = ''
    shift = -1 * shift
    for i in range(len(string)):
        char = string.lower()[i]
        if char in num:
            new_char = num[(num.index(char) + shift) % len(num)]
            result += new_char
        elif char in first:
            new_char = first[(first.index(char) + shift) % len(first)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        elif char in second:
            new_char = second[(second.index(char) + shift) % len(second)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        elif char in third:
            new_char = third[(third.index(char) + shift) % len(third)]
            if string[i].isupper():
                result += new_char.upper()
            else:
                result += new_char
        else:
            result += char

    print result
    return 0


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--string', '-s', type=str, required=True, help='the string to encode or decode')
    parser.add_argument('--shift', '-t', type=int, required=True, help='the shift value to use')
    parser.add_argument('--encode', '-e', required=False, action='store_true', help='encode the string')
    parser.add_argument('--decode', '-d', required=False, action='store_true', help='decode the string')
    args = parser.parse_args()
    setup()
    p = re.compile('[a-zA-Z0-9\\-{}]')
    if p.match(args.string) is not None:
        if args.encode:
            ret = encode(args.string, args.shift)
        else:
            if args.decode:
                ret = decode(args.string, args.shift)
        if ret is not 0:
            print 'Sorry, this cipher only uses the [a-zA-Z0-9\\-{}]'
    else:
        print 'Sorry, this cipher only uses the [a-zA-Z0-9\\-{}]'
    return


if __name__ == '__main__':
    main()
# okay decompiling jocipher.pyc

The challenge’s author left a decode function, and a command line helper.

We can use the python jocipher.py --decode STRING_TO_DECODE and try to bruteforce the shift, unfortunately there were multiple flags starting by WPI

wpi_jocipher.png

The flag was WPICTF{xkcd-keyboard-mash}

WPICTF - ISpy

description: Someone is always watching. Secure Your Webcam.

category: Recon - 200

wpi_ispy.png

This is the given jpg file:

wpi_ispy_given.png

After searching for hours, a hint has been published: /16:

webcam ? IP/16 ? Shodan !

Okay first lets retrieve the IP:

$ nslookup wpi.edu
Server:			192.168.1.1
Address:		192.168.1.1#53

Non-authoritative answer:
Name:		wpi.edu
Address:	130.215.36.26

And research on Shodan:

wpi_ispy_shodan.png

We finally found something interesting:

wpi_ispy_ip.png

wpi_ispy_flag.png

Flag is: WPI{DUCK_SAY_HELLO}

WPICTF - getaflag

description: Come on down and get your flag, all you have to do is enter the correct password …

category: Web - 150

wpi_getaflag.png

Trying some input:

wpi_getaflag_input.png

It seems that we have to guess the password, or maybe…?

wpi_getaflag_source.png

$ echo -n "SGV5IEdvdXRoYW0sIGRvbid0IGZvcmdldCB0byBibG9jayAvYXV0aC5waHAgYWZ0ZXIgeW91IHVwbG9hZCB0aGlzIGNoYWxsZW5nZSA7KQ==" | base64 -d
Hey Goutham, don't forget to block /auth.php after you upload this challenge ;) 

The auth.php file:

wpi_getaflag_auth.png

Hum -> extract($_GET) That means we can rewrite previous variable declarations !

Considering the script is using get_contents, we can suppose that changing the $passcode will make get_contents return an empty string.

Finally:

wpi_getaflag_flag.png

The flag is WPI{1_l0v3_PHP}