2020.1.14 업데이트에는 탈옥 설치가 필요하지 않습니다.
이 기사는 Moments에서 짧은 비디오 전달을 구현하기 위해 WeChat 바이너리 파일을 역전시키는 방법에 대한 튜토리얼입니다. 초기 어셈블리 코드부터 최종 재서명 설치 및 기타 작업까지 WeChat을 사용하는 방법을 단계별로 알려줍니다. 배우고 나면 다른 WeChat 기능을 쉽게 리버스 엔지니어링할 수 있습니다.
이 글은 길이 때문에 두 부분으로 나누어집니다. 첫 번째 부분은 역작업, 즉 구현에 필요한 기능과 방법을 찾는 방법을 설명합니다. 두 번째 부분은 탈옥되지 않은 머신에서 설치를 다시 서명하는 방법을 설명합니다. 탈옥된 컴퓨터에서 설치를 조정합니다.
텍스트의 두 번째 부분에서는 WeChat이 자동으로 빨간 봉투를 잡고 WeChat 단계 수를 수정하는 코드를 제공합니다. 이 코드는 이 기사의 루틴에 따라 단계별로 찾을 수 있으며 여기서는 반복하지 않습니다.
연습하기 전에 탈옥된 휴대폰을 준비한 후 아래 나열된 모든 도구를 설치해야 합니다. IDA와 Reveal은 모두 크랙 버전입니다. IDA의 정품 버전은 정말 2,000달러가 넘습니다. 하지만 리버스 엔지니어링을 전문으로 하는 회사가 아니라면 그럴 가치가 없습니다. 정품 버전을 사용해야 합니다. Windows의 다음 크랙 버전은 괜찮을 것입니다. 아직 Mac에서는 찾을 수 없습니다. 호퍼를 사용하면 매우 강력한 리버스 엔지니어링 도구인 Mac의 IDA를 대체할 수 있습니다. 더 이상 고민하지 말고 시작해 보세요!
참고: 이 문서에서 반전된 WeChat 바이너리 파일은 버전 6.3.28입니다. 다른 WeChat 버전인 경우 바이너리 파일의 기본 주소도 다릅니다.
MacOS + iPhone5S 9.1 탈옥 시스템용 역방향 환경 <br> 먼저 dumpdecrypted를 사용하여 WeChat의 셸을 부수고(방법을 모르는 경우 제가 작성한 이 튜토리얼을 읽어보세요) WeChat.decrypted 파일을 얻은 다음 먼저 분석을 위해 이 파일을 IDA에 넣습니다(약 60MB의 바이너리 파일). , IDA 분석에 거의 40분 소요 끝), class-dump를 사용하여 모든 헤더 파일 내보내기
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
나에겐 시어머니가 있다! 총 8,000개의 헤더 파일이 있는데, WeChat에는 정말 엄청난 양의 작업이 있습니다! 감정을 진정시키고 생각을 모으고 계속하십시오. 짧은 비디오의 다운로드 링크를 얻으려면 비디오를 재생하는 보기를 찾고 단서를 따라 짧은 비디오의 URL을 찾으십시오. Reveal을 사용하여 짧은 비디오의 재생 창을 봅니다. WCContentItemViewTemplateNewSigh 개체의 하위 보기에는 WCSightView, SightView 및 SightPlayerView가 포함됩니다. 동영상을 즐겨찾기에 저장할 때 동영상을 길게 누르면 옵션이 팝업되므로 방금 내보낸 헤더 파일에서 WCContentItemViewTemplateNewSight 클래스에 제스처 관련 메서드가 있을 수 있습니다.
- (void)onLongTouch;
- (void)onLongPressedWCSight:(id)arg1;
- (void)onLongPressedWCSightFullScreenWindow:(id)arg1;
이 방법들은 길게 누르기 동작과 관련되어 있습니다. IDA에서 해당 기능을 찾아 하나씩 살펴보세요. onLongPressedWCSight와 onLongPressedWCSightFullScreenWindow는 상대적으로 간단하고, onLongTouch는 상대적으로 길며, Favorites_Add 메소드가 내부적으로 호출되는 것을 발견했습니다. 영상을 길게 누르면 나오는 옵션이 Favorites인데, 이 함수 호출을 봤습니다.
ADRP X8, #selRef_sightVideoPath@PAGE
LDR X1, [X8,#selRef_sightVideoPath@PAGEOFF]
여기에서 짧은 영상의 주소를 얻었습니다. 이 기능이 컬렉션과 관련되어 있다고 추측할 수 있습니다.
(lldb) im li -o -f
[ 0] 0x000000000003c000 /var/mobile/Containers/Bundle/Application/2F1D52EC-C57E-4F95-B715-EF04351232E8/WeChat.app/WeChat(0x000000010003c000)
WeChat의 ASLR이 0x3c000임을 알 수 있습니다. IDA에서 이 세 가지 함수의 기본 주소를 찾아 각각 중단점을 설정합니다.
(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
WeChat으로 돌아가서 짧은 비디오를 길게 눌러 중단점이 어떻게 실행되는지 확인하세요.
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
……
중단점 2가 먼저 트리거된 다음 중단점 1이 트리거된 다음 중단점 2와 1이 각각 중단점 3이 매우 조용해질 때마다 트리거되는 것으로 나타났습니다. onLongPressedWCSightFullScreenWindow와 짧은 동영상 컬렉션 간의 연결을 제외할 수 있습니다. 남은 두 가지 방법에서 짧은 영상의 흔적을 찾아야 한다. C부터 V까지 찾아보고, 이미 시도하고 테스트한 단서를 따라 M을 찾아보세요! cycript를 사용하여 WeChat을 삽입하고 짧은 비디오를 재생하는 보기가 있는 컨트롤러를 가져옵니다.
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>"
WCContentItemViewTemplateNewSight가 응답자 체인을 통해 속한 컨트롤러를 찾으면 WCTimeLineViewController입니다. 이 클래스의 헤더 파일에서는 귀중한 단서를 찾을 수 없었지만, 짧은 영상이 위치한 뷰가 모든 iOS에서 가장 친숙한 TableView 및 셀 데이터인 MMTableVIewCell(위의 Reveal 분석 차트 참조)에 속해 있음을 확인했습니다. UITableViewDataSource - tableView:cellForRowAtIndexPath:
이 메소드를 통해 M의 그림자를 확실히 알 수 있습니다. IDA에서 [WCTimeLineViewController tableView:cellForRowAtIndexPath:]
찾고 기본 주소 0x10128B6B0을 찾습니다.
__text:000000010128B6B0 ADRP X8, #selRef_genNormalCell_indexPath_@PAGE
여기서 함수는 WCTimeLineViewController에서 셀을 생성하는 메소드입니다. 이 메소드 외에도 이 클래스에는 셀을 생성하는 세 가지 다른 메소드가 있습니다.
- (void)genABTestTipCell:(id)arg1 indexPath:(id)arg2;
- (void)genRedHeartCell:(id)arg1 indexPath:(id)arg2;
- (void)genUploadFailCell:(id)arg1 indexPath:(id)arg2;
문자 그대로의 의미로 보면 Normal은 작은 비디오 셀을 생성하는 방법이어야 함을 짐작할 수 있습니다. 계속해서 IDA에서 단서를 찾으세요
__text:0000000101287CC8 ADRP X8, #selRef_getTimelineDataItemOfIndex_@PAGE
위의 메소드는 genNormalCell:IndexPath:
메소드에서 찾을 수 있는데, 이 메소드가 TimeLine(moments) 데이터를 얻기 위한 메소드라는 것을 과감히 짐작할 수 있는데, 짧은 영상의 데이터도 이 메소드를 통해 얻어야 하는데, IDA는 이를 통해 알 수 있다. 이 메소드를 호출하면 DataItem을 얻는 selRef_getTimelineDataItemOfIndex_
메소드가 셀의 데이터 소스인 것 같습니다. 다음으로 LLDB를 사용하여 중단점을 설정하여 추측을 확인합니다. IDA: 0x101287CE4를 통해 이 메서드에 해당하는 기본 주소를 찾을 수 있습니다. 먼저 WeChat을 실행하는 ASLR 오프셋을 인쇄합니다.
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)
따라서 중단점의 위치는 0x50000+0x101287CE4입니다.
(lldb) br s -a 0x50000+0x101287CE4
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol63721$$WeChat + 252, address = 0x00000001012d7ce4
x0의 값을 인쇄합니다.
(lldb) po $x0
Class name: WCDataItem, addr: 0x15f5f03b0
tid: 12393001887435993280
username: wxid_z8twcz4o18fg12
createtime: 1477360950
commentUsers: (
)
contentObj: <WCContentItem: 0x15f57d000>
WCDataItem 객체를 가져옵니다. 여기서 x0 값은 selRef_getTimelineDataItemOfIndex_
실행 후 반환 값이며 x0 값을 변경합니다.
(lldb) register write $x0 0
(lldb) c
이때 새로 고치려는 작은 비디오의 내용이 모두 비어 있음을 알 수 있습니다.
이 시점에서 우리는 짧은 비디오의 소스 데이터를 얻는 방법을 찾았습니다. 문제는 이 WCDataItem을 어떻게 얻는가입니다. 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
......
selRef_getTimelineDataItemOfIndex_
에 전달된 매개변수는 x2입니다. x2에 전달된 x21은 unsigned long 데이터 유형인 selRef_calcDataItemIndex_
함수의 반환 값임을 알 수 있습니다. 분석을 계속하면 selRef_getTimelineDataItemOfIndex_
함수의 호출자는 이전 단계의 selRef_getService_
반환 값이며 중단점 분석 결과 WCFacade
개체인 것으로 나타났습니다. selRef_getTimelineDataItemOfIndex_
에 대한 호출을 정리해 보겠습니다.
호출자는 selRef_getService_
의 반환 값이고, 매개변수는 selRef_calcDataItemIndex_
의 반환 값입니다 . 이 두 함수에 주목하고 동일한 원리를 사용하여 호출을 구현하는 방법을 분석해 보겠습니다.
selRef_getService_
를 살펴보겠습니다.MMServiceCenter
개체인 것으로 나타났습니다. x19는 0x101287C88 위치에 할당되어 있습니다. [MMServiceCenter defaultCenter]
값이 반환됩니다.[WCFacade class]
의 반환 값임을 알 수 있습니다.selRef_calcDataItemIndex_
찾으세요.WCTimeLineViewController
.selRef_section
에서 들어오는 selRef_section
변수가 selRef_section
또는 x3인 것으로 확인되었습니다.WCTimeLineViewController - (void)genNormalCell:(id) indexPath:(id)
함수의 두 번째 매개변수 indexPath이므로 selRef_calcDataItemIndex_
의 매개변수는 [IndexPath section]
입니다.getTimelineDataItemOfIndex:
호출자는 다음을 전달할 수 있습니다. [[MMServiceCenter defaultCenter] getService:[WCFacade class]]
얻으려면 다음 함수를 통해 해당 매개변수를 얻을 수 있습니다.
[WCTimeLineViewController calcDataItemIndex:[indexPath section]]
항상 뭔가 빠진 듯한 느낌이 드시나요? 아직 indexPath를 얻지 못했습니다! 다음 단계는 indexPath를 얻는 것입니다. 우리는 [WCContentItemViewTemplateNewSight onLongTouch]
에 있으므로 [self nextResponder]
통해 순서대로 MMTableViewCell, MMTableView 및 WCTimeLineViewController를 얻은 다음 [MMTableView indexPathForCell:MMTableViewCell]
.
이 작업을 수행한 후 WCDataItem 개체를 얻었습니다. 다음 초점은 WCDataItem에 있어야 하며 마지막으로 원하는 짧은 비디오를 가져와야 합니다. 본 클래스의 헤더 파일에서 단서를 찾아보세요. 영상은 다운로드를 받아야만 재생이 가능하기 때문에 영상의 경로를 여기서 얻어야 하므로 URL과 경로에 관련된 속성이나 메소드를 주의깊게 살펴보시고 다음을 찾아보세요. 용의자.
@property(retain, nonatomic) NSString *sourceUrl2;
@property(retain, nonatomic) NSString *sourceUrl;
- (id)descriptionForKeyPaths;
- (id)keyPaths;
LLDB로 돌아가서 중단점과 함께 이 값을 인쇄하여 거기에 무엇이 있는지 확인하세요.
(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
귀중한 단서는 없지만 WCDataItem에 WCContentItem이 있다는 것을 알았습니다. 시작하는 유일한 방법은 헤더 파일을 살펴보겠습니다.
@property(retain, nonatomic) NSString *linkUrl;
@property(retain, nonatomic) NSString *linkUrl2;
@property(retain, nonatomic) NSMutableArray *mediaList;
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>
)
mediaList 배열에는 WCMediaItem 개체가 있습니다. 미디어는 일반적으로 비디오와 오디오를 나타내는 데 사용됩니다. 헤더 파일을 빠르게 찾아서 검색해 보세요.
@property(retain, nonatomic) WCUrl *dataUrl;
- (id)pathForData;
- (id)pathForSightData;
- (id)pathForTempAttachVideoData;
- (id)videoStreamForData;
위의 속성 및 방법 중에서 pathForSightData
짧은 비디오 경로를 얻을 가능성이 가장 높습니다. 계속해서 확인하세요.
(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
짧은 비디오의 네트워크 URL과 로컬 경로를 얻었습니다! 여기에서 iFunBox 또는 scp를 사용하여 샌드박스에서 이 파일을 복사하여 이 셀이 재생해야 하는 짧은 비디오인지 확인할 수 있습니다.
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
QuickTime으로 열어서 실제로 우리가 찾고 있는 짧은 비디오인지 확인하세요. URL이 올바른지 다시 확인하세요. 브라우저에서 위에 인쇄된 dataUrl을 열어도 이 짧은 동영상인지 확인하세요. 이 클래스를 분석하면 다음과 같은 결론을 내릴 수 있습니다.
이 시점에서 짧은 영상의 경로와 획득 방법에 대한 분석이 완료되었습니다. 전달을 위해서는 WeChat Moments 출시에 대한 지속적인 분석이 필요합니다.
이 섹션은 제가 짧은 비디오 전달 기능을 찾을 때 우회한 부분입니다. 결국 구현 방법을 찾지 못했습니다. 그러나 리버스 엔지니어링에서 일반적으로 사용되는 몇 가지 아이디어와 방법도 제공합니다. 읽고 싶지 않다면 두 번째 섹션으로 건너뛰세요.
짧은 영상의 촬영 인터페이스를 열고 cycript를 주입해야 합니다. 짧은 영상을 게시하는 데 어떤 방법이 사용되었는지 알아낸 다음 현재 창에 어떤 창이 있는지 확인해야 합니다(짧은 영상의 촬영이 완료되지 않았기 때문입니다). UIApplication의 keyWindow에서)
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>>"
)
현재 페이지에는 총 5개의 창이 있는 것으로 확인되었으며, 그 중 MMUIWindow는 짧은 동영상이 촬영된 창이며 UI 트리 구조를 인쇄합니다.
cy# [#0x127796440 recursiveDescription]
인쇄된 결과물이 꽤 길어서 올리지 않겠습니다. 짧은 동영상을 촬영할 수 있는 버튼인 이 버튼을 찾으세요.
| | | | | | <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>>
그런 다음 실행
cy# [#0x1277a9d70 setHidden:YES]
촬영 버튼이 사라진 것을 발견하고 의심을 확인했습니다. 버튼의 응답 이벤트를 찾으려면 target을 통해 찾을 수 있습니다.
cy# [#0x1277a9d70 allTargets]
[NSSet setWithArray:@[#"<MainFrameSightViewController: 0x1269a4600>"]]]
cy# [#0x1277a9d70 allControlEvents]
193
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:193]
null
버튼에 해당하는 동작이 없다는 걸 발견했는데, 이상하네요! UIButton에는 대상과 작업이 있어야 합니다. 그렇지 않으면 버튼이 이벤트에 응답할 수 없습니다. 다른 ControlEvent를 사용해 봅시다
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchDown]
@["btnPress"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpOutside]
@["btnRelease"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpInside]
@["btnRelease"]
이 세 가지 ControlEvent에는 해당 동작이 있는 것으로 나타났습니다. 이 세 가지 열거형의 값을 살펴보겠습니다.
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;
UIControlEventTouchDown은 1에 해당하고, UIControlEventTouchUpInside는 128에 해당하고, UIControlEventTouchUpOutside는 64에 해당하며, 세 값의 합은 정확히 193임을 알 수 있습니다! [#0x1277a9d70 allControlEvents]
호출 시 반환되는 값은 열거형이어야 합니다. 열거형이 여러 개인 경우 해당 값을 추가하세요. 나도 같은 느낌이야! 방금 우리는 세 가지 ControlEvents에 해당하는 작업을 인쇄하고 동적 분석을 위해 LLDB+IDA를 계속했습니다.
짧은 동영상을 게시할 수 있는 방법을 찾아야 하기 때문에 해당 btnPress
함수는 신경쓰지 않습니다. 촬영 버튼을 놓은 후 호출되는 btnRelease
메서드에 중점을 둡니다. IDA에서 이 메서드를 찾아 찾은 후 다음 중단점을 설정합니다.
(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
휴대폰으로 짧은 동영상을 촬영한 후 놓으면 중단점이 발생하여 추측이 정확하다는 것을 알 수 있습니다. 계속해서 분석해 보니 위 사진의 오른쪽 부분에 코드가 있는 것을 발견했습니다. 살펴보니 영상 공개로 바로 이동할 수 있는 방법이 없습니다. 하지만 잘 살펴보면 블록이 있습니다. 시스템의 지연 블록이며 0x102093760에 있습니다. 그런 다음 중단점을 따라 x16에 저장된 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>
전달된 매개변수 x2가 블록인 것으로 확인되었습니다. dispatch_after 함수를 다시 살펴보겠습니다.
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
이 함수에는 dispatch_time_t, dispatch_queue_t, dispatch_block_t라는 3개의 매개변수가 있습니다. 여기에 출력된 x2는 전달될 블록이므로 짧은 영상을 촬영한 후 지연이 있을 것이라고 추측한 후 방금 전달된 블록을 실행합니다. 따라서 x2는 반드시 있어야 합니다. 다른 메서드 호출이 있는 경우 다음 단계는 이 블록의 위치를 아는 것입니다.
(lldb) memory read --size 8 --format x 0x16fd49f88
0x16fd49f88: 0x000000019f8fd218 0x00000000c2000000
0x16fd49f98: 0x000000010214777c 0x0000000102fb0e60
0x16fd49fa8: 0x000000015da32600 0x000000015ea1a430
0x16fd49fb8: 0x000000015cf5fee0 0x000000016fd49ff0
0x000000010214777c는 블록의 위치입니다. 물론 현재 WeChat의 ASLR 오프셋을 빼야 합니다. IDA의 최종 주소는 0x10209377C입니다. 갑자기 이것이 btnRelease
sub_10209377C의 서브루틴임을 발견합니다. 이 서브루틴은 매우 간단하며 대상이 될 수 있는 selRef_logicCheckState_
메소드가 하나만 있습니다. 먼저 누가 이 메소드를 호출했는지 살펴보겠습니다.
(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>
MainFrameSightViewController 개체에 의해 여전히 호출되는 것을 발견했습니다. 그러면 selRef_logicCheckState_
도 이 클래스의 헤더 파일에 있어야 합니다.
- (void)logicCheckState:(int)arg1;
IDA의 왼쪽 창에서 [MainFrameSightViewController logicCheckState:]를 찾아보면 이 메서드가 매우 복잡하고 로직이 너무 많기 때문에 걱정하지 말고 천천히 살펴보세요. 우리는 0x102094D6C에서 스위치 점프를 찾았고 아이디어는 매우 명확했습니다. 짧은 비디오가 촬영된 라인만 찾고 LLDB가 라인을 보는 데 도움이 될 것입니다. 0x102094D6C에 중단점을 설정하세요. 이 중단점은 짧은 동영상을 촬영할 때 여러 번 실행됩니다. 촬영 전에 중단점을 비활성화하고, 놓기 전에 중단점을 활성화하고 이때 x8 값을 인쇄할 수 있습니다.
(lldb) p/x $x8
(unsigned long) $38 = 0x0000000102174e10
x8은 포인터이고, 그것이 가리키는 주소는 0x102174e10이다. 이 주소에서 현재 ASLR 오프셋을 뺀 값을 IDA에서 찾아보면 0x102094E10이라는 것을 알게 된다. 0x102094E24 위치 이후에는 0x1020951C4로 점프합니다. 이 분기에는 내용이 적고 세 가지 기능이 포함되어 있습니다.
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
그 중 selRef_finishWriter
와 selRef_turnCancelBtnForFinishRecording
에 주목해야 합니다. 이 두 가지 방법은 짧은 영상 녹화의 끝을 의미하는 것으로 보입니다. 단서는 이 두 가지 기능에 있을 가능성이 가장 높습니다. 호출자를 살펴보면 이 두 메서드가 MainFrameSightViewController에 속해 있음을 발견했습니다. IDA에서 이 두 메서드를 계속 확인하세요. 0x102094248의 selRef_finishWriter
끝 부분에서 f_switchToSendingPanel
이라는 메서드를 발견하고 중단점을 설정한 다음 비디오를 촬영했는데 이 메서드가 트리거되지 않는 것을 발견했습니다. 게시 인터페이스는 이 메소드를 통해 호출되어서는 안 되므로 계속해서 selRef_finishWriter
메소드로 돌아가서 0x1020941DC 위치에서 selRef_stopRecording
메소드를 호출하면 이를 인쇄하는 호출자는 이 메소드가 SightFacade
에 속함을 발견하고 계속해서 IDA에서 이 방법을 구현합니다. selRef_stopRecord
는 이 메소드의 0x101F9BED4 위치에서 다시 호출됩니다. 호출자는 또한 이 메소드가 SightCaptureLogicF4에 속한다는 사실을 발견합니다. 이것은 마치 양파 껍질을 벗기는 것과 비슷하며 이 메소드의 구현을 계속 찾습니다. selRef_finishWriting
은 이 메소드 내에서 0x101A98778 위치에서 다시 호출됩니다. 동일한 원리를 사용하면 이 메소드가 SightMovieWriter에 속함을 알 수 있습니다. 세 개의 레이어가 벗겨졌습니다. 계속해 보겠습니다.
SightMovieWriter - (void)finishWriting
이 위치에 브레이크포인트가 설정되고, 짧은 영상을 촬영한 후 브레이크포인트가 실행되어 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 }>
따라서 코드는 loc_10261D054로 점프하지 않고 왼쪽으로 이동합니다. 왼쪽 코드에서 이 블록은 서브루틴 sub_10261D0AC이고 주소는 0x10261D0AC입니다. 아래와 같이:
크게 두 줄로 나누어져 있는 것을 볼 수 있는데, 첫 번째 박스의 끝부분인 0x10261D108에 브레이크포인트를 설정하고 촬영이 완료된 후 브레이크포인트가 실행되면 x0의 값이 1로 출력되는 것을 볼 수 있습니다. 어셈블리 코드는 다음과 같습니다
__text:000000010261D104 CMP X0, #2
__text:000000010261D108 B.EQ loc_10261D234
B.EQ는 이전 단계의 결과가 0일 때만 loc_10261D234로 점프하는데, 여기서의 결과는 0이 아니다. 이전 단계의 결과가 0이 되도록 x0의 값을 2로 변경한다.
(lldb) po $x0
1
(lldb) register write $x0 2
(lldb) po $x0
2
이때 중단점을 해제하고 짧은 동영상 게시 인터페이스로 점프하기를 기다립니다. 결과적으로 아무런 응답 없이 이 인터페이스에 갇히게 되므로 점프를 구현하는 로직은 오른쪽 줄에 있어야 할 것 같습니다. 그리고 계속해서 오른쪽 라인을 따라 검색해 보면, 올바른 위치 0x10261D3AC에서 다음 메소드가 호출된 것으로 확인되었습니다.
- (void)finishWritingWithCompletionHandler:(void (^)(void))handler;
이 메소드는 시스템에서 제공하는 AVAssetWriter의 메소드로, 영상 작성이 완료된 후 수행하는 작업입니다. 여기서는 블록을 전달합니다. 매개변수는 1개이므로 해당 변수는 x2이고, 값은 입니다. x2가 인쇄됩니다.
(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
그리고 스택 메모리를 통해 블록 위치 0x10261D4B0을 찾습니다(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
두 가지 메소드만 호출하는데, 하나는 amr(오디오 형식)을 중지하는 selRef_stopAmr
이고, 다른 하나는 오디오를 압축하는 selRef_compressAudio
입니다. 이 두 메소드에는 촬영이 완료된 후 다음 작업을 배치하면 안 됩니다. 너무 오랫동안 나는 아직 아무것도 모르는 것 같습니다. 이 길은 죽은 것 같습니다. 문제에 빠지지 말고 전략적으로 후퇴하고 다른 입구를 찾으십시오.
역행의 기쁨은 진리를 찾는 길에서 성공의 기쁨을 경험하는 것입니다. 잘못된 방향으로 갈 수도 있고 진리에서 점점 멀어질 수도 있습니다. 낙심하지 말고 방향을 조정하고 유지하십시오. 앞으로 나아갑니다!
(WeChat이 백그라운드에서 비밀리에 업그레이드되었기 때문에 다음 내용은 WeChat 6.3.30 버전의 ASLR이며, 위 분석은 6.3.28 버전을 기준으로 합니다.)
친구 서클의 오른쪽 상단에 있는 카메라 버튼을 클릭하면 하단에 시트가 나타납니다. 첫 번째는 여기에서 시작하여 cycript를 사용하여 Sight 버튼이 해당하는 이벤트를 확인하는 것입니다. 에게.
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()
하단의 Sheet는 NewYearActionSheet이고, NewYearActionSheet의 UI 트리 구조 다이어그램을 인쇄합니다(너무 길어서 게시하지 않겠습니다). 그런 다음 Sight에 해당하는 UIButton이 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:"]
버튼에 바인딩된 이벤트는 UIControl의 actionsForTarget:forControlEvent:
메서드를 통해 찾을 수 있습니다. Sight 버튼의 트리거 메서드는 OnDefaultButtonTapped:
IDA로 돌아가서 NewYearActionSheet에서 이 메서드를 찾으면 selRef_dismissWithClickedButtonIndex_animated
호출자를 인쇄하면 여전히 NewYearActionSheet임을 알 수 있습니다. newYearActionSheet_clickedButtonAtIndex
메서드를 찾으려면 계속 클릭하세요. 호출자 x0을 인쇄하면 WCTimeLineViewController 클래스에 속한다는 것을 알 수 있습니다. 중단점을 따라가서 0x1012B78CC 위치에서 #selRef_showSightWindowForMomentWithMask_byViewController_scene
메소드를 호출합니다. 관찰을 통해 이 메소드의 호출자는 SightFacade에 있는 클래스인 것으로 추측됩니다. 이 방법을 찾기 위해 헤더 파일로 이동했습니다.
- (void)showSightWindowForMomentWithMask:(id)arg1 byViewController:(id)arg2 scene:(int)arg3;
이 방법은 작은 비디오 인터페이스로 점프하는 방법이어야 합니다. 아래에 해당 매개변수를 별도로 인쇄하세요.
(lldb) po $x2
<UIImage: 0x14f046660>, {320, 568}
(lldb) po $x3
<WCTimeLineViewController: 0x14e214800>
(lldb) po $x4
2
(lldb) po $x0
<SightFacade: 0x14f124b40>
그 중 x2, x3, x4는 각각 3개의 매개변수에 해당합니다. x0이 호출자입니다. 구현 방법을 보려면 이 메서드 내부로 이동하세요. 이 방법에서는 먼저 MainFrameSightViewController를 초기화한 다음 UINavigationController를 생성하여 MainFrameSightViewController를 넣은 다음 MMWindowController를 초기화하여 호출하는 것으로 확인되었습니다.
- (id)initWithViewController:(id)arg1 windowLevel:(int)arg2;
이 메서드는 UINavigationController를 삽입하고 짧은 비디오 촬영 인터페이스에 대한 모든 UI 생성 작업을 완료합니다. 촬영이 완료되면 게시 인터페이스로 들어갑니다. 이때 cycript를 사용하여 현재 컨트롤러가 SightMomentEditViewController인지 확인합니다. 이로부터 이전 촬영 인터페이스를 건너뛰고 직접 보면 충분하지 않을까 하는 생각이 들었습니다. 게시 인터페이스로 들어가시겠습니까? SightMomentEditViewController를 직접 생성하여 UINavigationController에 넣은 다음 이 탐색 컨트롤러를 MMWindowController에 넣습니다. **(확인을 위해 여기에 트윅을 작성했으며 구체적인 트윅 아이디어는 나중에 작성합니다.)** 결과적으로 게시 인터페이스가 실제로 팝업될 수 있지만 탐색 모음의 NavgationBar가 원본을 덮고 전체 인터페이스를 덮습니다. transparent 이고 보기 흉하며 게시가 완료된 후에도 전체 MMWindowController를 삭제할 수 없으며 여전히 게시 인터페이스에 남아 있습니다. 이는 우리가 원하는 결과는 아니지만 최소한 게시 인터페이스를 직접 호출할 수 있고 작은 비디오는 정상적으로 전달할 수 있으므로 많은 이점을 얻을 수 있습니다. 내 개인적인 추측으로는 현재 인터페이스를 삭제할 수 없는 이유는 MMWindowController가 TimeLine이 위치한 keyWindow와 동일하지 않은 새 창을 생성했기 때문이라고 생각됩니다. 굵은 추측입니다. 현재 WCTimeLineViewController에 SightMomentEditViewController를 직접 표시할 수는 없나요?
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
이렇게 표시하면 좋지 않을까요? 그러나 짧은 비디오가 공개될 때 인터페이스의 요소와 결합된 SightMomentEditViewController의 헤더 파일을 관찰하면 이 컨트롤러를 생성하려면 최소한 두 가지 속성이 필요한 것으로 추측됩니다. 하나는 짧은 비디오의 경로이고 다른 하나는 짧은 동영상의 썸네일입니다. 이 두 키는 SightMomentEditViewController에 속성이 부여되면 정상적으로 표시되어야 합니다.
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:^{
}];
짧은 비디오 게시 인터페이스가 정상적으로 표시되고 모든 기능을 정상적으로 사용할 수 있습니다. 유일한 문제는 돌아가기 버튼이 효과가 없고 SightMomentEditViewController를 삭제할 수 없다는 것입니다. cycript를 사용하여 왼쪽 버튼의 actionEvent를 확인하고 해당 응답 함수가 - (void)popSelf;
인지 확인합니다. 반환하기 위해 왼쪽 버튼을 클릭하면 팝 메서드가 트리거되지만 이 컨트롤러는 navgationController에 없으므로 유효하지 않습니다. 이 방법을 다시 실행해야 합니다.
- (void)popSelf
{
[self dismissViewControllerAnimated:YES completion:^{
}];
}
이때, 정상적으로 종료하려면 Return 버튼을 클릭하세요. 또한, WCContentItemViewTemplateNewSight에서 - (void)sendSightToFriend;
라는 메소드를 발견했는데, 지금까지 짧은 영상을 친구에게 직접 전달할 수 있는 기능이 발견되었습니다. .
짧은 비디오 전달은 Moments로 전달, 친구에게 전달, 로컬 사진 앨범에 저장, 짧은 비디오 링크를 임시보드에 복사 등 4가지 기능을 지원합니다. 짧은 동영상이 다운로드되지 않은 경우 길게 누르면 짧은 동영상의 URL 링크만 표시됩니다.
여기서는 WCContentItemViewTemplateNewSight 및 SightMomentEditViewController라는 두 클래스를 연결해야 합니다. WCContentItemViewTemplateNewSight에 onLongTouch 메서드를 연결한 다음 메뉴 팝업 메뉴를 추가하고, 구체적인 코드는 다음과 같습니다.
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];
특정 조정 파일을 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"//安装完成杀掉的进程
컨트롤 파일을 수정할 필요 없이 make package install
명령을 실행하여 휴대폰에 설치하면 위챗이 종료된 후 다시 위챗을 열 수 있는 기능이 추가되었습니다.
macport 설치(설치 프로세스에는 VPN 연결이 필요합니다. 그렇지 않으면 설치가 성공하지 못합니다)
MacPorts를 설치한 후 터미널을 열고 sudo port -v selfupdate
입력하여 MacPorts를 최신 버전으로 업데이트하면 시간이 오래 걸릴 수 있습니다.
MacPorts를 업데이트한 후 DPKG 파일을 설치하고 터미널에 sudo port -f install dpkg
입력하세요.
iOSOpendev를 다운로드하여 설치하세요. 설치에 실패하면 Command + L
사용하여 설치 중 문제를 확인할 수 있습니다.
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";
}
해결책은 다음과 같습니다. iOSOpenDevInstallSolve에서 사양 폴더를 다운로드하세요.
5. 설치 실패 문제를 해결하려면 4단계에서 다운로드한 사양 폴더를 엽니다. 그 안에 8개의 파일이 있어야 합니다. 여러 개의 xcode가 설치되어 있는 경우 해당 xcode에 넣어주세요.
(1) iPhoneOS로 시작하는 4개의 파일은 /Application/Xcode/Content/Developer/Platforms/IphoneOS.platform/Developer/Library/Xcode/Specifications 폴더에 배치됩니다. (그렇지 않은 경우에는 Specs 폴더를 직접 생성해주세요.)
(2) iPhone Simulator로 시작하는 다른 4개의 파일은 /Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications 폴더에 배치됩니다(그렇지 않은 경우 하나도 생성하십시오).
(3) /Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/ 폴더 아래에 usr 폴더를 생성하고, usr 폴더 아래에 bin이라는 폴더를 생성합니다.
참고: 때때로 설치에 실패했다는 메시지가 표시됩니다. Xcode에서 새 프로젝트를 엽니다. 프로젝트의 옵션 메뉴에 iOSOpenDev가 있으면 설치 메시지가 무시됩니다.
ipa 패키지를 설치하려면 itool과 같은 도구를 사용할 수도 있지만 ideviceinstaller는 설치 프로세스를 볼 수 있으므로 오류의 원인을 더 쉽게 찾을 수 있습니다.
명령을 실행하다
brew install ideviceinstaller
Brew 명령을 찾을 수 없다는 메시지가 표시되면 Mac에 Homebrew가 설치되지 않았음을 의미합니다.
일반적인 오류 메시지:
ERROR: Could not connect to lockdownd, error code -5
이때 libimobiledevice를 다시 설치하면 됩니다. (ideviceinstaller는 다른 플러그인을 많이 사용하기 때문입니다.)
다음 명령을 실행합니다.
$ brew uninstall libimobiledevice
$ brew install --HEAD libimobiledevice
iOS 앱 서명자 재서명 도구를 다운로드하세요*(많은 명령줄 작업을 절약하고 클릭 한 번으로 재서명하세요!)*
(3) 해킹된 WeChat 애플리케이션을 다운로드합니다.
AppStore 패키지는 암호화(셸링)되어 재서명할 수 없기 때문에 셸 패키지를 사용해야 합니다. dumpdecrypted를 사용하여 셸을 직접 덤프하거나 PP 지원 또는 itool 지원을 사용하여 탈옥된 버전을 직접 다운로드할 수 있습니다. 쉘링된 WeChat 애플리케이션의 .
(4) 욜로립 설치
yololib는 dylib를 WeChat 바이너리 파일에 삽입할 수 있으므로 Hook을 다운로드한 후 컴파일하고 yololib을 얻을 수 있습니다.
#####(1) 이전 단계에서 iOSOpendev가 설치되었습니다. 이제 Xcode에서 새 프로젝트를 엽니다. 여기서는 CaptainHook Tweak 프로젝트를 선택해야 합니다. 새로 생성된 프로젝트는 .mm 파일이 하나만 있으므로 이 파일에 모든 후크 메서드만 작성하면 됩니다.
탈옥되지 않은 시스템은 탈옥된 시스템과 같은 원래 애플리케이션을 연결하기 위해 조정 플러그인을 설치할 수 없기 때문에 CaptainHook는 런타임 메커니즘을 사용하여 매크로 명령을 사용하여 클래스 정의 및 메서드 교체와 같은 기능을 구현합니다. 다음은 사용 방법에 대한 간략한 소개입니다.
CHDeclareClass(WCContentItemViewTemplateNewSight);
CHDeclareClass(ClassName)
후크할 클래스를 나타내며 일반적으로 이 클래스에 대한 작업 시작 부분에 작성됩니다.
CHDeclareMethod0(id, WCContentItemViewTemplateNewSight, SLSightDataItem){......}
CHDeclareMethod(count, return_type, class_type, name1, arg1, ....)
새 메소드 생성을 의미하고, count는 이 메소드의 매개변수 수를 의미하고, return_type은 반환 유형을 의미하고, class_type은 이 메소드의 클래스 이름인 name1을 채웁니다. arg1은 메소드 이름을 의미하고, arg1은 첫 번째 매개변수를 나타내며, 매개변수가 없으면 공백으로 두는 식입니다.
CHOptimizedMethod0(self, void, WCContentItemViewTemplateNewSight, onLongTouch){
CHSuper(0, className, Method);//可选
......
}
CHOptimizedMethod(count, optimization, return_type, class_type, name1, type1, arg1)
원래 메서드를 연결하는 것을 의미합니다( CHSuper(0, className, Method)
원래 메소드를 복사한다는 의미, CHSuper는 현재 위치에서 원래 메소드를 호출한다는 의미), count는 후크 메소드 매개변수의 수를 의미하며 최적화는 일반적으로 자체를 채우고 return_type은 메소드 반환 값 유형, class_type은 클래스 이름을 채웁니다. 현재 클래스, name1은 메소드 이름, arg1은 매개변수, 매개변수가 없으면 arg를 채우는 식입니다.
CHConstructor
{
@autoreleasepool
{
CHLoadLateClass(WCContentItemViewTemplateNewSight);
CHHook(0, WCContentItemViewTemplateNewSight, onLongTouch);
}
}
CaptainHook의 진입 함수입니다. Hooked된 모든 클래스가 여기에 로드되도록 선언해야 하며, 클래스의 메소드도 여기에 Hooked로 선언되어야 합니다.
그런 다음 클래스와 메소드에 코드를 작성하면 됩니다. 코드가 너무 길어서 MMPlugin을 사용하여 github에 게시하지 않겠습니다.
이 프로젝트에는 짧은 동영상 전달, 빨간 봉투 자동 잡기, WeChat 운동 단계 수정 기능이 포함되어 있습니다. 빨간 봉투 자동 잡기 및 WeChat 운동 단계 수정 기능은 수동으로 끌 수 있습니다.
참고 : 시스템 클래스를 사용하는 경우 해당 클래스 라이브러리 (예 : UIKIT) 및 헤더 파일을 가져 오십시오. 그렇지 않으면 컴파일 중에 오류 가보고됩니다.
컴파일이 성공하면 제품 폴더에서 컴파일 된 정적 라이브러리를 찾을 수 있습니다.
Finder에서 찾아 나중에 사용하도록 복사하십시오.
이 시점에서 가져야 할 자료는 다음과 같습니다.
원래 WeChat 앱에서 wechat 이진 파일을 찾아서 wechat.app 및 wechatshareextensionnew.appex에서 시계 폴더를 삭제하십시오 .
다음 명령을 실행하여 mmplugin.dylib를 wechat 이진 파일에 주입하십시오.
LeonLei-MBP:WeChat gaoshilei$ ./yololib WeChat MMPlugin.dylib
이 명령을 실행할 때는 Yololib, Wechat 및 Wechat.app이 동일한 디렉토리에 있는지 확인하십시오.
완료 후 Mmplugin.dylib 및 wechat을 원본 wechat.app에 복사하여 원본 WeChat 파일을 덮어 씁니다.
iOS 앱 서명자를 열고 아래와 같이 다양한 매개 변수를 선택하십시오.
여기에서 선택한 것은 개인 개발자 인증서를 선택한 후에는 선택을 선택하면됩니다.
휴대 전화에 연결하고 다음 명령을 실행하여 iDeviceInstaller가 휴대폰에 연결되어 있는지 확인하십시오.
LeonLei-MBP:WeChat gaoshilei$ ideviceinfo
많은 휴대 전화 정보가 인쇄되면 연결이 성공하고 IPA 패키지가 실패하면 오류 프롬프트에 따라 조정하십시오. 설치하려면 다음 명령을 실행하십시오.
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
설치가 완료되면 휴대 전화에서 WeChat을 열어 우리가 추가 한 새로운 기능을 시도하십시오! 특정 링크가 고정되면 오류가보고됩니다. 렌더링을 참조하십시오 :