From 5dfd2676c2ece43d3d12e557bc651f28773593ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 2 Apr 2025 10:27:25 +0800 Subject: [PATCH] Binary: add comments --- src/util/binary.h | 13 +++++++++ src/util/binary_apple.c | 57 +++++++++++++++++++++++++++++++++++++++ src/util/binary_linux.c | 26 ++++++++++++++++++ src/util/binary_windows.c | 12 +++++++++ 4 files changed, 108 insertions(+) diff --git a/src/util/binary.h b/src/util/binary.h index 9ef0ac7e1..2d2d7849b 100644 --- a/src/util/binary.h +++ b/src/util/binary.h @@ -2,4 +2,17 @@ #include "fastfetch.h" +/** + * Extracts string literals from a binary file + * + * @param file Path to the binary file to extract strings from + * @param cb Callback function that will be called for each string found + * Return false from callback to stop extraction + * @param userdata User-provided data passed to the callback function + * @param minLength Minimum length of strings to extract + * + * @return NULL on success, error message on failure. + * @note This function won't return an error if no strings are found. + * Always check if strings are correctly extracted after this function all. + */ const char* ffBinaryExtractStrings(const char* file, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength); diff --git a/src/util/binary_apple.c b/src/util/binary_apple.c index 6efeb5d5a..9ee0ace96 100644 --- a/src/util/binary_apple.c +++ b/src/util/binary_apple.c @@ -14,12 +14,28 @@ // Ref: https://github.com/AlexDenisov/segment_dumper/blob/master/main.c +/** + * Helper function to read data from a file at a specific offset + */ static inline bool readData(FILE *objFile, void *buf, size_t size, off_t offset) { fseek(objFile, offset, SEEK_SET); return fread(buf, 1, size, objFile) == size; } +/** + * Handles a Mach-O section by extracting strings from the __cstring section + * + * @param objFile File handle to the Mach-O object file + * @param name Section name to check + * @param offset Offset of the section in the file + * @param size Size of the section + * @param cb Callback function to process strings + * @param userdata User data for the callback + * @param minLength Minimum string length to extract + * + * @return true to continue processing, false to stop + */ static bool handleMachSection(FILE *objFile, const char *name, off_t offset, size_t size, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength) { if (!ffStrEquals(name, "__cstring")) return true; @@ -43,6 +59,22 @@ static bool handleMachSection(FILE *objFile, const char *name, off_t offset, siz return true; } +/** + * Processes a Mach-O header (32-bit or 64-bit) + * + * This function parses the load commands in a Mach-O header, looking for + * LC_SEGMENT or LC_SEGMENT_64 commands that contain the __TEXT segment. + * It then processes the sections within that segment to extract strings. + * + * @param objFile File handle to the Mach-O object file + * @param offset Offset of the Mach header in the file + * @param is_64 Whether this is a 64-bit Mach-O header + * @param cb Callback function to process strings + * @param userdata User data for the callback + * @param minLength Minimum string length to extract + * + * @return NULL on success, error message on failure + */ static const char* dumpMachHeader(FILE *objFile, off_t offset, bool is_64, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength) { uint32_t ncmds; @@ -117,6 +149,20 @@ static const char* dumpMachHeader(FILE *objFile, off_t offset, bool is_64, bool return NULL; } +/** + * Processes a Fat binary header (Universal binary) + * + * This function handles the fat header of a universal binary, which can contain + * multiple Mach-O binaries for different architectures. It extracts and processes + * each embedded Mach-O file. + * + * @param objFile File handle to the universal binary + * @param cb Callback function to process strings + * @param userdata User data for the callback + * @param minLength Minimum string length to extract + * + * @return NULL on success, error message on failure + */ static const char* dumpFatHeader(FILE *objFile, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength) { struct fat_header header; @@ -165,21 +211,32 @@ static const char* dumpFatHeader(FILE *objFile, bool (*cb)(const char *str, uint return "Unsupported fat header"; } +/** + * Extracts string literals from a Mach-O (Apple) binary file + * + * This function supports both single-architecture Mach-O files and + * universal binaries (fat binaries) containing multiple architectures. + * It locates the __cstring section in the __TEXT segment which contains + * the string literals used in the program. + */ const char *ffBinaryExtractStrings(const char *machoFile, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength) { FF_AUTO_CLOSE_FILE FILE *objFile = fopen(machoFile, "rb"); if (objFile == NULL) return "File could not be opened"; + // Read the magic number to determine the type of binary uint32_t magic; if (!readData(objFile, &magic, sizeof(magic), 0)) return "read magic number failed"; + // Check for supported formats // MH_CIGAM and MH_CIGAM_64 seem to be no longer used, as `swap_mach_header` is marked as deprecated. // However FAT_CIGAM and FAT_CIGAM_64 are still used (/usr/bin/vim). if (magic != MH_MAGIC && magic != MH_MAGIC_64 && magic != FAT_CIGAM && magic != FAT_CIGAM_64 && magic != FAT_MAGIC && magic != FAT_MAGIC_64) return "Unsupported format or big endian mach-o file"; + // Process either a fat binary or a regular Mach-O binary if (magic == FAT_MAGIC || magic == FAT_MAGIC_64 || magic == FAT_CIGAM || magic == FAT_CIGAM_64) return dumpFatHeader(objFile, cb, userdata, minLength); else diff --git a/src/util/binary_linux.c b/src/util/binary_linux.c index 3b6ccca35..df89e160d 100644 --- a/src/util/binary_linux.c +++ b/src/util/binary_linux.c @@ -9,6 +9,9 @@ #include // #1254 #include +/** + * Structure to hold dynamically loaded libelf function pointers + */ struct FFElfData { FF_LIBRARY_SYMBOL(elf_version) FF_LIBRARY_SYMBOL(elf_begin) @@ -23,8 +26,19 @@ struct FFElfData { bool inited; } elfData; +/** + * Extracts string literals from an ELF (Linux/Unix) binary file + * + * This function loads the libelf library dynamically, opens the ELF file, + * locates the .rodata section (which contains string literals), and + * scans it for valid strings. Each string found is passed to the + * callback function for processing. + * + * The function supports both 32-bit and 64-bit ELF formats. + */ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) { + // Initialize libelf if not already done if (!elfData.inited) { elfData.inited = true; @@ -32,6 +46,7 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_version) if (elfData.ffelf_version(EV_CURRENT) == EV_NONE) return "elf_version() failed"; + // Load all required libelf functions FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_begin) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_getshdrstrndx) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_nextscn) @@ -47,12 +62,14 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s if (elfData.ffelf_end == NULL) return "load libelf failed"; + // Open the ELF file FF_AUTO_CLOSE_FD int fd = open(elfFile, O_RDONLY, 0); if (fd < 0) return "open() failed"; Elf* elf = elfData.ffelf_begin(fd, ELF_C_READ, NULL); if (elf == NULL) return "elf_begin() failed"; + // Get the section header string table index size_t shstrndx = 0; if (elfData.ffelf_getshdrstrndx(elf, &shstrndx) < 0) { @@ -60,9 +77,11 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s return "elf_getshdrstrndx() failed"; } + // Iterate through all sections, looking for .rodata which contains string literals Elf_Scn* scn = NULL; while ((scn = elfData.ffelf_nextscn(elf, scn)) != NULL) { + // Try 64-bit section header first, then 32-bit if that fails Elf64_Shdr* shdr64 = elfData.ffelf64_getshdr(scn); Elf32_Shdr* shdr32 = NULL; if (shdr64 == NULL) @@ -71,18 +90,22 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s if (shdr32 == NULL) continue; } + // Get the section name and check if it's .rodata const char* name = elfData.ffelf_strptr(elf, shstrndx, shdr64 ? shdr64->sh_name : shdr32->sh_name); if (name == NULL || !ffStrEquals(name, ".rodata")) continue; + // Get the section data Elf_Data* data = elfData.ffelf_getdata(scn, NULL); if (data == NULL) continue; + // Scan the section for string literals for (size_t off = 0; off < data->d_size; ++off) { const char* p = (const char*) data->d_buf + off; if (*p == '\0') continue; uint32_t len = (uint32_t) strlen(p); if (len < minLength) continue; + // Only process printable ASCII characters if (*p >= ' ' && *p <= '~') // Ignore control characters { if (!cb(p, len, userdata)) break; @@ -99,6 +122,9 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s #else +/** + * Fallback implementation when libelf is not available + */ const char* ffBinaryExtractStrings(const char* file, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) { FF_UNUSED(file, cb, userdata, minLength); diff --git a/src/util/binary_windows.c b/src/util/binary_windows.c index 7f9d842f1..e86cd5e5c 100644 --- a/src/util/binary_windows.c +++ b/src/util/binary_windows.c @@ -8,25 +8,37 @@ #include #include +/** + * Extracts string literals from a PE (Windows) executable + * + * This function maps the PE file into memory, locates the .rdata section + * (which typically contains string literals), and scans it for valid strings. + * Each string found is passed to the callback function for processing. + */ const char* ffBinaryExtractStrings(const char *peFile, bool (*cb)(const char *str, uint32_t len, void *userdata), void *userdata, uint32_t minLength) { + // Use MapAndLoad with cleanup attribute to ensure proper unloading __attribute__((__cleanup__(UnMapAndLoad))) LOADED_IMAGE loadedImage = {}; if (!MapAndLoad(peFile, NULL, &loadedImage, FALSE, TRUE)) return "File could not be loaded"; + // Iterate through all sections in the PE file for (ULONG i = 0; i < loadedImage.NumberOfSections; ++i) { PIMAGE_SECTION_HEADER section = &loadedImage.Sections[i]; + // Look for initialized data sections with the name ".rdata" which typically contains string literals if ((section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) && ffStrEquals((const char*) section->Name, ".rdata")) { uint8_t *data = (uint8_t *) loadedImage.MappedAddress + section->PointerToRawData; + // Scan the section for string literals for (size_t off = 0; off < section->SizeOfRawData; ++off) { const char* p = (const char*) data + off; if (*p == '\0') continue; uint32_t len = (uint32_t) strlen(p); if (len < minLength) continue; + // Only process printable ASCII characters if (*p >= ' ' && *p <= '~') // Ignore control characters { if (!cb(p, len, userdata)) break;