NiteCTF 2023 : Matryoshka

Context

They give us a very large text file.

Writeup

We notice that the file looks like base64 and upon decoding we get other types of encoding that we can decode step by step with this script.

with open("matryoshka.txt","r") as f :
    a = f.read()

from base64 import b64decode

with open("out.bin","wb") as f:
    f.write(b64decode(a))

with open("out.bin","r") as f:
    a = f.read()
    t = a.split(" ")
    b = bytes([int(i, 16) for i in t])
    with open("out1.bin", "wb") as k :
        k.write(b)

with open("out1.bin", "r") as f:
    a = f.read()
    t = a.split(" ")
    b = bytes([int(i, 10) for i in t])
    with open("out2.bin", "wb") as k :
        k.write(b)

with open("out2.bin", "r") as f:
    a = f.read()
    t = a.split(" ")
    b = bytes([int(i, 16) for i in t])
    with open("out3.bin", "wb") as k :
        k.write(b)

We find by using the file command that out2.bin is a JPEG file.

So we use either foremost or binwalk to extract the files that could be hidden inside that image.

$ foremost out.jpeg

00000653.png is extracted which looks like noise.

At first, I've tried to analyze that file by computing the distributions of values and it seems to be very random. So in the same philosophy as the other challenge phantas, I've tried to write out a file 3 bytes by 3 bytes where each of the 3 bytes is the RGB values of each pixel going in left to right and up and down order.

from PIL import Image
import numpy as np

im = Image.open("00000653.png")

px = im.load()
out = Image.new("RGB", (im.size[0],im.size[1]))
s = 0
for i in range(im.size[0]):
    for j in range(im.size[1]):
        s += px[i,j][0] | px[i,j][1] << 8 | px[i,j][2]<< 16

avg = s / (im.size[0] * im.size[1])
print(f'{avg=}')
s = 0
for i in range(im.size[0]):
    for j in range(im.size[1]):
        s += np.abs((px[i,j][0] | px[i,j][1] << 8 | px[i,j][2]<< 16) - avg)
dev = s / (im.size[0] * im.size[1])
eq = np.sqrt(dev)
print(f'{dev=}')
print(f'{eq=}')
res = out.load()
with open("out_png.jpeg", "wb") as f:
    print(px[1,0][0].to_bytes(1,"big"))
    print(px[1,0][1].to_bytes(1,"big"))
    print(px[1,0][2].to_bytes(1,"big"))
    for i in range(im.size[1]):
        for j in range(im.size[0]):
            print(f"{j=},{i=}")
            for k in px[j,i]:
                f.write(k.to_bytes(1,"big"))

We get another JPEG... Matryoshka dolls HAHAHAHAHAHAHAHA... :)

This one is actually easy as it is the same as one of the step before because we just need to use foremost/binwalk to extract the image.

$ foremost out_png.jpeg
$ tar -xvf arc.tar.gz

This time, we get a binary and instead of trying to reverse it using Ghidra or radare2. We can just use strings and it spits out a python script.

input1 = "2f6c616e672f4f626a6563743b295a0a001d001e07001f0c002000210100156a6176612f7574696c2f436f6c6c656374696f6e7301000773687566666c65010013284c6a6176612f7574696c2f4c6973743b29560700230100116a6176612f7574696c2f486173684d61700a002200030b001700260c0027002801000672656d6"
input2 = "cafebabe0000003d008e0a000200030700040c000500060100106a6176612f6c616e672f4f626a6563740100063c696e69743e0100032829560700080100136a6176612f7574696c2f41727261794c6973740a000700030a000b000c07000d0c000e000f0100106a6176612f6c616e672f537472696e6701000b746f436861724"
input3 = "17272617901000428295b430a001100120700130c001400150100136a6176612f6c616e672f43686172616374657201000776616c75654f660100182843294c6a6176612f6c616e672f4368617261637465723b0b001700180700190c001a001b01000e6a6176612f7574696c2f4c697374010003616464010015284c6a617661"
input4 = "76612f6c616e672f537472696e674275696c6465720a002f00030b002a00330c0034002e01000c6765744f7244656661756c740a001100360c003700380100096368617256616c75650100032829430a002f003a0c003b003c010006617070656e6401001c2843294c6a6176612f6c616e672f537472696e674275696c6465723"
input5 = "f76650100152849294c6a6176612f6c616e672f4f626a6563743b0b002a002b07002c0c002d002e01000d6a6176612f7574696c2f4d6170010003707574010038284c6a6176612f6c616e672f4f626a6563743b4c6a6176612f6c616e672f4f626a6563743b294c6a6176612f6c616e672f4f626a6563743b0700300100176a61"
input6 = "4e0100116a6176612f6c616e672f496e74656765720100087061727365496e74010016284c6a6176612f6c616e672f537472696e673b4929490800500100163638366436643566366333343739333337323335356608005201001e373633333566373733343739356633373330333035663664333436653739080054010012366"
input7 = "73244c6f6f6b757007008c01001e6a6176612f6c616e672f696e766f6b652f4d6574686f6448616e646c65730100064c6f6f6b75700021005e0002000000000004000100050006000100740000001d00010001000000052ab70001b10000000100750000000600010000000700090065006200010074000001600004000900000"
input8 = "b0a002f003e0c003f0040010008746f537472696e6701001428294c6a6176612f6c616e672f537472696e673b0a000b00420c004300440100066c656e6774680100032829490a000b00460c00470048010009737562737472696e67010016284949294c6a6176612f6c616e672f537472696e673b0a004a004b07004c0c004d00"
input9 = "5363937343635376233313566363833340800560100163566363433303663366333353566366533303737356608005801001633303636356636633331363633333566363936373764120000005a0c005b005c0100176d616b65436f6e63617457697468436f6e7374616e747301006e284c6a6176612f6c616e672f537472696e"
input10 = "673b4c6a6176612f6c616e672f537472696e673b4c6a6176612f6c616e672f537472696e673b4c6a6176612f6c616e672f537472696e673b4c6a6176612f6c616e672f537472696e673b294c6a6176612f6c616e672f537472696e673b0a005e005f0700600c006100620100046e697465010007636f6e76657274010026284c6"
input11 = "98284c6a6176612f6c616e672f696e766f6b652f4d6574686f6448616e646c6573244c6f6f6b75703b4c6a6176612f6c616e672f537472696e673b4c6a6176612f6c616e672f696e766f6b652f4d6574686f64547970653b4c6a6176612f6c616e672f537472696e673b5b4c6a6176612f6c616e672f4f626a6563743b294c6a6"
input12 = "0c005b00620a006f00700700710c007200730100136a6176612f696f2f5072696e7453747265616d0100077072696e746c6e010015284c6a6176612f6c616e672f537472696e673b2956010004436f646501000f4c696e654e756d6265725461626c6501000d537461636b4d61705461626c650700780100025b430100046d616"
input13 = "96e010016285b4c6a6176612f6c616e672f537472696e673b295601000a536f7572636546696c650100096e6974652e6a617661010010426f6f7473747261704d6574686f64730f06007f0a008000810700820c005b00830100246a6176612f6c616e672f696e766f6b652f537472696e67436f6e636174466163746f72790100"
input14 = "0c0bb000759b700094c2ab6000a4d2cbe3e03360415041da2001b2c15043436052b1505b80010b90016020057840401a7ffe52bb8001cbb002259b700244d2ab6000a4e2dbe360403360515051504a200252d15053436062c1506b800102b03b900250200c00011b90029030057840501a7ffdabb002f59b700314e2ab6000a3a"
input15 = "176612f6c616e672f696e766f6b652f43616c6c536974653b0800850100050101010101080087010025315f683474335f737472316e675f6d346e3170756c343731306e5f316e5f6a6176613a200101000c496e6e6572436c617373657307008a0100256a6176612f6c616e672f696e766f6b652f4d6574686f6448616e646c65"
input16 = "a6176612f6c616e672f537472696e673b294c6a6176612f6c616e672f537472696e673b0a005e00640c0065006201000d636f6e76657274537472696e6709006700680700690c006a006b0100106a6176612f6c616e672f53797374656d0100036f75740100154c6a6176612f696f2f5072696e7453747265616d3b120001006d"
input17 = "412573a052d2c19042b1905ba00590000b8005d3a061906b800633a07b200661907ba006c0000b6006eb10000000100750000002600090000002b0003002c0006002d0009002e000d002f001100300022003100290032003600330003007b00000002007c007d0000000e0002007e00010084007e0001008600880000000a00010089008b008d0019"
input18 = "a00b5001800bb001d00760000004a0006ff0013000507000b07001707007701010000f8001dff0017000607000b07001707002a07007701010000f80028ff0015000707000b07001707002a07002f07007701010000f80032000a0061006200010074000000780004000500000036bb002f59b700314c033d1c2ab60041a20022"
input19 = "2a1c1c0560b600454e2d1010b8004936042b150492b6003957840202a7ffdc2bb6003db00000000200750000001e0007000000210008002200120023001b002400230025002b00220031002700760000000c0002fd000a07002f01fa002600090079007a000100740000006f0005000800000037124f4c12514d12534e12553a0"
input20 = "041904be360503360615061505a2002f190415063436072c1507b800101507b80010b900320300c00011b6003536082d1508b6003957840601a7ffd02db6003db00000000200750000003e000f0000000a0008000b001f000c002b000b0031000f00350011003d001300560014006c001300720016007a00180096001900ae001"
print("Do you know what files always start with CAFE BABE?")
str = input2+input3+input1+input5+input4+input8+input6+input9+input10+input16+input12+input13+input11+input15+input7+input14+input20+input18+input19+input17
with open("pls_stop.class", "wb") as f:
    f.write(bytes.fromhex(str))

Running this python script and writing the result out, we get another binary file however this time this is a Java class file.

We decompile it using an online tool.

    import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

public class nite {
   public static String convertString(String var0) {
      ArrayList var1 = new ArrayList();
      char[] var2 = var0.toCharArray();
      int var3 = var2.length;

      int var4;
      for(var4 = 0; var4 < var3; ++var4) {
         char var5 = var2[var4];
         var1.add(var5);
      }

      Collections.shuffle(var1);
      HashMap var9 = new HashMap();
      char[] var10 = var0.toCharArray();
      var4 = var10.length;

      int var13;
      for(var13 = 0; var13 < var4; ++var13) {
         char var6 = var10[var13];
         var9.put(var6, (Character)var1.remove(0));
      }

      StringBuilder var11 = new StringBuilder();
      char[] var12 = var0.toCharArray();
      var13 = var12.length;

      for(int var14 = 0; var14 < var13; ++var14) {
         char var7 = var12[var14];
         char var8 = (Character)var9.getOrDefault(var7, var7);
         var11.append(var8);
      }

      return var11.toString();
   }

   private static String convert(String var0) {
      StringBuilder var1 = new StringBuilder();

      for(int var2 = 0; var2 < var0.length(); var2 += 2) {
         String var3 = var0.substring(var2, var2 + 2);
         int var4 = Integer.parseInt(var3, 16);
         var1.append((char)var4);
      }

      return var1.toString();
   }

   public static void main(String[] var0) {
      String var1 = "686d6d5f6c34793372355f";
      String var2 = "76335f7734795f3730305f6d346e79";
      String var3 = "6e6974657b315f6834";
      String var4 = "5f64306c6c355f6e30775f";
      String var5 = "30665f6c3166335f69677d";
      String var6 = convert(var3 + var2 + var4 + var1 + var5);
      String var7 = convertString(var6);
      System.out.println("1_h4t3_str1ng_m4n1pul4710n_1n_java: " + var7);
   }
}

We can just insert this line to print out the unscrambled text.

System.out.println(var6);

And GG WP ! We did it :

nite{1_h4v3_w4y_700_m4ny_d0ll5_n0w_hmm_l4y3r5_0f_l1f3_ig}
1_h4t3_str1ng_m4n1pul4710n_1n_java: _7}1l_v3i50v_i_v000vyi__vl0__0v_0_v3yyv_i_0h0v0rv__r0v74_