在C++中是否已经为文件系统迷你过滤器驱动程序编写了一个计算MD5哈希的函数

Is there a function already written to calculate MD5 hash in C++ for file system mini filter driver?

本文关键字:一个 计算 函数 哈希 MD5 是否 C++ 文件系统 驱动程序 过滤器      更新时间:2023-10-16

我正在寻找一个函数,该函数在C++中为迷你过滤器驱动程序计算MD5哈希。是否已经有一个用C++编写的函数可以在文件进入文件系统迷你过滤器驱动程序时计算文件的MD5哈希?

基本上,我要离开WDK 8.1的样本,称为"扫描仪文件系统Minifilter驱动程序"

在scanner.c文件中,我希望能够在将文件数据发送到用户模式代码(user>>scanuser.c)之前计算出文件的MD5哈希

您可以在此处查看源代码:

https://code.msdn.microsoft.com/windowshardware/Scanner-File-System-426c8cbe/view/SourceCode#content

但这是我想在过滤器>>扫描仪中修改的功能。c:/strong>

NTSTATUS
ScannerpScanFileInUserMode (
    _In_ PFLT_INSTANCE Instance,
    _In_ PFILE_OBJECT FileObject,
    _Out_ PBOOLEAN SafeToOpen
    )
/*++
Routine Description:
    This routine is called to send a request up to user mode to scan a given
    file and tell our caller whether it's safe to open this file.
    Note that if the scan fails, we set SafeToOpen to TRUE.  The scan may fail
    because the service hasn't started, or perhaps because this create/cleanup
    is for a directory, and there's no data to read & scan.
    If we failed creates when the service isn't running, there'd be a
    bootstrapping problem -- how would we ever load the .exe for the service?
Arguments:
    Instance - Handle to the filter instance for the scanner on this volume.
    FileObject - File to be scanned.
    SafeToOpen - Set to FALSE if the file is scanned successfully and it contains
                 foul language.
Return Value:
    The status of the operation, hopefully STATUS_SUCCESS.  The common failure
    status will probably be STATUS_INSUFFICIENT_RESOURCES.
--*/
{
    NTSTATUS status = STATUS_SUCCESS;
    PVOID buffer = NULL;
    ULONG bytesRead;
    PSCANNER_NOTIFICATION notification = NULL;
    FLT_VOLUME_PROPERTIES volumeProps;
    LARGE_INTEGER offset;
    ULONG replyLength, length;
    PFLT_VOLUME volume = NULL;
    *SafeToOpen = TRUE;
    //
    //  If not client port just return.
    //
    if (ScannerData.ClientPort == NULL) {
        return STATUS_SUCCESS;
    }
    try {
        //
        //  Obtain the volume object .
        //
        status = FltGetVolumeFromInstance( Instance, &volume );
        if (!NT_SUCCESS( status )) {
            leave;
        }
        //
        //  Determine sector size. Noncached I/O can only be done at sector size offsets, and in lengths which are
        //  multiples of sector size. A more efficient way is to make this call once and remember the sector size in the
        //  instance setup routine and setup an instance context where we can cache it.
        //
        status = FltGetVolumeProperties( volume,
                                         &volumeProps,
                                         sizeof( volumeProps ),
                                         &length );
        //
        //  STATUS_BUFFER_OVERFLOW can be returned - however we only need the properties, not the names
        //  hence we only check for error status.
        //
        if (NT_ERROR( status )) {
            leave;
        }
        length = max( SCANNER_READ_BUFFER_SIZE, volumeProps.SectorSize );
        //
        //  Use non-buffered i/o, so allocate aligned pool
        //
        buffer = FltAllocatePoolAlignedWithTag( Instance,
                                                NonPagedPool,
                                                length,
                                                'nacS' );
        if (NULL == buffer) {
            status = STATUS_INSUFFICIENT_RESOURCES;
            leave;
        }
        notification = ExAllocatePoolWithTag( NonPagedPool,
                                              sizeof( SCANNER_NOTIFICATION ),
                                              'nacS' );
        if(NULL == notification) {
            status = STATUS_INSUFFICIENT_RESOURCES;
            leave;
        }
        //
        //  Read the beginning of the file and pass the contents to user mode.
        //
        offset.QuadPart = bytesRead = 0;
        status = FltReadFile( Instance,
                              FileObject,
                              &offset,
                              length,
                              buffer,
                              FLTFL_IO_OPERATION_NON_CACHED |
                              FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET,
                              &bytesRead,
                              NULL,
                              NULL );
        if (NT_SUCCESS( status ) && (0 != bytesRead)) {
            notification->BytesToScan = (ULONG) bytesRead;
            //
            //  Copy only as much as the buffer can hold
            //
            RtlCopyMemory( &notification->Contents,
                           buffer,
                           min( notification->BytesToScan, SCANNER_READ_BUFFER_SIZE ) );
            replyLength = sizeof( SCANNER_REPLY );

//TODO:在这里计算过滤器的MD5哈希,并使用下面的FltSendMessage函数发送数据。我该如何在这里计算MD5?是否已经编写了一个函数来处理这样的事情?

            status = FltSendMessage( ScannerData.Filter,
                                     &ScannerData.ClientPort,
                                     notification,
                                     sizeof(SCANNER_NOTIFICATION),
                                     notification,
                                     &replyLength,
                                     NULL );
            if (STATUS_SUCCESS == status) {
                *SafeToOpen = ((PSCANNER_REPLY) notification)->SafeToOpen;
            } else {
                //
                //  Couldn't send message
                //
                DbgPrint( "!!! scanner.sys --- couldn't send message to user-mode to scan file, status 0x%Xn", status );
            }
        }
    } finally {
        if (NULL != buffer) {
            FltFreePoolAlignedWithTag( Instance, buffer, 'nacS' );
        }
        if (NULL != notification) {
            ExFreePoolWithTag( notification, 'nacS' );
        }
        if (NULL != volume) {
            FltObjectDereference( volume );
        }
    }
    return status;
}

尽管最初的海报没有发布任何步骤或试图展示他自己对他所面临的一系列问题的研究。答案很简单,但为了不重新发明轮子,我正在复制并粘贴来自rohitab 的代码

这是一个示例,您可能想要适应迷你过滤器的现有代码,如果您通读代码,这应该很容易。

#include <ntifs.h>
#include <wdm.h>
typedef unsigned char md5_byte_t; /* 8-bit byte */
typedef unsigned int md5_word_t; /* 32-bit word */
/* Define the state of the MD5 Algorithm. */
typedef struct md5_state_s {
    md5_word_t count[2];    /* message length in bits, lsw first */
    md5_word_t abcd[4];     /* digest buffer */
    md5_byte_t buf[64];     /* accumulate block */
} md5_state_t;
#define T_MASK ((md5_word_t)~0)
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
#define T3    0x242070db
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
#define T6    0x4787c62a
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
#define T9    0x698098d8
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
#define T13    0x6b901122
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
#define T16    0x49b40821
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
#define T19    0x265e5a51
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
#define T22    0x02441453
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
#define T25    0x21e1cde6
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
#define T28    0x455a14ed
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
#define T31    0x676f02d9
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
#define T35    0x6d9d6122
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
#define T38    0x4bdecfa9
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
#define T41    0x289b7ec6
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
#define T44    0x04881d05
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
#define T47    0x1fa27cf8
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
#define T50    0x432aff97
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
#define T53    0x655b59c3
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
#define T57    0x6fa87e4f
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
#define T60    0x4e0811a1
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
#define T63    0x2ad7d2bb
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
static void
md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
{
    md5_word_t
        a = pms->abcd[0], b = pms->abcd[1],
        c = pms->abcd[2], d = pms->abcd[3];
    md5_word_t t;
#if BYTE_ORDER > 0
    /* Define storage only for big-endian CPUs. */
    md5_word_t X[16];
#else
    /* Define storage for little-endian or both types of CPUs. */
    md5_word_t xbuf[16];
    const md5_word_t *X;
#endif
    {
#if BYTE_ORDER == 0
        /*
        * Determine dynamically whether this is a big-endian or
        * little-endian machine, since we can use a more efficient
        * algorithm on the latter.
        */
        static const int w = 1;
        if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
#endif
#if BYTE_ORDER <= 0      /* little-endian */
        {
            /*
            * On little-endian machines, we can process properly aligned
            * data without copying it.
            */
            if (!((data - (const md5_byte_t *)0) & 3)) {
                /* data are properly aligned */
                X = (const md5_word_t *)data;
            }
            else {
                /* not aligned */
                memcpy(xbuf, data, 64);
                X = xbuf;
            }
        }
#endif
#if BYTE_ORDER == 0
        else            /* dynamic big-endian */
#endif
#if BYTE_ORDER >= 0      /* big-endian */
        {
            /*
            * On big-endian machines, we must arrange the bytes in the
            * right order.
            */
            const md5_byte_t *xp = data;
            int i;
#  if BYTE_ORDER == 0
            X = xbuf;       /* (dynamic only) */
#  else
#    define xbuf X      /* (static only) */
#  endif
            for (i = 0; i < 16; ++i, xp += 4)
                xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
        }
#endif
    }
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
    /* Round 1. */
    /* Let [abcd k s i] denote the operation
    a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
#define SET(a, b, c, d, k, s, Ti)
    t = a + F(b, c, d) + X[k] + Ti; 
    a = ROTATE_LEFT(t, s) + b
    /* Do the following 16 operations. */
    SET(a, b, c, d, 0, 7, T1);
    SET(d, a, b, c, 1, 12, T2);
    SET(c, d, a, b, 2, 17, T3);
    SET(b, c, d, a, 3, 22, T4);
    SET(a, b, c, d, 4, 7, T5);
    SET(d, a, b, c, 5, 12, T6);
    SET(c, d, a, b, 6, 17, T7);
    SET(b, c, d, a, 7, 22, T8);
    SET(a, b, c, d, 8, 7, T9);
    SET(d, a, b, c, 9, 12, T10);
    SET(c, d, a, b, 10, 17, T11);
    SET(b, c, d, a, 11, 22, T12);
    SET(a, b, c, d, 12, 7, T13);
    SET(d, a, b, c, 13, 12, T14);
    SET(c, d, a, b, 14, 17, T15);
    SET(b, c, d, a, 15, 22, T16);
#undef SET
    /* Round 2. */
    /* Let [abcd k s i] denote the operation
    a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
#define SET(a, b, c, d, k, s, Ti)
    t = a + G(b, c, d) + X[k] + Ti; 
    a = ROTATE_LEFT(t, s) + b
    /* Do the following 16 operations. */
    SET(a, b, c, d, 1, 5, T17);
    SET(d, a, b, c, 6, 9, T18);
    SET(c, d, a, b, 11, 14, T19);
    SET(b, c, d, a, 0, 20, T20);
    SET(a, b, c, d, 5, 5, T21);
    SET(d, a, b, c, 10, 9, T22);
    SET(c, d, a, b, 15, 14, T23);
    SET(b, c, d, a, 4, 20, T24);
    SET(a, b, c, d, 9, 5, T25);
    SET(d, a, b, c, 14, 9, T26);
    SET(c, d, a, b, 3, 14, T27);
    SET(b, c, d, a, 8, 20, T28);
    SET(a, b, c, d, 13, 5, T29);
    SET(d, a, b, c, 2, 9, T30);
    SET(c, d, a, b, 7, 14, T31);
    SET(b, c, d, a, 12, 20, T32);
#undef SET
    /* Round 3. */
    /* Let [abcd k s t] denote the operation
    a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define SET(a, b, c, d, k, s, Ti)
    t = a + H(b, c, d) + X[k] + Ti; 
    a = ROTATE_LEFT(t, s) + b
    /* Do the following 16 operations. */
    SET(a, b, c, d, 5, 4, T33);
    SET(d, a, b, c, 8, 11, T34);
    SET(c, d, a, b, 11, 16, T35);
    SET(b, c, d, a, 14, 23, T36);
    SET(a, b, c, d, 1, 4, T37);
    SET(d, a, b, c, 4, 11, T38);
    SET(c, d, a, b, 7, 16, T39);
    SET(b, c, d, a, 10, 23, T40);
    SET(a, b, c, d, 13, 4, T41);
    SET(d, a, b, c, 0, 11, T42);
    SET(c, d, a, b, 3, 16, T43);
    SET(b, c, d, a, 6, 23, T44);
    SET(a, b, c, d, 9, 4, T45);
    SET(d, a, b, c, 12, 11, T46);
    SET(c, d, a, b, 15, 16, T47);
    SET(b, c, d, a, 2, 23, T48);
#undef SET
    /* Round 4. */
    /* Let [abcd k s t] denote the operation
    a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
#define SET(a, b, c, d, k, s, Ti)
    t = a + I(b, c, d) + X[k] + Ti; 
    a = ROTATE_LEFT(t, s) + b
    /* Do the following 16 operations. */
    SET(a, b, c, d, 0, 6, T49);
    SET(d, a, b, c, 7, 10, T50);
    SET(c, d, a, b, 14, 15, T51);
    SET(b, c, d, a, 5, 21, T52);
    SET(a, b, c, d, 12, 6, T53);
    SET(d, a, b, c, 3, 10, T54);
    SET(c, d, a, b, 10, 15, T55);
    SET(b, c, d, a, 1, 21, T56);
    SET(a, b, c, d, 8, 6, T57);
    SET(d, a, b, c, 15, 10, T58);
    SET(c, d, a, b, 6, 15, T59);
    SET(b, c, d, a, 13, 21, T60);
    SET(a, b, c, d, 4, 6, T61);
    SET(d, a, b, c, 11, 10, T62);
    SET(c, d, a, b, 2, 15, T63);
    SET(b, c, d, a, 9, 21, T64);
#undef SET
    /* Then perform the following additions. (That is increment each
    of the four registers by the value it had before this block
    was started.) */
    pms->abcd[0] += a;
    pms->abcd[1] += b;
    pms->abcd[2] += c;
    pms->abcd[3] += d;
}
void
md5_init(md5_state_t *pms)
{
    pms->count[0] = pms->count[1] = 0;
    pms->abcd[0] = 0x67452301;
    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
    pms->abcd[3] = 0x10325476;
}
void
md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
{
    const md5_byte_t *p = data;
    int left = nbytes;
    int offset = (pms->count[0] >> 3) & 63;
    md5_word_t nbits = (md5_word_t)(nbytes << 3);
    if (nbytes <= 0)
        return;
    /* Update the message length. */
    pms->count[1] += nbytes >> 29;
    pms->count[0] += nbits;
    if (pms->count[0] < nbits)
        pms->count[1]++;
    /* Process an initial partial block. */
    if (offset) {
        int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
        memcpy(pms->buf + offset, p, copy);
        if (offset + copy < 64)
            return;
        p += copy;
        left -= copy;
        md5_process(pms, pms->buf);
    }
    /* Process full blocks. */
    for (; left >= 64; p += 64, left -= 64)
        md5_process(pms, p);
    /* Process a final partial block. */
    if (left)
        memcpy(pms->buf, p, left);
}
void
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
{
    static const md5_byte_t pad[64] = {
        0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };
    md5_byte_t data[8];
    int i;
    for (i = 0; i < 8; ++i)
        data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
    md5_append(pms, data, 8);
    for (i = 0; i < 16; ++i)
        digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
}
VOID
MyUnload(
struct _DRIVER_OBJECT  *DriverObject
    )
{
    // Function body
}
char *                  /* O - MD5 sum in hex */
httpMD5String(const md5_byte_t *sum,    /* I - MD5 sum data */
char             md5[33])   /* O - MD5 sum in hex */
{
    int     i;          /* Looping var */
    char        *md5ptr;        /* Pointer into MD5 string */
    static char *hex = "0123456789abcdef";
    /* Hex digits */
    for (i = 16, md5ptr = md5; i > 0; i--, sum++)
    {
        *md5ptr++ = hex[*sum >> 4];
        *md5ptr++ = hex[*sum & 15];
    }
    *md5ptr = '';
    return (md5);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath){
    md5_state_t state;          /* MD5 state info */
    md5_byte_t  sum[16];        /* Sum data */
    char mmd5[33];
    NTSTATUS status;
    UNICODE_STRING fileNameU;
    OBJECT_ATTRIBUTES objectAttributes;
    HANDLE fileHandle;
    IO_STATUS_BLOCK ioStatusBlock;
    unsigned char readBuffer[4096];
    memset(&ioStatusBlock, 0, sizeof(ioStatusBlock));
    memset(&objectAttributes, 0, sizeof(objectAttributes));
    objectAttributes.Length = sizeof(objectAttributes);
    objectAttributes.Attributes = OBJ_CASE_INSENSITIVE;
    RtlInitUnicodeString(&fileNameU, L"\??\C:\test.txt");
    objectAttributes.ObjectName = &fileNameU;
    status = NtCreateFile(&fileHandle, FILE_READ_ATTRIBUTES | GENERIC_READ | SYNCHRONIZE, &objectAttributes, &ioStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
    DbgPrint("MD5 Initn");
    md5_init(&state);
    DbgPrint("MD5 Appendn");
    memset(readBuffer, 0, 4096);
    while (1){
        status = NtReadFile(fileHandle, NULL, NULL, NULL, (PVOID)&ioStatusBlock, (PVOID)&readBuffer, 1024, NULL, NULL);
        if (NT_SUCCESS(status)){
            md5_append(&state, (unsigned char*)readBuffer, strlen(readBuffer));
            memset(readBuffer, 0, 4096);
        }
        else {
            break;
        }
    }
    DbgPrint("MD5 Finishn");
    md5_finish(&state, sum);
    DbgPrint("Attempting to print Checksumn");
    httpMD5String(sum, mmd5);
    DbgPrint("%s", mmd5);
    return STATUS_SUCCESS;
}
void MD5( BYTE *szData, unsigned int uiSize, BYTE *bHash )
{
   HCRYPTPROV hCryptProv; 
   HCRYPTHASH hHash; 
   DWORD dwHashLen = 16; 
   if( CryptAcquireContext( &hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET ) )
   {
     if( CryptCreateHash( hCryptProv, CALG_MD5, 0, 0, &hHash ) )
     {
       if( CryptHashData(hHash, (const BYTE *)szData, uiSize, 0))
       {
         CryptGetHashParam( hHash, HP_HASHVAL, bHash, &dwHashLen, 0 );
       }
     }
   }
   CryptDestroyHash(hHash); 
   CryptReleaseContext(hCryptProv, 0);
}
相关文章: