Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add image hash support #121

Merged
merged 9 commits into from
Jun 26, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ If you installed SysinternalsEBPF via make install, you may need to add /usr/loc
```
sudo apt update
dotnet tool install --global dotnet-t4 --version 2.3.1
sudo apt -y install build-essential gcc g++ make cmake libelf-dev llvm clang libxml2 libxml2-dev libzstd1 git libgtest-dev apt-transport-https dirmngr googletest google-mock libgmock-dev libjson-glib-dev libc6-dev-i386
sudo apt -y install build-essential gcc g++ make cmake libelf-dev llvm clang libxml2 libxml2-dev libzstd1 git libgtest-dev apt-transport-https dirmngr googletest google-mock libgmock-dev libjson-glib-dev libc6-dev-i386 libssl-dev
```

### Rocky 9
Expand All @@ -30,7 +30,7 @@ sudo dnf install epel-release

sudo dnf update
dotnet tool install --global dotnet-t4 --version 2.3.1
sudo yum install gcc gcc-c++ make cmake llvm clang elfutils-libelf-devel rpm-build json-glib-devel python3 libxml2-devel gtest-devel gmock gmock-devel glibc-devel.i686
sudo yum install gcc gcc-c++ make cmake llvm clang elfutils-libelf-devel rpm-build json-glib-devel python3 libxml2-devel gtest-devel gmock gmock-devel glibc-devel.i686 openssl-devel
```

### Rocky 8
Expand All @@ -41,7 +41,7 @@ sudo dnf config-manager --set-enabled powertools

sudo dnf update
dotnet tool install --global dotnet-t4 --version 2.3.1
sudo yum install gcc gcc-c++ make cmake llvm clang elfutils-libelf-devel rpm-build json-glib-devel python3 libxml2-devel gtest-devel gmock gmock-devel glibc-devel.i686
sudo yum install gcc gcc-c++ make cmake llvm clang elfutils-libelf-devel rpm-build json-glib-devel python3 libxml2-devel gtest-devel gmock gmock-devel glibc-devel.i686 openssl-devel
```

### Debian 11
Expand All @@ -51,7 +51,7 @@ sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
sudo apt update
dotnet tool install --global dotnet-t4 --version 2.3.1
sudo apt -y install build-essential gcc g++ make cmake libelf-dev llvm clang libzstd1 git libjson-glib-dev libxml2 libxml2-dev googletest google-mock libgmock-dev libc6-dev-i386
sudo apt -y install build-essential gcc g++ make cmake libelf-dev llvm clang libzstd1 git libjson-glib-dev libxml2 libxml2-dev googletest google-mock libgmock-dev libc6-dev-i386 libssl-dev
```

## Build
Expand Down Expand Up @@ -102,4 +102,4 @@ make packages
The directories build/deb and build/rpm will be populated with the required
files. If dpkg-deb is available, the build/deb directory will be used to create
a deb package. Similarly if rpmbuild is available, the build/rpm directory will
be used to create an rpm package.
be used to create an rpm package.
11 changes: 8 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ set(CMAKE_CXX_FLAGS "-std=gnu++17")
#
find_package(LibXml2 2.0.0 REQUIRED)

#
# rely on openssl having been installed
#
find_package(OpenSSL REQUIRED)

#
# source directories
#
Expand Down Expand Up @@ -329,7 +334,7 @@ set(EBPF_DEPENDS
#
# link sysmon
#
target_link_libraries(sysmon sysinternalsEBPF "${LIBXML2_LIBRARIES}" pthread)
target_link_libraries(sysmon sysinternalsEBPF "${LIBXML2_LIBRARIES}" pthread OpenSSL::Crypto)


#
Expand Down Expand Up @@ -398,7 +403,7 @@ add_executable(sysmonUnitTests
"${PROJECT_BINARY_DIR}/yoursleep"
)

target_link_libraries(sysmonUnitTests "${LIBXML2_LIBRARIES}" ${GTEST_LIBRARIES} pthread)
target_link_libraries(sysmonUnitTests "${LIBXML2_LIBRARIES}" ${GTEST_LIBRARIES} pthread OpenSSL::Crypto)
target_include_directories(sysmonUnitTests PUBLIC
"${CMAKE_SOURCE_DIR}"
"${SYSMON_COMMON_SOURCE_DIR}"
Expand Down Expand Up @@ -616,4 +621,4 @@ foreach(EBPF_PROG IN LISTS EBPF_PROGS)
set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${EBPF_PROG}${EBPF_CORE_PROG_SUFFIX}.o)
endif()

endforeach(EBPF_PROG)
endforeach(EBPF_PROG)
116 changes: 116 additions & 0 deletions linuxHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <sys/stat.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <openssl/evp.h>

//--------------------------------------------------------------------
//
Expand Down Expand Up @@ -874,6 +875,121 @@ VOID LargeIntegerToSystemTimeString(
}
}

//--------------------------------------------------------------------
//
// LinuxGetFileHash
//
// Calculates image hash.
//
//--------------------------------------------------------------------
void LinuxGetFileHash(uint32_t hashType, PTCHAR imagePath, char *stringBuffer, size_t stringBufferSize)
{
unsigned char tmpReadBuffer[4096] = {};
unsigned char tmpHashBuffer[ALGO_MAX][256] = {};
unsigned char tmpHashPrefixBuffer[16] = {};
unsigned char tmpStringBuffer[256] = {};
char hashSeparator[] = ",";
char hashPrefix[3][8] = {"SHA1=", "MD5=", "SHA256="};
unsigned int hashSize[ALGO_MAX] = {};
unsigned int hashFlag[ALGO_MAX] = {};
size_t n;
FILE *filePtr;
EVP_MD_CTX *sha1_ctx, *md5_ctx, *sha256_ctx;

// Allocate digests context
sha1_ctx = EVP_MD_CTX_new();
md5_ctx = EVP_MD_CTX_new();
sha256_ctx = EVP_MD_CTX_new();
if ( !sha1_ctx || !md5_ctx || !sha256_ctx ) return;

EVP_DigestInit(sha1_ctx , EVP_sha1());
EVP_DigestInit(md5_ctx , EVP_md5());
EVP_DigestInit(sha256_ctx, EVP_sha256());

hashFlag[ALGO_SHA1] = (((hashType>>(ALGO_SHA1-1))&1) && (hashType & ALGO_MULTIPLE)) || (hashType == ALGO_SHA1);
hashFlag[ALGO_MD5] = (((hashType>>(ALGO_MD5-1))&1) && (hashType & ALGO_MULTIPLE)) || (hashType == ALGO_MD5);
hashFlag[ALGO_SHA256] = (((hashType>>(ALGO_SHA256-1))&1) && (hashType & ALGO_MULTIPLE)) || (hashType == ALGO_SHA256);

filePtr = fopen(imagePath, "rb");
if( !filePtr ) return;

// Read and hash image
while((n = fread(tmpReadBuffer, 1, sizeof(tmpReadBuffer), filePtr))){

if( hashFlag[ALGO_SHA1] ){
if (!EVP_DigestUpdate(sha1_ctx, tmpReadBuffer, n)){
fclose(filePtr);
return;
}
if ( !(hashType & ALGO_MULTIPLE) ) continue;
}

if( hashFlag[ALGO_MD5] ){
if (!EVP_DigestUpdate(md5_ctx, tmpReadBuffer, n)){
fclose(filePtr);
return;
}
if ( !(hashType & ALGO_MULTIPLE) ) continue;
}

if( hashFlag[ALGO_SHA256] ){
if (!EVP_DigestUpdate(sha256_ctx, tmpReadBuffer, n)){
fclose(filePtr);
return;
}
if ( !(hashType & ALGO_MULTIPLE) ) continue;
}

}
fclose(filePtr);

// Retrieve digest and cleanup
EVP_DigestFinal(sha1_ctx , tmpHashBuffer[ALGO_SHA1] , &hashSize[ALGO_SHA1]);
EVP_DigestFinal(md5_ctx , tmpHashBuffer[ALGO_MD5] , &hashSize[ALGO_MD5]);
EVP_DigestFinal(sha256_ctx, tmpHashBuffer[ALGO_SHA256], &hashSize[ALGO_SHA256]);

memset(tmpStringBuffer, 0, sizeof(tmpStringBuffer));
memset(tmpHashPrefixBuffer, 0, sizeof(tmpHashPrefixBuffer));

for(unsigned int algo=0;algo<ALGO_MAX;algo++){
if( hashFlag[algo] ){

// Add seperator if multiple hashes
if( *tmpStringBuffer ){
memset(tmpStringBuffer, 0, sizeof(tmpStringBuffer));
memset(tmpHashPrefixBuffer, 0, sizeof(tmpHashPrefixBuffer));

strcat((char *)tmpHashPrefixBuffer, hashSeparator);
}

// Add hash prefix "SHA1=", "MD5=" or "SHA256="
switch(algo){
case ALGO_SHA1:
strcat((char *)tmpHashPrefixBuffer, hashPrefix[ALGO_SHA1-1]);
break;
case ALGO_MD5:
strcat((char *)tmpHashPrefixBuffer, hashPrefix[ALGO_MD5-1]);
break;
case ALGO_SHA256:
strcat((char *)tmpHashPrefixBuffer, hashPrefix[ALGO_SHA256-1]);
break;
}

// Digest to hex
for(unsigned int i=0;i<hashSize[algo];i++){
snprintf((char *)tmpStringBuffer+i*2, sizeof(tmpStringBuffer), "%02x", tmpHashBuffer[algo][i]);
}

if(stringBufferSize > strnlen((char *)tmpHashPrefixBuffer, sizeof(tmpHashPrefixBuffer)) + strnlen((char *)tmpStringBuffer, sizeof(tmpStringBuffer))){
strcat(stringBuffer, (char *)tmpHashPrefixBuffer);
strcat(stringBuffer, (char *)tmpStringBuffer);
}

if( !(hashType & ALGO_MULTIPLE) ) return;
}
}
}

//--------------------------------------------------------------------
//
// GetTid
Expand Down
1 change: 1 addition & 0 deletions linuxHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ unsigned int LargeTimeMilliseconds( CONST PLARGE_INTEGER timestamp );
unsigned int LargeTimeNanoseconds( CONST PLARGE_INTEGER timestamp );
VOID LinuxFileTimeToLargeInteger( PLARGE_INTEGER timestamp, const my_statx_timestamp *filetime );
VOID LargeIntegerToSystemTimeString( char *s, size_t sLen, CONST PLARGE_INTEGER timestamp );
void LinuxGetFileHash(uint32_t hashType, PTCHAR imagePath, char *stringBuffer, size_t stringBufferSize);
pid_t GetTid();

#ifdef __cplusplus
Expand Down
2 changes: 1 addition & 1 deletion package/DEBIAN.in/control.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ Version: @PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@
Architecture: amd64
Maintainer: Sysinternals <syssite@microsoft.com>
Description: A system monitor based on eBPF, ported from Windows, that outputs events to Syslog
Depends: libc6 (>= 2.14), libgcc1 (>= 1:3.0), libstdc++6 (>= 5), libxml2 (>= 2.7.4), sysinternalsebpf (>= 1.2.0)
Depends: libc6 (>= 2.14), libgcc1 (>= 1:3.0), libstdc++6 (>= 5), libxml2 (>= 2.7.4), libssl-dev, sysinternalsebpf (>= 1.2.0)
Installed-Size: 58934

141 changes: 141 additions & 0 deletions sysmonforlinux.c
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,141 @@ void processProcessAccess(CONST PSYSMON_EVENT_HEADER eventHdr)
DispatchEvent(newEventHdr);
}

//--------------------------------------------------------------------
//
// processProcessCreate
//
// Handles process create events
//
//--------------------------------------------------------------------
void processProcessCreate(CONST PSYSMON_EVENT_HEADER eventHdr)
{
if(eventHdr == NULL) {
fprintf(stderr, "processProcessCreate invalid params\n");
return;
}

char newData[65536];

PSYSMON_EVENT_HEADER newEventHdr = (PSYSMON_EVENT_HEADER)newData;
newEventHdr->m_FieldFiltered = 0;
newEventHdr->m_PreFiltered = 0;
newEventHdr->m_SequenceNumber = 0;
newEventHdr->m_SessionId = 0;

PSYSMON_PROCESS_CREATE event = (PSYSMON_PROCESS_CREATE)&(eventHdr->m_EventBody);

newEventHdr->m_EventType = ProcessCreate;
PSYSMON_PROCESS_CREATE newEvent = &newEventHdr->m_EventBody.m_ProcessCreateEvent;
newEvent->m_ProcessId = event->m_ProcessId;
newEvent->m_ProcessObject = event->m_ProcessObject;
newEvent->m_ParentProcessObject = event->m_ParentProcessObject;
newEvent->m_ParentProcessId = event->m_ParentProcessId;
newEvent->m_AuditUserId = event->m_AuditUserId;
newEvent->m_SessionId = event->m_SessionId;
newEvent->m_AuthenticationId.LowPart = event->m_AuthenticationId.LowPart;
newEvent->m_AuthenticationId.HighPart = event->m_AuthenticationId.HighPart;
newEvent->m_ProcessKey = event->m_ProcessKey;
newEvent->m_CreateTime.QuadPart = event->m_CreateTime.QuadPart;

if(OPT_SET( HashAlgorithms )){
unsigned int *hashTypePtr = OPT_VALUE( HashAlgorithms );
newEvent->m_HashType = *hashTypePtr;
}
else{
newEvent->m_HashType = 0;
}

memset(newEvent->m_Extensions, 0, sizeof(newEvent->m_Extensions));
const char *ptr = (char *)(event + 1);
char *newPtr = (char *)(newEvent + 1);

memcpy(newPtr, ptr, event->m_Extensions[PC_Sid]);
newEvent->m_Extensions[PC_Sid] = event->m_Extensions[PC_Sid];
ptr += event->m_Extensions[PC_Sid];
newPtr += event->m_Extensions[PC_Sid];

memcpy(newPtr, ptr, event->m_Extensions[PC_ImagePath]);
newEvent->m_Extensions[PC_ImagePath] = event->m_Extensions[PC_ImagePath];
ptr += event->m_Extensions[PC_ImagePath];
newPtr += event->m_Extensions[PC_ImagePath];

memcpy(newPtr, ptr, event->m_Extensions[PC_CommandLine]);
newEvent->m_Extensions[PC_CommandLine] = event->m_Extensions[PC_CommandLine];
ptr += event->m_Extensions[PC_CommandLine];
newPtr += event->m_Extensions[PC_CommandLine];

memcpy(newPtr, ptr, event->m_Extensions[PC_CurrentDirectory]);
newEvent->m_Extensions[PC_CurrentDirectory] = event->m_Extensions[PC_CurrentDirectory];
newPtr += event->m_Extensions[PC_CurrentDirectory];

newEventHdr->m_EventSize = (uint32_t)((void *)newPtr - (void *)newEventHdr);

DispatchEvent(newEventHdr);
}

//--------------------------------------------------------------------
//
// processFileDelete
//
// Handles file delete events
//
//--------------------------------------------------------------------
void processFileDelete(CONST PSYSMON_EVENT_HEADER eventHdr)
{
if(eventHdr == NULL) {
fprintf(stderr, "processFileDelete invalid params\n");
return;
}

char newData[65536];

PSYSMON_EVENT_HEADER newEventHdr = (PSYSMON_EVENT_HEADER)newData;
newEventHdr->m_FieldFiltered = 0;
newEventHdr->m_PreFiltered = 0;
newEventHdr->m_SequenceNumber = 0;
newEventHdr->m_SessionId = 0;

PSYSMON_FILE_DELETE event = (PSYSMON_FILE_DELETE)&(eventHdr->m_EventBody);

newEventHdr->m_EventType = FileDelete;
PSYSMON_FILE_DELETE newEvent = &newEventHdr->m_EventBody.m_FileDeleteEvent;
newEvent->m_ProcessId = event->m_ProcessId;
newEvent->m_DeleteTime.QuadPart = event->m_DeleteTime.QuadPart;
newEvent->m_IsExecutable = event->m_IsExecutable;
newEvent->m_Archived[0] = event->m_Archived[0];
newEvent->m_TrackerId = event->m_TrackerId;

newEvent->m_HashType = 0xFF; // 0xFF will not be interpreted as any ALGO_*. Therefore, Hashes field will always be blank.

memset(newEvent->m_Extensions, 0, sizeof(newEvent->m_Extensions));
const char *ptr = (char *)(event + 1);
char *newPtr = (char *)(newEvent + 1);

memcpy(newPtr, ptr, event->m_Extensions[FD_Sid]);
newEvent->m_Extensions[FD_Sid] = event->m_Extensions[FD_Sid];
ptr += event->m_Extensions[FD_Sid];
newPtr += event->m_Extensions[FD_Sid];

memcpy(newPtr, ptr, event->m_Extensions[FD_FileName]);
newEvent->m_Extensions[FD_FileName] = event->m_Extensions[FD_FileName];
ptr += event->m_Extensions[FD_FileName];
newPtr += event->m_Extensions[FD_FileName];

memcpy(newPtr, ptr, event->m_Extensions[FD_ImagePath]);
newEvent->m_Extensions[FD_ImagePath] = event->m_Extensions[FD_ImagePath];
ptr += event->m_Extensions[FD_ImagePath];
newPtr += event->m_Extensions[FD_ImagePath];

memcpy(newPtr, ptr, event->m_Extensions[FD_Hash]);
newEvent->m_Extensions[FD_Hash] = event->m_Extensions[FD_Hash];
newPtr += event->m_Extensions[FD_Hash];

newEventHdr->m_EventSize = (uint32_t)((void *)newPtr - (void *)newEventHdr);

DispatchEvent(newEventHdr);
}

//--------------------------------------------------------------------
//
// handleEvent
Expand All @@ -565,12 +700,18 @@ static void handleEvent(void *ctx, int cpu, void *data, uint32_t size)
case LinuxFileOpen:
processFileOpen(eventHdr);
break;
case FileDelete:
processFileDelete(eventHdr);
break;
case LinuxNetworkEvent:
processNetworkEvent(eventHdr);
break;
case ProcessAccess:
processProcessAccess(eventHdr);
break;
case ProcessCreate:
processProcessCreate(eventHdr);
break;
case ProcessTerminate:
{
PSYSMON_PROCESS_TERMINATE event = (PSYSMON_PROCESS_TERMINATE)&eventHdr->m_EventBody.m_ProcessTerminateEvent;
Expand Down