RBOOL rpal_blob_remove ( rBlob blob, RU32 startOffset, RU32 size ) { RBOOL isSuccess = FALSE; _prBlob pBlob = (_prBlob)blob; if( NULL != pBlob && IS_WITHIN_BOUNDS( pBlob->pData + startOffset, size, pBlob->pData, pBlob->sizeUsed ) && rpal_memory_isValid( pBlob->pData ) ) { rpal_memory_memcpy( pBlob->pData + startOffset, pBlob->pData + startOffset + size, pBlob->sizeUsed - size - startOffset ); pBlob->sizeUsed -= size; if( rpal_memory_isValid( pBlob->pData ) ) { pBlob->pData = rpal_memory_realloc_from( pBlob->pData, pBlob->sizeUsed + sizeof( RWCHAR ), pBlob->from ); pBlob->currentSize = pBlob->sizeUsed; if( NULL != pBlob->pData ) { // We allocate WCHAR more than we need always to ensure that if the buffer contains a wide string it will be terminated rpal_memory_zero( pBlob->pData + pBlob->sizeUsed, sizeof( RWCHAR ) ); isSuccess = TRUE; } } } return isSuccess; }
static RBOOL isMemInModule ( RU64 memBase, RU64 memSize, _MemRange* memRanges, RU32 nRanges ) { RBOOL isInMod = FALSE; RU32 i = 0; if( NULL != memRanges ) { i = rpal_binsearch_array_closest( memRanges, nRanges, sizeof( _MemRange ), &memBase, (rpal_ordering_func)rpal_order_RU64, TRUE ); if( (RU32)(-1) != i ) { if( IS_WITHIN_BOUNDS( NUMBER_TO_PTR( memBase ), memSize, NUMBER_TO_PTR( memRanges[ i ].base ), memRanges[ i ].size ) ) { isInMod = TRUE; } } } return isInMod; }
RPVOID rpal_blob_arrElem ( rBlob blob, RU32 elemSize, RU32 elemIndex ) { RPVOID pElem = NULL; _prBlob b = (_prBlob)blob; RU32 offset = 0; if( rpal_memory_isValid( b ) ) { offset = elemSize * elemIndex; if( rpal_memory_isValid( b->pData ) && IS_WITHIN_BOUNDS( b->pData + ( offset ), elemSize, b->pData, b->sizeUsed ) ) { pElem = b->pData + offset; } } return pElem; }
RPRIVATE RVOID dnsKmDiffThread ( rEvent isTimeToStop ) { RU8 new_from_kernel[ DNS_KB_PACKET_BUFFER * 1024 ] = { 0 }; RU8 prev_from_kernel[ DNS_KB_PACKET_BUFFER * 1024 ] = { 0 }; RU32 sizeInNew = 0; RU32 sizeInPrev = 0; KernelAcqDnsPacket* pPacket = NULL; while( !rEvent_wait( isTimeToStop, 1000 ) ) { rpal_memory_zero( new_from_kernel, sizeof( new_from_kernel ) ); sizeInNew = sizeof( new_from_kernel ); if( !kAcq_getNewDnsPackets( (KernelAcqDnsPacket*)new_from_kernel, &sizeInNew ) ) { rpal_debug_warning( "kernel acquisition for new dns packets failed" ); break; } pPacket = (KernelAcqDnsPacket*)prev_from_kernel; while( IS_WITHIN_BOUNDS( pPacket, sizeof( *pPacket ), prev_from_kernel, sizeInPrev ) && 0 != pPacket->ts && IS_WITHIN_BOUNDS( pPacket, sizeof( *pPacket ) + pPacket->packetSize, prev_from_kernel, sizeInPrev ) ) { processDnsPacket( pPacket ); pPacket = (KernelAcqDnsPacket*)( (RPU8)pPacket + sizeof( *pPacket ) + pPacket->packetSize ); } rpal_memory_memcpy( prev_from_kernel, new_from_kernel, sizeInNew ); sizeInPrev = sizeInNew; } }
RBOOL rStack_free ( rStack stack, rStack_freeFunc freeFunc ) { RBOOL isSuccess = FALSE; _prStack pStack = (_prStack)stack; RPVOID tmpElem = NULL; RPVOID tmpBuff = NULL; RU32 tmpSize = 0; RU32 i = 0; if( NULL != stack ) { if( rMutex_lock( pStack->lock ) ) { isSuccess = TRUE; tmpElem = rpal_blob_getBuffer( pStack->blob ); tmpBuff = tmpElem; tmpSize = rpal_blob_getSize( pStack->blob ); for( i = 0; i < pStack->nElements; i++ ) { tmpElem = (RPU8)tmpElem + ( i * pStack->elemSize ); if( IS_WITHIN_BOUNDS( tmpElem, pStack->elemSize, tmpBuff, tmpSize ) ) { if( NULL != freeFunc ) { if( !freeFunc( tmpElem ) ) { isSuccess = FALSE; } } } } rpal_blob_free( pStack->blob ); rMutex_free( pStack->lock ); rpal_memory_free( pStack ); } } return isSuccess; }
// TODO: Implement PMAP_CANFAIL logic uint32_t pmap_enter(pmap_t *pmap, vaddr_t va, paddr_t pa, vm_prot_t vm_prot, pmap_flags_t pmap_flags) { // Must have a valid pmap kassert(pmap != NULL && pmap->pgd != NULL && IS_WITHIN_BOUNDS(pa) && IS_PAGE_ALIGNED(pa) && IS_PAGE_ALIGNED(va)); // Encode the protection bits in the page table entry // Encode the protection and pmap flags in the page table entry pte_t entry = PTE_CREATE(pa, PTE_AP0_BIT | PTE_S_BIT | PTE_ENCODE_PROTECTION(vm_prot, pmap_kernel()) | PTE_ENCODE_PMAP_FLAGS(pmap_flags)); // First check if the page table for the given va exists within the page directory. If not create the page table uint32_t pgd_index = PGD_GET_INDEX(va); if(!PDE_PGT_EXISTS(pmap->pgd->pde[pgd_index])) { // TODO: To get pa of pgt -> TRUNC_PAGE(pgt) and search kernel pgd & pgt for entry } kassert(entry != 0); return 0; }
static RBOOL isMemInModule ( RU64 memBase, RU64 memSize, rList modules ) { RBOOL isInMod = FALSE; rSequence mod = NULL; RU64 modBase = 0; RU64 modSize = 0; if( NULL != modules ) { rList_resetIterator( modules ); while( rList_getSEQUENCE( modules, RP_TAGS_DLL, &mod ) ) { if( rSequence_getPOINTER64( mod, RP_TAGS_BASE_ADDRESS, &modBase ) && rSequence_getRU64( mod, RP_TAGS_MEMORY_SIZE, &modSize ) ) { if( IS_WITHIN_BOUNDS( NUMBER_TO_PTR( memBase ), memSize, NUMBER_TO_PTR( modBase ), modSize ) ) { isInMod = TRUE; break; } } } rList_resetIterator( modules ); } return isInMod; }
RPRIVATE RVOID processDnsPacket ( KernelAcqDnsPacket* pDns ) { rSequence notification = NULL; RU32 i = 0; DnsLabel* pLabel = NULL; DnsHeader* dnsHeader = NULL; DnsResponseInfo* pResponseInfo = NULL; RCHAR domain[ DNS_LABEL_MAX_SIZE ] = { 0 }; RU16 recordType = 0; RU64 timestamp = 0; Atom parentAtom = { 0 }; if( NULL == pDns ) { return; } dnsHeader = (DnsHeader*)( (RPU8)pDns + sizeof( *pDns ) ); pLabel = (DnsLabel*)dnsHeader->data; // We are parsing DNS packets coming from the kernel. They may: // 1- Be requests and not responses, check there are Answers. // 2- Be maliciously crafter packets so we need extra checking for sanity. if( 0 == dnsHeader->anCount || 0 == dnsHeader->qr || DNS_SANITY_MAX_RECORDS < rpal_ntoh16( dnsHeader->qdCount ) || DNS_SANITY_MAX_RECORDS < rpal_ntoh16( dnsHeader->anCount ) ) { return; } // We need to walk the Questions first to get to the Answers // but we don't really care to record them since they'll be repeated // in the Answers. for( i = 0; i < rpal_ntoh16( dnsHeader->qdCount ); i++ ) { DnsQuestionInfo* pQInfo = NULL; pLabel = dnsReadLabels( pLabel, NULL, (RPU8)dnsHeader, pDns->packetSize, 0, 0 ); pQInfo = (DnsQuestionInfo*)( pLabel ); if( !IS_WITHIN_BOUNDS( pQInfo, sizeof( *pQInfo ), dnsHeader, pDns->packetSize ) ) { rpal_debug_warning( "error parsing dns packet" ); break; } pLabel = (DnsLabel*)( (RPU8)pQInfo + sizeof( *pQInfo ) ); } if( !IS_WITHIN_BOUNDS( pLabel, sizeof( RU16 ), dnsHeader, pDns->packetSize ) ) { rpal_debug_warning( "error parsing dns packet" ); return; } // This is what we care about, the Answers (which also point to each Question). // We will emit one event per Answer so as to keep the DNS_REQUEST event flat and atomic. for( i = 0; i < rpal_ntoh16( dnsHeader->anCount ); i++ ) { pResponseInfo = NULL; // This was the Question for this answer. rpal_memory_zero( domain, sizeof( domain ) ); pLabel = dnsReadLabels( pLabel, domain, (RPU8)dnsHeader, pDns->packetSize, 0, 0 ); pResponseInfo = (DnsResponseInfo*)pLabel; pLabel = (DnsLabel*)( (RPU8)pResponseInfo + sizeof( *pResponseInfo ) + rpal_ntoh16( pResponseInfo->rDataLength ) ); if( !IS_WITHIN_BOUNDS( pResponseInfo, sizeof( *pResponseInfo ), dnsHeader, pDns->packetSize ) ) { rpal_debug_warning( "error parsing dns packet" ); break; } if( NULL == ( notification = rSequence_new() ) ) { rpal_debug_warning( "error parsing dns packet" ); break; } // This is a timestamp coming from the kernel so it is not globally adjusted. // We'll adjust it with the global offset. timestamp = pDns->ts; timestamp += MSEC_FROM_SEC( rpal_time_getGlobalFromLocal( 0 ) ); // Try to relate the DNS request to the owner process, this only works on OSX // at the moment (since the kernel does not expose the PID at the packet capture // stage), and even on OSX it's the DNSResolver process. So it's not super useful // but regardless we have the mechanism here as it's better than nothing and when // we add better resolving in the kernel it will work transparently. parentAtom.key.process.pid = pDns->pid; parentAtom.key.category = RP_TAGS_NOTIFICATION_NEW_PROCESS; if( atoms_query( &parentAtom, timestamp ) ) { HbsSetParentAtom( notification, parentAtom.id ); } rSequence_addTIMESTAMP( notification, RP_TAGS_TIMESTAMP, timestamp ); rSequence_addSTRINGA( notification, RP_TAGS_DOMAIN_NAME, domain ); rSequence_addRU32( notification, RP_TAGS_PROCESS_ID, pDns->pid ); recordType = rpal_ntoh16( pResponseInfo->recordType ); rSequence_addRU16( notification, RP_TAGS_MESSAGE_ID, rpal_ntoh16( dnsHeader->msgId ) ); rSequence_addRU16( notification, RP_TAGS_DNS_TYPE, recordType ); if( DNS_A_RECORD == recordType ) { rSequence_addIPV4( notification, RP_TAGS_IP_ADDRESS, *(RU32*)pResponseInfo->rData ); } else if( DNS_AAAA_RECORD == recordType ) { rSequence_addIPV6( notification, RP_TAGS_IP_ADDRESS, pResponseInfo->rData ); } else if( DNS_CNAME_RECORD == recordType ) { // CNAME records will have another label as a value and not an IP. rpal_memory_zero( domain, sizeof( domain ) ); dnsReadLabels( (DnsLabel*)pResponseInfo->rData, domain, (RPU8)dnsHeader, pDns->packetSize, 0, 0 ); rSequence_addSTRINGA( notification, RP_TAGS_CNAME, domain ); } else { // Right now we only care for A, CNAME and AAAA records. rSequence_free( notification ); notification = NULL; continue; } hbs_publish( RP_TAGS_NOTIFICATION_DNS_REQUEST, notification ); rSequence_free( notification ); notification = NULL; } }
// Parses a label from a DNS packet and returns a pointer to the next byte after the label // or label chain to be used to continue parsing the packet. // If a human label is specified, will also assemble a human readable version of the labels // in the buffer. RPRIVATE DnsLabel* dnsReadLabels ( DnsLabel* pLabel, RCHAR humanLabel[ DNS_LABEL_MAX_SIZE ], RPU8 packetStart, RSIZET packetSize, RU32 labelOffset, RU32 recursiveDepth ) { RU32 copied = labelOffset; if( 3 < recursiveDepth ) { return NULL; } if( NULL == pLabel ) { return NULL; } while( IS_WITHIN_BOUNDS( pLabel, sizeof( *pLabel ), packetStart, packetSize ) && ( DNS_LABEL_IS_OFFSET( pLabel ) || ( IS_WITHIN_BOUNDS( pLabel, sizeof( *pLabel ) + pLabel->nChar, packetStart, packetSize ) && 0 != pLabel->nChar ) ) ) { // It's possible for a pointer to be terminating a traditional label if( DNS_LABEL_IS_OFFSET( pLabel ) ) { // Pointer to a label DnsLabel* tmpLabel = NULL; RU16 offset = DNS_LABEL_OFFSET( pLabel ); if( !IS_WITHIN_BOUNDS( (RPU8)packetStart + offset, sizeof( RU16 ), packetStart, packetSize ) ) { rpal_debug_warning( "error parsing dns packet" ); return NULL; } tmpLabel = (DnsLabel*)( (RPU8)packetStart + offset ); if( NULL == dnsReadLabels( tmpLabel, humanLabel, packetStart, packetSize, copied, recursiveDepth + 1 ) ) { return NULL; } // Pointers are always terminating the label. So since there is // no 0 terminated label we don't need to skip an extra byte, we // just skip the current label pointer value. pLabel = (DnsLabel*)( (RPU8)pLabel + sizeof( RU16 ) ); return pLabel; } else { if( DNS_LABEL_MAX_SIZE < copied + 1 + pLabel->nChar ) { rpal_debug_warning( "error parsing dns packet" ); return NULL; } if( NULL != humanLabel ) { if( 0 != copied ) { humanLabel[ copied ] = '.'; copied++; } rpal_memory_memcpy( (RPU8)humanLabel + copied, pLabel->label, pLabel->nChar ); copied += pLabel->nChar; } pLabel = (DnsLabel*)( (RPU8)pLabel + pLabel->nChar + 1 ); } } // We do a last sanity check. A valid label parsing should end in a 0-val nChar within // the buffer, so we check it's all valid, otherwise we'll assume an error and will return an error. if( !IS_WITHIN_BOUNDS( pLabel, sizeof( *pLabel ), packetStart, packetSize ) || 0 != pLabel->nChar ) { rpal_debug_warning( "error parsing dns packet" ); return NULL; } // Get to the next valid byte, so we skip the 0-termination. pLabel = (DnsLabel*)( (RPU8)pLabel + 1 ); return pLabel; }
static RPVOID lookForHiddenModulesIn ( rEvent isTimeToStop, RU32 processId ) { rList mods = NULL; rList map = NULL; rSequence region = NULL; RU8 memType = 0; RU8 memProtect = 0; RU64 memBase = 0; RU64 memSize = 0; RPU8 pMem = NULL; RBOOL isPrefetched = FALSE; RBOOL isCurrentExec = FALSE; RBOOL isHidden = FALSE; rSequence procInfo = NULL; #ifdef RPAL_PLATFORM_WINDOWS PIMAGE_DOS_HEADER pDos = NULL; PIMAGE_NT_HEADERS pNt = NULL; #endif if( NULL != ( mods = processLib_getProcessModules( processId ) ) ) { if( NULL != ( map = processLib_getProcessMemoryMap( processId ) ) ) { // Now we got all the info needed for a single process, compare while( rpal_memory_isValid( isTimeToStop ) && !rEvent_wait( isTimeToStop, 0 ) && ( isPrefetched || rList_getSEQUENCE( map, RP_TAGS_MEMORY_REGION, ®ion ) ) ) { if( isPrefetched ) { isPrefetched = FALSE; } if( rSequence_getRU8( region, RP_TAGS_MEMORY_TYPE, &memType ) && rSequence_getRU8( region, RP_TAGS_MEMORY_ACCESS, &memProtect ) && rSequence_getPOINTER64( region, RP_TAGS_BASE_ADDRESS, &memBase ) && rSequence_getRU64( region, RP_TAGS_MEMORY_SIZE, &memSize ) ) { if( PROCESSLIB_MEM_TYPE_PRIVATE == memType || PROCESSLIB_MEM_TYPE_MAPPED == memType ) { if( PROCESSLIB_MEM_ACCESS_EXECUTE == memProtect || PROCESSLIB_MEM_ACCESS_EXECUTE_READ == memProtect || PROCESSLIB_MEM_ACCESS_EXECUTE_READ_WRITE == memProtect || PROCESSLIB_MEM_ACCESS_EXECUTE_WRITE_COPY == memProtect ) { isCurrentExec = TRUE; } else { isCurrentExec = FALSE; } if( !isMemInModule( memBase, memSize, mods ) ) { // Exec memory found outside of a region marked to belong to // a module, keep looking in for module. if( ( 1024 * 1024 * 10 ) >= memSize && processLib_getProcessMemory( processId, NUMBER_TO_PTR( memBase ), memSize, (RPVOID*)&pMem ) ) { isHidden = FALSE; #ifdef RPAL_PLATFORM_WINDOWS // Let's just check for MZ and PE for now, we can get fancy later. pDos = (PIMAGE_DOS_HEADER)pMem; if( IS_WITHIN_BOUNDS( (RPU8)pMem, sizeof( IMAGE_DOS_HEADER ), pMem, memSize ) && IMAGE_DOS_SIGNATURE == pDos->e_magic ) { pNt = (PIMAGE_NT_HEADERS)( (RPU8)pDos + pDos->e_lfanew ); if( IS_WITHIN_BOUNDS( pNt, sizeof( *pNt ), pMem, memSize ) && IMAGE_NT_SIGNATURE == pNt->Signature ) { if( isCurrentExec ) { // If the current region is exec, we've got a hidden module. isHidden = TRUE; } else { // We need to check if the next section in memory is // executable and outside of known modules since the PE // headers may have been marked read-only before the .text. if( rList_getSEQUENCE( map, RP_TAGS_MEMORY_REGION, ®ion ) ) { isPrefetched = TRUE; if( ( PROCESSLIB_MEM_TYPE_PRIVATE == memType || PROCESSLIB_MEM_TYPE_MAPPED == memType ) && ( PROCESSLIB_MEM_ACCESS_EXECUTE == memProtect || PROCESSLIB_MEM_ACCESS_EXECUTE_READ == memProtect || PROCESSLIB_MEM_ACCESS_EXECUTE_READ_WRITE == memProtect || PROCESSLIB_MEM_ACCESS_EXECUTE_WRITE_COPY == memProtect ) ) { isHidden = TRUE; } } } } } #elif defined( RPAL_PLATFORM_LINUX ) || defined( RPAL_PLATFORM_MACOSX ) if( isCurrentExec && 0x7F == ( pMem )[ 0 ] && 'E' == ( pMem )[ 1 ] && 'L' == ( pMem )[ 2 ] && 'F' == ( pMem )[ 3 ] ) { isHidden = TRUE; } #endif rpal_memory_free( pMem ); if( isHidden && !rEvent_wait( isTimeToStop, 0 ) ) { rpal_debug_info( "found a hidden module in %d.", processId ); if( NULL != ( procInfo = processLib_getProcessInfo( processId ) ) ) { if( !rSequence_addSEQUENCE( region, RP_TAGS_PROCESS, procInfo ) ) { rSequence_free( procInfo ); } } rSequence_addTIMESTAMP( region, RP_TAGS_TIMESTAMP, rpal_time_getGlobal() ); notifications_publish( RP_TAGS_NOTIFICATION_HIDDEN_MODULE_DETECTED, region ); break; } rpal_thread_sleep( 10 ); } } } } } rList_free( map ); } rList_free( mods ); } return NULL; }