Depois de dominar as ferramentas mencionadas acima, você pode começar a desenvolver plug-ins. Primeiro, você deve pensar nas necessidades que o plug-in precisa implementar e encontrar as visualizações e controladores relevantes. A primeira etapa é usar class-dump para analisar o APP com shell. Class-dump pode analisar todos os nomes de classes e declarações de métodos com base na tabela de símbolos no arquivo Mach-O.
~ » ./class-dump -H -o /header/path WeChat
Depois de exportar os arquivos de cabeçalho, coloque-os em um projeto XCode para facilitar pesquisas subsequentes.
Depois de obter o arquivo de cabeçalho, você precisa bloqueá-lo na classe do controlador da interface relevante. Use OpenSSH para conectar-se ao iPhone desbloqueado com Mac, use Cycript para injetar o processo, chame o método [[UIApp keyWindow] recursiveDescription]
para obter a hierarquia da visualização atual, obtenha o endereço de uma visualização e chame repetidamente o [#0x2b2b2b00 nextResponder]
, você finalmente obterá a classe do controlador.
Neste ponto, geralmente existem dois pontos de entrada para continuar a análise reversa.
Se quisermos que a operação Hook seja acionada pelo evento touch de uma visualização na interface, geralmente há duas maneiras de localizar a ação:
[button allTargets]
para obter o endereço dos alvos, [button actionsForTarget: Targets forControlEvent: [button allControlEvents]]
para obter o método de ação correspondente.A fonte de dados do controlador pode ser usada durante a gravação do plug-in. A fonte de dados pode ser obtida analisando a lista de variáveis de membro da classe do controlador ou analisando o método proxy da fonte de dados de algumas visualizações (como TableView). Com uma fonte de dados, informações mais ricas sobre o controlador podem ser obtidas.
Um plug-in simples pode ser capaz de bloquear o alvo e conectá-lo usando apenas Cycript, mas em muitos casos a função alvo tem vários parâmetros e a lógica é relativamente complexa. Se você quiser saber os detalhes de sua implementação interna, você deve. use o artefato de análise estática IDA ---- Ele pode descompilar o código escrito em Objective-C em código assembly, e os detalhes de implementação da função ficarão claros à primeira vista. Ao analisar os resultados da desmontagem, devido às características de passagem de mensagens do Objective-C, o envio geral de mensagens na verdade chama a função objc_msgSend
. Basta lembrar o significado de cada parâmetro de objc_msgSend
e a convenção de chamada da arquitetura ARM, e você poderá restaurar com êxito a chamada de função a partir do código assembly. A convenção de chamada é que os primeiros quatro parâmetros da função são passados usando os registradores gerais R0-R3, mais parâmetros são colocados na pilha e o valor de retorno é armazenado no registrador R0. Então [aObject aMessage: arg1];
corresponde a objc_msgSend(aObject, aMessage, arg1);
R0 armazena o endereço do receptor da mensagem, R1 armazena o seletor e R2 armazena o primeiro endereço do parâmetro. O formato da mensagem para mais parâmetros é o seguinte:
objc_msgSend(R0, R1, R2, R3, *SP, *(SP + sizeOfLastArg), …)
Através do formato acima, a lógica de uma determinada função pode ser analisada passo a passo. Se isso for complementado pela depuração dinâmica de etapa única do LLDB e pela depuração de rastreamento de ponto de interrupção no endereço de uma determinada instrução assembly, isso ajudará a entender os detalhes da implementação da função.
Existem muitas soluções para a função alvo do Hook, mas em princípio todas são baseadas nas características dinâmicas do Objective-C e usam o Method Swizzling para substituir a implementação original. Desta vez apresentaremos detalhadamente o uso da biblioteca CaptainHook. Esta biblioteca é implementada com base em MSHookMessageEx()
no Cydia Substrate. A declaração desta função é:
void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old);
Após a instalação do iOSOpenDev, você pode usar o modelo CaptainHook Tweak para criar um projeto. A biblioteca CaptainHook introduz uma nova sintaxe para escrever funções Hook. Primeiro, carregue a classe onde a função a ser Hooked está localizada em CHConstructor()
, como CHLoadLateClass(UIView)
. Em seguida, registre a função CHHook(argNumber, className, arg1, arg2)
para ser Hooked. A definição macro de CHConstructor
é a seguinte:
#define CHConcat(a, b) CHConcat_(a, b)
#define CHConstructor static __attribute__((constructor)) void CHConcat(CHConstructor, __LINE__)()
O conteúdo após __attribute__((constructor))
tem garantia de execução quando o dylib é carregado, geralmente quando o programa é iniciado. Da mesma forma, outros símbolos são introduzidos através de definições macro.
Deixe-me apresentar como usar CaptainHook para declarar a função Hook e implementá-la diretamente no código.
CHDeclareClass ( BXViewController );
CHOptimizedMethod ( 0 , self , void , BXViewController , viewDidLoad ) {
CHSuper ( 0 , BXViewController , viewDidLoad );
/* HERE TO WRITE YOUR CODE */
}
CHDeclareMethod0 ( void , BXViewController , addFriends ) {
/* HERE TO WRITE YOUR CODE */
}
Após a conclusão da escrita, conecte-se ao iPhone em questão para compilação para garantir que uma biblioteca dinâmica correspondente à arquitetura seja gerada.
Use a ferramenta yololib para injetar o arquivo dylib compilado na lista Load Commands do arquivo executável Mach-O.
~ » ./yololib [binary] [dylib file]
A estrutura dos arquivos Mach-O consiste principalmente em três partes. A parte front-end é a estrutura do cabeçalho, que salva o tipo de plataforma do Mach-O, tipo de arquivo, número de LoadCommands e outras informações seguidas pelo cabeçalho é a parte dos comandos de carregamento. sua localização no arquivo virtual pode ser determinada. A ferramenta yololib altera as informações na seção Carregar comandos para carregar o dylib. O processo de implementação específico é o seguinte:
Como as informações dos comandos de carregamento mudam, tanto ncmds quanto sizeofcmds na estrutura do cabeçalho correspondente serão alterados, portanto, o cabeçalho deve ser modificado primeiro:
// 取出 Header
fseek(newFile, top, SEEK_SET);
struct mach_header mach;
fread(&mach, sizeof(struct mach_header), 1, newFile);
NSData* data = [DYLIB_PATH dataUsingEncoding:NSUTF8StringEncoding];
// 计算 dylib 的大小
uint32_t dylib_size = (uint32_t)[data length] + sizeof(struct dylib_command);
dylib_size += sizeof(long) - (dylib_size % sizeof(long));
// 修改 cmds 和 sizeofcmds
mach.ncmds += 1;
uint32_t sizeofcmds = mach.sizeofcmds;
mach.sizeofcmds += dylib_size;
// 写回修改后的 Header
fseek(newFile, -sizeof(struct mach_header), SEEK_CUR);
fwrite(&mach, sizeof(struct mach_header), 1, newFile);
Em seguida, altere a seção Load Commands e adicione informações de carregamento do dylib:
fseek(newFile, sizeofcmds, SEEK_CUR);
// 创建一个 dylib 类型的 command
struct dylib_command dyld;
fread(&dyld, sizeof(struct dylib_command), 1, newFile);
// 修改 dyld 结构体数据
dyld.cmd = LC_LOAD_DYLIB;
dyld.cmdsize = dylib_size;
dyld.dylib.compatibility_version = DYLIB_COMPATIBILITY_VERSION;
dyld.dylib.current_version = DYLIB_CURRENT_VER;
dyld.dylib.timestamp = 2;
dyld.dylib.name.offset = sizeof(struct dylib_command);
// 写回修改
fseek(newFile, -sizeof(struct dylib_command), SEEK_CUR);
fwrite(&dyld, sizeof(struct dylib_command), 1, newFile);
Os últimos dados gravados em dylib.
fwrite([data bytes], [data length], 1, newFile);
Use o comando codesign
para assinar novamente a biblioteca dinâmica gerada e todos os arquivos executáveis no APP (incluindo a extensão APP na pasta Plugin Use o comando xcrun -sdk iphoneos PackageApplication -v
para empacotar a biblioteca dinâmica e todos os arquivos juntos). Você entenderá todo o processo. Se houver um certificado corporativo, o aplicativo assinado e empacotado poderá ser instalado em um iPhone sem jailbreak que confie no certificado. Uma estrela do remo!