yololib實作了使用終端将dylib注入到ipa,當我們編寫Mac端工具的時候也可以調用NSTask執行終端,然而為了簡單化,這裡使用了純代碼的方式.
MachOView打開xxx.app/exec檔案的時候可以看到所有庫的加載路徑,注入dylib檔案其實也就是在exec檔案中添加一條dylib加載路徑
otool指令行同樣可以檢視到xxx.app/exec檔案庫的加載路徑
otool -L xxx.app/exec
+(void)injectMachoPath:(NSString *)machoPath dylibPath:(NSString *)dylibPath;
#include
#include
.m
+(void)injectMachoPath:(NSString *)machoPath dylibPath:(NSString *)dylibPath{
int fd = open(machoPath.UTF8String, O_RDWR, 0777);
if (fd < 0)
{
NSLog(@"Inject failed: failed to open %@",machoPath);
return;
}
else
{
uint32_t magic;
read(fd, &magic, sizeof(magic));
if (magic == MH_MAGIC || magic == MH_MAGIC_64)
{
lseek(fd, 0, SEEK_SET);
[self injectArchitecture:fd dylibPath:dylibPath exePath:machoPath];
}
else if (magic == FAT_MAGIC || magic == FAT_CIGAM)
{
struct fat_header header;
lseek(fd, 0, SEEK_SET);
read(fd, &header, sizeof(fat_header));
int nArch = header.nfat_arch;
if (magic == FAT_CIGAM) nArch = [self bigEndianToSmallEndian:header.nfat_arch];
struct fat_arch arch;
NSMutableArray *offsetArray = [NSMutableArray array];
for (int i = 0; i < nArch; i++)
{
memset(&arch, 0, sizeof(fat_arch));
read(fd, &arch, sizeof(fat_arch));
int offset = arch.offset;
if (magic == FAT_CIGAM) offset = [self bigEndianToSmallEndian:arch.offset];
[offsetArray addObject:[NSNumber numberWithUnsignedInt:offset]];
}
for (NSNumber *offsetNum in offsetArray)
{
lseek(fd, [offsetNum unsignedIntValue], SEEK_SET);
[self injectArchitecture:fd dylibPath:dylibPath exePath:machoPath];
}
}
close(fd);
}
}
+ (void)injectArchitecture:(int)fd dylibPath:(NSString *)dylibPath exePath:(NSString *)exePathForInfoOnly
{
off_t archPoint = lseek(fd, 0, SEEK_CUR);
struct mach_header header;
read(fd, &header, sizeof(header));
if (header.magic != MH_MAGIC && header.magic != MH_MAGIC_64)
{
NSLog(@"Inject failed: Invalid executable %@",exePathForInfoOnly);
}
else
{
if (header.magic == MH_MAGIC_64)
{
int delta = sizeof(mach_header_64) - sizeof(mach_header);
lseek(fd, delta, SEEK_CUR);
}
char *buffer = (char *)malloc(header.sizeofcmds + 2048);
read(fd, buffer, header.sizeofcmds);
if ([[NSFileManager defaultManager] fileExistsAtPath:dylibPath])
{
dylibPath = [@"@executable_path" stringByAppendingPathComponent:[dylibPath lastPathComponent]];
}
const char *dylib = dylibPath.UTF8String;
struct dylib_command *p = (struct dylib_command *)buffer;
struct dylib_command *last = NULL;
for (uint32_t i = 0; i < header.ncmds; i++, p = (struct dylib_command *)((char *)p + p->cmdsize))
{
if (p->cmd == LC_LOAD_DYLIB || p->cmd == LC_LOAD_WEAK_DYLIB)
{
char *name = (char *)p + p->dylib.name.offset;
if (strcmp(dylib, name) == 0)
{
NSLog(@"Already Injected: %@ with %s", exePathForInfoOnly, dylib);
close(fd);
return;
}
last = p;
}
}
if ((char *)p - buffer != header.sizeofcmds)
{
NSLog(@"LC payload not mismatch: %@", exePathForInfoOnly);
}
if (last)
{
struct dylib_command *inject = (struct dylib_command *)((char *)last + last->cmdsize);
char *movefrom = (char *)inject;
uint32_t cmdsize = sizeof(*inject) + (uint32_t)strlen(dylib) + 1;
cmdsize = (cmdsize + 0x10) & 0xFFFFFFF0;
char *moveout = (char *)inject + cmdsize;
for (int i = (int)(header.sizeofcmds - (movefrom - buffer) - 1); i >= 0; i--)
{
moveout[i] = movefrom[i];
}
memset(inject, 0, cmdsize);
inject->cmd = LC_LOAD_DYLIB;
inject->cmdsize = cmdsize;
inject->dylib.name.offset = sizeof(dylib_command);
inject->dylib.timestamp = 2;
inject->dylib.current_version = 0x00010000;
inject->dylib.compatibility_version = 0x00010000;
strcpy((char *)inject + inject->dylib.name.offset, dylib);
header.ncmds++;
header.sizeofcmds += inject->cmdsize;
lseek(fd, archPoint, SEEK_SET);
write(fd, &header, sizeof(header));
lseek(fd, archPoint + ((header.magic == MH_MAGIC_64) ? sizeof(mach_header_64) : sizeof(mach_header)), SEEK_SET);
write(fd, buffer, header.sizeofcmds);
}
else
{
NSLog(@"nject failed: No valid LC_LOAD_DYLIB %@",exePathForInfoOnly);
}
free(buffer);
}
}
+ (uint32_t)bigEndianToSmallEndian:(uint32_t)bigEndian
{
uint32_t smallEndian = 0;
unsigned char *small = (unsigned char *)&smallEndian;
unsigned char *big = (unsigned char *)&bigEndian;
for (int i=0; i<4; i++)
{
small[i] = big[3-i];
}
return smallEndian;
}
使用
[NEInjector injectMachoPath:@"~.xxx/Payload/xxx.app/machoFileName" dylibPath:@"~.xxx/Payload/xxx.app/xxxx.dylib"];
後續
當我們從xxxx助手下載下傳的無限金币遊戲ipa的時候,必定會存在以這種方式存在的啟動廣告.
使用install_name_tool 終端指令行進行替換,例如ipa原有dylib檔案名稱為xx.dylib,需要注入的為NE.dylib
$install_name_tool -change @executable_path/xx.dylib @executable_path/NE.dylib ~/xxxx.app/exec
執行成功後使用文中開頭的方法把NE.dylib注入
當然,注入了dylib,ipa需要進行重簽名