├── IcedDecrypt.py ├── PeGen.py ├── README.md └── requirements.txt /IcedDecrypt.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import base64 3 | import binascii 4 | import collections 5 | import hashlib 6 | import math 7 | import os 8 | import re 9 | import struct 10 | import time 11 | 12 | import pefile 13 | 14 | import PeGen 15 | 16 | rol = lambda val, r_bits, max_bits: \ 17 | (val << r_bits % max_bits) & (2 ** max_bits - 1) | \ 18 | ((val & (2 ** max_bits - 1)) >> (max_bits - (r_bits % max_bits))) # rol lambda func. python pls add 19 | 20 | ror = lambda val, r_bits, max_bits: \ 21 | ((val & (2 ** max_bits - 1)) >> r_bits % max_bits) | \ 22 | (val << (max_bits - (r_bits % max_bits)) & (2 ** max_bits - 1)) # ror lambda func. python pls add 23 | 24 | 25 | def entropy(data): 26 | e = 0 27 | counter = collections.Counter(data) 28 | data_len = len(data) 29 | for count in counter.values(): 30 | p_x = count / data_len 31 | e += - p_x * math.log2(p_x) 32 | return e 33 | 34 | 35 | class IcedDecrypt: 36 | def __init__(self, filename, output): 37 | self.filename = filename 38 | self.output = output 39 | 40 | def main(self): 41 | filein = open(self.filename, "rb") 42 | data = filein.read() 43 | filein.close() 44 | 45 | pe, isPe = self.detectPE(self.filename) # Detect PE file using pefile 46 | if isPe: 47 | print("[+] Warning: Not Guaranteed to work") 48 | dll_data = open(self.filename, "rb").read() 49 | c2BuffDict = {"C2Buff": b"", "Size": 0} 50 | c2BuffDict = self.extractC2Buff(c2BuffDict, dll_data) 51 | 52 | decDict = self.decryptWrapper(c2BuffDict) 53 | print("[+] Finished Decrypting And Extracting Data") 54 | outDict = self.parseC2Buffer(decDict) 55 | filename1 = f"out_{hashlib.md5(dll_data).hexdigest()}_{int(time.time())}_output" 56 | try: 57 | os.mkdir("Logs") 58 | except Exception as e: 59 | print(e) 60 | 61 | outfile = open(f"Logs/{filename1}.txt", "w+") 62 | outfile.write(str(outDict)) 63 | outfile.close() 64 | elif data[:2] == b"\x1F\x8B": # If Detect gzip header 65 | print("[+] Identified GZIPLoader") 66 | parsedData, key = self.parseGZIPLoader(data) # Parse Gzip File and extract data and key 67 | outdata = self.decrypt(parsedData, size=len(data) - 0x20, key=key) 68 | print("[+] Finished Decrypting Data") 69 | parsedDict = self.parseDec_GZIPLoader(outdata) 70 | self.genDropFiles(parsedDict) 71 | print("[+] Finished Extracting Drop Files") 72 | 73 | outDict = {"key": base64.b64encode(key).decode(), "size": len(data) - 20} 74 | 75 | else: # .dat file decryption attempt 76 | key = data[-0x10:] # license.dat key offset = last 16 bytes of data 77 | print("[+] decryption key for dat file: %s" % binascii.hexlify(key).decode("utf-8")) 78 | outdata = self.decrypt(data, size=len(data) - 20, key=key) 79 | print("[+] entropy check: %s" % entropy(outdata)) 80 | print("[+] Finished Decrypting Data") 81 | try: 82 | os.mkdir("Assembled_Payloads_Debug") 83 | except Exception as e: 84 | print(e) 85 | 86 | try: 87 | os.mkdir("Assembled_Payloads") 88 | except Exception as e: 89 | print(e) 90 | 91 | filename = f"out_{hashlib.md5(outdata).hexdigest()}_{int(time.time())}" 92 | print("[+] output file for decrypted .dat file: %s" % filename) 93 | outfile = open(f"Assembled_Payloads_Debug/{filename}.file", "wb") 94 | outfile.write(outdata) 95 | outfile.close() 96 | outDict = {"key": base64.b64encode(key).decode(), "size": len(data) - 20} 97 | 98 | parsedData, parsedDict = self.ParseData(outdata) 99 | pe_gen = PeGen.PEGen(1, parsedData, parsedDict) # Supply ParsedData and ParsedDict, arg1 = machType (0,1) 100 | 101 | fixed = pe_gen.genExecutable() 102 | outfile = open(f"Assembled_Payloads/{filename}_fixed_dll.file", "w+b") 103 | outfile.write(fixed) 104 | outfile.close() 105 | 106 | try: 107 | os.mkdir("Logs") 108 | except Exception as e: 109 | print(e) 110 | 111 | outfile = open(f"Logs/{filename}.txt", "w+") 112 | outfile.write(str(parsedDict)) 113 | outfile.close() 114 | 115 | def parseC2Buffer(self, dataDict): # read values from 0xA9 bytes large data block 116 | data = base64.b64decode(dataDict["Decrypted_Data"]) 117 | buildID = int.from_bytes(data[:4], byteorder="little") 118 | uriLen = int.from_bytes(data[4:8], byteorder="little") 119 | uri = data[8:8 + uriLen] 120 | counter = 8 + uriLen 121 | 122 | count2 = self.incNulls(data[counter:]) 123 | counter = counter + count2 124 | strLen = data[counter] 125 | c2_1 = data[counter:strLen + counter] 126 | counter += strLen 127 | strLen = data[counter] 128 | c2_2 = data[counter:strLen + counter] 129 | unkData = data[-0x14:] 130 | outDict = {"BuildID": buildID, "uri": uri.decode(), "c2_1": c2_1[1:-1].decode(), "c2_2": c2_2[1:-1].decode(), 131 | "UnknownData": base64.b64encode(unkData).decode()} 132 | return outDict 133 | 134 | def ParseData(self, data): 135 | buffer = data[0x81:] 136 | """Struct payload_segment_config { 137 | qword ImageBase; 138 | dword ImageVirtualSize; 139 | dword ImageEntryPoint; 140 | dword Import Table Offset; 141 | dword Import table Virtual Offset; 142 | dword Import Table Size; 143 | } 144 | """ 145 | 146 | dataSizeTemp = int.from_bytes(buffer[0x8:0xC], byteorder="little") 147 | 148 | outBuffer = b"\x00" * int(0x1000 * (round(dataSizeTemp / 0x1000))) 149 | dataSegments = int.from_bytes(buffer[0x1c:0x20], byteorder="little") 150 | imagBase = int.from_bytes(buffer[:0x8], byteorder="little") 151 | 152 | imagSize = int.from_bytes(buffer[0x8:0xC], byteorder="little") 153 | imagEntry = int.from_bytes(buffer[0xC:0x10], byteorder="little") 154 | impTableOff = int.from_bytes(buffer[0x10:0x14], byteorder="little") 155 | impTableVirtOff = int.from_bytes(buffer[0x14:0x18], byteorder="little") 156 | impTableSize = int.from_bytes(buffer[0x18:0x1C], byteorder="little") 157 | 158 | tempOut = b"" 159 | print(dataSegments) 160 | segDict = {"SegmentIndex": 0, "SegmentSize": 0, "RawSegmentOffset": 0, 161 | "VirtualSegmentOffset": 0} # Initialize dicts for unpacking values 162 | bigDict = {"Segments": [], "ImageBase": imagBase, "ImageSize": imagSize, "ImageBaseEntry": imagEntry, 163 | "ImportTableOffset": impTableOff, "ImportTableSize": impTableSize} 164 | 165 | print(bigDict) 166 | 167 | totalSize = 0 168 | for i in range(dataSegments): 169 | """Source: malwarebytes analysis of iced 170 | struct section { 171 | dword VA; 172 | dword Virtual_Size 173 | dword raw_offset 174 | dword raw_size 175 | byte access 176 | }""" 177 | 178 | x = i * 0x11 # each buffer is 0x11 bytes large, with the first header being 0x20 bytes large 179 | dataOff = int.from_bytes(buffer[0x28 + x:0x2c + x], byteorder="little") 180 | dataSize = int.from_bytes(buffer[0x2C + x:0x30 + x], byteorder="little") 181 | virtualOffset = int.from_bytes(buffer[0x20 + x:0x24 + x], byteorder="little") 182 | 183 | print(f"[+] Data Segment Size {hex(dataSize)}") 184 | print(f"[+] Data Offset {hex(dataOff)}") 185 | print(f"[+] Virtual Offset: {hex(virtualOffset)}") 186 | segDict["SegmentIndex"] = i 187 | segDict["SegmentSize"] = dataSize 188 | segDict["RawSegmentOffset"] = dataOff 189 | segDict["VirtualSegmentOffset"] = virtualOffset 190 | bigDict["Segments"].append(segDict) 191 | segDict = {"SegmentIndex": 0, "SegmentSize": 0, "RawSegmentOffset": 0, 192 | "VirtualSegmentOffset": 0} # zero dict 193 | totalSize = int(0x1000 * (round((totalSize + dataSize) / 0x1000))) 194 | 195 | tempOut = outBuffer[:virtualOffset] + buffer[dataOff:dataOff + dataSize] + outBuffer[ 196 | virtualOffset + dataSize:] 197 | outBuffer = tempOut 198 | bigDict["SizeOfImage"] = totalSize + 0x1000 199 | return outBuffer, bigDict 200 | 201 | def fixKey(self, key, x, y): 202 | tempKey = b"" 203 | tempVal = key[y:y + 4] 204 | tempVal = int.from_bytes(tempVal, byteorder="little") # First grab from y 205 | rotVal = (tempVal & 7) & 0xFF 206 | tempVal = key[x:x + 4] # then grab from x 207 | tempVal = int.from_bytes(tempVal, byteorder="little") 208 | tempVal = ror(tempVal, rotVal, 32) 209 | tempVal += 1 210 | tempValX = tempVal.to_bytes(4, byteorder="little") # save to storage var 211 | rotVal = (tempVal & 7) & 0xFF # Gen rotVal 212 | 213 | tempVal = key[y:y + 4] # then grab from y (again) 214 | tempVal = int.from_bytes(tempVal, byteorder="little") 215 | tempVal = ror(tempVal, rotVal, 32) 216 | tempVal += 1 217 | tempValY = tempVal.to_bytes(4, byteorder="little") 218 | 219 | # fix Key 220 | tempKey = key[:x] + tempValX + key[x + 4:] 221 | tempKey = tempKey[:y] + tempValY + tempKey[y + 4:] 222 | 223 | return tempKey 224 | 225 | def decrypt(self, data, size, key): 226 | outList = [] 227 | if size > 400: 228 | print(f"[+] Size of data: {len(data)}") 229 | print(f"[+] Size: {size}") 230 | for i in range(size): 231 | x = (i & 3) 232 | y = ((i + 1) & 3) 233 | 234 | c = key[y * 4] + key[x * 4] 235 | c = (c ^ data[i]) & 0xFF 236 | 237 | outList.append(c.to_bytes(1, byteorder="little")) 238 | 239 | key = self.fixKey(key, x * 4, y * 4) 240 | 241 | return b''.join(outList) 242 | 243 | def extractC2Buff(self, outdict, data): 244 | pe = pefile.PE(data=data) 245 | 246 | for section in pe.sections: 247 | if b".data" in section.Name: 248 | RVA_Base = section.PointerToRawData 249 | if RVA_Base == 0x0: 250 | RVA_Base = section.VirtualAddress 251 | 252 | break 253 | C2BuffOffset = RVA_Base 254 | C2BuffSize = rb"\x66\xC7.....\x48\xC7.....(....)[\xFF\xE8]" # Use Regex to find C2 Buffer in exe 255 | dataSizeMatch = re.search(C2BuffSize, data, re.DOTALL) 256 | 257 | if dataSizeMatch: 258 | dataSize = struct.unpack("