微信二次开发,实时位置共享添加导航路线

微信中的位置功能有发送位置功能和实时位置共享,发送位置现在已经支持导航功能,但是个人感觉路线导航放在实时共享中更为实用,再就是地图图层只有标准图层而没有卫星模式。插件实现的功能有在实时位置共享功能中添加步行导航路线,支持切换地图图层。现在来看下效果。








导航路线的颜色是随机的,可以获取多人的路线。

下面来说下详细的分析完成过程;

准备工作:
安装微信、去壳提出二进制文件、导出头文件。

使用Reveal分析


如上图,我们可以发现 位置共享 使用了 TrackRoomView这个类,那么来查看它的头文件,里面方法变量很多,实现了很多Delegate,稍作整理,如下

@interface TrackRoomView : MMUIWindow <UIAlertViewDelegate, MMHeadImageAnnotationViewDelegate, ITrackRoomMgrExt, TrackRoomTopBarViewDelegate, MKMapViewDelegate, UIActionSheetDelegate, ILocationMgrExt, IWXTalkExt, IAUAudioDeviceExt, UIGestureRecognizerDelegate>
{
    MMDelegateProxy<TrackRoomViewDelegate> *_trackRoomViewDelegate;
    BOOL _isInit;
    BOOL _getMicSucc;
    BOOL _showAllHead;
    BOOL _isMapRegionChanging;
    BOOL _shouldIgnoreAccuracy;
    BOOL _intermediateAnimation;
    BOOL _shouldHideAllAnnotation;
    BOOL _shouldZoomToSeeAll;
    BOOL _isLoadFinish;
    BOOL _drivingMode;
    BOOL _refreshOK;
    int _gpsTag;
    struct timeval _startLocatingTime;
    struct timeval _startShowTime;
    UIView *_micMeterCircleView;
    POIInfo *_poiInfo;
    unsigned int _scene;
    UIButton *_micButton;
    UIButton *_myLocationButton;
    NSString *_chatname;
    MKMapView *_mapView;
    UIAlertView *_trackErrorAlertView;
    CAShapeLayer *_micMeterCircle;
    MMTimeChecker *_updateUserLocationTimer;
    MMLoadingView *_loadingView;
    CDStruct_feeb6407 _destinationRegion;
    TrackRoomTopBarView *_topbarView;
    HeadClusterView *_headClusterView;
    NavigateLogicController *_navigateLogicController;
    AnnotationLogicController *_annotationLogicController;
}

@property(nonatomic) MMDelegateProxy<TrackRoomViewDelegate> *trackRoomViewDelegate; // @synthesize trackRoomViewDelegate=_trackRoomViewDelegate;
- (id).cxx_construct;


#pragma mark - UIView
- (void)initView;
- (void)initMapView;
- (void)dealloc;
- (id)initWithFrame:(struct CGRect)arg1;


#pragma mark - TrackRoomTopBarViewDelegate
- (void)onCenterMapAt:(id)arg1;
- (void)onMinimizeButtonClick;
- (void)onCloseButtonClick;

#pragma mark - UIAlertViewDelegate
- (void)alertView:(id)arg1 clickedButtonAtIndex:(int)arg2;

#pragma mark - MMHeadImageAnnotationViewDelegate
- (void)onClickDot:(id)arg1 AtPoint:(struct CGPoint)arg2;
- (void)onClickCallout:(id)arg1 AtPoint:(struct CGPoint)arg2;

#pragma mark - ITrackRoomMgrExt
- (void)OnHeadingChanged:(double)arg1;
- (void)OnTrackRoomError:(int)arg1 Message:(id)arg2;
- (void)OnRefreshTrackRoom:(id)arg1 Type:(int)arg2;

#pragma mark - MKMapViewDelegate
- (void)mapView:(id)arg1 didDeselectAnnotationView:(id)arg2;
- (void)mapView:(id)arg1 didSelectAnnotationView:(id)arg2;
- (void)mapView:(id)arg1 didUpdateUserLocation:(id)arg2;
- (void)mapView:(id)arg1 didAddAnnotationViews:(id)arg2;
- (id)mapView:(id)arg1 viewForOverlay:(id)arg2;
- (id)mapView:(id)arg1 viewForAnnotation:(id)arg2;
- (void)mapView:(id)arg1 regionDidChangeAnimated:(BOOL)arg2;
- (void)mapView:(id)arg1 regionWillChangeAnimated:(BOOL)arg2;

#pragma mark - UIActionSheetDelegate
- (void)actionSheet:(id)arg1 clickedButtonAtIndex:(int)arg2;

#pragma mark - ILocationMgrExt
- (void)onGPSLocationChanged:(id)arg1 withTag:(int)arg2;

#pragma mark - IWXTalkExt
- (void)onRemoteControlCheckShouldStop;
- (void)onRemoteControlCheckShouldPlay;
- (void)OnRestart;
- (void)OnPause;
- (void)OnReConnecting;
- (void)onKickOutFromWXTalkRoom:(id)arg1;
- (void)OnNobodyTalking;
- (void)OnForceStopRecord;
- (void)OnError:(id)arg1 ErrNo:(int)arg2;
- (void)OnSomeoneTalking:(id)arg1;
- (void)OnGetMicrophoneResult:(int)arg1;
- (void)OnOpenWXTalkModeOK:(id)arg1;


#pragma mark - audioDeviceRestart
- (void)audioDeviceMeterLevel:(id)arg1 Peak:(float)arg2;


#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(id)arg1 shouldRecognizeSimultaneouslyWithGestureRecognizer:(id)arg2;





#pragma mark - Delegate END


- (void)navigateTo:(id)arg1 Name:(id)arg2;

// hahahha
- (id)getDisplayNameByUsername:(id)arg1;

- (void)setAnnotation:(id)arg1 Coordinate:(CDStruct_c3b9c2ee)arg2 Animated:(BOOL)arg3 Duration:(float)arg4;
- (void)updateHeadAnnotation:(id)arg1;


- (void)doMeterAnimationOnMainThread:(id)arg1;
- (void)closeTalk;
- (BOOL)openTalk:(id)arg1;


- (void)trySeeAll;


- (BOOL)initTrack;


- (void)didDragMap:(id)arg1;

- (void)bringFriendToFront:(id)arg1;
- (void)bringMyselfToFront;

- (id)getUserLocation;
- (void)setRegion:(CDStruct_feeb6407)arg1 AlwaysAnimated:(BOOL)arg2;
- (void)onSetIgnoreAccuracy;

- (BOOL)isLocationOK:(id)arg1;

- (void)stopUpdateUserLocation;
- (void)startUpdateUserLocation;

- (void)stopTimerCheckUserLocation;
- (void)startTimerCheckUserLocation;
- (void)onTimerUpdateLocation;

- (void)resizeMapToShowAllHeadAnimated:(id)arg1;
- (void)onNavigateToPOI:(id)arg1;
- (void)setAllHeadAnnotationShowCallout:(BOOL)arg1 Animated:(BOOL)arg2;
- (void)showLocationCalloutViewAnimated:(BOOL)arg1;
- (BOOL)showClusterViewOnPoint:(struct CGPoint)arg1;

- (void)onMapTapped:(id)arg1;
- (void)onMicButtonReleased;
- (void)onMicButtonPressed;
- (void)onMyLocationButtonClick;



- (void)closeTrackRoom:(unsigned int)arg1;
- (void)stopLoading;
- (void)startLoadingNonBlock;

- (id)getLocationText:(CDStruct_c3b9c2ee)arg1;
- (void)enterForeground;
- (void)enterBackground;
- (void)hideTrackRoomView;
- (void)showTrackRoomView;
- (void)exitTracking:(unsigned int)arg1;
- (void)doStartTrack;
- (BOOL)startTrackWithChatname:(id)arg1 POIInfo:(id)arg2 Scene:(unsigned int)arg3

可以看到变量中有MKMapView,可以确定它就是界面中显示的Mapview了。因为是实时的,所以应该有一个方法实时接收传送过来的数据,在这么多的方法里想凭借名字来猜测似乎很困难,很大的可能性是在某个Delegate中实现的,拿出“杀手锏”吧,哈哈。。把所有函数都hook上,打印看结果。
编写tweak,安装运行,结果我们在后台打印中看到这个类中方法的调用顺序,确定了两个重要方法为实时接收数据的,即

- (void)OnHeadingChanged:(double)arg1;

- (void)OnRefreshTrackRoom:(id)arg1 Type:(int)arg2;

第1个 OnHeadingChanged 方法是当手机的陀螺仪方向变化实将被调用
第2个OnRefreshTrackRoom:Type: 方法则是不停的被调用,那么重点来研究这个方法。

onrefreshtrackroom:type:方法是 #pragma mark - ITrackRoomMgrExt这个Delegate中的方法,查看ITrackRoomMgrExt-Protocol.h中的Onrefreshtrackroom:type: ,完整方法是这样的

- (void)OnRefreshTrackRoom:(NSArray *)arg1 Type:(int)arg2;

很容易就确定了方法的参数类型,第1个参数是NSArray类型,第2个参数是int类型。知道这个,就可以去查看NSArray里存储的是什么对象啦,编辑tweak(哪位大神知道怎么用cycript打印函数参数呢…),只保留OnRefreshTrackRoom:Type:这个函数

- (void)OnHeadingChanged:(double)arg1{
  %orig;
  NSLog(@" onHeadingChanged %f",arg1);

}

- (void)OnRefreshTrackRoom:(id)arg1 Type:(int)arg2{
  %orig;
  NSLog(@"arg2: %d",arg2);
  for(id obj in arg1)
  {
    NSLog(@"================arg1 =%@=============================array count = %d ===========================",obj,[arg1 count]);
  }

}

会看到如下打印结果

arg2: 2
Apr 25 14:22:22 Supery MicroMessenger[5089] <Warning>: ================arg1 =<UserPositionItem: 0x7741b20>=============================array count = 1 ===========================
Apr 25 14:22:23 Supery MicroMessenger[5089] <Warning>:  onHeadingChanged 7.587538
Apr 25 14:22:24 Supery MicroMessenger[5089] <Warning>: arg2: 2
Apr 25 14:22:24 Supery MicroMessenger[5089] <Warning>: ================arg1 =<UserPositionItem: 0xcea0af0>=============================array count = 1 ===========================
Apr 25 14:22:25 Supery MicroMessenger[5089] <Warning>:  onHeadingChanged 8.609774
Apr 25 14:22:26 Supery MicroMessenger[5089] <Warning>: arg2: 2
Apr 25 14:22:26 Supery MicroMessenger[5089] <Warning>: ================arg1 =<UserPositionItem: 0xce93d00>=============================array count = 1 ===========================
Apr 25 14:22:29 Supery MicroMessenger[5089] <Warning>: arg2: 2
Apr 25 14:22:29 Supery MicroMessenger[5089] <Warning>: ================arg1 =<UserPositionItem: 0xce93c20>=============================array count = 1 ===========================
Apr 25 14:22:29 Supery MicroMessenger[5089] <Warning>:  onHeadingChanged 7.602205
Apr 25 14:22:30 Supery MicroMessenger[5089] <Warning>:  onHeadingChanged 8.626570
Apr 25 14:22:31 Supery MicroMessenger[5089] <Warning>: arg2: 2
Apr 25 14:22:31 Supery MicroMessenger[5089] <Warning>: ================arg1 =<UserPositionItem: 0xce93d00>=============================array count = 1 ===========================
Apr 25 14:22:34 Supery MicroMessenger[5089] <Warning>: arg2: 2
Apr 25 14:22:34 Supery MicroMessenger[5089] <Warning>: ================arg1 =<UserPositionItem: 0xce89a40>=============================array count = 1 ===========================
Apr 25 14:22:34 Supery MicroMessenger[5089] <Warning>:  onHeadingChanged 7.623564
Apr 25 14:22:37 Supery MicroMessenger[5089] <Warning>: arg2: 2
Apr 25 14:22:37 Supery MicroMessenger[5089] <Warning>: ================arg1 =<UserPositionItem: 0xceaf730>=============================array count = 1 ===========================
Apr 25 14:22:39 Supery MicroMessenger[5089] <Warning>: arg2: 2
Apr 25 14:22:39 Supery MicroMessenger[5089] <Warning>: ================arg1 =<UserPositionItem: 0xceb7be0>=============================array count = 1 ===========================
Apr 25 14:22:40 Supery MicroMessenger[5089] <Warning>:  onHeadingChanged 8.636289
Apr 25 14:22:41 Supery MicroMessenger[5089] <Warning>: arg2: 2
Apr 25 14:22:41 Supery MicroMessenger[5089] <Warning>: ================arg1 =<UserPositionItem: 0x4d5f880>=============================array count = 1 ===========================
Apr 25 14:22:43 Supery MicroMessenger[5089] <Warning>:  onHeadingChanged 9.657076

从打印的结果发现,arg1中存储的是 UserPositionItem 类型,arg2当前值是2, 当前手机的方向发生变化时,调用OnHeadingChanged:方法。
下面来查看UserPositionItem为何物,打开UserPositionItem.h,原型如下


@interface UserPositionItem : PBGeneratedMessage
{
    unsigned int hasUsername:1;
    unsigned int hasPosition:1;
    NSString *username;
    PositionItem *position;
}

+ (id)parseFromData:(id)arg1;
@property(retain) PositionItem *position; // @synthesize position;
@property BOOL hasPosition; // @synthesize hasPosition;
@property(retain) NSString *username; // @synthesize username;
@property BOOL hasUsername; // @synthesize hasUsername;
- (id)SetPosition:(id)arg1;
- (id)SetUsername:(id)arg1;
- (id)mergeFromCodedInputStream:(id)arg1;
- (int)serializedSize;
- (void)writeToCodedOutputStream:(id)arg1;
- (id)init;
- (void)dealloc;

关键内容是 username和 PositionItem这两项是我们所需要的,再次查看 PositionItem.h 看其结构

@interface PositionItem : PBGeneratedMessage
{
    unsigned int hasLatitude:1;
    unsigned int hasLongitude:1;
    unsigned int hasHeading:1;
    double latitude;
    double longitude;
    double heading;
}

+ (id)parseFromData:(id)arg1;
@property double heading; // @synthesize heading;
@property BOOL hasHeading; // @synthesize hasHeading;
@property double longitude; // @synthesize longitude;
@property BOOL hasLongitude; // @synthesize hasLongitude;
@property double latitude; // @synthesize latitude;
@property BOOL hasLatitude; // @synthesize hasLatitude;
- (id)SetHeading:(double)arg1;
- (id)SetLongitude:(double)arg1;
- (id)SetLatitude:(double)arg1;
- (id)mergeFromCodedInputStream:(id)arg1;
- (int)serializedSize;
- (void)writeToCodedOutputStream:(id)arg1;
- (id)init;
- (void)dealloc;

其中
double latitude;
double longitude;
double heading;
是我们需要的数据,分别是经纬度和方向值。
构造完成的UserPositionItem和PositionItem为:

@interface PositionItem
{
    double latitude;
    double longitude;
    double heading;
}
@property double heading;
@property double longitude;
@property double latitude;
@end

@interface UserPositionItem
{
    NSString *username;
    PositionItem *position;
}
@property(retain) PositionItem *position;
@property(retain) NSString *username;
@end

当每次获取到其他人的最新坐标时我们都将他们存起来,绘制轨迹需要知道A B 两点的坐标才行,现在已经知道了获取B点坐标方法,那么A点坐标在哪呢,当然是你的手机本地获取的GPS坐标信处,hook mapView:(id)arg1 didUpdateUserLocation:(id)arg2 方法。

- (void)mapView:(id)arg1 didUpdateUserLocation:(id)arg2{
        %orig;

        MKUserLocation* userLocation = (MKUserLocation *)arg2;
        CLLocationCoordinate2D lc = CLLocationCoordinate2DMake(userLocation.coordinate.latitude, userLocation.coordinate.longitude);
}

已知A B两点坐标,通过向GOOGLE请求即可获取导航轨迹坐标点。然后绘制到地图上就OK了,再者是,更改地图地图模式通过下面这段代码,

MKMapView* mapView_;
              object_getInstanceVariable(self,"_mapView",(void**)&mapView_);
              [mapView_ setMapType:MKMapTypeHybrid];

基本思路就是这样,完整代码地址 源码
微博

1 个赞

好厉害,学习了,前两天刚好看到这个插件,终于看到分析过程了,感谢楼主的分享

微信的聊天消息能不能hack???

当然可以,书上有一章就是hack了WhatsApp的消息,相同的思路可以应用到微信里

大哥,你是用lldb,debugserver动态分析的微信程序吗?