A atualização 2020.1.14 não requer instalação do jailbreak
Este artigo é um tutorial sobre como reverter o arquivo binário do WeChat para realizar o encaminhamento de vídeos curtos no Moments, desde o código assembly inicial até a instalação final da nova assinatura e outras operações, ele ensinará passo a passo como jogar com o WeChat! Depois de aprender, será fácil fazer engenharia reversa de outras funções do WeChat.
Este artigo está dividido em duas partes devido à sua extensão. A primeira parte explica o trabalho inverso, ou seja, como encontrar as funções e métodos relevantes para implementar. A segunda parte explica como reassinar a instalação em máquinas sem jailbreak e. ajustar a instalação em máquinas desbloqueadas. Processo detalhado.
A segunda parte do texto também fornece o código para o WeChat pegar automaticamente os envelopes vermelhos e modificar o número de etapas do WeChat. Eles podem ser encontrados passo a passo seguindo a rotina deste artigo e não serão repetidos aqui.
Antes de praticar, você precisa preparar um telefone desbloqueado e instalar todas as ferramentas listadas abaixo. IDA e Reveal são versões crackeadas. A versão original do IDA custa mais de 2.000 dólares. No entanto, se você não é uma empresa especializada em engenharia reversa, não existe. preciso usar a versão original. A próxima versão crackeada do Windows ficará OK, ainda não consigo encontrá-la no Mac. Você pode usar o hopper para substituir o IDA no Mac, que também é uma ferramenta de engenharia reversa muito poderosa. Sem mais delongas, vamos começar!
Nota: O arquivo binário do WeChat invertido neste artigo é a versão 6.3.28. Se for uma versão diferente do WeChat, o endereço base no arquivo binário também será diferente.
Ambiente reverso para máquina desbloqueada MacOS + iPhone5S 9.1 <br> Primeiro use dumpdecrypted para quebrar o shell do WeChat (se você não sabe como, leia este tutorial escrito por mim), obtenha um arquivo WeChat.decrypted e primeiro jogue esse arquivo no IDA para análise (um arquivo binário de cerca de 60 MB leva cerca de 40 minutos para analisar pelo IDA End), use class-dump para exportar todos os arquivos de cabeçalho
LeonLei-MBP:~ gaoshilei$ class-dump -S -s -H /Users/gaoshilei/Desktop/reverse/binary_for_class-dump/WeChat.decrypted -o /Users/gaoshilei/Desktop/reverse/binary_for_class-dump/class-Header/WeChat
Eu tenho uma sogra! Há um total de 8.000 arquivos de cabeçalho e o WeChat realmente dá muito trabalho! Acalme suas emoções, organize seus pensamentos e continue. Para obter o link de download de um vídeo curto, encontre a visualização que reproduz o vídeo e siga as dicas para encontrar o URL do vídeo curto. Use Reveal para visualizar a janela de reprodução do vídeo curto. Você pode ver que o objeto WCContentItemViewTemplateNewSigh é a janela de reprodução do vídeo curto. Suas subvisualizações incluem WCSightView, SightView e SightPlayerView. Ao salvar um vídeo como favorito, mantenha pressionado o vídeo para exibir a opção, portanto, pode haver métodos relacionados a gestos na classe WCContentItemViewTemplateNewSight Encontre pistas no arquivo de cabeçalho que acabou de ser exportado.
- (void)onLongTouch;
- (void)onLongPressedWCSight:(id)arg1;
- (void)onLongPressedWCSightFullScreenWindow:(id)arg1;
Esses métodos estão relacionados ao gesto de pressão longa. Encontre essas funções no IDA e visualize-as uma por uma. onLongPressedWCSight e onLongPressedWCSightFullScreenWindow são relativamente simples, onLongTouch é relativamente longo, e descobri que o método Favorites_Add é chamado internamente, porque quando você pressiona longamente o vídeo, uma opção que sai é Favorites, e vi essa chamada de função
ADRP X8, #selRef_sightVideoPath@PAGE
LDR X1, [X8,#selRef_sightVideoPath@PAGEOFF]
Peguei o endereço do pequeno vídeo aqui. Pode-se especular que esta função está relacionada à coleção.
(lldb) im li -o -f
[ 0] 0x000000000003c000 /var/mobile/Containers/Bundle/Application/2F1D52EC-C57E-4F95-B715-EF04351232E8/WeChat.app/WeChat(0x000000010003c000)
Você pode ver que o ASLR do WeChat é 0x3c000. Encontre os endereços base dessas três funções no IDA e defina os pontos de interrupção respectivamente.
(lldb) br s -a 0x1020D3A10+0x3c000
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol110094$$WeChat + 28, address = 0x000000010210fa10
(lldb) br s -a 0x1020D3370+0x3c000
Breakpoint 2: where = WeChat`___lldb_unnamed_symbol110091$$WeChat + 8, address = 0x000000010210f370
(lldb) br s -a 0x1020D33E4+0x3c000
Breakpoint 3: where = WeChat`___lldb_unnamed_symbol110092$$WeChat + 12, address = 0x000000010210f3e4
Volte para o WeChat e mantenha pressionado o vídeo curto para ver como o ponto de interrupção é acionado.
Process 3721 stopped
* thread #1: tid = 0x658fc, 0x000000010210f370 WeChat`___lldb_unnamed_symbol110091$$WeChat + 8, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
frame #0: 0x000000010210f370 WeChat`___lldb_unnamed_symbol110091$$WeChat + 8
WeChat`___lldb_unnamed_symbol110091$$WeChat:
-> 0x10210f370 <+8>: add x29, sp, #16 ; =16
0x10210f374 <+12>: mov x19, x0
0x10210f378 <+16>: adrp x8, 4968
0x10210f37c <+20>: ldr x0, [x8, #744]
(lldb) c
Process 3721 resuming
Process 3721 stopped
* thread #1: tid = 0x658fc, 0x000000010210fa10 WeChat`___lldb_unnamed_symbol110094$$WeChat + 28, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x000000010210fa10 WeChat`___lldb_unnamed_symbol110094$$WeChat + 28
WeChat`___lldb_unnamed_symbol110094$$WeChat:
-> 0x10210fa10 <+28>: add x29, sp, #96 ; =96
0x10210fa14 <+32>: sub sp, sp, #96 ; =96
0x10210fa18 <+36>: mov x19, x0
0x10210fa1c <+40>: adrp x8, 4863
……
Verificou-se que o ponto de interrupção 2 foi acionado primeiro, depois o ponto de interrupção 1 foi acionado e, em seguida, os pontos de interrupção 2 e 1 foram acionados uma vez cada. Você pode excluir a conexão entre onLongPressedWCSightFullScreenWindow e a coleção de vídeos curtos. Os vestígios de vídeos curtos devem ser encontrados nos dois métodos restantes. Encontre C a V e encontre M seguindo as pistas, que foram experimentadas e testadas! Use o cycript para injetar o WeChat e obter o Controlador onde está localizada a visualização que reproduz o vídeo curto.
cy# [#0x138c18030 nextResponder]
#"<WCTimeLineCellView: 0x138c34620; frame = (0 0; 319 249); tag = 1048577; layer = <CALayer: 0x138362ba0>>"
cy# [#0x138c34620 nextResponder]
#"<UITableViewCellContentView: 0x138223c70; frame = (0 0; 320 256); gestureRecognizers = <NSArray: 0x1384ec480>; layer = <CALayer: 0x138081dc0>>"
cy# [#0x138223c70 nextResponder]
#"<MMTableViewCell: 0x138c9f930; baseClass = UITableViewCell; frame = (0 307; 320 256); autoresize = W; layer = <CALayer: 0x1382dcd10>>"
cy# [#0x138c9f930 nextResponder]
#"<UITableViewWrapperView: 0x137b57800; frame = (0 0; 320 504); gestureRecognizers = <NSArray: 0x1383db660>; layer = <CALayer: 0x138af20c0>; contentOffset: {0, 0}; contentSize: {320, 504}>"
cy# [#0x137b57800 nextResponder]
#"<MMTableView: 0x137b8ae00; baseClass = UITableView; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x138adb590>; layer = <CALayer: 0x138956890>; contentOffset: {0, 99.5}; contentSize: {320, 3193}>"
cy# [#0x137b8ae00 nextResponder]
#"<UIView: 0x138ade5c0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x138ac9990>>"
cy# [#0x138ade5c0 nextResponder]
#"<WCTimeLineViewController: 0x1379eb000>"
Encontre o controlador ao qual WCContentItemViewTemplateNewSight pertence por meio da cadeia de resposta e é WCTimeLineViewController. Nenhuma pista valiosa foi encontrada no arquivo de cabeçalho desta classe, mas notamos que a visualização onde o vídeo curto está localizado pertence a MMTableVIewCell (veja o gráfico de análise Reveal acima), que é o TableView e dados de célula mais familiares para cada iOS. É atribuído através do método proxy de UITableViewDataSource - tableView:cellForRowAtIndexPath:
Através deste método, você pode definitivamente conhecer a sombra de M. Encontre [WCTimeLineViewController tableView:cellForRowAtIndexPath:]
no IDA e localize o endereço base 0x10128B6B0:
__text:000000010128B6B0 ADRP X8, #selRef_genNormalCell_indexPath_@PAGE
A função aqui é o método para gerar células em WCTimeLineViewController. Além deste método, existem três outros métodos para gerar células nesta classe:
- (void)genABTestTipCell:(id)arg1 indexPath:(id)arg2;
- (void)genRedHeartCell:(id)arg1 indexPath:(id)arg2;
- (void)genUploadFailCell:(id)arg1 indexPath:(id)arg2;
Pelo significado literal, podemos adivinhar que normal deveria ser um método para gerar pequenas células de vídeo. Continue procurando pistas no IDA
__text:0000000101287CC8 ADRP X8, #selRef_getTimelineDataItemOfIndex_@PAGE
O método acima é encontrado no método genNormalCell:IndexPath:
Você pode adivinhar com segurança que este método é um método para obter dados do TimeLine (momentos). Os dados do vídeo curto também devem ser obtidos por meio deste método, e o IDA pode ver o. chame neste método. Um método chamado selRef_getTimelineDataItemOfIndex_
, obter DataItem parece ser a fonte de dados da célula! Em seguida, use o LLDB para definir pontos de interrupção para verificar a conjectura. Você pode encontrar o endereço base correspondente a este método através do IDA: 0x101287CE4. Primeiro imprima o deslocamento ASLR da execução do WeChat.
LeonLei-MBP:~ gaoshilei$ lldb
(lldb) process connect connect://localhost:1234
(lldb) im li -o -f
[0] 0x0000000000050000 /var/mobile/Containers/Bundle/Application/2DCE8F30-9B6B-4652-901C-37EB1FF2A40D/WeChat.app/WeChat(0x0000000100050000)
Portanto, a localização do nosso ponto de interrupção é 0x50000+0x101287CE4
(lldb) br s -a 0x50000+0x101287CE4
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol63721$$WeChat + 252, address = 0x00000001012d7ce4
Imprima o valor de x0
(lldb) po $x0
Class name: WCDataItem, addr: 0x15f5f03b0
tid: 12393001887435993280
username: wxid_z8twcz4o18fg12
createtime: 1477360950
commentUsers: (
)
contentObj: <WCContentItem: 0x15f57d000>
Obtenha um objeto WCDataItem O valor de x0 aqui é o valor de retorno após a execução de selRef_getTimelineDataItemOfIndex_
e, em seguida, altere o valor de x0.
(lldb) register write $x0 0
(lldb) c
Neste momento, você descobrirá que o conteúdo do pequeno vídeo que queremos atualizar está todo vazio.
Neste ponto, encontramos o método para obter os dados de origem do vídeo curto. A questão é como obtemos este WCDataItem? Continue observando o status de chamada da função de análise IDA:
WCTimeLineViewController - (void)genNormalCell:(id) indexPath:(id)
__text:0000000101287BCC STP X28, X27, [SP,#var_60]!
__text:0000000101287BD0 STP X26, X25, [SP,#0x60+var_50]
__text:0000000101287BD4 STP X24, X23, [SP,#0x60+var_40]
__text:0000000101287BD8 STP X22, X21, [SP,#0x60+var_30]
__text:0000000101287BDC STP X20, X19, [SP,#0x60+var_20]
__text:0000000101287BE0 STP X29, X30, [SP,#0x60+var_10]
__text:0000000101287BE4 ADD X29, SP, #0x60+var_10
__text:0000000101287BE8 SUB SP, SP, #0x80
__text:0000000101287BEC MOV X19, X3
__text:0000000101287BF0 MOV X22, X0
__text:0000000101287BF4 MOV W25, #0x100000
__text:0000000101287BF8 MOVK W25, #1
__text:0000000101287BFC MOV X0, X2
__text:0000000101287C00 BL _objc_retain
__text:0000000101287C04 MOV X28, X0
__text:0000000101287C08 MOV X0, X19
__text:0000000101287C0C BL _objc_retain
__text:0000000101287C10 MOV X20, X0
__text:0000000101287C14 STR X20, [SP,#0xE0+var_98]
__text:0000000101287C18 ADRP X8, #selRef_row@PAGE
__text:0000000101287C1C LDR X1, [X8,#selRef_row@PAGEOFF]
__text:0000000101287C20 BL _objc_msgSend
__text:0000000101287C24 MOV X26, X0
__text:0000000101287C28 ADRP X8, #selRef_section@PAGE
__text:0000000101287C2C LDR X19, [X8,#selRef_section@PAGEOFF]
__text:0000000101287C30 MOV X0, X20
__text:0000000101287C34 MOV X1, X19
__text:0000000101287C38 BL _objc_msgSend
__text:0000000101287C3C STR X0, [SP,#0xE0+var_A8]
__text:0000000101287C40 MOV X0, X20
__text:0000000101287C44 MOV X1, X19
__text:0000000101287C48 BL _objc_msgSend
__text:0000000101287C4C MOV X2, X0
__text:0000000101287C50 ADRP X8, #selRef_calcDataItemIndex_@PAGE
__text:0000000101287C54 LDR X1, [X8,#selRef_calcDataItemIndex_@PAGEOFF]
__text:0000000101287C58 MOV X0, X22
__text:0000000101287C5C BL _objc_msgSend
__text:0000000101287C60 MOV X21, X0
__text:0000000101287C64 STR X21, [SP,#0xE0+var_C0]
__text:0000000101287C68 ADRP X8, #classRef_MMServiceCenter@PAGE
__text:0000000101287C6C LDR X0, [X8,#classRef_MMServiceCenter@PAGEOFF]
__text:0000000101287C70 ADRP X8, #selRef_defaultCenter@PAGE
__text:0000000101287C74 LDR X1, [X8,#selRef_defaultCenter@PAGEOFF]
__text:0000000101287C78 STR X1, [SP,#0xE0+var_B8]
__text:0000000101287C7C BL _objc_msgSend
__text:0000000101287C80 MOV X29, X29
__text:0000000101287C84 BL _objc_retainAutoreleasedReturnValue
__text:0000000101287C88 MOV X19, X0
__text:0000000101287C8C ADRP X8, #classRef_WCFacade@PAGE
__text:0000000101287C90 LDR X0, [X8,#classRef_WCFacade@PAGEOFF]
__text:0000000101287C94 ADRP X8, #selRef_class@PAGE
__text:0000000101287C98 LDR X1, [X8,#selRef_class@PAGEOFF]
__text:0000000101287C9C STR X1, [SP,#0xE0+var_B0]
__text:0000000101287CA0 BL _objc_msgSend
__text:0000000101287CA4 MOV X2, X0
__text:0000000101287CA8 ADRP X8, #selRef_getService_@PAGE
__text:0000000101287CAC LDR X1, [X8,#selRef_getService_@PAGEOFF]
__text:0000000101287CB0 STR X1, [SP,#0xE0+var_A0]
__text:0000000101287CB4 MOV X0, X19
__text:0000000101287CB8 BL _objc_msgSend
__text:0000000101287CBC MOV X29, X29
__text:0000000101287CC0 BL _objc_retainAutoreleasedReturnValue
__text:0000000101287CC4 MOV X20, X0
__text:0000000101287CC8 ADRP X8, #selRef_getTimelineDataItemOfIndex_@PAGE
__text:0000000101287CCC LDR X1, [X8,#selRef_getTimelineDataItemOfIndex_@PAGEOFF]
__text:0000000101287CD0 STR X1, [SP,#0xE0+var_C8]
__text:0000000101287CD4 MOV X2, X21
__text:0000000101287CD8 BL _objc_msgSend
__text:0000000101287CDC MOV X29, X29
__text:0000000101287CE0 BL _objc_retainAutoreleasedReturnValue
__text:0000000101287CE4 MOV X21, X0
__text:0000000101287CE8 MOV X0, X20
......
O parâmetro passado para selRef_getTimelineDataItemOfIndex_
é x2 Você pode ver que x21 passado para x2 é o valor de retorno da função selRef_calcDataItemIndex_
, que é um tipo de dados longo não assinado. Continuando a análise, o chamador selRef_getTimelineDataItemOfIndex_
é o valor de retorno de selRef_getService_
na etapa anterior. Após a análise do ponto de interrupção, verifica-se que se trata de um objeto WCFacade
. Vamos resolver as chamadas para selRef_getTimelineDataItemOfIndex_
:
O chamador é o valor de retorno de selRef_getService_
; o parâmetro é o valor de retorno de selRef_calcDataItemIndex_
<br> Vamos voltar nossa atenção para essas duas funções e usar o mesmo princípio para analisar como elas implementam a chamada.
selRef_getService_
:MMServiceCenter
Olhando para cima, x19 é atribuído na posição 0x101287C88. é o retorno do [MMServiceCenter defaultCenter]
.[WCFacade class]
.selRef_calcDataItemIndex_
:WCTimeLineViewController
seu chamador x0 na posição 0x101287C58.selRef_section
0x101287C4C, verifica-se que selRef_section
parâmetro de entrada vem de selRef_section
ou é x3.WCTimeLineViewController - (void)genNormalCell:(id) indexPath:(id)
, portanto, o parâmetro de selRef_calcDataItemIndex_
é [IndexPath section]
.getTimelineDataItemOfIndex:
pode passar [[MMServiceCenter defaultCenter] getService:[WCFacade class]]
para obter, seus parâmetros podem ser obtidos através da seguinte função
[WCTimeLineViewController calcDataItemIndex:[indexPath section]]
Sempre sente que algo está faltando? Ainda não obtivemos o indexPath! O próximo passo é obter o indexPath, que é relativamente simples, pois estamos em [WCContentItemViewTemplateNewSight onLongTouch]
, para que possamos obter MMTableViewCell, MMTableView e WCTimeLineViewController em sequência através de [self nextResponder]
, e então obter o indexPath através de [MMTableView indexPathForCell:MMTableViewCell]
.
Depois de fazer isso, obtivemos o objeto WCDataItem. O próximo foco deve estar em WCDataItem e, finalmente, obter o pequeno vídeo que desejamos. Procure pistas no arquivo de cabeçalho desta classe Como o vídeo só pode ser reproduzido após o download, o caminho do vídeo deve ser obtido aqui, então preste atenção aos atributos ou métodos relacionados ao URL e ao caminho, e a seguir encontre o seguinte. suspeitos.
@property(retain, nonatomic) NSString *sourceUrl2;
@property(retain, nonatomic) NSString *sourceUrl;
- (id)descriptionForKeyPaths;
- (id)keyPaths;
Volte ao LLDB e imprima esses valores com pontos de interrupção para ver o que há lá.
(lldb) po [$x0 keyPaths]
<__NSArrayI 0x15f74e9d0>(
tid,
username,
createtime,
commentUsers,
contentObj
)
(lldb) po [$x0 descriptionForKeyPaths]
Class name: WCDataItem, addr: 0x15f5f03b0
tid: 12393001887435993280
username: wxid_z8twcz4o18fg12
createtime: 1477360950
commentUsers: (
)
contentObj: <WCContentItem: 0x15f57d000>
(lldb) po [$x0 sourceUrl]
nil
(lldb) po [$x0 sourceUrl2]
nil
Não há pistas valiosas, mas notei que existe um WCContentItem em WCDataItem. Parece que a única maneira de começar é aqui.
@property(retain, nonatomic) NSString *linkUrl;
@property(retain, nonatomic) NSString *linkUrl2;
@property(retain, nonatomic) NSMutableArray *mediaList;
Imprima em LLDB
(lldb) po [[$x0 valueForKey:@"contentObj"] linkUrl]
https://support.weixin.qq.com/cgi-bin/mmsupport-bin/readtemplate?t=page/common_page__upgrade&v=1
(lldb) po [[$x0 valueForKey:@"contentObj"] linkUrl2]
nil
(lldb) po [[$x0 valueForKey:@"contentObj"] mediaList]
<__NSArrayM 0x15f985e10>(
<WCMediaItem: 0x15dfebdf0>
)
Há um objeto WCMediaItem no array mediaList Media geralmente é usado para representar vídeo e áudio. Encontre rapidamente o arquivo de cabeçalho e pesquise-o.
@property(retain, nonatomic) WCUrl *dataUrl;
- (id)pathForData;
- (id)pathForSightData;
- (id)pathForTempAttachVideoData;
- (id)videoStreamForData;
Entre os atributos e métodos acima, pathForSightData
é o que tem maior probabilidade de obter o caminho curto do vídeo. Continue para verificar.
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] dataUrl]
type[1], url[http://vweixinf.tc.qq.com/102/20202/snsvideodownload?filekey=30270201010420301e020166040253480410d14adcddf086f4e131d11a5b1cca1bdf0203039fa00400&bizid=1023&hy=SH&fileparam=302c0201010425302302040fde55e20204580ebd3602024eea02031e8d7d02030f42400204d970370a0201000400], enckey[0], encIdx[-1], token[]
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] pathForData]
/var/mobile/Containers/Data/Application/7C3A6322-1F57-49A0-ACDE-6EF0ED74D137/Library/WechatPrivate/6f696a1b596ce2499419d844f90418aa/wc/media/5/53/8fb0cdd77208de5b56169fb3458b45
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] pathForSightData]
/var/mobile/Containers/Data/Application/7C3A6322-1F57-49A0-ACDE-6EF0ED74D137/Library/WechatPrivate/6f696a1b596ce2499419d844f90418aa/wc/media/5/53/8fb0cdd77208de5b56169fb3458b45.mp4
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] pathForAttachVideoData]
nil
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] videoStreamForData]
nil
Obtive o URL da rede e o caminho local do vídeo curto! Aqui você pode usar iFunBox ou scp para copiar este arquivo da sandbox para ver se é um vídeo curto que esta célula deve reproduzir.
LeonLei-MBP:~ gaoshilei$ scp [email protected]:/var/mobile/Containers/Data/Application/7C3A6322-1F57-49A0-ACDE-6EF0ED74D137/Library/WechatPrivate/6f696a1b596ce2499419d844f90418aa/wc/media/5/53/8fb0cdd77208de5b56169fb3458b45.mp4 Desktop/
8fb0cdd77208de5b56169fb3458b45.mp4 100% 232KB 231.9KB/s 00:00
Abra-o com QuickTime e descubra que é realmente o vídeo curto que procuramos. Verifique novamente se a URL está correta. Abra o dataUrl impresso acima no navegador e descubra que também é este pequeno vídeo. Analisando esta classe podemos tirar as seguintes conclusões:
Neste ponto, a análise do caminho e do método de aquisição do vídeo curto foi concluída. Para conseguir o encaminhamento, precisamos continuar a analisar o lançamento do WeChat Moments.
Esta seção é um desvio que fiz ao procurar a função de encaminhamento de vídeo curto. No final, não encontrei uma maneira de implementá-la. No entanto, ela também fornece algumas idéias e métodos comumente usados em engenharia reversa. Se você não quiser lê-lo, pode pular para a segunda seção.
Abra a interface de gravação do vídeo curto e use o cycript para injetá-lo. Precisamos descobrir qual método é usado para publicar o vídeo curto e, em seguida, verificar quais janelas estão na janela atual (porque a gravação do vídeo curto não está). feito na keyWindow do UIApplication)
cy# [UIApp windows].toString()
(
"<iConsoleWindow: 0x125f75e20; baseClass = UIWindow; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x125f77b70>; layer = <UIWindowLayer: 0x125df4810>>",
"<SvrErrorTipWindow: 0x127414d40; baseClass = UIWindow; frame = (0 64; 320 45); hidden = YES; gestureRecognizers = <NSArray: 0x12740d930>; layer = <UIWindowLayer: 0x1274030b0>>",
"<MMUIWindow: 0x127796440; baseClass = UIWindow; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x1278083c0>; layer = <UIWindowLayer: 0x127796750>>",
"<UITextEffectsWindow: 0x1270e0d40; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x1270b4ba0>>",
"<NewYearActionSheet: 0x127797e10; baseClass = UIWindow; frame = (0 0; 320 568); hidden = YES; userInteractionEnabled = NO; layer = <UIWindowLayer: 0x1277d5490>>"
)
Verifica-se que a página atual possui um total de 5 janelas, entre as quais MMUIWindow é a janela onde o pequeno vídeo foi gravado. Imprime sua estrutura em árvore da UI.
cy# [#0x127796440 recursiveDescription]
O resultado impresso é bastante longo, então não vou publicá-lo. Encontre este botão que é o botão para gravar vídeos curtos
| | | | | | <UIButton: 0x1277a9d70; frame = (89.5 368.827; 141 141); opaque = NO; gestureRecognizers = <NSArray: 0x1277aaeb0>; layer = <CALayer: 0x1277a9600>>
| | | | | | | <UIView: 0x1277aa0a0; frame = (0 0; 141 141); userInteractionEnabled = NO; tag = 252707333; layer = <CALayer: 0x1277aa210>>
| | | | | | | | <UIImageView: 0x1277aa2e0; frame = (0 0; 141 141); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1277aa490>>
e então execute
cy# [#0x1277a9d70 setHidden:YES]
Descobri que o botão de disparo desapareceu, o que confirmou minha suspeita. Para encontrar o evento de resposta do botão, você pode encontrá-lo através do target
cy# [#0x1277a9d70 allTargets]
[NSSet setWithArray:@[#"<MainFrameSightViewController: 0x1269a4600>"]]]
cy# [#0x1277a9d70 allControlEvents]
193
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:193]
null
Descobri que o botão não tem ação correspondente, o que é estranho! UIButton deve ter um alvo e uma ação, caso contrário o Button não poderá responder aos eventos. Vamos tentar outros ControlEvents
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchDown]
@["btnPress"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpOutside]
@["btnRelease"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpInside]
@["btnRelease"]
Acontece que esses três ControlEvents possuem ações correspondentes. Vamos dar uma olhada nos valores dessas três enumerações.
typedef enum UIControlEvents : NSUInteger {
UIControlEventTouchDown = 1 << 0,
UIControlEventTouchDownRepeat = 1 << 1,
UIControlEventTouchDragInside = 1 << 2,
UIControlEventTouchDragOutside = 1 << 3,
UIControlEventTouchDragEnter = 1 << 4,
UIControlEventTouchDragExit = 1 << 5,
UIControlEventTouchUpInside = 1 << 6,
UIControlEventTouchUpOutside = 1 << 7,
UIControlEventTouchCancel = 1 << 8,
......
} UIControlEvents;
Pode-se observar que UIControlEventTouchDown corresponde a 1, UIControlEventTouchUpInside corresponde a 128, UIControlEventTouchUpOutside corresponde a 64 e a soma dos três é exatamente 193! Acontece que ao chamar [#0x1277a9d70 allControlEvents]
, o valor retornado deve ser uma enumeração. Se houver várias enumerações, adicione seus valores. Isso não é um pouco confuso? Eu sinto o mesmo! Agora há pouco imprimimos as ações correspondentes aos três ControlEvents e continuamos com LLDB+IDA para análise dinâmica.
Como precisamos encontrar uma maneira de publicar vídeos curtos, não nos importamos com a função btnPress
correspondente. Nós nos concentramos em btnRelease
, um método que será chamado após o botão de disparo ser liberado. Encontre este método no IDA e defina o próximo ponto de interrupção após encontrá-lo
(lldb) br s -a 0xac000+0x10209369C
Breakpoint 4: where = WeChat`___lldb_unnamed_symbol108894$$WeChat + 32, address = 0x000000010213f69c
Process 3813 stopped
* thread #1: tid = 0xf1ef0, 0x000000010213f69c WeChat`___lldb_unnamed_symbol108894$$WeChat + 32, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
frame #0: 0x000000010213f69c WeChat`___lldb_unnamed_symbol108894$$WeChat + 32
WeChat`___lldb_unnamed_symbol108894$$WeChat:
-> 0x10213f69c <+32>: bl 0x1028d0b60 ; symbol stub for: objc_msgSend
0x10213f6a0 <+36>: cmp w0, #2 ; =2
0x10213f6a4 <+40>: b.ne 0x10213f6dc ; <+96>
0x10213f6a8 <+44>: adrp x8, 5489
Grave um pequeno vídeo com seu celular e depois solte-o, acionando o breakpoint, que mostra que nosso palpite está correto. Continuando a análise, descobrimos que o código está do lado direito da imagem acima. Depois de olhar, não há como pular para o lançamento do vídeo, porém, se você olhar com atenção, há um bloco, que é o. bloco de atraso do sistema e está localizado em 0x102093760. Então seguimos o ponto de interrupção e saltamos para o endereço armazenado em x16 em 0x1028255A0
(lldb) si
Process 3873 stopped
* thread #1: tid = 0xf62c4, 0x00000001028d9598 WeChat`dispatch_after, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x00000001028d9598 WeChat`dispatch_after
WeChat`dispatch_after:
-> 0x1028d9598 <+0>: adrp x16, 1655
0x1028d959c <+4>: ldr x16, [x16, #1056]
0x1028d95a0 <+8>: br x16
WeChat`dispatch_apply:
0x1028d95a4 <+0>: adrp x16, 1655
(lldb) po $x2
<__NSStackBlock__: 0x16fd49f88>
Verifica-se que o parâmetro x2 passado é um bloco. Vamos revisar a função dispatch_after novamente.
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
Esta função tem três parâmetros, a saber, dispatch_time_t, dispatch_queue_t e dispatch_block_t. O x2 impresso aqui é o bloco a ser passado, então achamos que haverá um atraso após a gravação do vídeo curto e, em seguida, executar o bloco que acabou de ser passado. então x2 deve ser Se houver outras chamadas de método, o próximo passo é saber a localização deste bloco.
(lldb) memory read --size 8 --format x 0x16fd49f88
0x16fd49f88: 0x000000019f8fd218 0x00000000c2000000
0x16fd49f98: 0x000000010214777c 0x0000000102fb0e60
0x16fd49fa8: 0x000000015da32600 0x000000015ea1a430
0x16fd49fb8: 0x000000015cf5fee0 0x000000016fd49ff0
0x000000010214777c é a localização do bloco. Claro, o deslocamento ASLR atual do WeChat deve ser subtraído. O endereço final no IDA é btnRelease
. Esta sub-rotina é muito simples e possui apenas um método selRef_logicCheckState_
que pode ser nosso alvo. Vamos primeiro ver quem chamou esse método
(lldb) br s -a 0xb4000+0x1020937BC
......
Process 3873 stopped
* thread #1: tid = 0xf62c4, 0x00000001021477bc WeChat`___lldb_unnamed_symbol108895$$WeChat + 64, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
frame #0: 0x00000001021477bc WeChat`___lldb_unnamed_symbol108895$$WeChat + 64
WeChat`___lldb_unnamed_symbol108895$$WeChat:
-> 0x1021477bc <+64>: adrp x8, 5489
0x1021477c0 <+68>: ldr x1, [x8, #1552]
0x1021477c4 <+72>: orr w2, wzr, #0x1
0x1021477c8 <+76>: ldp x29, x30, [sp, #16]
(lldb) po $x0
<MainFrameSightViewController: 0x15d1f0c00>
Descobri que ele ainda era chamado pelo objeto MainFrameSightViewController, então selRef_logicCheckState_
também deve estar no arquivo de cabeçalho desta classe. Procurei e encontrei.
- (void)logicCheckState:(int)arg1;
Procure por [MainFrameSightViewController logicCheckState:] na janela esquerda do IDA e descubra que esse método é super complicado e tem muita lógica, então não se preocupe e faça isso devagar. Encontramos um switch jump em 0x102094D6C, e a ideia era muito clara. Só precisamos encontrar a linha onde o pequeno vídeo foi filmado e olhar para baixo. Defina um ponto de interrupção em 0x102094D6C. Este ponto de interrupção será acionado várias vezes ao gravar vídeos curtos. Você pode desativar o ponto de interrupção antes de gravar, ativar o ponto de interrupção antes de soltá-lo e imprimir o valor x8 neste momento.
(lldb) p/x $x8
(unsigned long) $38 = 0x0000000102174e10
x8 é um ponteiro e o endereço para o qual ele aponta é 0x102174e10. Use este endereço menos o deslocamento ASLR atual para encontrar o endereço base no IDA. Ele é encontrado como 0x102094E10. até o fim. Após a posição 0x102094E24, pule para 0x1020951C4. Este branch tem menos conteúdo e contém três funções.
loc_1020951C4
ADRP X8, #selRef_hideTips@PAGE
LDR X1, [X8,#selRef_hideTips@PAGEOFF]
MOV X0, X19
BL _objc_msgSend
ADRP X8, #selRef_finishWriter@PAGE
LDR X1, [X8,#selRef_finishWriter@PAGEOFF]
MOV X0, X19
BL _objc_msgSend
ADRP X8, #selRef_turnCancelBtnForFinishRecording@PAGE
LDR X1, [X8,#selRef_turnCancelBtnForFinishRecording@PAGEOFF]
MOV X0, X19
BL _objc_msgSend
B loc_102095288
Entre eles, selRef_finishWriter
e selRef_turnCancelBtnForFinishRecording
precisam ser focados. Esses dois métodos parecem significar o fim da gravação de vídeo curto. As pistas provavelmente estão nessas duas funções. Observando o chamador, descobrimos que esses dois métodos pertencem ao MainFrameSightViewController. Continue verificando esses dois métodos no IDA. Encontrei um método chamado f_switchToSendingPanel
perto do final de selRef_finishWriter
em 0x102094248, defini um ponto de interrupção e gravei o vídeo, descobri que esse método não foi acionado. A interface de publicação não deve ser chamada por meio deste método, então continue retornando selRef_finishWriter
; chame o método selRef_stopRecording
no local 0x1020941DC, e o chamador que o imprime descobre que esse método pertence a SightFacade
e continua procurando o implementação deste método no IDA. selRef_stopRecord
é chamado novamente na posição 0x101F9BED4 deste método. O chamador também descobre que este método pertence a SightCaptureLogicF4. É um pouco como descascar uma cebola e continua procurando a implementação deste método. selRef_finishWriting
é chamado novamente na posição 0x101A98778 dentro deste método. Usando o mesmo princípio, verifica-se que este método pertence ao SightMovieWriter. Três camadas foram removidas, vamos continuar:
Duas linhas são divididas na posição 0x10261D004 no SightMovieWriter - (void)finishWriting
Um ponto de interrupção é definido nesta posição e, em seguida, o ponto de interrupção é acionado após a gravação do vídeo curto e o valor de x19 é impresso.
(lldb) po $x19
<OS_dispatch_queue: CAPTURE.CALLBACK[0x13610bcd0] = { xrefcnt = 0x4, refcnt = 0x4, suspend_cnt = 0x0, locked = 1, target = com.apple.root.default-qos.overcommit[0x1a0aa3700], width = 0x0, running = 0x0, barrier = 1 }>
Portanto, o código não irá pular para loc_10261D054, mas para a esquerda. No código à esquerda, verifica-se que um bloco está habilitado. Este bloco é a sub-rotina sub-rotina sub_10261D0AC e o endereço é 0x10261D0AC. conforme mostrado abaixo:
Pode-se observar que ele está dividido principalmente em duas linhas. Definimos um ponto de interrupção no final da primeira caixa, que é 0x10261D108. Após o disparo do ponto de interrupção, o valor de x0 é impresso como 1. O código assembly aqui. é
__text:000000010261D104 CMP X0, #2
__text:000000010261D108 B.EQ loc_10261D234
B.EQ irá pular para loc_10261D234 somente quando o resultado da etapa anterior for 0, mas o resultado aqui não for 0. Altere o valor de x0 para 2 para que o resultado da etapa anterior seja 0.
(lldb) po $x0
1
(lldb) register write $x0 2
(lldb) po $x0
2
Neste momento, libere o ponto de interrupção e espere para pular para a interface de publicação de vídeo curto. O resultado é que ele fica preso nesta interface sem qualquer resposta, então acho que a lógica para perceber o salto deve estar na linha à direita, e continue pesquisando ao longo da linha à direita: Descobriu-se que o seguinte método foi chamado em 0x10261D3AC à direita
- (void)finishWritingWithCompletionHandler:(void (^)(void))handler;
Este método é um método em AVAssetWriter fornecido pelo sistema. É uma operação a ser realizada após a conclusão da gravação do vídeo. Aqui, um bloco é passado. Como há apenas um parâmetro, a variável correspondente é x2 e o valor. de x2 é impresso.
(lldb) po $x2
<__NSStackBlock__: 0x16e086c78>
(lldb) memory read --size 8 --format x 0x16e086c78
0x16e086c78: 0x00000001a0aa5218 0x00000000c2000000
0x16e086c88: 0x00000001026d94b0 0x0000000102fc98c0
0x16e086c98: 0x0000000136229fd0 0x000000016e086d00
0x16e086ca8: 0x00000001997f5318 0xfffffffec9e882ff
E encontre a posição do bloco 0x10261D4B0 através da memória da pilha (o deslocamento ASLR precisa ser subtraído)
sub_10261D4B0
var_20= -0x20
var_10= -0x10
STP X20, X19, [SP,#var_20]!
STP X29, X30, [SP,#0x20+var_10]
ADD X29, SP, #0x20+var_10
MOV X19, X0
LDR X0, [X19,#0x20]
ADRP X8, #selRef_stopAmr@PAGE
LDR X1, [X8,#selRef_stopAmr@PAGEOFF]
BL _objc_msgSend
LDR X0, [X19,#0x20]
ADRP X8, #selRef_compressAudio@PAGE
LDR X1, [X8,#selRef_compressAudio@PAGEOFF]
LDP X29, X30, [SP,#0x20+var_10]
LDP X20, X19, [SP+0x20+var_20],#0x20
B _objc_msgSend
; End of function sub_10261D4B0
Apenas dois métodos são chamados, um é selRef_stopAmr
para parar amr (um formato de áudio) e o outro é selRef_compressAudio
para compactar áudio. A próxima operação após a filmagem não deve ser colocada nesses dois métodos que estou procurando há tanto tempo. e ainda não tenho ideia. Essa estrada parece morta, não se meta em encrencas, recue estrategicamente e procure outras entradas.
A diversão de andar ao contrário é que você pode experimentar a alegria do sucesso no caminho para encontrar a verdade. Você também pode ir na direção errada e se afastar cada vez mais da verdade. e siga em frente!
(Como o WeChat foi atualizado secretamente em segundo plano, o conteúdo a seguir é o ASLR do WeChat versão 6.3.30, e a análise acima é baseada na versão 6.3.28)
Observe que quando você clica no botão da câmera no canto superior direito do círculo de amigos, uma planilha aparecerá na parte inferior. O primeiro é o vídeo curto do Sight Comece aqui e use o cycript para ver a qual evento o botão Sight corresponde. para.
iPhone-5S:~ root# cycript -p "WeChat"
cy# [UIApp windows].toString()
`(
"<iConsoleWindow: 0x14d6ccc00; baseClass = UIWindow; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x14d7df110>; layer = <UIWindowLayer: 0x14d7d6f60>>",
"<SvrErrorTipWindow: 0x14eaa5800; baseClass = UIWindow; frame = (0 0; 320 45); hidden = YES; gestureRecognizers = <NSArray: 0x14e9e8950>; layer = <UIWindowLayer: 0x14e9e6510>>",
"<UITextEffectsWindow: 0x14ec38ba0; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x14ec39360>>",
"<UITextEffectsWindow: 0x14e9c67a0; frame = (0 0; 320 568); layer = <UIWindowLayer: 0x14d683ff0>>",
"<UIRemoteKeyboardWindow: 0x14f226e40; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x14d6f4de0>>",
"<NewYearActionSheet: 0x14f1704a0; baseClass = UIWindow; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x14ef9bf90>; layer = <UIWindowLayer: 0x14ef61a20>>"
)`
cy# [#0x14f1704a0 recursiveDescription].toString()
A planilha na parte inferior é NewYearActionSheet e, em seguida, imprima o diagrama da estrutura em árvore da IU de NewYearActionSheet (é muito longo, então não vou publicá-lo). Em seguida, descubra que o UIButton correspondente ao Sight é 0x14f36d470
cy# [#0x14f36d470 allTargets]
[NSSet setWithArray:@[#"<NewYearActionSheet: 0x14f1704a0; baseClass = UIWindow; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x14ef9bf90>; layer = <UIWindowLayer: 0x14ef61a20>>"]]]
cy# [#0x14f36d470 allControlEvents]
64
cy# [#0x14f36d470 actionsForTarget:#0x14f1704a0 forControlEvent:64]
@["OnDefaultButtonTapped:"]
O evento vinculado ao botão pode ser encontrado através do selRef_dismissWithClickedButtonIndex_animated
actionsForTarget:forControlEvent:
do UIControl. O método de disparo do botão Sight é OnDefaultButtonTapped:
Volte para IDA e encontre este método em NewYearActionSheet. , ao imprimir seu chamador, descobrimos que ainda é NewYearActionSheet. Continue clicando para encontrar o método newYearActionSheet_clickedButtonAtIndex
. Parece que não é o próprio NewYearActionSheet. Siga o ponto de interrupção e chame o método #selRef_showSightWindowForMomentWithMask_byViewController_scene
na posição 0x1012B78CC. Através da observação, descobrimos que o chamador deste método é o valor de retorno x0 na posição 0x1012B78AC. fui ao arquivo de cabeçalho para encontrá-lo.
- (void)showSightWindowForMomentWithMask:(id)arg1 byViewController:(id)arg2 scene:(int)arg3;
Este método deve ser o método para saltar para a pequena interface de vídeo. Imprima seus parâmetros separadamente abaixo
(lldb) po $x2
<UIImage: 0x14f046660>, {320, 568}
(lldb) po $x3
<WCTimeLineViewController: 0x14e214800>
(lldb) po $x4
2
(lldb) po $x0
<SightFacade: 0x14f124b40>
Entre eles, x2, x3 e x4 correspondem a três parâmetros, respectivamente, x0 é o chamador. Vá para o interior deste método para ver como implementá-lo. Verifica-se que a interface de gravação de vídeo curto é inicializada neste método. Primeiro, um MainFrameSightViewController é inicializado, então um UINavigationController é criado para colocar o MainFrameSightViewController nele e, em seguida, um MMWindowController é inicializado para chamar.
- (id)initWithViewController:(id)arg1 windowLevel:(int)arg2;
O método coloca o UINavigationController e conclui todo o trabalho de criação da IU para a interface de gravação de vídeo curto. Após a conclusão da filmagem, entre na interface de publicação. Neste momento, use o cycript para descobrir que o controlador atual é SightMomentEditViewController. A partir disso, surge uma ideia: não é suficiente pular a interface de filmagem anterior e entrar diretamente na interface de publicação. ? Nós mesmos criamos um SightMomentEditViewController e o colocamos no UINavigationController e, em seguida, colocamos esse controlador de navegação no MMWindowController. **(Escrevi o ajuste aqui para verificação, e as ideias específicas de ajuste serão escritas posteriormente)** O resultado é que a interface de publicação pode realmente aparecer, mas a NavgationBar da barra de navegação cobre a original e toda a interface é transparente, é feia e, após a conclusão da publicação, todo o MMWindowController não pode ser destruído e ainda permanece na interface de publicação. Este não é o resultado que desejamos, mas ganhamos muito. Pelo menos podemos chamar diretamente a interface de publicação, e pequenos vídeos podem ser encaminhados normalmente. Meu palpite pessoal é que a razão pela qual a interface atual não pode ser destruída é porque MMWindowController criou uma nova janela, que não é a mesma que keyWindow onde TimeLine está localizado. O método de disparo de botão de SightMomentEditViewController não pode destruir esta janela, então eu tenho um. palpite ousado. Não posso simplesmente exibir o SightMomentEditViewController diretamente no WCTimeLineViewController atual?
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
Não seria bom exibi-lo assim? Porém, ao observar o arquivo de cabeçalho do SightMomentEditViewController, combinado com os elementos da interface quando o vídeo curto é lançado, especula-se que a criação deste controlador requer pelo menos dois atributos, um é o caminho do vídeo curto, e o outro é a miniatura do vídeo curto Essas duas chaves Se o atributo for fornecido ao SightMomentEditViewController, ele deverá ser exibido normalmente.
SightMomentEditViewController *editSightVC = [[%c(SightMomentEditViewController) alloc] init];
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UIImage *image = [[self valueForKey:@"_sightView"] getImage];
[editSightVC setRealMoviePath:localPath];
[editSightVC setMoviePath:localPath];
[editSightVC setRealThumbImage:image];
[editSightVC setThumbImage:image];
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
A interface de publicação de vídeos curtos pode ser exibida normalmente e todas as funções podem ser usadas normalmente. O único problema é que o botão de retorno não tem efeito e o SightMomentEditViewController não pode ser destruído. Use cycript para verificar o actionEvent do botão esquerdo e descubra que sua função de resposta é - (void)popSelf;
Clicar no botão esquerdo para retornar aciona o método pop, mas este controlador não está no navgationController, portanto é inválido. precisa refazer este método.
- (void)popSelf
{
[self dismissViewControllerAnimated:YES completion:^{
}];
}
Neste momento, clique no botão retornar para sair normalmente. Além disso, um método chamado - (void)sendSightToFriend;
foi encontrado em WCContentItemViewTemplateNewSight, que pode encaminhar vídeos curtos diretamente para amigos. .
O encaminhamento de vídeos curtos suporta 4 funções, encaminhar para Moments, encaminhar para amigos, salvar no álbum de fotos local e copiar o link do vídeo curto para a área de transferência. Se o vídeo curto não for baixado, pressionar longamente mostrará apenas o link da URL do vídeo curto.
Aqui precisamos conectar duas classes, nomeadamente WCContentItemViewTemplateNewSight e SightMomentEditViewController. Conectar o método onLongTouch em WCContentItemViewTemplateNewSight e, em seguida, adicionar o menu pop-up do menu e adicionar os métodos de resposta por sua vez.
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UISaveVideoAtPathToSavedPhotosAlbum(localPath, nil, nil, nil);
}
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UISaveVideoAtPathToSavedPhotosAlbum(localPath, nil, nil, nil);
SightMomentEditViewController *editSightVC = [[%c(SightMomentEditViewController) alloc] init];
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UIImage *image = [[self valueForKey:@"_sightView"] getImage];
[editSightVC setRealMoviePath:localPath];
[editSightVC setMoviePath:localPath];
[editSightVC setRealThumbImage:image];
[editSightVC setThumbImage:image];
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
[self sendSightToFriend];
UIMenuController *menuController = [UIMenuController sharedMenuController];
if (menuController.isMenuVisible) return;//防止出现menu闪屏的情况
[self becomeFirstResponder];
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
BOOL isExist =[[NSFileManager defaultManager] fileExistsAtPath:localPath];
UIMenuItem *retweetMenuItem = [[UIMenuItem alloc] initWithTitle:@"朋友圈" action:@selector(SLRetweetSight)];
UIMenuItem *saveToDiskMenuItem = [[UIMenuItem alloc] initWithTitle:@"保存到相册" action:@selector(SLSightSaveToDisk)];
UIMenuItem *sendToFriendsMenuItem = [[UIMenuItem alloc] initWithTitle:@"好友" action:@selector(SLSightSendToFriends)];
UIMenuItem *copyURLMenuItem = [[UIMenuItem alloc] initWithTitle:@"复制链接" action:@selector(SLSightCopyUrl)];
if(isExist){
[menuController setMenuItems:@[retweetMenuItem,sendToFriendsMenuItem,saveToDiskMenuItem,copyURLMenuItem]];
}else{
[menuController setMenuItems:@[copyURLMenuItem]];
}
[menuController setTargetRect:CGRectZero inView:self];
[menuController setMenuVisible:YES animated:YES];
Coloquei o arquivo de ajuste específico no github WCSightRetweet
@interface WCUrl : NSObject
@property(retain, nonatomic) NSString *url;
@end
@interface WCContentItem : NSObject
@property(retain, nonatomic) NSMutableArray *mediaList;
@end
@interface WCDataItem : NSObject
@property(retain, nonatomic) WCContentItem *contentObj;
@end
@interface WCMediaItem : NSObject
@property(retain, nonatomic) WCUrl *dataUrl;
- (id)pathForSightData;
@end
@interface MMServiceCenter : NSObject
+ (id)defaultCenter;
- (id)getService:(Class)arg1;
@end
@interface WCFacade : NSObject
- (id)getTimelineDataItemOfIndex:(long long)arg1;
@end
@interface WCSightView : UIView
- (id)getImage;
@end
@interface WCContentItemViewTemplateNewSight : UIView{
WCSightView *_sightView;
}
- (WCMediaItem *)iOSREMediaItemFromSight;
- (void)iOSREOnSaveToDisk;
- (void)iOSREOnCopyURL;
- (void)sendSightToFriend;
@end
@interface SightMomentEditViewController : UIViewController
@property(retain, nonatomic) NSString *moviePath;
@property(retain, nonatomic) NSString *realMoviePath;
@property(retain, nonatomic) UIImage *thumbImage;
@property(retain, nonatomic) UIImage *realThumbImage;
- (void)makeInputController;
@end
@interface MMWindowController : NSObject
- (id)initWithViewController:(id)arg1 windowLevel:(int)arg2;
- (void)showWindowAnimated:(_Bool)arg1;
@end
@interface WCTimeLineViewController : UIViewController
- (long long)calcDataItemIndex:(long long)arg1;
@end
@interface MMTableViewCell : UIView
@end
@interface MMTableView : UIView
- (id)indexPathForCell:(id)cell;
@end
THEOS_DEVICE_IP = 192.168.0.115//手机所在的IP
include $(THEOS)/makefiles/common.mk
ARCHS = arm64//支持的CPU架构
TWEAK_NAME = WCTimelineSightRetweet
WCTimelineSightRetweet_FILES = Tweak.xm
WCTimelineSightRetweet_FRAMEWORKS = UIKit CoreGraphics//导入系统的framework
include $(THEOS_MAKE_PATH)/tweak.mk
after-install::
install.exec "killall -9 WeChat"//安装完成杀掉的进程
O arquivo de controle não precisa ser modificado e, em seguida, execute o comando make package install
para instalá-lo no celular. O WeChat será eliminado e, em seguida, abra o WeChat novamente. A função de encaminhar vídeos curtos foi adicionada.
Instale macports (o processo de instalação requer uma conexão VPN, caso contrário a instalação não será bem-sucedida)
Após instalar o MacPorts, abra o terminal e digite sudo port -v selfupdate
para atualizar o MacPorts para a versão mais recente, o que pode levar muito tempo.
Após atualizar o MacPorts, instale o arquivo DPKG e digite sudo port -f install dpkg
no terminal.
Baixe e instale o iOSOpendev Se a instalação falhar, você pode usar Command + L
para verificar se há problemas durante a instalação.
PackageKit: Install Failed: Error Domain=PKInstallErrorDomain Code=112 "运行软件包“iOSOpenDev-1.6-2.pkg”的脚本时出错。" UserInfo={NSFilePath=./postinstall, NSURL=file://localhost/Users/ice/Downloads/iOSOpenDev-1.6-2.pkg#iodsetup.pkg, PKInstallPackageIdentifier=com.iosopendev.iosopendev162.iod-setup.pkg, NSLocalizedDescription=运行软件包“iOSOpenDev-1.6-2.pkg”的脚本时出错。} {
NSFilePath = "./postinstall";
NSLocalizedDescription = "U8fd0U884cU8f6fU4ef6U5305U201ciOSOpenDev-1.6-2.pkgU201dU7684U811aU672cU65f6U51faU9519U3002";
NSURL = "file://localhost/Users/ice/Downloads/iOSOpenDev-1.6-2.pkg#iodsetup.pkg";
PKInstallPackageIdentifier = "com.iosopendev.iosopendev162.iod-setup.pkg";
}
Aqui está uma solução: Baixe a pasta Especificações em iOSOpenDevInstallSolve
5. Para corrigir o problema de falha na instalação, abra a pasta Especificações baixada na etapa 4. Deve haver 8 arquivos nela. Se você tiver vários xcodes instalados, coloque-os nos xcodes correspondentes.
(1) Os quatro arquivos começando com iPhoneOS são colocados na pasta /Application/Xcode/Content/Developer/Platforms/IphoneOS.platform/Developer/Library/Xcode/Specifications (caso contrário, crie você mesmo uma pasta de Especificações)
(2) Os outros quatro arquivos começando com iPhone Simulator são colocados na pasta /Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications (se não, crie um também)
(3) Crie uma pasta usr na pasta /Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/ e crie uma pasta chamada bin na pasta usr.
Nota: Às vezes, haverá um aviso de que a instalação falhou. Abra um novo projeto no Xcode. Se houver iOSOpenDev no menu de opções do projeto, significa que a instalação foi bem-sucedida.
Para instalar o pacote ipa, você também pode usar ferramentas como o itool, mas o ideviceinstaller pode ver o processo de instalação, o que facilita encontrar a causa do erro.
executar comando
brew install ideviceinstaller
Se você for avisado de que o comando brew não pode ser encontrado, significa que o Homebrew não foi instalado no seu Mac.
Mensagens de erro comuns:
ERROR: Could not connect to lockdownd, error code -5
Neste momento, basta reinstalar o libimobiledevice (porque o ideviceinstaller depende de muitos outros plug-ins)
Execute o seguinte comando:
$ brew uninstall libimobiledevice
$ brew install --HEAD libimobiledevice
Baixe a ferramenta de nova assinatura do iOS App Signer* (salve muitas operações de linha de comando, assine novamente com um clique!)*
(3) Baixe o aplicativo WeChat hackeado
Como o pacote AppStore é criptografado (com shell) e não pode ser assinado novamente, você deve usar um pacote com shell. Você pode usar dumpdecrypted para despejar o shell sozinho ou pode usar diretamente o assistente PP ou o assistente itool para baixar a versão desbloqueada. do aplicativo WeChat que foi shellado.
(4) Instale o Yololib
O yololib pode injetar dylib no arquivo binário do WeChat, para que seu Hook possa ser eficaz. Após o download, compile e obtenha o yololib.
#####(1) Gerar biblioteca estática. iOSOpendev foi instalado na etapa anterior. Agora abra um novo projeto no Xcode. O projeto iOSOpendev aparecerá na interface de seleção de projetos. Existe apenas um arquivo .mm de projeto recém-criado, só precisamos escrever todos os métodos de gancho neste arquivo.
Como máquinas sem jailbreak não podem instalar plug-ins de ajuste para conectar aplicativos originais como máquinas desbloqueadas, CaptainHook usa o mecanismo Runtime para implementar funções como definição de classe e substituição de método usando comandos de macro.
CHDeclareClass(WCContentItemViewTemplateNewSight);
CHDeclareClass(ClassName)
indica qual classe conectar e geralmente é escrita no início da operação nesta classe.
CHDeclareMethod0(id, WCContentItemViewTemplateNewSight, SLSightDataItem){......}
CHDeclareMethod(count, return_type, class_type, name1, arg1, ....)
significa criar um novo método, count significa o número de parâmetros deste método, return_type significa o tipo de retorno, class_type preenche o nome da classe deste método, name1 significa o nome do método, arg1 representa o primeiro parâmetro, se não houver parâmetro deixe em branco e assim por diante.
CHOptimizedMethod0(self, void, WCContentItemViewTemplateNewSight, onLongTouch){
CHSuper(0, className, Method);//可选
......
}
CHOptimizedMethod(count, optimization, return_type, class_type, name1, type1, arg1)
significa conectar o método original (se CHSuper(0, className, Method)
significa copiar o método original, CHSuper significa chamar o método original na posição atual), contagem significa o número de parâmetros do método de gancho, otimização geralmente preenche self, return_type é o tipo de valor de retorno do método, class_type preenche o nome da classe de a classe atual, name1 é o nome do método, arg1 é o parâmetro, se não houver parâmetros, preencha arg e assim por diante.
CHConstructor
{
@autoreleasepool
{
CHLoadLateClass(WCContentItemViewTemplateNewSight);
CHHook(0, WCContentItemViewTemplateNewSight, onLongTouch);
}
}
Esta é a função de entrada do CaptainHook. Todas as classes viciadas devem ser declaradas aqui para serem carregadas, e os métodos da classe devem ser declarados fisgados aqui.
Então você pode escrever código nas classes e métodos. O código é muito longo, então não vou publicá-lo no github com MMPlugin.
Este projeto inclui as funções de encaminhar vídeos curtos, capturar automaticamente envelopes vermelhos e modificar as etapas do exercício WeChat. As funções de capturar automaticamente envelopes vermelhos e modificar as etapas do exercício WeChat podem ser desativadas manualmente.
Nota: Se você usar classes do sistema, lembre -se de importar a biblioteca de classes correspondente (como o UIKIT) e os arquivos de cabeçalho, caso contrário, um erro será relatado durante a compilação.
Após a compilação bem -sucedida, você pode encontrar a biblioteca estática compilada na pasta de produtos.
Encontre -o no Finder e copie -o para uso posterior.
Os materiais que você deve ter neste momento incluem:
Encontre o arquivo binário do WeChat no aplicativo WeChat original e copie -o para uso posterior.
Execute o seguinte comando para injetar mmplugin.dylib no arquivo binário do WeChat.
LeonLei-MBP:WeChat gaoshilei$ ./yololib WeChat MMPlugin.dylib
Ao executar este comando, verifique se Yololib, WeChat e WeChat.App estão no mesmo diretório.
Após a conclusão, copie mmplugin.dylib e wechat para o weChat.app original, substituindo o arquivo WeChat original.
Abra o assinante do aplicativo iOS e selecione vários parâmetros como mostrado abaixo:
O que eu escolheu aqui é um certificado de nível corporativo.
Conecte -se ao seu telefone celular e execute o seguinte comando para verificar se o ideviceInstaller está conectado ao telefone celular:
LeonLei-MBP:WeChat gaoshilei$ ideviceinfo
Se muitas informações do telefone celular forem impressas, isso significa que a conexão é bem -sucedida e você pode instalar o pacote IPA. Execute o seguinte comando para instalar:
LeonLei-MBP:WeChat gaoshilei$ ideviceinstaller -i WeChat.ipa
WARNING: could not locate iTunesMetadata.plist in archive !
WARNING: could not locate Payload/WeChat.app/SC_Info/WeChat.sinf in archive !
Copying ' WeChat.ipa ' to device... DONE.
Installing ' com.xxxxxxxxxxxx '
- CreatingStagingDirectory (5%)
- ExtractingPackage (15%)
- InspectingPackage (20%)
- TakingInstallLock (20%)
- PreflightingApplication (30%)
- InstallingEmbeddedProfile (30%)
- VerifyingApplication (40%)
- CreatingContainer (50%)
- InstallingApplication (60%)
- PostflightingApplication (70%)
- SandboxingApplication (80%)
- GeneratingApplicationMap (90%)
- Complete
Após a conclusão da instalação, abra o WeChat no seu telefone para experimentar os novos recursos que adicionamos! Se um determinado link estiver preso, um erro será relatado. Por favor, veja as renderizações: