dev

Python 🐍

Base64 custom alphabet#

Here’s a brief Python code snippet for decoding base64 data that uses a non-standard alphabet. The Darkgate sample was observed to use this custom alphabet.

python 9 lines
1import base64
2
3def custom_b64_decode(s):
4     custom_base64 = "KHkFLg9RnhcZNSDl1TsOj2JveVUpfC4Bq67XyIbm5Q8EGi3A=Madwr0uYzt+oWPx"
5     std_base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/="
6     o = str(s).translate(str(s).maketrans(custom_base64, std_base64chars))
7     return base64.b64decode(o)
8
9custom_b64_decode("jrIO2L2S")
output 1 line
b'SYSTEM'

PE resources extraction with pefile#

Snippet of code to read each resource of a PE:

python 17 lines
 1pe = pefile.PE("<path to PE>")
 2
 3offset: int = 0
 4size: int = 0
 5resource_type: str = ""
 6
 7for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
 8    resource_type = str(entry.name)
 9    for directory in entry.directory.entries:
10	for resource in directory.directory.entries:
11	    offset = resource.data.struct.OffsetToData
12	    size = resource.data.struct.Size
13	    content = pe.get_memory_mapped_image()[offset : offset + size]
14
15	    print(
16		f"read resource {resource_type} at offset 0x{offset:x} on 0x{size:x} bytes"
17	    )

CLSID identifier #

A Python helper to identify the object (class and interface) from the CLSID or the RIID. The snippet below is trimmed, the complete script (with the full CLSID table) is available here:

The CoCreateInstance function can create various types of COM objects depending on the CLSID provided. Here are some examples of what these objects could be:

  1. Standard COM Objects: These are typical COM objects implementing various functionalities, such as document handling, media processing, or system components.
  2. Automation Objects: Objects that provide OLE Automation interfaces, making them accessible from scripting languages like VBScript, JavaScript, and PowerShell.
  3. ActiveX Controls: These are COM objects that can be embedded in applications or web pages to provide interactive features.
  4. Shell Extensions: COM objects that extend the functionality of the Windows Shell, such as custom context menu handlers, icon handlers, or property sheet handlers.
  5. Service Objects: Objects that provide specific services to other applications, like data access or messaging.https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cocreateinstance
c++ 7 lines
1  HRESULT CoCreateInstance(
2  [in]  REFCLSID  rclsid,
3  [in]  LPUNKNOWN pUnkOuter,
4  [in]  DWORD     dwClsContext,
5  [in]  REFIID    riid,
6  [out] LPVOID    *ppv
7);
Code Snippet 1: CoCreateInstance function prototype
example of Object creation using the class identifier
Figure 1: example of Object creation using the class identifier
CLSID export
Figure 2: CLSID export
python 80 lines
 1import struct
 2
 3CLSID = {
 4    "TaskScheduler": "0F87369F-A4E5-4CFC-BD3E-73E6154572DD",
 5    "ITaskFolderCollection": "79184A66-8664-423F-97F1-637356A5D812",
 6    "ITaskFolder": "8CFAC062-A080-4C15-9A88-AA7C2AF80DFC",
 7    "IRegisteredTask": "9C86F320-DEE3-4DD1-B972-A303F26B061E",
 8    "IRunningTask": "653758FB-7B9A-4F1E-A471-BEEB8E9B834E",
 9    "IRunningTaskCollection": "6A67614B-6828-4FEC-AA54-6D52E8F1F2DB",
10    "ITaskDefinition": "F5BC8FC5-536D-4F77-B852-FBC1356FDEB6",
11    "IRegistrationInfo": "416D8B73-CB41-4EA1-805C-9BE9A5AC4A74",
12    "ITriggerCollection": "85DF5081-1B24-4F32-878A-D9D14DF4CB77",
13    "ITrigger": "09941815-EA89-4B5B-89E0-2A773801FAC3",
14    "IRepetitionPattern": "7FB9ACF1-26BE-400E-85B5-294B9C75DFD6",
15    "ITaskSettings": "8FD4711D-2D02-4C8C-87E3-EFF699DE127E",
16    "IIdleSettings": "84594461-0053-4342-A8FD-088FABF11F32",
17    "INetworkSettings": "9F7DEA84-C30B-4245-80B6-00E9F646F1B4",
18    "IPrincipal": "D98D51E5-C9B4-496A-A9C1-18980261CF0F",
19    "IActionCollection": "02820E19-7B98-4ED2-B2E8-FDCCCEFF619B",
20    "IAction": "BAE54997-48B1-4CBE-9965-D6BE263EBEA4",
21    "IRegisteredTaskCollection": "86627EB4-42A7-41E4-A4D9-AC33A72F2D52",
22    "ITaskService": "2FABA4C7-4DA9-4013-9697-20CC3FD40F85",
23    "ITaskHandler": "839D7762-5121-4009-9234-4F0D19394F04",
24    "ITaskHandlerStatus": "EAEC7A8F-27A0-4DDC-8675-14726A01A38A",
25    "ITaskVariables": "3E4C9351-D966-4B8B-BB87-CEBA68BB0107",
26    "ITaskNamedValuePair": "39038068-2B46-4AFD-8662-7BB6F868D221",
27    "ITaskNamedValueCollection": "B4EF826B-63C3-46E4-A504-EF69E4F7EA4D",
28    "IIdleTrigger": "D537D2B0-9FB3-4D34-9739-1FF5CE7B1EF3",
29    "ILogonTrigger": "72DADE38-FAE4-4B3E-BAF4-5D009AF02B1C",
30    "ISessionStateChangeTrigger": "754DA71B-4385-4475-9DD9-598294FA3641",
31    "IEventTrigger": "D45B0167-9653-4EEF-B94F-0732CA7AF251",
32    "ITimeTrigger": "B45747E0-EBA7-4276-9F29-85C5BB300006",
33    "IDailyTrigger": "126C5CD8-B288-41D5-8DBF-E491446ADC5C",
34    "IWeeklyTrigger": "5038FC98-82FF-436D-8728-A512A57C9DC1",
35    "IMonthlyTrigger": "97C45EF1-6B02-4A1A-9C0E-1EBFBA1500AC",
36    "IMonthlyDOWTrigger": "77D025A3-90FA-43AA-B52E-CDA5499B946A",
37    "IBootTrigger": "2A9C35DA-D357-41F4-BBC1-207AC1B1F3CB",
38    "IRegistrationTrigger": "4C8FEC3A-C218-4E0C-B23D-629024DB91A2",
39    "IExecAction": "4C3D624D-FD6B-49A3-B9B7-09CB3CD3F047",
40    "IExecAction2": "F2A82542-BDA5-4E6B-9143-E2BF4F8987B6",
41    "IShowMessageAction": "505E9E68-AF89-46B8-A30F-56162A83D537",
42    "IComHandlerAction": "6D2FD252-75C5-4F66-90BA-2A7D8CC3039F",
43    "IEmailAction": "10F62C64-7E16-4314-A0C2-0C3683F99D40",
44    "IPrincipal2": "248919AE-E345-4A6D-8AEB-E0D3165C904E",
45    "ITaskSettings2": "2C05C3F0-6EED-4C05-A15F-ED7D7A98A369",
46    "ITaskSettings3": "0AD9D0D7-0C7F-4EBB-9A5F-D1C648DCA528",
47    "IMaintenanceSettings": "A6024FA8-9652-4ADB-A6BF-5CFCD877A7BA",
48    "TaskHandlerPS": "F2A69DB7-DA2C-4352-9066-86FEE6DACAC9",
49    "TaskHandlerStatusPS": "9F15266D-D7BA-48F0-93C1-E6895F6FE5AC",
50}
51
52
53def bytes_to_clsid(byte_data: bytes) -> str:
54    if len(byte_data) != 16:
55        raise ValueError("A CLSID must be 16 bytes long")
56
57    # Unpack bytes according to CLSID structure
58    part1, part2, part3, part4_and_part5 = struct.unpack("<IHH8s", byte_data[:16])
59
60    # Split part4_and_part5 into two parts
61    part4 = part4_and_part5[:2]
62    part5 = part4_and_part5[2:8]
63    part6 = byte_data[8:16]
64
65    # Format the CLSID
66    clsid = f"{part1:08X}-{part2:04X}-{part3:04X}-{part4.hex().upper()}-{part5.hex().upper()}{part6.hex().upper()}"
67    return clsid
68
69
70def identify_object(raw_id: str) -> str:
71    byte_data = bytes.fromhex(raw_id)
72    r_clsid = bytes_to_clsid(byte_data)
73
74    for name, clsid in CLSID.items():
75        if clsid.lower() in r_clsid.lower():
76            return f"{r_clsid} -> {name}"
77    return ""
78
79
80identify_object("9F36870FE5A4FC4CBD3E73E6154572DD")
output 1 line
0F87369F-A4E5-4CFC-BD3E-73E6154572DDBD3E73E6154572DD -> TaskScheduler

To no overstock the script all the CLSID are not included in it, however a script on this gist contains all the extracted CLSID and RIID from WINE repository is available. The snippet to extract the ID is the following one:

python 16 lines
 1import os
 2clsids = {}
 3
 4# make sure to put the script at the root of wine repo
 5for root_dir, _, filenames in os.walk("include/"):
 6    for filename in filenames:
 7	with open(os.path.join(root_dir,  filename), "r") as f:
 8	    content = f.read().split("\n")
 9	    for line_nb, line in enumerate(content):
10		if "uuid(" in line:
11		    if "interface" in content[line_nb+2] or "coclass" in content[line_nb+2]:
12			clsid = line.strip().replace('uuid(', '').replace(')','')
13			name = content[line_nb+2].split()[1].strip()
14			clsids[name] = clsid
15
16print(clsids)
Code Snippet 2: snippet to extract the CLSID and RIID from wine/include

XTEA - eXtended TEA#

In cryptography, XTEA (eXtended TEA) is a block cipher designed to correct weaknesses in TEA. The cipher’s designers were David Wheeler and Roger Needham of the Cambridge Computer Laboratory, and the algorithm was presented in an unpublished technical report in 1997 (Needham and Wheeler, 1997). It is not subject to any patents.

Source: wikipedia.org: XTEA

Key features#

  • Block size: 64 bits;
  • Key size: 128 bits;
  • Rounds: 64 rounds of encryption, divided into pairs of Feistel rounds.

Basic structure#

  • Feistel network: XTEA uses a Feistel structure where the input 64-bit block is split into two 32-bit halves (typically labeled v0 and v1). In each round, one half is modified based on a combination of the other half, the key, and a round-dependent constant called the delta.
  • Round operations: Each round uses XOR, addition, and bitwise shifts.
  • Key scheduling: The 128-bit key is split into four 32-bit subkeys (k0, k1, k2, k3), which are used in a cyclic manner across the rounds.

Summary of process#

  1. Split the 64-bit input block into two 32-bit halves.
  2. Perform 64 rounds of encryption using XOR, addition, and bitwise shifts.
  3. Use the 128-bit key, broken into four subkeys, in a cyclic manner.
  4. After 64 rounds, the two 32-bit halves are combined to produce the encrypted 64-bit block.

NB: The delta constante value is defined as the result of 2^32 divided by the golden ratio.

python 6 lines
1import math
2
3_32bit_integer: float = 2**32
4golden_ratio: float = (1 + math.sqrt(5)) / 2
5
6print(f"delta = 0x{int(_32bit_integer // golden_ratio):x}")
output 1 line
delta = 0x9e3779b9

Implementation#

Based on this public C implementation:

c 25 lines
 1#include <stdint.h>
 2
 3/* take 64 bits of data in v[0] and v[1] and 128 bits of key[0] - key[3] */
 4
 5void encipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
 6    unsigned int i;
 7    uint32_t v0=v[0], v1=v[1], sum=0, delta=0x9E3779B9;
 8    for (i=0; i < num_rounds; i++) {
 9        v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
10        sum += delta;
11        v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
12    }
13    v[0]=v0; v[1]=v1;
14}
15
16void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) {
17    unsigned int i;
18    uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
19    for (i=0; i < num_rounds; i++) {
20        v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
21        sum -= delta;
22        v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
23    }
24    v[0]=v0; v[1]=v1;
25}

The following is an implementation of the XTEA algorithm in Python3:

python 98 lines
 1import struct
 2
 3# ensure to remain in the size of unsigned long
 4rshift = lambda v, n: (v >> n) & 0xffffffff
 5lshift = lambda v, n: (v << n) & 0xffffffff
 6
 7class XTEA:
 8
 9    XTEA_DELTA: int  = 0x9e3779b9
10
11    @staticmethod
12    def encrypt_block(block: bytes, key: bytes, round: int = 64, endian: str = "!") -> bytes:
13        """xtea 32 bit block data encryption"""
14
15        assert len(key) == 16
16        assert len(block) == 8
17
18        v0, v1 = struct.unpack(endian + "2L", block)
19        _key: list[int] = struct.unpack(endian + "4L", key)
20        _sum: int = 0
21        _delta: int = XTEA.XTEA_DELTA
22
23        for i in range(round):
24            v0 = (v0 + (((lshift(v1, 4) ^ rshift(v1, 5)) + v1) ^ (_sum + _key[_sum & 3]))) & 0xffffffff
25            _sum = (_sum + _delta) & 0xffffffff
26            v1 = (v1 + (((lshift(v0, 4) ^ rshift(v0, 5)) + v0) ^ (_sum + _key[(_sum >> 11) & 3]))) & 0xffffffff
27
28        return struct.pack(endian + "2L", v0, v1)
29
30    @staticmethod
31    def decrypt_block(block: bytes, key: bytes, round: int = 64, endian: str = "!") -> bytes:
32        """xtea 32 bit block data decryption """
33
34        assert len(key) == 16
35        assert len(block) == 8
36
37        v0, v1 = struct.unpack(endian + "2L", block)
38        _key: list[int] = struct.unpack(endian + "4L", key)
39        _delta: int = XTEA.XTEA_DELTA
40        _sum: int = (_delta * round) & 0xffffffff
41
42        for i in range(round):
43            v1 = (v1 - (((lshift(v0, 4) ^ rshift(v0, 5)) + v0) ^ (_sum + _key[rshift(_sum, 11) & 3]))) & 0xffffffff
44            _sum = (_sum - _delta) & 0xffffffff
45            v0 = (v0 - (((lshift(v1, 4) ^ rshift(v1, 5)) + v1) ^ (_sum + _key[_sum & 3]))) & 0xffffffff
46
47        return struct.pack(endian + "2L", v0, v1)
48
49
50    @staticmethod
51    def decrypt(ciphertext: bytes, key: bytes, round: int = 64, endian: str = "!") -> bytes:
52        """xtea decryption of a complete ciphertext"""
53
54        ciphertext = XTEA.pad(ciphertext, 8, b"\x00")
55        return XTEA.__xtea_wrapper(XTEA.decrypt_block, ciphertext, key, round, endian)
56
57    @staticmethod
58    def encrypt(cleartext: bytes, key: bytes, round: int = 64, endian: str = "!") -> bytes:
59        """xtea encryption of a complete cleartext"""
60
61        cleartext = XTEA.pad(cleartext, 8, b"\x90")
62        return XTEA.__xtea_wrapper(XTEA.encrypt_block, cleartext, key, round, endian)
63
64
65    @staticmethod
66    def __xtea_wrapper(xtea_function: callable, blob: bytes, key: bytes, round: int = 64, endian: str ="!") -> bytes:
67        """decrypt a complete payload, key must be 16 bytes long"""
68
69        assert len(key) == 16
70        output: bytes = b""
71
72        for block_index in range(0, len(blob), 8):
73            output += xtea_function(blob[block_index: block_index + 8], key, round, endian)
74
75        return output
76
77    @staticmethod
78    def pad(data: bytes, block_size: int = 8, padding_byte: bytes = b"\x90") -> bytes:
79
80        if not len(data) % block_size:
81            return data
82
83        pad_size = len(data) + (block_size - len(data) % block_size)
84        print(f"add {pad_size - len(data)} {padding_byte} byte(s) padding")
85        data = data.ljust(pad_size, padding_byte)
86
87        return data
88
89# note that the key below will be padded to 16 bytes with null byte
90# to fit the algorithm standard, the data to encrypt will also be
91# padded to a multiple of 8 (here 2 padding bytes)
92key = XTEA.pad(b"Hello there", 16, b"\x00")
93cleartext = XTEA.pad(b"General Kenobi", 8, b"\x90")
94print(f"encrypt `{cleartext}` with xTEA algorithm, key=`{key}`")
95ciphertext = XTEA.encrypt(key=key, cleartext=cleartext, round=32)
96print(f"ciphertext: {ciphertext.hex()}")
97decrypted = XTEA.decrypt(key=key, ciphertext=ciphertext, round=32)
98assert decrypted == cleartext
output 4 lines
add 5 b'\x00' byte(s) padding
add 2 b'\x90' byte(s) padding
encrypt `b'General Kenobi\x90\x90'` with xTEA algorithm, key=`b'Hello there\x00\x00\x00\x00\x00'`
ciphertext: 36939f6855904465a5dd89f714429dad

FNV-1#

Fowler–Noll–Vo is a hashing algorithm, whose speucode is:

output 8 lines
algorithm fnv-1 is
    hash := FNV_offset_basis

    for each byte_of_data to be hashed do
        hash := hash × FNV_prime
        hash := hash XOR byte_of_data

    return hash

There is a variant of this algorithm called FNV-1**a** where the XOR and the multiplication are switched. The XOR operation is performed prior to the multiplication.

The Python implementation is as fallow:

python 96 lines
 1from enum import Enum
 2from typing import Union
 3
 4
 5class ARCH(Enum):
 6    """Supported hash architecture to handle FNV1
 7    PRIME, init offset and the bit mask for max value"""
 8    X32 = 32
 9    X64 = 64
10
11class FNV1Variant(Enum):
12    FNV1 = "FNV-1"    # Original FNV-1 (multiply, then XOR)
13    FNV1A = "FNV-1A"  # FNV-1a variant (XOR, then multiply)
14
15
16class FNV1Hash:
17    """Implementation of FNV hash algorithms (FNV-1 and FNV-1a)
18    for 32-bit and 64-bit architectures."""
19
20     # FNV constants
21    _CONSTANTS = {
22        ARCH.X32: {
23            'prime': 0x01000193,
24            'init': 0x811C9DC5,
25            'size': 2**32
26        },
27        ARCH.X64: {
28            'prime': 0x00000100000001B3,
29            'init': 0xCBF29CE484222325,
30            'size': 2**64
31        }
32    }
33
34
35    @classmethod
36    def hash(cls,
37             data: Union[bytes, str],
38             arch: ARCH = ARCH.X32,
39             variant: FNV1Variant = FNV1Variant.FNV1) -> int:
40        """
41        Compute the FNV hash of the input data
42
43        Args:
44            data: The data to hash, can be string or bytes
45            arch: Architecture (32-bit or 64-bit)
46            variant: FNV1 or FNV1A
47
48        Returns: computed hash as an integer
49
50        Raises:
51            TypeError: if input data is not str or bytes
52            ValueError: if ARCH is not supported
53        """
54
55        if isinstance(data, str):
56            data = data.encode()
57        elif not isinstance(data, bytes):
58            raise TypeError(f"Input data must be str or bytes not: {type(data)}")
59
60        if arch not in cls._CONSTANTS:
61            raise ValueError(f"Unsupported architecture: {arch}")
62
63        constants = cls._CONSTANTS[arch]
64        hval = constants['init']
65        fnv_prime = constants['prime']
66        fnv_size = constants['size']
67
68        if variant is FNV1Variant.FNV1A:
69            # FNV-1A: XOR then multiply
70            for byte in data:
71                hval = hval ^ byte
72                hval = (hval * fnv_prime) % fnv_size
73        else: # FNV1
74            # FNV-1: multiply then XOR
75            for byte in data:
76                hval = (hval * fnv_prime) % fnv_size
77                hval = hval ^ byte
78
79        return hval
80
81
82class FNV1aHash:
83    """Alias for FNVHash class with FNV-1a variant."""
84
85    @classmethod
86    def hash(cls, data: Union[bytes, str], arch: ARCH = ARCH.X32) -> int:
87        """Calculate the FNV-1a hash of input data."""
88        return FNV1Hash.hash(data, arch, FNV1Variant.FNV1A)
89
90test_input = "Lotus"
91
92hash_32_fnv1a = FNV1aHash.hash(test_input)
93hash_32_fnv1 = FNV1Hash.hash(test_input)
94
95print(f"FNV-1a (32-bit) of '{test_input}' is 0x{hash_32_fnv1a:08x}")
96print(f"FNV-1  (32-bit) of '{test_input}' is 0x{hash_32_fnv1:08x}")
output 2 lines
FNV-1a (32-bit) of 'Lotus' is 0xeac834bc
FNV-1  (32-bit) of 'Lotus' is 0x8861a036