Defcon Quals 2019 : Vitor

Vitor

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 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

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. Shellkod su sekilde

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 basei 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ı oluşuyor. 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;)}

14 Likes