Today we would like to analyze certain interesting tasks that were offered to participants of the past contests.

**History and Geography**

**Tasks and the Atmosphere**Traditionally, tasks and infrastructure are prepared based on a legend of the contest, which would turn a set of tasks into a fascinating competition. Last year, PHDays CTF participants tried to save the fictional world D’Errorim. The upcoming contest will continue the plot.

PHDays CTF\’s tasks are usually based on vulnerabilities that can exist in real life. What\’s more, the contest is remarkable for its game mechanics that make it possible to implement various strategies during the game (for more details visit the PHDays website).

The contest\’s organizers often develop additional tasks that are not directly related to system hacking. For instance, during PHDays 2012, participants could earn extra points by finding flags in containers filled with scrap paper, while PHDays III teams needed to get over the Labyrinth with laser field and motion detectors, secret doors, a room with bugs and other obstacles.

However, main points could be earned by solving different tasks related to information security. Let\’s get into the details of some of them.

**Analysis**PHDays CTF Quals is a task-based CTF contest, which means that teams should solve tasks and win points. Tasks can be classified into:

- Forensic (computer forensic science),
- Reverse (binary code analysis),
- Pwn (vulnerability exploitation),
- Admin (admin skills),
- Network (knowledge of network infrastructure and protocols),
- Crypto (cryptography),
- Stegano (steganography),
- PPC (professional programming and coding),
- Web (detecting and using web vulnerabilities),
- Misc (miscellaneous).

Let\’s start from the latest.

**Nonobvious Solution**

PHDays IV CTF Quals had a task that implied decryption of a message hidden in an MP3 file.

As a rule, when you need to extract a hidden message from a container, you can use a steganography solution. To solve the task you can also select a decryption software and to run it using valid keys. In other words, usually the key to success lies in finding the solution that was set up by the developers.

But our case is different. When we open the given file in a text editor, it looks like this:

There\’s metadata in the ID3 format at the beginning. We can see the TRCK tag (track number) and some text.

**RGB7 5,183, NULL RGB6 0,42,159 RGB5 194,244,68 RGB4 47,77,6 RGB3 44,73,141 RGB2 140,207,72 RGB1 120,156,203**

We can divide the data into seven records (from RGB7 to RGB1):

**RGB7 5,183, NULL****RGB6 0,42,159****RGB5 194,244,68****RGB4 47,77,6****RGB3 44,73,141****RGB2 140,207,72****RGB1 120,156,203**

There are three values after each RGB identifier. The values are given in numbers except one case, when it is NULL. It is an array of records, each of them containing up to three single-byte values. We can sort out the records, combine them, turn decimals code into symbols and convert them to hexadecimal using, for example, the following program:

>>> a = [120,156,203, 140,207,72, 44,73,141, 47,77,6, 194,244,68, 0,42,159, 5,183]

>>> print \”\”.join(map(chr, a)).encode(\”hex\”)

As a result, we\’ll get:

789ccb8ccf482c498d2f4d06c2f444002a9f05b7

The hexadecimal sequence starts with 0x78 0x9С bytes, which tells us that the zlib data compression algorithm is used. When you use zlib for compression with default parameters, the output sequence starts with these bytes.

By calling zlib\’s decompress function in Python we can unpack the message.

>>> import zlib

>>> print zlib.decompress(\”\”.join(map(chr, a)))

At this point we can see the text:

**i_hate_ucucuga**

So this is the flag that participants should have sent to the contest\’s organizers.

**Improper Cryptography**

This task belongs to the Crypto category. According to the legend, a communication session was intercepted, and participants needed to decrypt the transmitted messages.

First of all, we can see a key exchange process and transmission of encrypted data. We need to define the cryptographic basis on which the communication was built.

The task is called “mars”, which might mean Modified RSA. Each key consists of two parts, the second part being equal to 0x010001 == 65537. It is a frequently used RSA (e) exponent, which means that in the communication session public keys are exchanged first (n1/e1, n2/e2), then messages encrypted using these keys are exchanged (c1, c2).

If it really is something similar to RSA, then ci = pow(mi, ei, ni). We need to calculate m1 и m2.

pow is a modular exponentiation function, pow(val, еxp, modulus) == valexp % modulus.

According to the RSA algorithm:

- m
_{i}= pow(c_{i}, d_{i}, n_{i}), - d
_{i}*e_{i}≡ 1 mod φ(n_{i}), - n
_{i}— a product of several prime numbers, - φ(n) — Euler\’s phi function, the positive integers less than or equal to n that are relatively prime to n.

In this task, the n_{1} and n_{2} length is 1535 bits, which means that factorization (decomposition into prime multipliers) cannot be applied.

The extended Euclidean algorithm implementation in Python is at help:

def egcd(a, b): # Extended Greatest Common Divisor if a == 0: return (b, 0, 1) else: g, y, x = egcd (b % a, a) return (g, x – (b // a) * y, y)

We need to define the greatest common divisor (GCD) n_{1} and n_{2}:

gcd = egcd(n1,n2)[0]

The length of GCD(n_{1, }n_{2}) is 1024 bits. Let\’s find other divisors of n_{1} and n_{2}:

p1 = n1 / gcd

p2 = n2 / gcd

p_{1} and p_{2} — are prime numbers with the length of 512 bits, cd is a composite number with the length of 1024 bits (most likely it\’s 512*512), and it\’s too large for factorization as well.

Let\’s consider the case when the unknown messages mi can be represented in numbers that do not exceed p_{i}.

Let n_{i} = p_{i}*q*r, then as 0 < m_{i} < p_{i} the following expression will be true:pow(m

_{i}, e

_{i}, n

_{i}) % p

_{i}== pow(m

_{i}, e

_{i}, p

_{i})

Then the exponent used to decrypt d’i should comply with the following expression:

e_{i}*d’_{i} ≡ 1 mod φ(p_{i})

The value of d’_{i} can be obtained by the calculation of the algebraic supplement:

d’_{i} = invmod(e_{i}, φ(p_{i}))

For a prime number p_{i} it is true that: φ(p_{i}) == p_{i-1},

Therefore:

d’_{i} = invmod(e_{i}, p_{i-1})

Calculation of the algebraic supplement is performed by the following function in Python:

def invmod(a, m): # Modular inversion g, x, y = egcd (a, m) if g == 1: return x % m raise Exception(\”modular inverse does not exist\”)

We will also need a function that converts the number to a string and displays the text from the last symbol ‘\\0’ to the end of the string:

def showX(v): print (\”%0256X\” % v).decode(\”hex\”).split(\’\\0\’)[-1]

We calculate di, then perform decryption:

d1 = invmod(e, p1-1)

d2 = invmod(e, p2-1)

showX(pow(c1, d1, p1))

showX(pow(c2, d2, p2))

Getting the result:

**REQUEST: GET_FLAG (SIGNATURE: 5e2d5e0323591b1c).****RESPONSE: its_n0t_ab0ut_p4dd1ng**

The flag was «**its_n0t_ab0ut_p4dd1ng**».

**The SECC Task**The contestants are given: the archive source.tar.gz, with the files

*ecc.py*and

*task.py*, containing the scheme of key verification, implemented elliptic curve cryptography. It is known, that connecting to port 5555 via the address 195.133.87.171, one can establish a connection with some server:

nc 195.133.87.171 5555

password: secch4l*

It is reasonable to start with analyzing the given files. One can even try to run them.

I didn\’t have a *libnum* module, so I had to write it. For the module, implementing the above mentioned function of modular inversion and the extended Euclidean algorithm used by it would do:

def egcd(a, b): # Extended Greatest Common Divisor if a == 0: return (b, 0, 1) else: g, y, x = egcd (b % a, a) return (g, x – (b // a) * y, y)

def invmod(a, m): # Modular inversion g, x, y = egcd (a % m, m) if g != 1: raise Exception(\”modular inverse does not exist\”) else: return x % m

So, here is the function *main* from *task.py*:

def main(): print \”Auth:“ auth = raw_input() if hashlib.sha1(auth).hexdigest() != \”375d5c01ca1b8c3863024d10aac7713472eb5033\”: # secch4l* print \”nope“ return prefix = os.urandom(8) print \”Proof of work, please“ print \”Prefix is (hexed) \”, prefix.encode(\”hex\”) test = raw_input().decode(\”hex\”) if not test.startswith(prefix) or len(test) > 16: print \”nope“ return h = hashlib.sha1(test).hexdigest() if not h.startswith(\”000000\”): print \”nope“ return

goflag()

The string is read, the SHA-1value from which should equal the defined value (\”secch4l*\”).

Then the function generates a random 8-byte prefix, sent to the client. The bytes are encoded as a hex string. In response, the client should send a string no longer than 16 bytes and starting with the defined prefix, also the first 3 bytes of the SHA-1 value from tis string should be null. With all the steps successfully completed, the function *goflag()* is called.

The next code fragment establishes server connection, sends the password, receives the prefix, calculates and sends the response:

def readLn(sock): a = [] while True: c = sock.recv(1) if \’\\n\’ == c: return \”\”.join(a) a.append(c)

HOST = \”195.133.87.171\”PORT = 5555sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)sock.connect((HOST, PORT))print readLn(sock) # Auth:sock.send(\”secch4l*\\n\”)print readLn(sock) # Proof of work, pleases = readLn(sock) print s # Prefix is (hexed) 0b3997e62b9ffbf4prefix = s.split()[-1].decode(\”hex\”)for i in xrange(0x7FFFFFFF): s = \”%s%X\” % (prefix, i) if hashlib.sha1(s).digest()[:3] == \’\\0\\0\\0\’: breaksock.send(s + \’\\n\’)

After this code is executed on the client side, the server runs the function goflag(), which returns something like that:

**EC PASSWORD CHECK****R = 572115218124168948525078362547166172445820217705568707355669424304224832114****SHARED SECRET = R ^ PASSWORD****ENCRYPTED MESSAGE: 7a93846a011e0d0382e94f32d705239e6298169dcec20da5d6**

So, what is happening in the *goflag* function of *task.py*?

def goflag(): print \”EC PASSWORD CHECK\” r = random.randint(31337, 1 << 250) R = p256.power(G, r) print \”R =\”, R print \”SHARED SECRET = R ^ PASSWORD\” S = p256.power(R, PASSWORD) key = p256.derive(S) cipher = encrypt(FLAG, key) print \”ENCRYPTED MESSAGE:\”, cipher.encode(\”hex\”)

Here asymmetric cryptography based on elliptic curves is used. The recommended by NIST curve P-256 is chosen. The implementation of the curve points operations contains no obvious vulnerabilities.

We have the *R* value, but without knowing the PASSWORD value (which is read by the server from password.txt) we cannot calculate *S*. The *S* value would let us easily obtain the key. Maybe, the encryption implementation is faulty?

Here is the encrypt function from *task.py*:

def encrypt(msg, key): iv = os.urandom(8) stream = hashlib.sha256(iv + key).digest() stream = hashlib.sha256(stream + iv + key).digest() cipher = iv + xor(msg, stream) return cipher

The code shows that the encrypted message is proceded by a random 8-byte initialization vector (iv), and the encryption is performed via a XOR shift of the flag with the gamma, which is generated as an output of two SHA-256 calculations. Without the key it is unreal to obtain the gamma value. But how the program gets the key?

Here is the derive function from *task.py*:

def derive(self, p):

return hashlib.sha256(str((p[0] << 10) / p[1])).digest()

So, the value of the *S* point (which is made of two coordinates — x and y) is used as the SHA-256 input. In fact, the value *str(int(x*1024/y))* is input to the hash function. Since the values of x and y are close (they are large integer numbers), the result of the arithmetic operations should be close to 1024 (although the result may exceed it by several times).

So due to the specifics of derive implementation, the *key* value may have a limited number of states. One can just use brute force them, trying to decrypt the message for every key, and if the message contains only of graphic characters — we got the flag.

import hashlib, eccenc = \”7a93846a011e0d0382e94f32d705239e6298169dcec20da5d6\”.decode(\”hex\”)iv = enc[:8]def decrypt(key): stream = hashlib.sha256(iv + key).digest() stream = hashlib.sha256(stream + iv + key).digest() return ecc.xor(enc[8:], stream)

for i in xrange(0x7FFFFFFF): s = decrypt(hashlib.sha256(str(i)).digest()) for c in bytearray(s): if c = 128: break else: print s # ecc_is_too_s3cure break

So the \”ecc_is_too_s3cure\” string is the flag.

**Reverse Engineering, or Shadelt900**

Reversing is one more popular type of CTF tasks. Apart from CTF, the individual challenge called Best Reverser is also among PHDays contests.

The task Shadelt900, just as the three tasks described above, was the part of PHDays IV CTF Quals, which took place in January 2014. The teams had to decrypt the image file called \”derrorim_enc.bmp\”. The name of the tool applied for encryption was given – Shadelt9000.exe, but no decryptor can be found. Here is the image:

A closer examination of Shadelt9000.exe shows that the application uses OpenGL. There is also the copyright sign: inflate 1.2.8 Copyright 1995-2013 Mark Adler, indicating that the tool uses the popular compression library zlib.

Looking via a disassembler where calls to zlib originate from, you can quite quickly find this code batch:

from zlib import decompress as unZbase = 0x47C000 – 0x7AE00 # data section baseab=open(\”ShadeIt9000.exe\”, \”rb\”).read()open(\”1.txt\”, \”w\”).write(unZ(ab[0x47F660-base:],-15))open(\”2.txt\”, \”w\”).write(unZ(ab[0x47F7B8-base:],-15))

After unpacking, the file 1.txt appears to contain a pixel shader:

#version 330uniform sampler2D u_texture;uniform sampler2D u_gamma;varying vec4 texCoord0;varying vec3 v_param;uint func(vec3 co){ return uint(fract(sin(dot(co ,vec3(17.1684, 94.3498, 124.9547))) * 68431.4621) * 255.);}uvec3 rol(uvec3 value, int shift) { return (value <> (8 – shift));}const uvec3 m = uvec3(0xff);void main(){ uvec3 t = uvec3(texture2D(u_texture, vec2(texCoord0)).rgb * 0xff) & m; uvec3 g = uvec3(texture2D(u_gamma, vec2(texCoord0)).rgb * 0xff) & m; int s = int(mod(func(v_param), 8)); t = rol(t, s); vec3 c = vec3((t ^ g) & m) / 0xff; gl_FragColor = vec4(c, 1.);}

The 2.txt file contains a vertex shader:

attribute vec3 a_param;varying vec4 texCoord0;varying vec3 v_param;void main(void){ gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; texCoord0 = gl_MultiTexCoord0; v_param = a_param;}

The principal information about the pixel shader is in red color:

void main(){ uvec3 t = uvec3(texture2D(u_texture, vec2(texCoord0)).rgb * 0xff) & m; uvec3 g = uvec3(texture2D(u_gamma, vec2(texCoord0)).rgb * 0xff) & m; int s = int(mod(func(v_param), 8)); t = rol(t, s); vec3 c = vec3((t ^ g) & m) / 0xff; gl_FragColor = vec4(c, 1.);}

The *t* variable contains the current item of the processed texture (of the input file), and the *g* variable contains the current gamma item (randomly generated). The *s* variable contains a certain value, later used for the *s* cyclic shift. The output value is actually computed this way:

(rol(t,s) ^ g)

Running the program several times with the same input file shows that g changes for each item with every run, however t and s always remain the same.

Let\’s find how the gamma is generated:

unsigned char *pbGamma = malloc(cbGamma);srand(time(0));for (i = 0; i < cbGamma; i++) { pbGamma[i] = rand();}

Here you can see that it depends on the current time.

The initial archive allows finding out that derrorim_enc.bmp was created on 01/21/2014 at 18:37:52.

We can obtain the value the time() function would return at that time:

>>> import time

>>> print hex(int(time.mktime((2014,1,21, 18,37,52, 0,0,0))))

**0x52de8640**

Now let\’s copy ShadeIt9000.exe to ShadeIt9000_f.exe and modify it.

With the shift 00015557, the bytes

E8 A5 31 01 00

should be changed to

B8 40 86 DE 52

This equals changing

call _time

to

mov eax,52de8640h

So we got the version of ShadeIt9000_f which will always encode with the gamma that was used at the encryption moment of the file in question.

Now let\’s prepare the values which will help decoding the image:

import osbmp=open(\”derrorim_enc.bmp\”, \”rb\”).read()hdr = bmp[:0x36]abData = bytearray(bmp[0x36:])cbBody = len(bmp) – len(hdr)open(\”00.bmp\”, \”wb\”).write(hdr + \’\\0\’*cbBody)open(\”XX.bmp\”, \”wb\”).write(hdr + \’\\2\’*cbBody)os.system(\”ShadeIt9000_f.exe 00.bmp\”)os.system(\”ShadeIt9000_f.exe XX.bmp\”)

The file 00_enc.bmp will have the result of image encoding, consisting of null bytes. This will be the very gamma per se.

The file XX_enc.bmp will have the result of image encoding, consisting of bytes with the value 2. This helps us find out by how many bits every byte was cyclically shifted.

Finally, let\’s decode Shadelt9000:

def rol(v,i): return (((v<

>(8-i)) & 0xFF))def ror(v,i): return (((v>>i) & 0xFF) | ((v<<(8-i)) & 0xFF))dRot = {rol(1,i):i for i in xrange(8)}bmp=open(\”derrorim_enc.bmp\”, \”rb\”).read()hdr = bmp[:0x36]abData = bytearray(bmp[0x36:])abGamma = bytearray(open(\”00_enc.bmp\”, \”rb\”).read()[0x36:])abRot = bytearray(open(\”XX_enc.bmp\”, \”rb\”).read()[0x36:])for i,b in enumerate(abGamma): abRot[i] = dRot[abRot[i] ^ b]for i,b in enumerate(abGamma): abData[i] = ror(abData[i] ^ b, abRot[i])open(\”derrorim.bmp\”, \”wb\”).write(hdr + str(abData))

Here is the result:

Above I described a valid but not the most efficient way to solve the task. There is a shorter path.

Right after the vertex shader, with the addresses 0x47F848 and 0x47F9A0 there is the zlib-packed code of the pixel and vertex shaders rady for reversing. Perhaps, the code was accidentally left there be the task developer. Or possibly the developer planted the code on purpose

.

The codes of the vertex shader are identical for encoding and decoding, so there is no sense in tackling them. What if to modify the pixel shader?

Let\’s copy ShadeIt9000_f.exe into ShadeIt9000_d.exe and change it:

00015775: 60 F6 ==> 48 F8

Then let\’s run ShadeIt9000_d.exe derrorim_enc.bmp. This allows obtaining the decoded file derrorim_enc_enc.bmp, which (except some small) is the same as the image we got via the Python scrypt.

That\’s all for today! Thank you for reading it, you are welcome to comment and ask questions!

P. S. The archive of PHDays CTF finals and quals tasks is available on the PHDays website.

This comment has been removed by a blog administrator.

This comment has been removed by a blog administrator.

This comment has been removed by a blog administrator.

This comment has been removed by a blog administrator.