Hello,
Recently we experienced an attack against our super secure MEOW-5000 network. Forensic analysis discovered evidence of the files PurrMachine.exe and PetTheKitty.jpg; however, these files were ultimately unrecoverable. We suspect PurrMachine.exe to be a downloader and do not know what role PetTheKitty.jpg plays (likely a second-stage payload). Our incident responders were able to recover malicious traffic from the infected machine. Please analyze the PCAP file and extract additional artifacts.
Looking forward to your analysis, ~Meow
7zip password: flare
We are given a PCAP file in this challenge. If we follow the first TCP stream, we can see some clear signs of traffic formatting, with ME0W being present in every message.
From observation, the traffic header has the following format
(DWORD) 'ME0W'
(DWORD) Length of message
(DWORD) Length of message (duplicate)
(Variable length) message
In stream 0, we have 2 messages, one being a PNG file and the other being a file that has the file signature PA30.
The PNG file isn’t very useful, just a picture of a cat, so we take a look at the other file. Searching for PA30, we learn that it is the Intra Package Delta format which is also used by Microsoft for Windows update patches.
This post (which we found from this Github issue leading to another Github issue) was particularly useful, and the author wrote a challenge for another CTF based on this format, so we could use the challenge solution on Github. We tried to run it as it was initially but it gave us some error so we had to rewrite it.
fromctypesimport(windll,wintypes,cast,c_ubyte,c_uint64,POINTER,LittleEndianStructure,byref,c_size_t)ORIGINAL_FILE="server0.png"DIFF_FILE="server1.bin"OUT_FILE="out.bin"DELTA_FLAG_TYPE=c_uint64DELTA_FLAG_NONE=0x00000000DELTA_APPLY_FLAG_ALLOW_PA19=0x00000001classDELTA_INPUT(LittleEndianStructure):_fields_=[('lpStart',wintypes.LPVOID),('uSize',c_size_t),('Editable',wintypes.BOOL)]classDELTA_OUTPUT(LittleEndianStructure):_fields_=[('lpStart',wintypes.LPVOID),('uSize',c_size_t)]ApplyDeltaB=windll.msdelta.ApplyDeltaBApplyDeltaB.argtypes=[DELTA_FLAG_TYPE,DELTA_INPUT,DELTA_INPUT,POINTER(DELTA_OUTPUT),]ApplyDeltaB.rettype=wintypes.BOOLDeltaFree=windll.msdelta.DeltaFreeDeltaFree.argtypes=[wintypes.LPVOID]DeltaFree.rettype=wintypes.BOOLdefapply_delta(original,delta):dd=DELTA_INPUT()ds=DELTA_INPUT()dout=DELTA_OUTPUT()ds.lpStart=cast(original,wintypes.LPVOID)ds.uSize=len(original)ds.Editable=Falsedd.lpStart=cast(delta,wintypes.LPVOID)dd.uSize=len(delta)dd.Editable=Falseres=windll.msdelta.ApplyDeltaB(DELTA_APPLY_FLAG_ALLOW_PA19,ds,dd,byref(dout))ifres:print("Success!")else:error=windll.kernel32.GetLastError()print(f"ApplyDeltaB failed with error {error}")returnreturnbytes((c_ubyte*dout.uSize).from_address(dout.lpStart))defmain():withopen(ORIGINAL_FILE,'rb')asfp:original=fp.read()withopen(DIFF_FILE,'rb')asfp:delta=fp.read()patched_bytes=apply_delta(original,delta)withopen(OUT_FILE,'wb')asfp:fp.write(patched_bytes)if__name__=='__main__':main()
This write-up was for the same challenge and similar but it didn’t use ctypes and we ultimately didn’t use it.
Applying the diff to the PNG file, we get a PE file.
Analysis
The PE file is a 32-bit DLL with a single exported function Le_Meow.
Le_Meow connects to xn--zn8hrcq4eeadihijjk.flare-on.com:1337, then creates a thread at sub_10001CA3 which does some irrelevant UI stuff, then Le_Meow continues to call sub_100015D4.
sub_100015D4 creates a cmd.exe process, then goes into a command loop where it reads from it, then sends it to sub_100015D4, which xors the output with meoow, then uses the result to diff with Src, which is probably a buffer of null bytes, using sub_10001000 which is a wrapper for CreateDeltaB, then sends the result to the server in the following format
(DWORD) 'ME0W'
(DWORD) Length of original message
(DWORD) Length of the PA30 delta
(Variable length) PA30 Delta
Afterwards sub_100015D4 calls sub_1000128A which is the opposite and receives the delta from the server in the same format, then parses it in the same format, so we can write our script to parse all the messages and see what commands were being sent to and fro.
We see the messages in this format in stream 1, where we get back-and-forth PA30 files between the client and server.
fromctypesimport(windll,wintypes,cast,c_ubyte,c_uint64,POINTER,LittleEndianStructure,byref,c_size_t)XOR_KEY=b'meoow'DELTA_FLAG_TYPE=c_uint64DELTA_FLAG_NONE=0x00000000DELTA_APPLY_FLAG_ALLOW_PA19=0x00000001classDELTA_INPUT(LittleEndianStructure):_fields_=[('lpStart',wintypes.LPVOID),('uSize',c_size_t),('Editable',wintypes.BOOL)]classDELTA_OUTPUT(LittleEndianStructure):_fields_=[('lpStart',wintypes.LPVOID),('uSize',c_size_t)]ApplyDeltaB=windll.msdelta.ApplyDeltaBApplyDeltaB.argtypes=[DELTA_FLAG_TYPE,DELTA_INPUT,DELTA_INPUT,POINTER(DELTA_OUTPUT),]ApplyDeltaB.rettype=wintypes.BOOLDeltaFree=windll.msdelta.DeltaFreeDeltaFree.argtypes=[wintypes.LPVOID]DeltaFree.rettype=wintypes.BOOLdefapply_delta(original,delta):dd=DELTA_INPUT()ds=DELTA_INPUT()dout=DELTA_OUTPUT()ds.lpStart=cast(original,wintypes.LPVOID)ds.uSize=len(original)ds.Editable=Falsedd.lpStart=cast(delta,wintypes.LPVOID)dd.uSize=len(delta)dd.Editable=Falseres=windll.msdelta.ApplyDeltaB(DELTA_APPLY_FLAG_ALLOW_PA19,ds,dd,byref(dout))ifnotres:error=windll.kernel32.GetLastError()raisef"ApplyDeltaB failed with error {error}"returnbytes((c_ubyte*dout.uSize).from_address(dout.lpStart))defxor_bytes(data,key):returnbytes([b^key[i%len(key)]fori,binenumerate(data)])defget_next_message(data):original_length=int.from_bytes(data[4:8],byteorder='little')diff_length=int.from_bytes(data[8:12],byteorder='little')diff_bytes=data[12:12+diff_length]data=data[12+diff_length:]returnoriginal_length,diff_bytes,datadefparse_stream(data,base_data):foriinrange(74):original_length,diff_bytes,data=get_next_message(data)decoded_bytes=apply_delta(base_data,diff_bytes)[:original_length]decoded_bytes=xor_bytes(decoded_bytes,XOR_KEY)print("Server"ifi%2else"Client")print("------")print(decoded_bytes.decode('ansi'))print("\n\n")defmain():withopen('stream','rb')asfp:data=fp.read()base_data=b'\x00'*0x100parse_stream(data,base_data)if__name__=='__main__':main()
The output of the script shows all the different commands sent by the server and responses from the client, and scrolling through we find this important response from the client: