Tags: crypto 

Rating:

# Locked Dungeon 2
### Points : 498
### Solved by 25 Teams

Here we get python script :

```python
#!/usr/bin/env python2.7

from hashlib import sha256, md5
from Crypto.Cipher import AES
import os
import random
from binascii import hexlify, unhexlify
from base64 import b64decode, b64encode
import sys

BLOCK_SIZE = 16
KEY = os.urandom(16)
IV = os.urandom(16)

pad_len = lambda inp: (BLOCK_SIZE - len(inp) % BLOCK_SIZE)
pad = lambda inp: inp + chr(pad_len(inp))*pad_len(inp)
unpad = lambda inp: inp[:-ord(inp[-1])]

class AESCipher:
def __init__(self, key):
self.key = sha256(key).digest()

def __encrypt(self, _str, iv):
cipher = AES.new(self.key, AES.MODE_CBC, iv)
padded_str = pad(_str)
return cipher.encrypt(padded_str)

def encrypt_wrapper(self, _str, iv):
return b64encode(iv + self.__encrypt(_str, iv))

def __decrypt(self, enc_str, iv):
cipher = AES.new(self.key, AES.MODE_CBC, iv)
decrypted_str = cipher.decrypt(enc_str)
decrypted_str = unpad(decrypted_str)
return decrypted_str

def decrypt_wrapper(self, encoded_enc_str):
enc_str = b64decode(encoded_enc_str)
return self.__decrypt(enc_str[16:], enc_str[:16])

if __name__ == "__main__":
with open("flag.txt") as fd:
flag = fd.read()
flag_size = len(flag)
key=KEY
insertion_range = flag_size//BLOCK_SIZE
insertion_position = random.randrange(insertion_range)*BLOCK_SIZE
mod_flag = flag[:insertion_position] + "send_modflag_enc" + flag[insertion_position:]
aescipher = AESCipher(key)
enc_mod_flag = aescipher.encrypt_wrapper(mod_flag, IV)
sys.stdout.write(enc_mod_flag)
sys.stdout.write('\n')
sys.stdout.flush()
next_level = False
for i in range(insertion_range):
sys.stdout.write("What do you want me to do?\n")
sys.stdout.flush()
enc_recv_str = raw_input()
dec_recv_str = aescipher.decrypt_wrapper(enc_recv_str)
print dec_recv_str
if "get_modflag_md5" in dec_recv_str:
next_level = True
sys.stdout.write("Dungeon goes deeper..\n")
sys.stdout.flush()
break
else:
sys.stdout.write("I am gonna ask again!\n")
sys.stdout.flush()

if next_level:
len_enc_mod_flag = len(enc_mod_flag)
inp_size_limit = int(len_enc_mod_flag*4/3) + 50
for i in xrange(500):
enc_recv_str = raw_input()
if len(enc_recv_str) > inp_size_limit:
continue
dec_recv_str = aescipher.decrypt_wrapper(enc_recv_str)
sys.stdout.write(b64encode(md5(dec_recv_str).digest()))
sys.stdout.write("\n")
sys.stdout.flush()
```

So basically here we given a script of encryption,the first step is to modify dec_recv_str to contain string `get_modflag_md5` instead of `send_modflag_enc` .Encryption using AES-CBC so,IV is applied after the Block-Key Encryption .We can modify our IV payload with `new_iv` = `target string` ^ `real string` ^ `old_iv` .Using this `new_iv` as our payload,we can get to the `The next_level` .This only can work if result of `insertion_position = random.randrange(insertion_range)*BLOCK_SIZE` is 0.So we need to retry multiple times.

After that in `next_level` we can bruteforce flag ber byte using md5 encryption.It works if we can adjust the last byte of IV to desired pad because padding of message use `unpad = lambda inp: inp[:-ord(inp[-1])]` .It caused we can easily generate first byte of message until n-1 length of message per process(16 byte).We can only get 15 out of 16 character of flag per process cause `[:-ord(inp[-1])]` cannot get the entire message.Use some bruteforce after that and get the flag.

## Solve.py
```python
from pwn import *
from base64 import *
import hashlib

flag=[]
tes=16
tes2=32
jadi=''
while True:
try:
p = remote("chal1.swampctf.com" ,1460)
# p = process('./dungeon2.py')
data = p.recvline()[:-1]
data = b64decode(data)

iv = data[:16]
awal = data[16:32]
mula = "send_modflag_enc"
target = "get_modflag_md5\x01"

iv_baru =''
for i in range(16): #change IV to make decrypted string to "target" from "mula"
iv_baru += chr(ord(target[i]) ^ ord(iv[i]) ^ ord(mula[i]))

payload = iv_baru + awal
payload = b64encode(payload)

p.recv(1024)
p.sendline(payload)
lol2 = p.recv(1024)

if len(jadi) == 15: #max 15 byte flag per process
flag.append(jadi)
print flag
tes+=16
tes2+=16
jadi = ''

if len(flag) == 3: #get the entire flag,the last 16 byte blocks are just padding
break

if "Dungeon goes deeper" in lol2 :
print "Succes Entering Dungeon,processing..."
for j in range(256): #Bruteforce last byte IV to search flag per byte using md5
payload2 = data[tes:tes2][:-1] + chr(j) + data[tes2:tes2+16]
p.sendline(b64encode(payload2))
hasil = p.recvline()[:-1]
for k in range(0x20,0x7f):
if hashlib.md5(jadi+chr(k)).digest() == b64decode(hasil):
print "Get flag byte :" , chr(k)
jadi+=chr(k)
break
if len(jadi)==15:
break
print "Piece of flag gathered per process : ",jadi
else:
print "Can't enter,try again ..."
p.close()
except:
print "Service Error :("
pass

#After we got 3 separate flag string,we need to join them and bruteforce some word
#cause every process we only can get 15/16 character

#Possible Flag :
# flag{Ev3n_dunge --> 0 or o or O
# ns_are_un5af3_w --> i or I or 1
# th_vu1n_padding --> we know last char is } , so just need brute 2 word
print flag
print "Possible FLAG :"
for i in ("0oO"):
for j in ("1iI"):
print flag[0] + i + flag[1] + j + flag[2] + "}"
```

### Wrote by : Yeraisci77