DEF CON Quals 2019 : Vitor

Vitor

mm

Defcon 2019 qualifiersda sorulmuş android reverse kategorisine ait bir ctf sorusu. Matruşka misali birkaç bölümden oluşuyor. Soruda bize bir apk veriliyor. Uygulama bizden input olarak flagi istiyor ve doğruluğunu kontrol ediyor.

Aşama 1 : Dex

Verilen apkyı jadx gibi araçlarla decompile edip java kodunu inceleyebiliyoruz. Classlara göz attığımızda fc class’ını görüyoruz. Genel olarak her aşamada yapılacak olanlar birbirine benzer. Sadece ilk aşama için detaylı bir açıklama yapacağım. Flagi kontrol eden fonksiyonumuz şu şekilde:

public static boolean cf(MainActivity mainActivity, String str) {
boolean z = false;
try {
cfa(mainActivity, p1EncFn);
cfa(mainActivity, p5EncFn);
cfa(mainActivity, randEncFn);
cfa(mainActivity, rand2EncFn);
if (str.startsWith("OOO{") && str.endsWith("}") && str.length() == 45) {
if (cf(mainActivity, dp1(mainActivity, new File(mainActivity.getFilesDir(), p1EncFn), g0(str.substring(4, 44))), str)) {
File file = new File(mainActivity.getFilesDir(), "bam.html");
WebView webView = mainActivity.mWebView;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("file:///");
stringBuilder.append(file.getAbsolutePath());
stringBuilder.append("?flag=");
stringBuilder.append(Uri.encode(str));
webView.loadUrl(stringBuilder.toString());
z = mValid;
}
}
} catch (Exception e) {
}
return z;
}

İlk kontrolden anlıyoruzki Flag OOO{ ile başlayıp } ile bitiyor. Uzunluğu 45. Ardından sırasıyla incelersek:

g0(str.substring(4, 44))) : g0 fonksiyonuna flagimizin süslü parantezler içerisinde kalan input yollanıyor. g0 fonksiyonu :

public static byte[] g0(String str) {
int i;
byte[] bArr = new byte[4];
byte[] bytes = str.getBytes();
for (i = 0; i < 4; i++) {
bArr[i] = (byte) null;
}
for (int i2 = 0; i2 < 10; i2++) {
for (i = 0; i < 4; i++) {
bArr[i] = (byte) ((byte) (bArr[i] ^ bytes[(i2 * 4) + i]));
}
}
return bArr;
}

Fonksiyon 4 bytelık bir array döndürüyor. Bu 4 byte’ı da şu şekilde hesaplıyor:

byte[0] = flag[0]^flag[8]^flag[16]^flag[24]^flag[32]
byte[1] = flag[1]^flag[9]^flag[17]^flag[25]^flag[33]
byte[2] = flag[2]^flag[10]^flag[18]^flag[26]^flag[34]
byte[3] = flag[3]^flag[11]^flag[19]^flag[27]^flag[35]

g0 fonksiyonundan dönen 4byte, dp1 fonksiyonuna 3. parametre olarak gidiyor.

dp1(mainActivity, new File(mainActivity.getFilesDir(), p1EncFn), g0(str.substring(4, 44)))

private static File dp1(Context context, File file, byte[] bArr) throws Exception {
byte[] hash = hash(bArr);
byte[] readAllBytes = Files.readAllBytes(file.toPath());
try {
AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(initVector);
Key secretKeySpec = new SecretKeySpec(hash, "AES");
Cipher instance = Cipher.getInstance("AES/CBC/PKCS5PADDING");
instance.init(2, secretKeySpec, ivParameterSpec);
readAllBytes = instance.doFinal(readAllBytes);
File file2 = new File(context.getFilesDir(), p1Fn);
OutputStream fileOutputStream = new FileOutputStream(file2);
fileOutputStream.write(readAllBytes, 0, readAllBytes.length);
fileOutputStream.flush();
fileOutputStream.close();
return file2;
} catch (Exception e) {
return null;
}
}

Bu fonksiyonda önce 4byte’ın md5ini alıyor. Bu digest değerini aes keyi olarak kullanıyor ve input olarak verilen dosyayı decrypt ediyor. dp1’i çağırıken class’ın başında tanımlanmış
public static String p1EncFn = "ckxalskuaewlkszdva"; değeri kullanılıyor. Apk’ımızın içerisindeki asset klasorune bakarsak bu dosyayı görebiliyoruz. Peki bu dosyayı decrypt edip ne yapıyor ?

private static boolean cf(Context context, File file, String str) {
File file2 = new File(context.getFilesDir().getAbsolutePath());
try {
Class loadClass = new DexClassLoader(file.getAbsolutePath(), file2.getAbsolutePath(), file2.getAbsolutePath(), ClassLoader.getSystemClassLoader()).loadClass("ooo.p1.P1");
return ((Boolean) loadClass.getDeclaredMethod("cf", new Class[]{Context.class, String.class}).invoke(loadClass, new Object[]{context, str})).booleanValue();
} catch (Exception e) {
return false;
}
}

Decrypt edilen dosya DexClassLoader APIsi ile contexte yükleniyor. Bu olayı android malwarelerinde çok görüyoruz. Tersine mühendisliği zorlaştırmak için “packing” yöntemini sıklıkla kullanıyorlar.

ooo.p1.P1 class’ı invoke edilip o class içerisinden bir fonksiyon çağırılıyor. Bu fonksiyon boolean bir değer döndürüyor ( O fonksiyonda baska fonksiyonlar cagiriyor tabi ). Ve flagin doğruluğu kontrol edilmiş oluyor .

Aşama 1’i toparlasak:

  • Flagin belli indexlerini XORla ve 4byte’ı al
  • md5
  • ckxalskuaewlkszdva dosyasını bu keyle decrypt et
  • decrypt edilen dosya içerisinden ooo.p1.P1 classını load et.

Peki biz bu 4byte’ı nasıl bulabiliriz ?

Biliyoruzki decrypt edilen dosyanın bir jar dosyası olması lazım. Neden ? Çünkü kullanılan DexClassLoader fonksiyonu dosya formatı olarak jar kabul ediyor. Jar dosyaları da bir nevi zip ve zip dosyalarının Magic Byte’ını yani dosya tipini anladığımız header kısmını biliyoruz. \x50\x4b\x03\x04 dosya headerı olarak bu byteları arayacağız.

Şimdi brute için şu şekilde bir python scripti yazalım:

from Crypto.Cipher import AES
import hashlib

IV = bytes([19, 55, 19, 55, 19, 55, 19, 55, 19, 55, 19, 55, 19, 55, 19, 55])
N_BIT = 4
S_BIT = 2**7 + 1

DATA = open('ckxalskuaewlkszdva', 'rb').read(32)


for key_1 in range(S_BIT):
for key_2 in range(S_BIT):
print(key_1, key_2, end='\r')
for key_3 in range(S_BIT):
for key_4 in range(S_BIT):
key = hashlib.md5(
bytes([key_1, key_2, key_3, key_4])).digest()
aes = AES.new(key, AES.MODE_CBC, IV)
decrypted_data = aes.decrypt(DATA)
if decrypted_data.startswith(b'\x50\x4b\x03\x04'):
print(key_1, key_2, key_3, key_4)
IV = bytes([19, 55, 19, 55, 19, 55, 19, 55, 19, 55, 19, 55, 19, 55, 19, 55])
f= open("ckxalskuaewlkszdva","rb")
DATA = f.read()
f.close()
key = hashlib.md5(
bytes([key_1, key_2, key_3, key_4])).digest()
aes = AES.new(key, AES.MODE_CBC, IV)
decrypted_data = aes.decrypt(DATA)
f = open("out.zip","wb")
f.write(decrypted_data)
f.close()

Girdiğimiz stringler sonuçta ASCII yani 7bitlik aralıkta olduğu için XOR sonucu elde edilecek byte aralığıda bu aralığa sahip olmak zorunda.

Burda dikkatinizi DATA = open('ckxalskuaewlkszdva', 'rb').read(32) satırına vermenizi rica ediyorum. Neden 32 byte okuyorum burada ?

aes

AES CBC encryption şemasında sizin dosyanız ne kadar büyüse de ilk bloğun son halini ilgilendiren bir olay gerçekleşmiyor. Yani biz sadece ilk blok için deneme yaparak keyi bulabiliriz. Bu çok büyük ölçüde işimizi kolaylaştırıyor. 1.6 MB dosyanın boyutunu 32byte’a düşürmüş oluyoruz.

Bu scripti çalıştırdığımızda birkaç dakika içerisinde ilk keyimizi alıyoruz.

Key 1 : \x17\x01\x2f\x03

Her aşamamızda flagin belirli bytelarına dair bilgi ede ede ilerliyoruz.

Aşama 2 : SO

İlk aşamaya benzer bir dex dosyası ile karşılaşıyoruz.

Bu sefer jar dosyası yerine bir adet .so dosyası oluşturulması gerekiyor ve XORlanan indexler değişik.

for (int i2 = 0; i2 < 10; i2 += 2) {
for (i = 0; i < 4; i++) {
bArr[i] = (byte) ((byte) (bArr[i] ^ bytes[((i2 + 1) * 4) + i]));
}
}

byte[0] = flag[4]^flag[12]^flag[20]^flag[28]^flag[36]
byte[1] = flag[5]^flag[13]^flag[21]^flag[29]^flag[37]
byte[2] = flag[6]^flag[14]^flag[22]^flag[30]^flag[38]
byte[3] = flag[7]^flag[15]^flag[23]^flag[31]^flag[39]

Aynı scripti kullanarak zip headeri yerine .so dosyanın yani ELF headerini \x73\x45\x4c\x46 yazıyoruz, input dosyasını mmdffuoscjdamcnssn olarak değiştirip ikinici keyimizi de alıyoruz.

Key2 : \x3c\x27\x43\x60

Aşama 3 Shellkod:

Bu sefer işler değişiyor. Biliyoruzki so dosyasından xxx fonksiyonu çağırılıyor.

private native String xxx(String str, String str2);

So dosyanı IDA ile açıp incelediğimizde bu fonksiyonu görebiliyoruz.

ida1.png

Daha kolay anlaşılması için değişken ve fonksiyonlari isimlendirdim.

Önce asset klasorunden xtszswemcwohpluqmi dosyası okunuyor ve key hesaplandıktan sonra bu dosya decrypt ediliyor. Bu sefer biraz olaylar farklı. Keyi hesaplamak için fonksiyon şu şekilde:

K2_0 = 0;
flaga = flag + 4;
for ( i = 3; i < 10; i += 3 )
K2_0 ^= *&flaga[4 * (i - 1)];
return K2_0;

yani :

byte[0] = flag[8]^flag[20]^flag[32]
byte[1] = flag[9]^flag[21]^flag[33]
byte[2] = flag[10]^flag[22]^flag[34]
byte[3] = flag[11]^flag[23]^flag[35]

Key hesaplandiktan sonra:

for ( i = 0; i < 100; ++i )
{
*&p3enc[4 * i] ^= K2_0;
K2_0 += 0x31333337;
}

fonksiyonuna veriliyor ve dosya bu şekilde decrypt ediliyor. Peki bu sefer neye göre decrypt edicez ? Decrypt edildikten sonra bir assembly kodu elde ediceğimiz belli. Çünkü fonksiyonun devamında bu alani fonksiyon şeklinde çağrılıyor.

ida2

Bu cepte. Fonksiyon çağırılmadan önceki android log’unun bastığı değere baktığımızda

Jumping to nopsled in 3, 2, 1, ... stringini görüyoruz. Nop sled ne demekti shellkod demekti. NOP instructionunı shellkod yazan herkes illaki kullanmıştır. Instruction’ın byte karşılığı 0x90. Sled olunca da bundan bir sürü demek. Decrypt ederken 4byte 4byte gidildiği için encrypted dosyanın ilk 4 byteını 0x90909090 ile XORlarsak keyimizi elde ederiz.

hex(0x90909090^0xfef3b7de) : \x6e\x63\x27\x4e keyini veriyor. Bu key ile dosyayi decrypt etmek istersek :

Key3: \x6e\x63\x27\x4e

from pwn import *

f = open("xtszswemcwohpluqmi","rb")
data = f.read()
f.close()

#K2_0 = hex(0x90909090^0xfef3b7de)
#endianess
K2_0 = 0x4e27636e

f = open("shell_out.dat","wb")

for i in range(100):

f.write(xor(data[4*i:4*i+4],p32(K2_0)))
K2_0 += 0x31333337
K2_0 = K2_0 & 0xffffffff

f.close()

Oluşan dosyayı ndisasm -b 32 shell_out.dat şeklinde kontrol edebilirsiniz.

Aşama 4 : ROP

En zor kısım burası. Shellkodun ne yaptığını anlamamız gerek.

Fonksiyona girmeden önce stackteki değerlerimize bakalım:

.text:00008C8F                 mov     edx, [ebp+flag?]
.text:00008C92 mov ebx, [ebp+cx_data]
.text:00008C98 mov esi, [ebp+size]
.text:00008C9E mov [esp], edx ; s
.text:00008CA1 mov [esp+4], ebx ; src
.text:00008CA5 mov [esp+8], esi ; n
.text:00008CB5 call ecx

Shellkod çağırılırken ret adresi de pushlanacağı için stack değerlerimiz 4 artıyor.

...
...
90 nop
E800000000 call 0x25 // 0x25i stacke pushla
5B pop ebx // ebx = 0x25
83EB05 sub ebx,byte +0x5 // ebx = 0x20
83EB20 sub ebx,byte +0x20 // ebx = 0x0 + (Shellkodun yüklendiği base)
8B7C2404 mov edi,[esp+0x4] //flag
31C9 xor ecx,ecx
83C710 add edi,byte +0x10 //4byte kaydır = flag[0]
BA02000000 mov edx,0x2
330F xor ecx,[edi] //flag[i:i+4] ^ ecx
83C710 add edi,byte +0x10 //4byte kaydır
83EA01 sub edx,byte +0x1 //edxi azalt
75F6 jnz 0x3a //loop 2 yapıcak yani flag[0:4] ^ flag[16:20]
83EF20 sub edi,byte +0x20 //flagın basına geri dön
89D8 mov eax,ebx //eax = 0
05C8000000 add eax,0xc8 //eax = 200;
BA32000000 mov edx,0x32 //edx = 50;
3108 xor [eax],ecx //ecxte key vardı; 200 ofsetindeki değerler anlamsız
83C004 add eax,byte +0x4 //4byte ilerle
83EA01 sub edx,byte +0x1 //edx-1 ; 50 kez xorlanıcak; 50*4 =200; 200+200=400 tam dosya boyu
75F6 jnz 0x53 //loop; yine bir decryption
89D8 mov eax,ebx //eax = 0
052C010000 add eax,0x12c //eax = 300
BA19000000 mov edx,0x19 //edx = 25
0118 add [eax],ebx //[eax]taki değerlere base'i ekle
83C004 add eax,byte +0x4 //4byte ilerle
83EA01 sub edx,byte +0x1 //dec
75F6 jnz 0x69 //loop
89D8 mov eax,ebx //eax = base
052C010000 add eax,0x12c //eax = 300
89E2 mov edx,esp //save esp
8B7C2404 mov edi,[esp+0x4] //edi = flag
8B742408 mov esi,[esp+0x8] //esi = data
8B5C240C mov ebx,[esp+0xc] //ebx = size
89C4 mov esp,eax //esp = 300
C3 ret

Simdi kodumuz ne yapıyor kabaca bahsedersek:

  • Key hesapla
  • 200-400 arasını bu key ile xorla
  • 300den sonrası için shellkodun yüklendiği base’i ekle
  • esp’ye 300un offsetini ata
  • ret
  • ret’ten sonra kod executionu 300den devam edicek

Şimdi bizim if’i geçebilmemiz için bu fonksiyondan dönen değerin 0x31337 olması lazım. Şuan görünürde bu değer yok. 300’e gittikten sonra bu işi yapması lazım.

200-400 arasını XORlayacak keyi bulmak için bu aralığa bir bakalım.

000000c0: 9090 9090 9090 9090 fa18 3313 42db b0d3  ..........3.B...
000000d0: 46db f09a b2db b815 8129 3dd0 c1de 37d0 F........)=...7.
000000e0: c1f3 37d0 3c19 f04b 819b df0b 81a0 3a13 ..7.<..K......:.
000000f0: 4218 f0ab 750b 3013 8191 ebd0 cbd3 f09a B...u.0.........
00000100: 8adb f2d2 4adb 02da 812b 7c07 812b 7c3b ....J....+|..+|;
00000110: 8188 a383 d288 a383 d288 a383 d288 a383 ................
00000120: d288 a383 d288 a383 d288 a383 4419 3313 ............D.3.
00000130: 4b19 3313 4f19 3313 9b18 3313 9e18 3313 K.3.O.3...3...3.
00000140: 4019 3313 a218 3313 a618 3313 ab18 3313 @.3...3...3...3.
00000150: b118 3313 c918 3313 4218 3313 4218 3313 ..3...3.B.3.B.3.
00000160: 4218 3313 4218 3313 4218 3313 4218 3313 B.3.B.3.B.3.B.3.
00000170: 4218 3313 4218 3313 4218 3313 4218 3313 B.3.B.3.B.3.B.3.
00000180: 4218 3313 4218 3313 4218 3313 4218 3313 B.3.B.3.B.3.B.3.

Bloğun sonuna bakarsanız hep tekrar eden değerler görüyoruz. Bloğun başı işe anlamsız datalardan oluşuyor. Burda bloğun sonunun 0x0 lerle bittiğini düşünerek XOR keyi olarak 0x42183313 kullandım.

00000000  B800000000        mov eax,0x0
00000005 C3 ret
00000006 83C004 add eax,byte +0x4
00000009 C3 ret
0000000A C3 ret
0000000B 89F0 mov eax,esi
0000000D C3 ret
0000000E 8B06 mov eax,[esi]
00000010 C3 ret
00000011 310E xor [esi],ecx
00000013 C3 ret
00000014 83C604 add esi,byte +0x4
00000017 C3 ret
00000018 83EB04 sub ebx,byte +0x4
0000001B C3 ret
0000001C 7E01 jng 0x1f
0000001E C3 ret
0000001F 58 pop eax
00000020 C3 ret
00000021 83EC18 sub esp,byte +0x18
00000024 C3 ret
00000025 B809000000 mov eax,0x9
0000002A C3 ret
0000002B B837130300 mov eax,0x31337
00000030 C3 ret
00000031 89D8 mov eax,ebx
00000033 C3 ret
00000034 89CB mov ebx,ecx
00000036 C3 ret
00000037 89C8 mov eax,ecx
00000039 C3 ret
0000003A C1C108 rol ecx,byte 0x8
0000003D C3 ret
0000003E 31C9 xor ecx,ecx
00000040 C3 ret
00000041 334F14 xor ecx,[edi+0x14]
00000044 C3 ret
00000045 334F28 xor ecx,[edi+0x28]
00000048 C3 ret
...
00000063 90 nop
00000064 06 push es
00000065 0100 add [eax],eax
00000067 0009 add [ecx],cl
00000069 0100 add [eax],eax
0000006B 000D010000D9 add [dword 0xd9000001],cl
00000071 0000 add [eax],al
00000073 00DC add ah,bl
00000075 0000 add [eax],al
00000077 0002 add [edx],al
00000079 0100 add [eax],eax
0000007B 00E0 add al,ah
0000007D 0000 add [eax],al
0000007F 00E4 add ah,ah
00000081 0000 add [eax],al
00000083 00E9 add cl,ch
00000085 0000 add [eax],al
00000087 00F3 add bl,dh


Bloğumuzun başı rop gadgetleri. Altı ise bu gadgetların adresleri. Disasm bozuk göstersede şu şekilde :

0x0106 0x0109 0x010d 0x00d9 .. 0x00f3

Bu değerler espye atanmıştı. ret yapıldıkça bu değerler poplana poplana gidicek. Yani bu bizim çalışacak kodumuz. Şimdi fonksiyonun 0x31337 dönmesini istiyorduk. Bakalım rop chain döndürüyormu. Son offset 0xf3

200’u 0 kabul edip hesaplarsak : hex(0xf3-200) : 0x2b . Şimdi yukarıdan 0x2bye bakalım.

0000002B  B837130300        mov eax,0x31337
00000030 C3 ret

evet istediğimiz değer dönüyor. Rop için key:

Bu aşamadaki key şu şekilde hesaplanıyor :

byte[0] = flag[16]^flag[32]
byte[1] = flag[17]^flag[33]
byte[2] = flag[18]^flag[34]
byte[3] = flag[19]^flag[35]

Bulduğumuz xor keyi :

Key4 : \x42\x18\x33\x13

Aşama 5 : JS

Tabi rop chain sadece 0x31337 döndürmüyor ondan öncesinde fonksiyona verilen başka bir encrypted datayı decrypt ediyor.

ida3

Bir html dosyası. Html dosyasini rop chain oluşturuyor. Cağırılan gadgetlara bakalim:

xor_ecx_ecx         
xor_ecx_[edi+20] //flag[20:24]
xor_ecx_[edi+40] //flag[40:44] ^ flag[20:24]
xor_[esi]_ecx //eside encrypted data var
add_esi_4 //esi_4
rol_ecx_8 //rotate left ecx
sub_ebx_4 //size - 4
loop
mov_eax_31337

Rotate left yapmadan önce ilk aşamada direk gelen xorkeyi kullaniliyor. Bu yüzden yine xor keyini hesaplayabiliriz. Headerin html olduğunu biliyoruz. Encrypted dosyanın ilk 4byte’ı ile xorlarsak

Key5: = hex(0x3c68746d^0x6a452630): \x56\x2d\x52\x5d

byte[0] = flag[20]^flag[40]
byte[1] = flag[21]^flag[41]
byte[2] = flag[22]^flag[42]
byte[3] = flag[23]^flag[43]

Decryption routinini implemente edicek python scripti yazalim:

f = open("assets/cxnvhaekljlkjxxqkq","rb")
data = f.read()
f.close()
#rol8 4 iterasyondan sonra kendisine geri dönücek
a = [0x562d525d,0x5d562d52,0x525d562d,0x2d525d56]

f = open("bam.html","wb")

for i in range(int(len(data)/4)):

f.write(xor(data[4*i:4*i+4],p32(a[i%4])))

f.close()

Bam.htmli ilk açtığımız apk dosyasından hatırlarsak :

File file = new File(mainActivity.getFilesDir(), "bam.html");
WebView webView = mainActivity.mWebView;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("file:///");
stringBuilder.append(file.getAbsolutePath());
stringBuilder.append("?flag=");
stringBuilder.append(Uri.encode(str));
webView.loadUrl(stringBuilder.toString());
z = mValid;

Son aşama olarak flag stringi bu html dosyasına parametre olarak gidiyor. Html dosyasını açtığımızda obfuscated bir javascriptle karşılaşıyoruz. theempire_ saolsun bu adımı tak diye çözdü. Flagin 24. karakterinden sornasinın => pHd_1w_e4rL13r;)} ‘a eşit olması gerektiğini anlıyoruz.

Elimizde 5 key ve flagin 24-44 arası karakterleri var.

key0_0 = "4 ^ 12 ^ 20 ^ 28 ^ 36"
key0_1 = "5 ^ 13 ^ 21 ^ 29 ^ 37"
key0_2 = "6 ^ 14 ^ 22 ^ 30 ^ 38"
key0_3 = "7 ^ 15 ^ 23 ^ 31 ^ 39"

key1_0 = "8 ^ 16 ^ 24 ^ 32 ^ 40"
key1_1 = "9 ^ 17 ^ 25 ^ 33 ^ 41"
key1_0 = "10 ^ 18 ^ 26 ^ 34 ^ 42"
key1_1 = "11 ^ 19 ^ 27 ^ 35 ^ 43"

key2_0 = "12 ^ 24 ^ 36"
key2_1 = "13 ^ 25 ^ 37"
key2_2 = "14 ^ 26 ^ 38"
key2_3 = "15 ^ 27 ^ 39"

key3_0 = "16 ^ 32"
key3_1 = "17 ^ 33"
key3_2 = "18 ^ 34"
key3_3 = "19 ^ 35"

key4_0 = "20 ^ 40"
key4_1 = "21 ^ 41"
key4_2 = "22 ^ 42"
key4_3 = "23 ^ 43"

Biraz kopya cekerek script yazarsak.

from pwn import *
key0 = b'\x17\x01\x2f\x03'
key1 = b'\x3c\x27\x43\x60'
key2 = b'\x6e\x63\x27\x4e'
key3 = b'\x42\x18\x33\x13'
key4 = b'\x56\x2d\x52\x5d'

f6, f7, f8, f9, f10 = group(4, ' => pHd_1w_e4rL13r;)')
f5 = xor(key4, f10)
f4 = xor(key3, f8)
f3 = xor(xor(key2, f6), f9)
f2 = xor(xor(xor(xor(key1, f4), f6), f8), f10)
f1 = xor(xor(xor(xor(xor(key0, key1), f3), f5), f7), f9)

theflag = 'OOO{' + f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9 + f10 + '}'
print theflag

flag : OOO{pox&mpuzz,U_solve_it => pHd_1w_e4rL13r;)}