La actualización 2020.1.14 no requiere instalación de jailbreak
Este artículo es un tutorial sobre cómo revertir el archivo binario de WeChat para realizar el reenvío de videos cortos en Moments, desde el código ensamblador inicial hasta la instalación final de la nueva firma y otras operaciones, le enseñará paso a paso cómo jugar con WeChat. Una vez que lo aprenda, será fácil realizar ingeniería inversa en otras funciones de WeChat.
Este artículo está dividido en dos partes debido a su extensión. La primera parte explica el trabajo inverso, es decir, cómo encontrar las funciones y métodos relevantes para implementar. La segunda parte explica cómo volver a firmar la instalación en máquinas sin jailbreak. Ajustar la instalación en máquinas con jailbreak. Proceso detallado.
La segunda parte del texto también proporciona el código para que WeChat tome automáticamente los sobres rojos y modifique la cantidad de pasos de WeChat. Estos se pueden encontrar paso a paso siguiendo la rutina de este artículo y no se repetirán aquí.
Antes de practicar, debes preparar un teléfono con jailbreak e instalar todas las herramientas que se enumeran a continuación. IDA y Reveal son versiones descifradas. La versión genuina de IDA cuesta más de 2000 dólares. Realmente vale la pena por una herramienta de ingeniería inversa tan increíble. Sin embargo, si no eres una empresa que se especializa en ingeniería inversa, no existe. Necesito usar la versión original. La próxima versión descifrada de Windows estará bien, todavía no puedo encontrarla en Mac. Puedes usar hopper para reemplazar IDA en Mac, que también es una herramienta de ingeniería inversa muy poderosa. Sin más preámbulos, ¡comencemos!
Nota: El archivo binario de WeChat invertido en este artículo es la versión 6.3.28. Si es una versión de WeChat diferente, la dirección base en el archivo binario también es diferente.
Entorno inverso para máquina con jailbreak MacOS + iPhone5S 9.1 <br> Primero use dumpdecrypted para destruir el shell de WeChat (si no sabe cómo, lea este tutorial escrito por mí), obtenga un archivo WeChat.decrypted y primero arroje este archivo a IDA para su análisis (un archivo binario de aproximadamente 60 MB). tarda unos 40 minutos en ser analizado por IDA End), use class-dump para exportar todos los archivos de encabezado
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
¡Tengo suegra! Hay un total de 8000 archivos de encabezado, ¡y WeChat realmente tiene una gran cantidad de trabajo! Calma tus emociones, ordena tus pensamientos y continúa. Para obtener el enlace de descarga de un video corto, busque la Vista que reproduce el video y siga las pistas para encontrar la URL del video corto. Utilice Reveal para ver la ventana de reproducción del video corto. Puede ver que el objeto WCContentItemViewTemplateNewSigh es la ventana de reproducción del video corto. Sus subvistas incluyen WCSightView, SightView y SightPlayerView. Al guardar un video como favorito, mantenga presionado el video para que aparezca la opción, por lo que puede haber métodos relacionados con gestos en la clase WCContentItemViewTemplateNewSight. Encuentre pistas en el archivo de encabezado recién exportado.
- (void)onLongTouch;
- (void)onLongPressedWCSight:(id)arg1;
- (void)onLongPressedWCSightFullScreenWindow:(id)arg1;
Estos métodos están relacionados con el gesto de pulsación larga. Busque estas funciones en IDA y véalas una por una. onLongPressedWCSight y onLongPressedWCSightFullScreenWindow son relativamente simples, onLongTouch es relativamente largo, y encontré que el método Favorites_Add se llama internamente, porque cuando mantienes presionado el video, una opción que sale es Favoritos, y vi esta función llamar
ADRP X8, #selRef_sightVideoPath@PAGE
LDR X1, [X8,#selRef_sightVideoPath@PAGEOFF]
Obtuve la dirección del video corto aquí. Se puede especular que esta función está relacionada con la colección. Rompamos el punto y probémoslo.
(lldb) im li -o -f
[ 0] 0x000000000003c000 /var/mobile/Containers/Bundle/Application/2F1D52EC-C57E-4F95-B715-EF04351232E8/WeChat.app/WeChat(0x000000010003c000)
Puede ver que el ASLR de WeChat es 0x3c000. Encuentre las direcciones base de estas tres funciones en IDA y establezca puntos de interrupción 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
Vuelva a WeChat y mantenga presionado el video corto para ver cómo se activa el punto de interrupción.
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
……
Se descubrió que el punto de interrupción 2 se activó primero, luego el punto de interrupción 1 y luego los puntos de interrupción 2 y 1 una vez que cada uno de ellos estuvo muy silencioso. Puede excluir la conexión entre onLongPressedWCSightFullScreenWindow y la colección de videos cortos. Los rastros de vídeos cortos deben encontrarse en los dos métodos restantes. Encuentre C a V y encuentre M siguiendo las pistas, ¡lo cual ha sido probado y comprobado! Utilice cycript para inyectar WeChat y obtener el controlador donde se encuentra la vista que reproduce el vídeo corto.
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>"
Busque el controlador al que pertenece WCContentItemViewTemplateNewSight a través de la cadena de respuesta y es WCTimeLineViewController. No se encontraron pistas valiosas en el archivo de encabezado de esta clase, pero notamos que la vista donde se encuentra el video corto pertenece a MMTableVIewCell (consulte el cuadro de análisis Reveal arriba), que es el TableView y los datos de celda más familiares para cada iOS. Se asigna mediante el método proxy de UITableViewDataSource - tableView:cellForRowAtIndexPath:
A través de este método, definitivamente puedes conocer la sombra de M. Busque [WCTimeLineViewController tableView:cellForRowAtIndexPath:]
en IDA y localice la dirección base 0x10128B6B0:
__text:000000010128B6B0 ADRP X8, #selRef_genNormalCell_indexPath_@PAGE
La función aquí es el método para generar celdas en WCTimeLineViewController. Además de este método, hay otros tres métodos para generar celdas en esta clase:
- (void)genABTestTipCell:(id)arg1 indexPath:(id)arg2;
- (void)genRedHeartCell:(id)arg1 indexPath:(id)arg2;
- (void)genUploadFailCell:(id)arg1 indexPath:(id)arg2;
Del significado literal, podemos adivinar que normal debería ser un método para generar pequeñas celdas de video. Continuar buscando pistas en IDA
__text:0000000101287CC8 ADRP X8, #selRef_getTimelineDataItemOfIndex_@PAGE
El método anterior se encuentra en genNormalCell:IndexPath:
:. Puede adivinar con seguridad que este método es un método para obtener datos de TimeLine (momentos). Los datos del video corto también deben obtenerse a través de este método, y IDA puede verlos. Llame a este método. Un método llamado selRef_getTimelineDataItemOfIndex_
, ¡la obtención de DataItem parece ser la fuente de datos de la celda! A continuación, utilice LLDB para establecer puntos de interrupción para verificar la conjetura. Puede encontrar la dirección base correspondiente a este método a través de IDA: 0x101287CE4 Primero imprima el desplazamiento ASLR de WeChat en ejecución.
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)
Entonces la ubicación de nuestro punto de interrupción es 0x50000+0x101287CE4
(lldb) br s -a 0x50000+0x101287CE4
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol63721$$WeChat + 252, address = 0x00000001012d7ce4
Imprime el valor de x0
(lldb) po $x0
Class name: WCDataItem, addr: 0x15f5f03b0
tid: 12393001887435993280
username: wxid_z8twcz4o18fg12
createtime: 1477360950
commentUsers: (
)
contentObj: <WCContentItem: 0x15f57d000>
Obtenga un objeto WCDataItem. El valor de x0 aquí es el valor de retorno después de la ejecución de selRef_getTimelineDataItemOfIndex_
y luego cambie el valor de x0.
(lldb) register write $x0 0
(lldb) c
En este momento, encontrará que el contenido del pequeño video que queremos actualizar está vacío.
En este punto, hemos encontrado el método para obtener los datos de origen del video corto. La pregunta es ¿cómo obtenemos este WCDataItem? Continúe observando el estado de llamada de la función de análisis 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
......
El parámetro pasado a selRef_getTimelineDataItemOfIndex_
es x2. Puede ver que x21 pasado a x2 es el valor de retorno de la función selRef_calcDataItemIndex_
, que es un tipo de datos largos sin firmar. Continuando con el análisis, la persona que llama a selRef_getTimelineDataItemOfIndex_
es el valor de retorno de selRef_getService_
en el paso anterior. Después del análisis del punto de interrupción, se descubre que es un objeto WCFacade
. Clasifiquemos las llamadas a selRef_getTimelineDataItemOfIndex_
:
La persona que llama es el valor de retorno de selRef_getService_
; el parámetro es el valor de retorno de selRef_calcDataItemIndex_
<br> Dirijamos nuestra atención a esas dos funciones y usemos el mismo principio para analizar cómo implementan la llamada.
selRef_getService_
:MMServiceCenter
. Al buscar, x19 está asignado en la posición 0x101287C88. es el retorno del [MMServiceCenter defaultCenter]
.[WCFacade class]
.selRef_calcDataItemIndex_
:WCTimeLineViewController
a la persona que llama x0 en la posición 0x101287C58.selRef_section
0x101287C4C, se encuentra que selRef_section
parámetro entrante proviene de selRef_section
o es x3.WCTimeLineViewController - (void)genNormalCell:(id) indexPath:(id)
, por lo que el parámetro de selRef_calcDataItemIndex_
es [IndexPath section]
.getTimelineDataItemOfIndex:
puede pasar [[MMServiceCenter defaultCenter] getService:[WCFacade class]]
Para obtener, sus parámetros se pueden obtener a través de la siguiente función.
[WCTimeLineViewController calcDataItemIndex:[indexPath section]]
¿Siempre sientes que falta algo? ¡Aún no hemos obtenido el indexPath! El siguiente paso es obtener indexPath, que es relativamente simple, porque estamos en [WCContentItemViewTemplateNewSight onLongTouch]
, por lo que podemos obtener MMTableViewCell, MMTableView y WCTimeLineViewController en secuencia a través de [self nextResponder]
, y luego obtener indexPath a través de [MMTableView indexPathForCell:MMTableViewCell]
.
Después de hacer esto, hemos obtenido el objeto WCDataItem. El siguiente foco debe estar en WCDataItem y finalmente obtener el video corto que queremos. Busque pistas en el archivo de encabezado de esta clase. Debido a que el video solo se puede reproducir después de descargarlo, la ruta del video debe obtenerse aquí, así que preste atención a los atributos o métodos relacionados con la URL y la ruta, y luego busque lo siguiente. sospechosos.
@property(retain, nonatomic) NSString *sourceUrl2;
@property(retain, nonatomic) NSString *sourceUrl;
- (id)descriptionForKeyPaths;
- (id)keyPaths;
Regrese a LLDB e imprima estos valores con puntos de interrupción para ver qué hay allí.
(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
No hay pistas valiosas, pero noté que hay un WCContentItem en WCDataItem. ¡Parece que la única forma de comenzar es aquí!
@property(retain, nonatomic) NSString *linkUrl;
@property(retain, nonatomic) NSString *linkUrl2;
@property(retain, nonatomic) NSMutableArray *mediaList;
Imprímelo en 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>
)
Hay un objeto WCMediaItem en la matriz mediaList. Los medios se usan generalmente para representar video y audio. ¡Supongo que esto es todo! Encuentre rápidamente el archivo de encabezado y búsquelo.
@property(retain, nonatomic) WCUrl *dataUrl;
- (id)pathForData;
- (id)pathForSightData;
- (id)pathForTempAttachVideoData;
- (id)videoStreamForData;
Entre los atributos y métodos anteriores, pathForSightData
es el que tiene más probabilidades de obtener la ruta de video corta. Continúe verificando.
(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
¡Obtuve la URL de la red y la ruta local del video corto! Aquí puedes usar iFunBox o scp para copiar este archivo desde el sandbox y ver si es un video corto que esta celda debería reproducir.
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
Ábrelo con QuickTime y descubre que efectivamente es el vídeo corto que estamos buscando. Verifique nuevamente si la URL es correcta. Abra la URL de datos impresa arriba en el navegador y descubra que también es este breve video. Analizando esta clase podemos sacar las siguientes conclusiones:
En este punto, se ha completado el análisis de la ruta y el método de adquisición del video corto. Para lograr el reenvío, debemos continuar analizando el lanzamiento de WeChat Moments.
Esta sección es un desvío que tomé cuando buscaba la función de reenvío de videos cortos. Al final, no encontré una manera de implementarla. Sin embargo, también proporciona algunas ideas y métodos de uso común en ingeniería inversa. Si no quieres leerlo, puedes pasar a la segunda sección.
Abra la interfaz de grabación del video corto y use cycript para inyectarlo. Necesitamos averiguar qué método se usa para publicar el video corto y luego verificar qué ventanas están en la ventana actual (porque la grabación del video corto no lo es). hecho en la ventana clave de la aplicación UIA)
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>>"
)
Se encuentra que la página actual tiene un total de 5 ventanas, entre las cuales MMUIWindow es la ventana donde se grabó el video corto. Imprima su estructura de árbol de interfaz de usuario.
cy# [#0x127796440 recursiveDescription]
El resultado impreso es bastante largo, así que no lo publicaré. Encuentra este botón que es el botón para grabar videos cortos.
| | | | | | <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>>
y luego ejecutar
cy# [#0x1277a9d70 setHidden:YES]
Descubrí que el botón de disparo desapareció, lo que confirmó mi sospecha. Para encontrar el evento de respuesta del botón, puede encontrarlo a través de target
cy# [#0x1277a9d70 allTargets]
[NSSet setWithArray:@[#"<MainFrameSightViewController: 0x1269a4600>"]]]
cy# [#0x1277a9d70 allControlEvents]
193
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:193]
null
Descubrí que el botón no tiene ninguna acción correspondiente, ¡lo cual es extraño! UIButton debe tener un objetivo y una acción; de lo contrario, el botón no puede responder a eventos. Probemos otros ControlEvents
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchDown]
@["btnPress"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpOutside]
@["btnRelease"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpInside]
@["btnRelease"]
Resulta que estos tres ControlEvents tienen acciones correspondientes. Echemos un vistazo a los valores de estas tres enumeraciones.
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;
Se puede ver que UIControlEventTouchDown corresponde a 1, UIControlEventTouchUpInside corresponde a 128, UIControlEventTouchUpOutside corresponde a 64 y la suma de los tres es exactamente 193. Resulta que al llamar a [#0x1277a9d70 allControlEvents]
, el valor devuelto debería ser una enumeración. Si hay varias enumeraciones, agregue sus valores. ¡Siento lo mismo! Justo ahora imprimimos las acciones correspondientes a los tres ControlEvents y continuamos con LLDB+IDA para el análisis dinámico.
Debido a que necesitamos encontrar una manera de publicar videos cortos, no nos importa la función btnPress
correspondiente. Nos centramos en btnRelease
, un método que se llamará después de soltar el botón de disparo. Encuentre este método en IDA y establezca el siguiente punto de interrupción después de encontrarlo
(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
Graba un vídeo corto con tu teléfono móvil y luego suéltalo, lo que activa el punto de interrupción, lo que demuestra que nuestra suposición es correcta. Continuando con el análisis, encontramos que el código está en el lado derecho de la imagen de arriba. Después de mirarlo, no hay forma de saltar al lanzamiento del video. Sin embargo, si miras con atención, hay un bloque, que es el. bloque de retardo del sistema y está ubicado en 0x102093760. Luego seguimos el punto de interrupción y saltamos a la dirección almacenada en x16 en 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>
Se descubre que el parámetro x2 pasado es un bloque. Revisemos la función despacho_después nuevamente.
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
Esta función tiene tres parámetros, a saber, despacho_time_t, despacho_queue_t y despacho_block_t. El x2 impreso aquí es el bloque que se pasará, por lo que suponemos que habrá un retraso después de grabar el video corto y luego ejecutar el bloque que acaba de pasar. entonces x2 debe ser Si hay otras llamadas a métodos, el siguiente paso es conocer la ubicación de este bloque.
(lldb) memory read --size 8 --format x 0x16fd49f88
0x16fd49f88: 0x000000019f8fd218 0x00000000c2000000
0x16fd49f98: 0x000000010214777c 0x0000000102fb0e60
0x16fd49fa8: 0x000000015da32600 0x000000015ea1a430
0x16fd49fb8: 0x000000015cf5fee0 0x000000016fd49ff0
0x000000010214777c es la ubicación del bloque. Por supuesto, se debe restar el desplazamiento ASLR actual de WeChat. La dirección final en IDA es 0x10209377C. De repente, se descubre que esta es la subrutina de btnRelease
sub_10209377C. Esta subrutina es muy simple y tiene un solo método selRef_logicCheckState_
que puede ser nuestro objetivo. Primero veamos quién llamó a este 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>
Descubrí que todavía lo llama el objeto MainFrameSightViewController, por lo que selRef_logicCheckState_
también debe estar en el archivo de encabezado de esta clase. Lo busqué y lo encontré.
- (void)logicCheckState:(int)arg1;
Busque [MainFrameSightViewController logicCheckState:] en la ventana izquierda de IDA y descubra que este método es súper complicado y tiene demasiada lógica, así que no se preocupe y revíselo lentamente. Encontramos un salto de interruptor en 0x102094D6C y la idea era muy clara. Solo necesitamos encontrar la línea donde se grabó el video corto y mirar hacia abajo nos ayudará a ver la línea. Establezca un punto de interrupción en 0x102094D6C. Este punto de interrupción se activará varias veces al grabar videos cortos. Puede desactivar el punto de interrupción antes de grabar, habilitar el punto de interrupción antes de soltarlo e imprimir el valor x8 en este momento.
(lldb) p/x $x8
(unsigned long) $38 = 0x0000000102174e10
x8 es un puntero y la dirección a la que apunta es 0x102174e10. Utilice esta dirección menos el desplazamiento ASLR actual para encontrar la dirección base en IDA. Se encuentra que es 0x102094E10. Se encuentra la línea de procesamiento lógico para completar el disparo. Después de la posición 0x102094E24, salte a 0x1020951C4. Esta rama tiene menos contenido y contiene tres funciones.
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 ellos, es necesario centrarse en selRef_finishWriter
y selRef_turnCancelBtnForFinishRecording
. Estos dos métodos parecen significar el final de la grabación de videos cortos. Las pistas probablemente estén en estas dos funciones. Al observar a la persona que llama, descubrimos que estos dos métodos pertenecen a MainFrameSightViewController. Continúe verificando estos dos métodos en IDA. Encontré un método llamado f_switchToSendingPanel
cerca del final de selRef_finishWriter
en 0x102094248, establecí un punto de interrupción y luego grabé el video. Descubrí que este método no se activó. La interfaz de publicación no debe llamarse a través de este método, así que continúe regresando selRef_finishWriter
; llame al método selRef_stopRecording
en la ubicación de 0x1020941DC, y la persona que llama que lo imprime descubre que este método pertenece a SightFacade
y continúa buscando el implementación de este método en IDA. Se vuelve a llamar selRef_stopRecord
en la posición 0x101F9BED4 de este método. La persona que llama también descubre que este método pertenece a SightCaptureLogicF4. Es un poco como pelar una cebolla y continúa buscando la implementación de este método. Se vuelve a llamar selRef_finishWriting
en la posición 0x101A98778 dentro de este método. Utilizando el mismo principio, se descubre que este método pertenece a SightMovieWriter. Se han despegado tres capas, continuemos:
Se dividen dos líneas en la posición 0x10261D004 en SightMovieWriter - (void)finishWriting
Se establece un punto de interrupción en esta posición, y luego el punto de interrupción se activa después de grabar el video corto y se imprime el valor de x19.
(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 }>
Por lo tanto, el código no saltará a loc_10261D054 sino a la izquierda. En el código de la izquierda, se encuentra que un bloque está habilitado. Este bloque es la subrutina sub_10261D0AC y la dirección es 0x10261D0AC. como se muestra a continuación:
Se puede ver que se divide principalmente en dos líneas. Establecemos un punto de interrupción al final del primer cuadro, que es 0x10261D108. Después de activar el punto de interrupción, el valor de x0 se imprime como 1. El código ensamblador aquí. es
__text:000000010261D104 CMP X0, #2
__text:000000010261D108 B.EQ loc_10261D234
B.EQ saltará a loc_10261D234 solo cuando el resultado del paso anterior sea 0, pero el resultado aquí no sea 0. Cambie el valor de x0 a 2 para que el resultado del paso anterior sea 0.
(lldb) po $x0
1
(lldb) register write $x0 2
(lldb) po $x0
2
En este momento, libere el punto de interrupción y espere para saltar a la interfaz de publicación de videos cortos. El resultado es que se queda atascado en esta interfaz sin ninguna respuesta, por lo que supongo que la lógica para realizar el salto debería estar en la línea de la derecha. y continúe buscando a lo largo de la línea de la derecha: Se encontró que se llamó al siguiente método en 0x10261D3AC a la derecha
- (void)finishWritingWithCompletionHandler:(void (^)(void))handler;
Este método es un método en AVAssetWriter proporcionado por el sistema. Es una operación que se debe realizar después de que se complete la escritura del video. Aquí, se pasa un bloque. Debido a que solo hay un parámetro, la variable correspondiente es x2 y el valor. de x2 se imprime.
(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
Y encuentre la posición del bloque 0x10261D4B0 a través de la memoria de la pila (es necesario restar el desplazamiento de ASLR)
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
Solo se llaman dos métodos, uno es selRef_stopAmr
para detener amr (un formato de audio) y el otro es selRef_compressAudio
para comprimir audio. La siguiente operación después de disparar no debe colocarse en estos dos métodos. y todavía no tengo ni idea. Este camino parece estar muerto, no te metas en problemas, retírate estratégicamente y busca otras entradas.
Lo divertido de ir al revés es que puedes experimentar la alegría del éxito en el camino hacia la verdad. También puedes ir en la dirección equivocada y alejarte cada vez más de la verdad. No te desanimes, ajusta la dirección. ¡y sigue adelante!
(Dado que WeChat se actualizó en segundo plano en secreto, el siguiente contenido es el ASLR de WeChat versión 6.3.30, y el análisis anterior se basa en la versión 6.3.28)
Tenga en cuenta que cuando hace clic en el botón de la cámara en la esquina superior derecha del círculo de amigos, aparecerá una hoja en la parte inferior. La primera es el video corto de Vista. Comience aquí y use cycript para ver a qué evento corresponde el botón Vista. a.
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()
La hoja en la parte inferior es NewYearActionSheet y luego imprima el diagrama de estructura de árbol de la interfaz de usuario de NewYearActionSheet (es demasiado largo, por lo que no lo publicaré). Luego encuentre que el UIButton correspondiente a Sight es 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:"]
El evento vinculado al botón se puede encontrar a través actionsForTarget:forControlEvent:
de UIControl. El método de activación del botón Sight es OnDefaultButtonTapped:
Regrese a IDA y busque este método en NewYearActionSheet. Continuamos analizando solo este método selRef_dismissWithClickedButtonIndex_animated
Al imprimir su llamador, encontramos que todavía es NewYearActionSheet. Continúe haciendo clic para encontrar el método newYearActionSheet_clickedButtonAtIndex
. Parece que no es NewYearActionSheet en sí. Al imprimir el llamador x0, encontramos que pertenece a la clase WCTimeLineViewController. Siga el punto de interrupción y llame al método #selRef_showSightWindowForMomentWithMask_byViewController_scene
en la posición 0x1012B78CC. A través de la observación, encontramos que la persona que llama a este método es el valor de retorno x0 en la posición 0x1012B78AC. Supongo que este método está en SightFacade. Fui al archivo de encabezado para encontrarlo y encontré este método.
- (void)showSightWindowForMomentWithMask:(id)arg1 byViewController:(id)arg2 scene:(int)arg3;
Este método debería ser el método para saltar a la pequeña interfaz de video. Imprima sus parámetros por separado a continuación
(lldb) po $x2
<UIImage: 0x14f046660>, {320, 568}
(lldb) po $x3
<WCTimeLineViewController: 0x14e214800>
(lldb) po $x4
2
(lldb) po $x0
<SightFacade: 0x14f124b40>
Entre ellos, x2, x3 y x4 corresponden a tres parámetros respectivamente. x0 es la persona que llama. Salte al interior de este método para ver cómo implementarlo. Se descubre que la interfaz de grabación de video corto se inicializa en este método. Primero, se inicializa un MainFrameSightViewController, luego se crea un UINavigationController para colocar el MainFrameSightViewController y luego se inicializa un MMWindowController para llamar.
- (id)initWithViewController:(id)arg1 windowLevel:(int)arg2;
El método coloca UINavigationController y completa todo el trabajo de creación de la interfaz de usuario para la interfaz de grabación de videos cortos. Una vez completado el rodaje, ingrese a la interfaz de publicación. En este momento, use cycript para encontrar que el controlador actual es SightMomentEditViewController. De esto, surge una idea, ¿no es suficiente omitir la interfaz de disparo anterior e ingresar directamente a la interfaz de publicación? ? Creamos un SightMomentEditViewController nosotros mismos y lo colocamos en UINavigationController, y luego colocamos este controlador de navegación en MMWindowController. **(He escrito el ajuste aquí para verificación, y las ideas de ajuste específicas se escriben más adelante)** El resultado es que la interfaz de publicación puede aparecer, pero la barra de navegación de la barra de navegación cubre la original y toda la La interfaz es transparente, es fea y una vez completada la publicación, todo el MMWindowController no se puede destruir y aún permanece en la interfaz de publicación. Este no es el resultado que queremos, pero ganamos mucho. Al menos podemos llamar directamente a la interfaz de publicación y los videos pequeños se pueden reenviar normalmente. Mi suposición personal es que la razón por la que la interfaz actual no se puede destruir es porque MMWindowController creó una nueva ventana, que no es la misma que la ventana clave donde se encuentra TimeLine. El método de activación del botón de SightMomentEditViewController no puede destruir esta ventana, por lo que tengo una. Supongo que no puedo simplemente mostrar SightMomentEditViewController directamente en el WCTimeLineViewController actual.
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
¿No sería bonito mostrarlo así? Sin embargo, al observar el archivo de encabezado de SightMomentEditViewController, combinado con los elementos en la interfaz cuando se publica el video corto, se especula que la creación de este controlador requiere al menos dos atributos, uno es la ruta del video corto y el otro es la miniatura del video corto. Estas dos claves. Si el atributo se asigna a SightMomentEditViewController, debería mostrarse 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:^{
}];
La interfaz de publicación de videos cortos se puede mostrar normalmente y todas las funciones se pueden usar normalmente. El único problema es que el botón de retorno no tiene ningún efecto y SightMomentEditViewController no se puede destruir. Use cycript para verificar el actionEvent del botón izquierdo y descubra que su función de respuesta es - (void)popSelf;
Al hacer clic en el botón izquierdo para regresar, se activa el método pop, pero este controlador no está en navgationController, por lo que no es válido. Necesito rehacer este método.
- (void)popSelf
{
[self dismissViewControllerAnimated:YES completion:^{
}];
}
En este momento, haga clic en el botón Volver para salir normalmente. Además, se encontró un método llamado - (void)sendSightToFriend;
en WCContentItemViewTemplateNewSight, que puede reenviar videos cortos directamente a amigos. Hasta ahora, se ha encontrado la función de reenviar videos cortos. .
El reenvío de videos cortos admite 4 funciones: reenviar a Momentos, reenviar a amigos, guardar en un álbum de fotos local y copiar el enlace del video corto al tablero. Si el video corto no se descarga, al presionarlo prolongadamente solo se mostrará el enlace URL del video corto.
Aquí necesitamos conectar dos clases, a saber, WCContentItemViewTemplateNewSight y SightMomentEditViewController. Conecte el método onLongTouch en WCContentItemViewTemplateNewSight y luego agregue el menú emergente y agregue los métodos de respuesta a su vez. El código específico es el siguiente:
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];
Puse el archivo de ajuste específico en 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"//安装完成杀掉的进程
No es necesario modificar el archivo de control y luego ejecutar el comando make package install
para instalarlo en el teléfono móvil. Se eliminará WeChat y luego se abrirá WeChat nuevamente. Se agregó la función de reenviar videos cortos.
Instale macports (el proceso de instalación requiere una conexión VPN; de lo contrario, la instalación no se realizará correctamente)
Después de instalar MacPorts, abra la terminal e ingrese sudo port -v selfupdate
para actualizar MacPorts a la última versión, lo que puede llevar mucho tiempo.
Después de actualizar MacPorts, instale el archivo DPKG e ingrese sudo port -f install dpkg
en la terminal.
Descargue e instale iOSOpendev. Si la instalación falla, puede usar Command + L
para verificar si hay problemas durante la instalación.
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";
}
Aquí hay una solución: descargue la carpeta Especificaciones en iOSOpenDevInstallSolve
5. Para solucionar el problema de falla de instalación, abra la carpeta Especificaciones descargada en el paso 4. Debe haber 8 archivos en ella. Si tiene varios xcodes instalados, colóquelos en los xcodes correspondientes.
(1) Los cuatro archivos que comienzan con iPhoneOS se colocan en la carpeta /Application/Xcode/Content/Developer/Platforms/IphoneOS.platform/Developer/Library/Xcode/Specifications (si no, cree una carpeta de Especificaciones usted mismo)
(2) Los otros cuatro archivos que comienzan con iPhone Simulator se colocan en la carpeta /Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications (si no, cree uno también)
(3) Cree una carpeta usr en la carpeta /Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/ y cree una carpeta llamada bin debajo de la carpeta usr.
Nota: A veces aparecerá un mensaje que indica que la instalación falló. Abra un nuevo proyecto en Xcode. Si hay iOSOpenDev en el menú de opciones del proyecto, significa que la instalación se realizó correctamente.
Para instalar el paquete ipa también puedes utilizar herramientas como itool, pero ideviceinstaller puede ver el proceso de instalación, lo que nos facilita encontrar la causa del error.
ejecutar comando
brew install ideviceinstaller
Si se le indica que no se puede encontrar el comando brew, significa que Homebrew no se ha instalado en su Mac.
Mensajes de error comunes:
ERROR: Could not connect to lockdownd, error code -5
En este momento, simplemente reinstale libimobiledevice (porque ideviceinstaller depende de muchos otros complementos)
Ejecute el siguiente comando:
$ brew uninstall libimobiledevice
$ brew install --HEAD libimobiledevice
Descargue la herramienta para volver a firmar iOS App Signer* (guarde muchas operaciones de línea de comandos, ¡vuelva a firmar con un solo clic!)*
(3) Descargue la aplicación WeChat pirateada
Debido a que el paquete de AppStore está cifrado (descascado) y no se puede volver a firmar, debe usar uno sin cáscara. Puede usar dumpdecrypted para volcar el shell usted mismo, o puede usar directamente el asistente PP o el asistente itool para descargar la versión con jailbreak. de la aplicación WeChat que ha sido bombardeada.
(4) Instalar yololib
yololib puede inyectar dylib en el archivo binario de WeChat, para que su Hook pueda ser efectivo Después de descargar, compilar y obtener yololib.
#####(1) Generar biblioteca estática. iOSOpendev se instaló en el paso anterior. Ahora abra un nuevo proyecto en Xcode. El proyecto iOSOpendev aparecerá en la interfaz de selección de proyectos. Aquí debemos seleccionar el proyecto CaptainHook Tweak. Solo hay un proyecto recién creado. Archivo mm, solo necesitamos escribir todos los métodos de enlace en este archivo.
Debido a que las máquinas sin jailbreak no pueden instalar complementos de ajuste para conectar aplicaciones originales como máquinas con jailbreak, CaptainHook usa el mecanismo Runtime para implementar funciones como la definición de clases y el reemplazo de métodos usando comandos macro. Aquí hay una breve introducción sobre cómo usarlo:
CHDeclareClass(WCContentItemViewTemplateNewSight);
CHDeclareClass(ClassName)
indica qué clase enlazar y generalmente se escribe al comienzo de la operación en esta clase.
CHDeclareMethod0(id, WCContentItemViewTemplateNewSight, SLSightDataItem){......}
CHDeclareMethod(count, return_type, class_type, name1, arg1, ....)
significa crear un nuevo método, count significa el número de parámetros de este método, return_type significa el tipo de retorno, class_type completa el nombre de clase de este método, name1 significa el nombre del método, arg1 representa el primer parámetro, si no hay ningún parámetro, déjelo en blanco, y así sucesivamente.
CHOptimizedMethod0(self, void, WCContentItemViewTemplateNewSight, onLongTouch){
CHSuper(0, className, Method);//可选
......
}
CHOptimizedMethod(count, optimization, return_type, class_type, name1, type1, arg1)
significa conectar el método original (si CHSuper(0, className, Method)
significa copiar el método original, CHSuper significa llamar al método original en la posición actual), contar significa el número de parámetros del método de enlace, la optimización generalmente se completa auto, return_type es el tipo de valor de retorno del método, class_type completa el nombre de clase de la clase actual, nombre1 es el nombre del método, arg1 es el parámetro, si no hay parámetros, complete arg, etc.
CHConstructor
{
@autoreleasepool
{
CHLoadLateClass(WCContentItemViewTemplateNewSight);
CHHook(0, WCContentItemViewTemplateNewSight, onLongTouch);
}
}
Esta es la función de entrada de CaptainHook. Todas las clases enganchadas deben declararse aquí para cargarse, y los métodos de la clase deben declararse aquí.
Luego puedes escribir código en las clases y métodos. El código es demasiado largo, así que no lo publicaré. Lo puse en github con MMPlugin.
Este proyecto incluye las funciones de reenviar videos cortos, tomar sobres rojos automáticamente y modificar los pasos del ejercicio de WeChat. Las funciones de tomar sobres rojos automáticamente y modificar los pasos del ejercicio de WeChat se pueden desactivar manualmente.
Nota: Si usa clases de sistema, recuerde importar la biblioteca de clase correspondiente (como UIKIT) y los archivos de encabezado, de lo contrario, se informará un error durante la compilación.
Después de una compilación exitosa, puede encontrar la biblioteca estática compilada en la carpeta de productos.
Encuéntralo en Finder y cópielo para su uso posterior.
Los materiales que debe tener en este momento incluyen:
Encuentre el archivo binario de WeChat de la aplicación WeChat original y copie para su uso posterior.
Ejecute el siguiente comando para inyectar mmplugin.dylib en el archivo binario WeChat.
LeonLei-MBP:WeChat gaoshilei$ ./yololib WeChat MMPlugin.dylib
Al ejecutar este comando, asegúrese de que Yololib, WeChat y WeChat.App estén en el mismo directorio.
Después de completar, copie mmplugin.dylib y WeChat en el weChat.app original, sobrescribiendo el archivo WeChat original.
Abra el firmante de la aplicación iOS y seleccione varios parámetros como se muestra a continuación:
Lo que elegí aquí es un certificado de desarrollador personal.
Conéctese a su teléfono móvil y ejecute el siguiente comando para verificar si IDeviceInstaller está conectado al teléfono móvil:
LeonLei-MBP:WeChat gaoshilei$ ideviceinfo
Si se imprime mucha información del teléfono móvil, significa que la conexión es exitosa y puede instalar el paquete IPA. Ejecute el siguiente 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
Después de completar la instalación, abra WeChat en su teléfono para probar las nuevas funciones que agregamos. Si un enlace determinado está atascado, se informará un error. Consulte las representaciones: