Flare-On CTF 2020 Challenge 6: codeit

Challenge

Reverse engineer this little compiled script to figure out what you need to do to make it give you the flag (as a QR code).

Observations

We are provided with a UPX-packed executable that prompts us to enter text to be encoded as a QR code.

prompt

Extracting the Script

Loading the unpacked program into Binary Ninja, we can see that it is pretty large. Fortunately, the string “This is a third-party compiled AutoIt script” immediately indicates we do not have to continue looking at the assembly. AutoIt is a freeware scripting language for automating Windows GUI interaction that can be compiled into a runnable executable.

We can use the AutoIt Extractor tool that is provided by default inside Flare VM to extract the original AutoIt script.

exe2aut

The script can be found here.

Reversing the Script

The script is lightly obfuscated with many of the function and variable names having been changed to junk values. At the end of the script there is a global variable named $os that contains an array of hex string values that are decoded by the last function. Whenever $os[$somenum] is called, the script gets the unobfuscated string value from the encoded array.

def decode(enc):
    out = ""
    for i in range(0, len(enc), 2):
        out += chr(int(enc[i: i + 2], 16))
    return out

dlit = "7374727563743b75696e7420626653697a653b75696e7420626652657365727665643b75696e742062664f6666426974733b"
dlit += "75696e7420626953697a653b696e7420626957696474683b696e742062694865696768743b7573686f7274206269506c616e"
dlit += "65733b7573686f7274206269426974436f756e743b75696e74206269436f6d7072657373696f6e3b75696e7420626953697a"
dlit += "65496d6167653b696e742062695850656c735065724d657465723b696e742062695950656c735065724d657465723b75696e"
dlit += "74206269436c72557365643b75696e74206269436c72496d706f7274616e743b656e647374727563743b4FD5$626653697a6"
dlit += "54FD5$626652657365727665644FD5$62664f6666426974734FD5$626953697a654FD5$626957696474684FD5$6269486569"
dlit += "6768744FD5$6269506c616e65734FD5$6269426974436f756e744FD5$6269436f6d7072657373696f6e4FD5$626953697a65"
dlit += "496d6167654FD5$62695850656c735065724d657465724FD5$62695950656c735065724d657465724FD5$6269436c7255736"
dlit += "5644FD5$6269436c72496d706f7274616e744FD5$7374727563743b4FD5$627974655b4FD5$5d3b4FD5$656e647374727563"
dlit += "744FD5$4FD5$2e626d704FD5$5c4FD5$2e646c6c4FD5$7374727563743b64776f72643b636861725b313032345d3b656e647"
dlit += "374727563744FD5$6b65726e656c33322e646c6c4FD5$696e744FD5$476574436f6d70757465724e616d65414FD5$7074724"
dlit += "FD5$436f6465497420506c7573214FD5$7374727563743b627974655b4FD5$5d3b656e647374727563744FD5$73747275637"
dlit += "43b627974655b35345d3b627974655b4FD5$7374727563743b7074723b7074723b64776f72643b627974655b33325d3b656e"
dlit += "647374727563744FD5$61647661706933322e646c6c4FD5$437279707441637175697265436f6e74657874414FD5$64776f7"
dlit += "2644FD5$4372797074437265617465486173684FD5$437279707448617368446174614FD5$7374727563742a4FD5$4372797"
dlit += "07447657448617368506172616d4FD5$30784FD5$30383032304FD5$30303031304FD5$36363030304FD5$30323030304FD5"
dlit += "$303030304FD5$43443442334FD5$32433635304FD5$43463231424FD5$44413138344FD5$44383931334FD5$45364639324"
dlit += "FD5$30413337414FD5$34463339364FD5$33373336434FD5$30343243344FD5$35394541304FD5$37423739454FD5$413434"
dlit += "33464FD5$46443138394FD5$38424145344FD5$39423131354FD5$46364342314FD5$45324137434FD5$31414233434FD5$3"
dlit += "4433235364FD5$31324135314FD5$39303335464FD5$31384642334FD5$42313735324FD5$38423341454FD5$43414633444"
dlit += "FD5$34383045394FD5$38424638414FD5$36333544414FD5$46393734454FD5$30303133354FD5$33354432334FD5$314534"
dlit += "42374FD5$35423243334FD5$38423830344FD5$43374145344FD5$44323636414FD5$33374233364FD5$46324335354FD5$3"
dlit += "5424633414FD5$39454136414FD5$35384243384FD5$46393036434FD5$43363635454FD5$41453243454FD5$36304632434"
dlit += "FD5$44453338464FD5$44333032364FD5$39434334434FD5$45354242304FD5$39303437324FD5$46463942444FD5$323646"
dlit += "39314FD5$31394238434FD5$34383446454FD5$36394542394FD5$33344634334FD5$46454544454FD5$44434542414FD5$3"
dlit += "7393134364FD5$30383139464FD5$42323146314FD5$30463833324FD5$42324135444FD5$34443737324FD5$44423132434"
dlit += "FD5$33424544394FD5$34374636464FD5$37303641454FD5$34343131414FD5$35324FD5$7374727563743b7074723b70747"
dlit += "23b64776f72643b627974655b383139325d3b627974655b4FD5$5d3b64776f72643b656e647374727563744FD5$437279707"
dlit += "4496d706f72744b65794FD5$4372797074446563727970744FD5$464c4152454FD5$4552414c464FD5$43727970744465737"
dlit += "4726f794b65794FD5$437279707452656c65617365436f6e746578744FD5$437279707444657374726f79486173684FD5$73"
dlit += "74727563743b7074723b7074723b64776f72643b627974655b31365d3b656e647374727563744FD5$7374727563743b64776"
dlit += "f72643b64776f72643b64776f72643b64776f72643b64776f72643b627974655b3132385d3b656e647374727563744FD5$47"
dlit += "657456657273696f6e4578414FD5$456e746572207465787420746f20656e636f64654FD5$43616e2068617a20636f64653f"
dlit += "4FD5$4FD5$48656c704FD5$41626f757420436f6465497420506c7573214FD5$7374727563743b64776f72643b64776f7264"
dlit += "3b627974655b333931385d3b656e647374727563744FD5$696e743a636465636c4FD5$6a75737447656e6572617465515253"
dlit += "796d626f6c4FD5$7374724FD5$6a757374436f6e76657274515253796d626f6c546f4269746d6170506978656c734FD5$546"
dlit += "869732070726f6772616d2067656e65726174657320515220636f646573207573696e6720515220436f64652047656e65726"
dlit += "1746f72202868747470733a2f2f7777772e6e6179756b692e696f2f706167652f71722d636f64652d67656e657261746f722"
dlit += "d6c6962726172792920646576656c6f706564206279204e6179756b692e204FD5$515220436f64652047656e657261746f72"
dlit += "20697320617661696c61626c65206f6e20476974487562202868747470733a2f2f6769746875622e636f6d2f6e6179756b69"
dlit += "2f51522d436f64652d67656e657261746f722920616e64206f70656e2d736f757263656420756e6465722074686520666f6c"
dlit += "6c6f77696e67207065726d697373697665204d4954204c6963656e7365202868747470733a2f2f6769746875622e636f6d2f"
dlit += "6e6179756b692f51522d436f64652d67656e657261746f72236c6963656e7365293a4FD5$436f7079726967687420c2a9203"
dlit += "23032302050726f6a656374204e6179756b692e20284d4954204c6963656e7365294FD5$68747470733a2f2f7777772e6e61"
dlit += "79756b692e696f2f706167652f71722d636f64652d67656e657261746f722d6c6962726172794FD5$5065726d697373696f6"
dlit += "e20697320686572656279206772616e7465642c2066726565206f66206368617267652c20746f20616e7920706572736f6e2"
dlit += "06f627461696e696e67206120636f7079206f66207468697320736f66747761726520616e64206173736f636961746564206"
dlit += "46f63756d656e746174696f6e2066696c6573202874686520536f667477617265292c20746f206465616c20696e207468652"
dlit += "0536f66747761726520776974686f7574207265737472696374696f6e2c20696e636c7564696e6720776974686f7574206c6"
dlit += "96d69746174696f6e207468652072696768747320746f207573652c20636f70792c206d6f646966792c206d657267652c207"
dlit += "075626c6973682c20646973747269627574652c207375626c6963656e73652c20616e642f6f722073656c6c20636f7069657"
dlit += "3206f662074686520536f6674776172652c20616e6420746f207065726d697420706572736f6e7320746f2077686f6d20746"
dlit += "86520536f667477617265206973206675726e697368656420746f20646f20736f2c207375626a65637420746f20746865206"
dlit += "66f6c6c6f77696e6720636f6e646974696f6e733a4FD5$312e205468652061626f766520636f70797269676874206e6f7469"
dlit += "636520616e642074686973207065726d697373696f6e206e6f74696365207368616c6c20626520696e636c7564656420696e"
dlit += "20616c6c20636f70696573206f72207375627374616e7469616c20706f7274696f6e73206f662074686520536f6674776172"
dlit += "652e4FD5$322e2054686520536f6674776172652069732070726f76696465642061732069732c20776974686f75742077617"
dlit += "272616e7479206f6620616e79206b696e642c2065787072657373206f7220696d706c6965642c20696e636c7564696e67206"
dlit += "27574206e6f74206c696d6974656420746f207468652077617272616e74696573206f66206d65726368616e746162696c697"
dlit += "4792c206669746e65737320666f72206120706172746963756c617220707572706f736520616e64206e6f6e696e6672696e6"
dlit += "7656d656e742e20496e206e6f206576656e74207368616c6c2074686520617574686f7273206f7220636f707972696768742"
dlit += "0686f6c64657273206265206c6961626c6520666f7220616e7920636c61696d2c2064616d61676573206f72206f746865722"
dlit += "06c696162696c6974792c207768657468657220696e20616e20616374696f6e206f6620636f6e74726163742c20746f72742"
dlit += "06f72206f74686572776973652c2061726973696e672066726f6d2c206f7574206f66206f7220696e20636f6e6e656374696"
dlit += "f6e20776974682074686520536f667477617265206f722074686520757365206f72206f74686572206465616c696e6773206"
dlit += "96e2074686520536f6674776172652e4FD5$7374727563743b7573686f72743b656e647374727563744FD5$7374727563743"
dlit += "b627974653b627974653b627974653b656e647374727563744FD5$43726561746546696c654FD5$75696e744FD5$53657446"
dlit += "696c65506f696e7465724FD5$6c6f6e674FD5$577269746546696c654FD5$7374727563743b64776f72643b656e647374727"
dlit += "563744FD5$5265616446696c654FD5$436c6f736548616e646c654FD5$44656c65746546696c65414FD5$47657446696c655"
dlit += "3697a65"
os = dlit.split("4FD5$")

for i in range(len(os)):
    print(str(i + 1) + ": " + decode(os[i]))

The unobfuscated strings, with indexing starting at 1, are included below:

1: struct;uint bfSize;uint bfReserved;uint bfOffBits;uint biSize;int biWidth;int biHeight;ushort biPlanes;ushort biBitCount;uint biCompression;uint biSizeImage;int biXPelsPerMeter;int biYPelsPerMeter;uint biClrUsed;uint biClrImportant;endstruct;
2: bfSize
3: bfReserved
4: bfOffBits
5: biSize
6: biWidth
7: biHeight
8: biPlanes
9: biBitCount
10: biCompression
11: biSizeImage
12: biXPelsPerMeter
13: biYPelsPerMeter
14: biClrUsed
15: biClrImportant
16: struct;
17: byte[
18: ];
19: endstruct
20:
21: .bmp
22: \
23: .dll
24: struct;dword;char[1024];endstruct
25: kernel32.dll
26: int
27: GetComputerNameA
28: ptr
29: CodeIt Plus!
30: struct;byte[
31: ];endstruct
32: struct;byte[54];byte[
33: struct;ptr;ptr;dword;byte[32];endstruct
34: advapi32.dll
35: CryptAcquireContextA
36: dword
37: CryptCreateHash
38: CryptHashData
39: struct*
40: CryptGetHashParam
41: 0x
42: 08020
43: 00010
44: 66000
45: 02000
46: 0000
47: CD4B3
48: 2C650
49: CF21B
50: DA184
51: D8913
52: E6F92
53: 0A37A
54: 4F396
55: 3736C
56: 042C4
57: 59EA0
58: 7B79E
59: A443F
60: FD189
61: 8BAE4
62: 9B115
63: F6CB1
64: E2A7C
65: 1AB3C
66: 4C256
67: 12A51
68: 9035F
69: 18FB3
70: B1752
71: 8B3AE
72: CAF3D
73: 480E9
74: 8BF8A
75: 635DA
76: F974E
77: 00135
78: 35D23
79: 1E4B7
80: 5B2C3
81: 8B804
82: C7AE4
83: D266A
84: 37B36
85: F2C55
86: 5BF3A
87: 9EA6A
88: 58BC8
89: F906C
90: C665E
91: AE2CE
92: 60F2C
93: DE38F
94: D3026
95: 9CC4C
96: E5BB0
97: 90472
98: FF9BD
99: 26F91
100: 19B8C
101: 484FE
102: 69EB9
103: 34F43
104: FEEDE
105: DCEBA
106: 79146
107: 0819F
108: B21F1
109: 0F832
110: B2A5D
111: 4D772
112: DB12C
113: 3BED9
114: 47F6F
115: 706AE
116: 4411A
117: 52
118: struct;ptr;ptr;dword;byte[8192];byte[
119: ];dword;endstruct
120: CryptImportKey
121: CryptDecrypt
122: FLARE
123: ERALF
124: CryptDestroyKey
125: CryptReleaseContext
126: CryptDestroyHash
127: struct;ptr;ptr;dword;byte[16];endstruct
128: struct;dword;dword;dword;dword;dword;byte[128];endstruct
129: GetVersionExA
130: Enter text to encode
131: Can haz code?
132:
133: Help
134: About CodeIt Plus!
135: struct;dword;dword;byte[3918];endstruct
136: int:cdecl
137: justGenerateQRSymbol
138: str
139: justConvertQRSymbolToBitmapPixels
140: This program generates QR codes using QR Code Generator (https://www.nayuki.io/page/qr-code-generator-library) developed by Nayuki.
141: QR Code Generator is available on GitHub (https://github.com/nayuki/QR-Code-generator) and open-sourced under the following permissive MIT License (https://github.com/nayuki/QR-Code-generator#license):
142: Copyright © 2020 Project Nayuki. (MIT License)
143: https://www.nayuki.io/page/qr-code-generator-library
144: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
145: 1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
146: 2. The Software is provided as is, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.
147: struct;ushort;endstruct
148: struct;byte;byte;byte;endstruct
149: CreateFile
150: uint
151: SetFilePointer
152: long
153: WriteFile
154: struct;dword;endstruct
155: ReadFile
156: CloseHandle
157: DeleteFileA
158: GetFileSize

Using the decoded strings, we can begin to go through the script and rename obfuscated locations. Inside the main GUI loop, the script passes our input to a third-party DLL that generates the QR code. It is only after the QR code has already been generated that the flag is added on.

Case $buttonEvent
    Local $input = GUICtrlRead($inputEvent)

    If $input Then
        Local $dllname = LoadEmbeddedFile(26)
        Local $qrstruct = DllStructCreate("struct;dword;dword;byte[3918];endstruct")
        Local $fljfojrihf = DllCall($dllname, "int:cdecl", "justGenerateQRSymbol", "struct*", $qrstruct, "str", $input)

        If $fljfojrihf[0] <> 0 Then
            GenerateFlag($qrstruct)

            Local $somestruct = CreateSomeStruct(
                (DllStructGetData($qrstruct, 1) * DllStructGetData($qrstruct, 2)),  ; width * height
                (DllStructGetData($qrstruct, 1) * DllStructGetData($qrstruct, 2)),  ; width * height
                1024
            )

            $fljfojrihf = DllCall($dllname, "int:cdecl", "justConvertQRSymbolToBitmapPixels", "struct*", $qrstruct, "struct*", $somestruct[1])
...

The GenerateFlag function gets the computer name using GetComputerNameA. It is then modified in some way by ChangeComputerName before the script hashes it and checks whether it equals the correct hash value.

Func GenerateFlag(ByRef $qrstruct)
    Local $computername = GetComputerName()

    If $computername <> -1 Then
        $computername = Binary(StringLower(BinaryToString($computername)))  ; Computer name must be lowercase ASCII
        Local $computernameraw = DllStructCreate("struct;byte[" & BinaryLen($computername) & "];endstruct")

        DllStructSetData($computernameraw, 1, $computername)
        ChangeComputerName($computernameraw)

        Local $flnttmjfea = DllStructCreate("struct;ptr;ptr;dword;byte[32];endstruct")
        DllStructSetData($flnttmjfea, 3, 32)

        Local $fluzytjacb = DllCall("advapi32.dll", "int", "CryptAcquireContextA",
            DECODE($os[$flshmemjjj]), DllStructGetPtr($flnttmjfea, 1),
            DECODE($os[$flhqjglfws]), 0,
            DECODE($os[$flwvzhffsc]), 0,
            DECODE($os[$flfrtkctqe]), 24,
            DECODE($os[$fljhsdaeav]), 4026531840
        )
...

ChangeComputerName reads the contents of the embedded sprite.bmp file and uses it to add some constants to the calculated computer name.

Func ChangeComputerName(ByRef $computername)
    Local $spritefile = LoadEmbeddedFile(14)
    Local $file = CreateFile2($spritefile)

    If $file <> -1 Then
        Local $filesize = GetFileSize($file)  ; Get size of the sprite file

        If $filesize <> -1 And DllStructGetSize($computername) < $filesize - 54 Then
            ; Read the contents of sprite.bmp into a struct
            Local $bmpdata = DllStructCreate("struct;byte[" & $filesize & "];endstruct")
            Local $flskuanqbg = ReadFile($file, $bmpdata)

            If $flskuanqbg <> -1 Then
                ; Break the bmp into header and data fields
                Local $bmp = DllStructCreate("struct;byte[54];byte[" & $filesize - 54 & "];endstruct", DllStructGetPtr($bmpdata))
                Local $count = 1
                Local $str = " "

                ; For each char in computer name
                For $i = 1 To DllStructGetSize($computername)
                    ; Number of a binary value
                    Local $c = Number(DllStructGetData($computername, 1, $i))

                    For $j = 6 To 0 Step -1
                        $c += BitShift(BitAND(Number(DllStructGetData($bmp, 2, $count)), 1), -1 * $j)
                        $count += 1
                    Next

                    $str &= Chr(BitShift($c, 1) + BitShift(BitAND($c, 1), -7))
                Next

                ; Change the computer name to whatever str becomes
                DllStructSetData($computername, 1, $str)
            EndIf
        EndIf
        CloseHandle($file)
    EndIf
    DeleteFile($spritefile)
EndFunc   ;==>ChangeComputerName

By rewriting this logic in Python, we can check what each constant value is before it is added to the computer name.

# Computer name must be lowercase ASCII
computername = "a" * 13
computername = computername.lower()

with open("sprite.bmp", "rb") as f:
    data = f.read()
    filesize = len(data)

    if len(computername) < filesize - 54:
        # Split the BMP by header and data
        header = data[:54]
        data = data[54:]
        count = 0
        outstr = ""

        for i in range(0, len(computername)):
            c = ord(computername[i])
            origc = c

            for j in range(7, 0, -1):
                j -= 1
                b = data[count]
                b = b & 1   # Either 0 or 1
                b = b << j  # Multiples
                c += b
                count += 1

            print("Modified by constant: " + chr(c - origc))

Putting the characters together gives us the expected computer name: aut01tfan1999.

We can now run the program and hook the call to GetComputerNameA with a debugger. The QR code contains the flag.

Flag: L00ks_L1k3_Y0u_D1dnt_Run_Aut0_Tim3_0n_Th1s_0ne!@flare-on.com