Swiftgram/tools/main.cpp
2023-08-25 13:01:43 +04:00

321 lines
9.8 KiB
C++

#include <mach-o/arch.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdio.h>
#include <cstdio>
#include <vector>
#include <string>
#include <array>
static uint32_t funcSwap32(uint32_t input) {
return OSSwapBigToHostInt32(input);
}
static uint32_t funcNoSwap32(uint32_t input) {
return OSSwapLittleToHostInt32(input);
}
static bool cleanArch(std::vector<uint8_t> &archData, bool &isEncrypted) {
uint32_t (*swap32)(uint32_t) = funcNoSwap32;
uint32_t offset = 0;
const struct mach_header* header = (struct mach_header*)(archData.data() + offset);
switch (header->magic) {
case MH_CIGAM:
swap32 = funcSwap32;
case MH_MAGIC:
offset += sizeof(struct mach_header);
break;
case MH_CIGAM_64:
swap32 = funcSwap32;
case MH_MAGIC_64:
offset += sizeof(struct mach_header_64);
break;
default:
return false;
}
uint32_t commandCount = swap32(header->ncmds);
for (uint32_t i = 0; i < commandCount; i++) {
const struct load_command* loadCommand = (const struct load_command*)(archData.data() + offset);
uint32_t commandSize = swap32(loadCommand->cmdsize);
uint32_t commandType = swap32(loadCommand->cmd);
if (commandType == LC_CODE_SIGNATURE) {
const struct linkedit_data_command *dataCommand = (const struct linkedit_data_command *)(archData.data() + offset);
uint32_t dataOffset = swap32(dataCommand->dataoff);
uint32_t dataSize = swap32(dataCommand->datasize);
// account for different signature size
memset(archData.data() + offset + offsetof(linkedit_data_command, datasize), 0, sizeof(uint32_t));
// remove signature
archData.erase(archData.begin() + dataOffset, archData.begin() + dataOffset + dataSize);
} else if (commandType == LC_SEGMENT_64) {
const struct segment_command_64 *segmentCommand = (const struct segment_command_64 *)(archData.data() + offset);
std::string segmentName = std::string(segmentCommand->segname);
if (segmentName == "__LINKEDIT") {
// account for different signature size
memset(archData.data() + offset + offsetof(segment_command_64, vmsize), 0, sizeof(uint32_t));
// account for different file size because of signatures
memset(archData.data() + offset + offsetof(segment_command_64, filesize), 0, sizeof(uint32_t));
}
} else if (commandType == LC_ID_DYLIB) {
// account for dylib timestamp
memset(archData.data() + offset + offsetof(dylib_command, dylib) + offsetof(struct dylib, timestamp), 0, sizeof(uint32_t));
} else if (commandType == LC_UUID) {
// account for dylib uuid
memset(archData.data() + offset + offsetof(uuid_command, uuid), 0, 16);
} else if (commandType == LC_ENCRYPTION_INFO_64) {
const struct encryption_info_command_64 *encryptionInfoCommand = (const struct encryption_info_command_64 *)(archData.data() + offset);
if (encryptionInfoCommand->cryptid != 0) {
isEncrypted = true;
}
// The App Store has begun to change offsets in LC_ENCRYPTION_INFO
memset(archData.data() + offset + offsetof(encryption_info_command_64, cryptoff), 0, sizeof(uint32_t));
memset(archData.data() + offset + offsetof(encryption_info_command_64, cryptsize), 0, sizeof(uint32_t));
}
offset += commandSize;
}
return true;
}
static std::vector<uint8_t> parseFat(std::vector<uint8_t> const &fileData) {
size_t offset = 0;
const struct fat_header *fatHeader = (const struct fat_header *)fileData.data();
offset += sizeof(*fatHeader);
size_t initialOffset = offset;
uint32_t archCount = OSSwapBigToHostInt32(fatHeader->nfat_arch);
for (uint32_t i = 0; i < archCount; i++) {
const struct fat_arch *arch = (const struct fat_arch *)(fileData.data() + offset);
offset += sizeof(*arch);
uint32_t archOffset = OSSwapBigToHostInt32(arch->offset);
uint32_t archSize = OSSwapBigToHostInt32(arch->size);
cpu_type_t cputype = OSSwapBigToHostInt32(arch->cputype);
if (cputype == CPU_TYPE_ARM64) {
std::vector<uint8_t> archData;
archData.resize(archSize);
memcpy(archData.data(), fileData.data() + archOffset, archSize);
return archData;
}
}
offset = initialOffset;
for (uint32_t i = 0; i < archCount; i++) {
const struct fat_arch *arch = (const struct fat_arch *)(fileData.data() + offset);
offset += sizeof(*arch);
uint32_t archOffset = OSSwapBigToHostInt32(arch->offset);
uint32_t archSize = OSSwapBigToHostInt32(arch->size);
cpu_type_t cputype = OSSwapBigToHostInt32(arch->cputype);
cpu_type_t cpusubtype = OSSwapBigToHostInt32(arch->cpusubtype);
if (cputype == CPU_TYPE_ARM && cpusubtype == CPU_SUBTYPE_ARM_V7K) {
std::vector<uint8_t> archData;
archData.resize(archSize);
memcpy(archData.data(), fileData.data() + archOffset, archSize);
return archData;
}
}
return std::vector<uint8_t>();
}
static std::vector<uint8_t> parseMachO(std::vector<uint8_t> const &fileData) {
const uint32_t *magic = (const uint32_t *)fileData.data();
if (*magic == FAT_CIGAM || *magic == FAT_MAGIC) {
return parseFat(fileData);
} else {
return fileData;
}
}
static std::vector<uint8_t> readFile(std::string const &file) {
int fd = open(file.c_str(), O_RDONLY);
if (fd == -1) {
return std::vector<uint8_t>();
}
struct stat st;
fstat(fd, &st);
std::vector<uint8_t> fileData;
fileData.resize((size_t)st.st_size);
read(fd, fileData.data(), (size_t)st.st_size);
close(fd);
return fileData;
}
static void writeDataToFile(std::vector<uint8_t> const &data, std::string const &path) {
int fd = open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fd == -1) {
return;
}
write(fd, data.data(), data.size());
close(fd);
}
static std::vector<uint8_t> stripSwiftSymbols(std::string const &file) {
std::string command;
command += "xcrun bitcode_strip \"";
command += file;
command += "\" -r -o \"";
command += file;
command += ".stripped\"";
uint8_t buffer[128];
FILE *pipe = popen(command.c_str(), "r");
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (true) {
size_t readBytes = fread(buffer, 1, 128, pipe);
if (readBytes <= 0) {
break;
}
}
pclose(pipe);
command = "";
command += "codesign --remove-signature \"";
command += file;
command += ".stripped\"";
pipe = popen(command.c_str(), "r");
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (true) {
size_t readBytes = fread(buffer, 1, 128, pipe);
if (readBytes <= 0) {
break;
}
}
pclose(pipe);
command = "";
command += "xcrun strip -ST -o /dev/stdout \"";
command += file;
command += ".stripped\" 2> /dev/null";
std::vector<uint8_t> result;
pipe = popen(command.c_str(), "r");
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (true) {
size_t readBytes = fread(buffer, 1, 128, pipe);
if (readBytes <= 0) {
break;
}
result.insert(result.end(), buffer, buffer + readBytes);
}
pclose(pipe);
command = "";
command += "rm \"";
command += file;
command += ".stripped\"";
pipe = popen(command.c_str(), "r");
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (true) {
size_t readBytes = fread(buffer, 1, 128, pipe);
if (readBytes <= 0) {
break;
}
}
pclose(pipe);
return result;
}
static bool endsWith(std::string const &mainStr, std::string const &toMatch) {
if(mainStr.size() >= toMatch.size() && mainStr.compare(mainStr.size() - toMatch.size(), toMatch.size(), toMatch) == 0) {
return true;
} else {
return false;
}
}
int main(int argc, const char *argv[]) {
if (argc != 3) {
printf("Usage: machofilediff file1 file2\n");
return 1;
}
std::string file1 = argv[1];
std::string file2 = argv[2];
std::vector<uint8_t> fileData1;
if (endsWith(file1, ".dylib")) {
fileData1 = stripSwiftSymbols(file1);
} else {
fileData1 = readFile(file1);
}
std::vector<uint8_t> fileData2;
if (endsWith(file2, ".dylib")) {
fileData2 = stripSwiftSymbols(file2);
} else {
fileData2 = readFile(file2);
}
std::vector<uint8_t> arch1 = parseMachO(fileData1);
if (arch1.size() == 0) {
printf("Couldn't parse %s\n", file1.c_str());
return 1;
}
std::vector<uint8_t> arch2 = parseMachO(fileData2);
if (arch2.size() == 0) {
printf("Couldn't parse %s\n", file2.c_str());
return 1;
}
bool arch1Encrypted = false;
bool arch2Encrypted = false;
cleanArch(arch1, arch1Encrypted);
cleanArch(arch2, arch2Encrypted);
if (arch1 == arch2) {
printf("Equal\n");
return 0;
} else {
if (arch1Encrypted || arch2Encrypted) {
printf("Encrypted\n");
} else {
printf("Not Equal\n");
}
return 1;
}
return 0;
}