UIUCTF 2021 - plugin

Categories: re
2021-08-04
by not_really

I downloaded this cool plugin from the Web but it needs an activation code, can you help me?

Author: Vanilla

Files: FlagChecker.zsc

plugin-1

This is a flag checker but in a zsc file. Trying to look up the format for this file gives no results; we can either try to look into the file ourselves, or we can create some scripts in ZBrush to see how they compile.

Hex Editor

The first 0x1400 bytes seem to be almost all the same between all of the built-in plugins, so let’s just go to the bottom of the file to SRBZ where we can assume the real code is at.

plugin-2

This seems pretty repetitive. Are these vm instructions? If we change the row width then we can see a pattern.

plugin-3

We don’t have enough information, so let’s hop into ZBrush to see if we can’t figure anything out.

ZBrush

plugin-4

We can either click “Load” to load a Zscript zsc file or a txt file. If we load a txt file, it will “compile” it to a zsc file and run that instead; this makes it really easy for us to write a bunch of similar scripts and see how changes affect the zsc file in a hex editor.

Scripting docs are here. We can use StrAsk like the flag zsc uses.

script1.txt: [StrAsk, "", "What is the Flag"]

plugin-5

If this was just a vm, we would be able to see our string in the script; but we can’t, so maybe there is some encryption. I made 8 more scripts with slight variations.

script2.txt: [StrAsk, "", "What is the Flah"]
script3.txt: [StrAsk, "", "What is the Flai"]
script4.txt: [StrAsk, "", "What is the Flaj"]
script5.txt: [StrAsk, "", "What is the Flak"]
script6.txt: [StrAsk, "", "aaaaaaaaaaaaaaaaaaaaaa"]
script7.txt: [StrAsk, "", "aaaaaaaaaaaaaaaaaaaaab"]
script8.txt: [StrAsk, "", "baaaaaaaaaaaaaaaaaaaaa"]
script9.txt: [StrAsk, "", "caaaaaaaaaaaaaaaaaaaaa"]

The difference between script1.zsc and script2.zsc is two bytes, not one. Interesting.

plugin-6

If we compare script2.zsc with script6.zsc:

plugin-7

The size of the string is different, so some of the earlier bytes could mean that, but the thing that tipped me off were all the 00s. Could it just be simple XOR with the previous character? I wrote a script to try it.

for j in range(255): #idk what the starting byte is
    ba = bytearray.fromhex("DF 08 27 06 33 32 18 47 0E 00 0E 0E 75 3F 09 15 54 49 1A 53 54 1C 0D 45 66 2A 0D 09 4A 7F 57 0A")
    for i in range(len(ba)):
        if i == 0:
            ba[i] ^= j
        else:
            ba[i] ^= ba[i-1]

    print(j)
    print(ba.decode("latin-1"))
...
131
\TsuFtl+%%+%Pofs'nt'sob'Akfo%Z
132
[StrAsk,"","What is the Flah"]

133
ZRus@rj-##-#Vi`u!hr!uid!Gm`i#\♂☺
...

Yep, that’s all it is. Let’s just put in the flag checker script.

91
VarDef,flag,""][VarDef,good,0][IButton,"Check Flag","Check the Flag",[VarSet,flag,[StrAsk,"","What is the Flag"]][VarSet,goodç¯ÍÆÄ«ÂÁЫØÐ÷ñÏæíä÷ë¯åïâäÞ®°²ªªÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯳޾¾²²´ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯲޾¾²³¶ª)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,2]==117)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,3]==99)]][VarAdd,good,[Val,BOOL([StrT×ìÂðà¯åïâ䯷޾¾²²µªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯶޾¾²³±ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯵޾¾²±°ªÞÞØÕâñÂçç¯ägood,[Val,BOOL([StrToAsc,flag,7]==119)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,8]==104)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,¯ºÞ¾¾²²²ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯲³Þ¾¾º¶ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯲²Þ¾¾²²³ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯,BOOL([StrToAsc,flag,12]==101)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,13]==101)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,14]==10³³ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯲¶Þ¾¾²²¶ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯲µÞ¾¾º¶ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«Ø[StrToAsc,flag,17]==100)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,18]==97)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,19]==109)]][VaâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯱³Þ¾¾º¶ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯱²Þ¾¾²²¶ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðsc,flag,22]==116)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,23]==97)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,24]==110)]][VarAdd,goììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯱¶Þ¾¾²³³ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯱µÞ¾¾º´ªÞÞØÕâñÂçç¯äììç¯ØÕâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâä¯,27]==114)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,28]==100)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,29]==115)]][VarAdd,good,[Vaâï¯ÁÌÌÏ«ØÐ÷ñ×ìÂðà¯åïâ䯰³Þ¾¾²±¶ªÞÞØÊå¯äììç¾°²¯ØÍì÷毡Àìññæà÷£Åïâ䡯¡Àëæàè£Åïâ䡯¶¯³û³³Åų³Þ¯ØÍì÷毡Ôñìíä£Åïâ䡯¡Àëæàè£Åïâ䡯¶¯KW▼!!WWWW::KWKVWWKKKVWW:jmgggggggggg

Oof – that doesn’t look right. The string is correct for 126 characters; after that, it is incorrect for 126 characters; then, it is correct for 126 characters.

plugin-8

A bunch of 0x83 bytes all line up every 126 characters, so what happens when we ignore them?

for j in range(255): #idk what the starting byte is
    ba = bytearray.fromhex("0D 37 13 36 21...")
    newBa = bytearray()
    lastByte = j
    for b in ba:
        if b != 0x83:
            lastByte ^= b
        
        newBa.append(lastByte)

    print(j)
    print(newBa.decode("latin-1"))
91
VarDef,flag,""][VarDef,good,0][IButton,"Check Flag","Check the Flag",[VarSet,flag,[StrAsk,"","What is the Flag"]][VarSet,good,NEG(ABS([StrLength,flag]-31))][VarAdd,good,[Val,BOOL([StrToAsc,flag,0]==117)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,1]==105)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,2]==117)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,3]==99)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,4]==116)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,5]==102)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,6]==123)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,7]==119)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,8]==104)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,9]==111)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,10]==95)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,11]==110)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,12]==101)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,13]==101)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,14]==100)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,15]==115)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,16]==95)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,17]==100)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,18]==97)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,19]==109)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,20]==95)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,21]==115)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,22]==116)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,23]==97)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,24]==110)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,25]==100)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,26]==97)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,27]==114)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,28]==100)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,29]==115)]][VarAdd,good,[Val,BOOL([StrToAsc,flag,30]==125)]][If,good=31,[Note,"Correct Flag","Check Flag",5,0x00FF00],[Note,"Wrong Flag","Check Flag",5,ÈԜ¢¢ÔÔÔÔ¹¹ÈÔÈÕÔÔÈÈÈÕÔÔ¹éîääääääääää

Some of the code gets cut off, but it looks like the important flag checking stuff is here.

[VarDef,flag,""]
[VarDef,good,0]
[IButton,"Check Flag","Check the Flag",[VarSet,flag,[StrAsk,"","What is the Flag"]]
[VarSet,good,NEG(ABS([StrLength,flag]-31))]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,0]==117)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,1]==105)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,2]==117)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,3]==99)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,4]==116)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,5]==102)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,6]==123)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,7]==119)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,8]==104)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,9]==111)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,10]==95)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,11]==110)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,12]==101)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,13]==101)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,14]==100)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,15]==115)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,16]==95)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,17]==100)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,18]==97)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,19]==109)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,20]==95)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,21]==115)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,22]==116)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,23]==97)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,24]==110)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,25]==100)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,26]==97)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,27]==114)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,28]==100)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,29]==115)]]
[VarAdd,good,[Val,BOOL([StrToAsc,flag,30]==125)]]
[If,good=31,[Note,"Correct Flag","Check Flag",5,0x00FF00],[Note,"Wrong Flag","Check Flag",5,0x00FF00],[Note,"Wrong Flag","Check Flag",5,0xFF0000]],
0,100,,,100]

Easy per character comparison that we can plug into CyberChef.

uiuctf{who_needs_dam_standards}