Wednesday, 14 November 2018

Postfuscator - Square CTF 2018

This is the writeup for Postfuscator challenge in Square CTF 2018.

We were provided a shell script which takes a key as an input and writes the key inside a postscript file.

The postscript file was obfuscated.

Postscript is a stack based language and ghostscript interpreter can be used to debug it.

So, this is how the generated Postscript file looks like:


Important things to note from the Postscript file:

1. The size of the input is 65 bytes.
2. encrypt_str is a function which generates an encrypted string by doing an XOR encryption using an 8 byte key, "4L0ksa1t" and the input.
3. test_str is a function which takes the encrypted output generated by encrypt_str() function and processes it.
4. Inside test_str() function, buf_z holds an encoded string. We can see that it is decoded as follows:

buf = LZWDecode(ASCIIHexDecode(buf_z))

So, how do we decode it? We could rewrite the implementation for ASCIIHexDecode() and LZWDecode() and then pass the encoded string to it ;)

Or we could leverage the ghostscript interpreter to output this value for us.

In ghostscript interpreter, you can print the value of a variable using "=" operator.

So, to print the value of buf which will store the decoded output, we can add a "buf =" statement after buf.

It is important to note that if you only use the "=" operator, it will pop the value from the top of the stack. Doing this will change the stack and have an impact on execution of program as it proceeds. So, its better to use "buf ="

After decoding it, we get:

buf = "1712009367807218646859018292134521568805686127287089876612468382748236461208592688982686121828975882178245515674851882"

test_str() function also has a for loop which can be rewritten in Python as follows:


Since Postscript is a stack based language, when the encrypt_str function was called to encrypt our input with an 8 byte key, each encrypted byte was pushed on to the stack. Also, the value is stored in decimal form and not hex which is important to note as well.

So, each encrypted value is read from the stack and based on the length of it, the corresponding number of digits from buf are compared with it. Each time they are equal, the variable ok is incremented by the length of the value which was compared.

At the end of the FOR loop, if ok == len(buf), then it means that our input was correct.

Also, len(buf) = 118

To solve this challenge, we need to find an input such that when it is encrypted with an 8 byte XOR key: 4L0ksa1t, then each value of the encrypted output should match the corresponding place in the buffer.

In the shell script provided to us which was used to generate the PostScript file, we can see the following line:

echo $key | tr -Cd a-f0-9 >> postfuscator.ps

This means that our input contains the charset: a-f0-9 (only hex values)

Also, a "%" symbol was added as a  prefix to the input.

Now, I generated all the possible encrypted values for each combination of XOR between the digits of input and each byte of the 8 byte XOR key.

For example:

When the first byte of our XOR key: "4" is encrypted with each possible combination of input charset, we get:

['4', '%', 17]
['4', '0', 4]
['4', '1', 5]
['4', '2', 6]
['4', '3', 7]
['4', '4', 0]
['4', '5', 1]
['4', '6', 2]
['4', '7', 3]
['4', '8', 12]
['4', '9', 13]
['4', 'a', 85]
['4', 'b', 86]
['4', 'c', 87]
['4', 'd', 80]
['4', 'e', 81]
['4', 'f', 82]
 

As you can see, 17 are the first two digits of the buffer. So, we know that the first value in our input should be: %

Similarly, the possible mappings for the next byte of the XOR key are:

['L', '%', 105]
['L', '0', 124]
['L', '1', 125]
['L', '2', 126]
['L', '3', 127]
['L', '4', 120]
['L', '5', 121]
['L', '6', 122]
['L', '7', 123]
['L', '8', 116]
['L', '9', 117]
['L', 'a', 45]
['L', 'b', 46]
['L', 'c', 47]
['L', 'd', 40]
['L', 'e', 41]
['L', 'f', 42]


We can see that input value of 4 after encrypting with "L" gives 120 which are the next 3 digits of the buffer.

Similarly, we can continue finding the remaining bytes of the input and the final value of input is: 

406016abbe1ac8a7a2d7140232c58f68bc9932424e778c025b2893efa5d0edff

We can pass this value as a key to the shell script so that it generates a postscript file. The flag will be present inside the newly generated Postscript as file as shown below:


Flag: flag-6016abbe1ac8a7a2d714

c0d3inj3cT



No comments:

Post a Comment