效果图:
需求:
长按图片/视频view可复制其URL或者直接保存到本地相册
环境
- iPhone6s iOS9.3.1 (已越狱)
- Instagram v10.21.0
1.复制URL
- 利用Reveal查看界面信息,可以看到展示图片的cell是
IGFeedItemPhotoCell
,它的地址为0x1301ae200
- 利用cycript打印该对象信息
iPhone:~ root# ps -e | grep /var
394 ?? 0:07.42 /usr/libexec/pkd -d/var/db/PlugInKit-Annotations
24948 ?? 0:00.24 /private/var/containers/Bundle/Application/318E34E7-4465-4512-8F10-5D0BB2FF58A9/Shadowrocket.app/PlugIns/Today.appex/Today
26358 ?? 0:03.46 /var/containers/Bundle/Application/EA32B23A-2D1D-469D-8D23-9B944DF6D369/WeChat.app/WeChat
26452 ?? 0:35.02 /var/containers/Bundle/Application/207B96EC-A857-42D5-BBD8-5CAB1FCBE716/Instagram.app/Instagram
26454 ?? 0:01.19 /var/containers/Bundle/Application/318E34E7-4465-4512-8F10-5D0BB2FF58A9/Shadowrocket.app/Shadowrocket
26459 ?? 0:03.55 /private/var/containers/Bundle/Application/318E34E7-4465-4512-8F10-5D0BB2FF58A9/Shadowrocket.app/PlugIns/PacketTunnel.appex/PacketTunnel
26515 ttys000 0:00.01 grep /var
iPhone:~ root# cycript -p Instagram
cy# cell = #0x1301ae200
#"<<IGFeedItemPhotoCell: 0x1301ae200; baseClass = UICollectionViewCell; frame = (0 157; 375 375); gestureRecognizers = <NSArray: 0x1301af770>; layer = <CALayer: 0x1301ae190>>;
<IGFeedItem: 0x12ef5e210; user: manutd14_; pk: 1523977558113167704_320867481; mediatype: 1, photo:
<IGPhoto: 0x12ef5f330, URL: <IGTypedURL: 0x12eff6f50, URL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-15/s150x150/e35/18722609_1388781834536648_69799940183818240_n.jpg?ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA==.2-w150h150, width: 150.000000, height:150.000000>>,
video: <IGVideo: 0x12ef603b0, URLs: {(\n)}>>; <<IGFeedPhotoView: 0x1301afc70; frame = (0 0; 375 375); gestureRecognizers = <NSArray: 0x13004caa0>;
layer = <CALayer: 0x1301af930>>: imageURL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-15/e35/18722609_1388781834536648_69799940183818240_n.jpg?ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA==.2-w720h720;
<<IGImageProgressView: 0x1301b3d90; frame = (0 0; 375 375); layer = <CALayer: 0x12ef0db30>>: imageURL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-15/e35/18722609_1388781834536648_69799940183818240_n.jpg?ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA==.2-w720h720;>;>;>"
cy#
很容易发现有很多Url,其中感觉https://scontent-nrt1-1.cdninstagram.com/t51.2885-15/s150x150/e35/18722609_1388781834536648_69799940183818240_n.jpg?ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA==.2-w150h150
应该就是图片的url,将其粘贴到浏览器验证下(需要用VPN才能访问到):
很明显,它只是一个缩略图,再者通过url中的150x150
也可以看出图片大小,不是我想要的,继续。
- 利用class-dump将app头文件导出来,结合上面cycript打印的对象信息,从
IGFeedItemPhotoCell
的头文件中�猜测出下面有用信息:
@interface IGFeedItemPhotoCell : IGFeedItemMediaCell <IGFeedPhotoViewDelegate, IGZoomControllerLoadingDelegate>
{
IGFeedItem *_post;
}
- (id)post;
在其父类IGFeedItemMediaCell
中有:
@property(readonly, nonatomic) IGFeedItem *post;
继续查看IGFeedItem
头文件发现:
@property(readonly) IGPhoto *photo;
继续查看IGPhoto
却没有发现直接返回url的方法或者有关url的属性,不过有一个数组属性可疑,用cycript看看是什么东西。
@property(retain, nonatomic) NSArray *imageVersions;
cy# item = [cell post]
#"<IGFeedItem: 0x12ef5e210; user: manutd14_; pk: 1523977558113167704_320867481; mediatype: 1,
photo: <IGPhoto: 0x12ef5f330, URL: <IGTypedURL: 0x130a9de00, URL:
https://scontent-nrt1-1.cdninstagram.com/t51.2885-15/s150x150/e35/18722609_1388781834536648_69799940183818240_n.jpg
?ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA==.2-w150h150, width: 150.000000, height:150.000000>>, video:
<IGVideo: 0x12ef603b0, URLs: {(\n)}>>"
cy# photo = [item photo]
#"<IGPhoto: 0x12ef5f330, URL: <IGTypedURL: 0x12edc2ee0, URL: https://scontent-nrt1-
1.cdninstagram.com/t51.2885-15/s150x150/e35/18722609_1388781834536648_69799940
183818240_n.jpg?ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA==.2-
w150h150, width: 150.000000, height:150.000000>>"
cy# [photo imageVersions]
@[#"<IGTypedURL: 0x12ef5f480, URL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-
15/s150x150/e35/18722609_1388781834536648_69799940183818240_n.jpg?
ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA%3D%3D.2, width: 150.000000, height:150.000000>",#"
<IGTypedURL: 0x12ef5e420, URL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-
15/s240x240/e35/18722609_1388781834536648_69799940183818240_n.jpg?
ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA%3D%3D.2, width: 240.000000, height:240.000000>",#"
<IGTypedURL: 0x12ef5e440, URL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-
15/s320x320/e35/18722609_1388781834536648_69799940183818240_n.jpg?
ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA%3D%3D.2, width: 320.000000, height:320.000000>",#"
<IGTypedURL: 0x12ef5f510, URL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-
15/s480x480/e35/18722609_1388781834536648_69799940183818240_n.jpg?
ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA%3D%3D.2, width: 480.000000, height:480.000000>",#"
<IGTypedURL: 0x12ef5f530, URL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-
15/s640x640/sh0.08/e35/18722609_1388781834536648_69799940183818240_n.jpg?
ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA%3D%3D.2, width: 640.000000, height:640.000000>",#"
<IGTypedURL: 0x12ef5fec0, URL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-
15/e35/18722609_1388781834536648_69799940183818240_n.jpg?
ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA%3D%3D.2, width: 720.000000, height:720.000000>"]
数组里面有很多url,把最后width: 720.000000, height:720.000000
的url粘贴到浏览器中:
没错!这就是我们想要的图片。
再查看IGTypedURL
头文件:
@class NSURL;
@interface IGTypedURL : NSObject <NSCoding>
{
NSURL *_url;
double _width;
double _height;
}
- (void).cxx_destruct; // IMP=0x0000000100b740fc
- (id)description; // IMP=0x0000000100b73eb4
- (void)encodeWithCoder:(id)arg1; // IMP=0x0000000100b73df8
@property(readonly, nonatomic) double height; // @synthesize height=_height;
- (id)initWithCoder:(id)arg1; // IMP=0x0000000100b73d04
- (id)initWithURL:(id)arg1 width:(double)arg2 height:(double)arg3; // IMP=0x0000000100b73c3c
- (_Bool)isEqual:(id)arg1; // IMP=0x0000000100b73f38
@property(readonly, nonatomic) NSURL *url; // @synthesize url=_url;
@property(readonly, nonatomic) double width; // @synthesize width=_width;
@end
cy# imageArray = [photo imageVersions]
cy# imageUrl = [imageArray lastObject]
#"<IGTypedURL: 0x12ef5fec0, URL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-15/e35/18722609_1388781834536648_69799940183818240_n.jpg?ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA%3D%3D.2, width: 720.000000, height:720.000000>"
cy# url = [imageUrl url]
#"https://scontent-nrt1-1.cdninstagram.com/t51.2885-15/e35/18722609_1388781834536648_69799940183818240_n.jpg?ig_cache_key=MTUyMzk3NzU1ODExMzE2NzcwNA%3D%3D.2"
- 这样我们就成功找到了图片的URL地址。图片找到了。那小视频和这个的套路是一样的,可以很轻松拿到:
cy# videoCell = #0x1308abf20
#"<IGFeedItemVideoCell: 0x1308abf20; baseClass = UICollectionViewCell; frame = (0 797; 375 375);
gestureRecognizers = <NSArray: 0x12ee56a30>; layer = <CALayer: 0x130326af0>>"
cy# videoItem = [videoCell post]
#"<IGFeedItem: 0x12ef62270; user: sportscenter; pk: 1524328809808109209_505182045; mediatype: 2, photo:
<IGPhoto: 0x12ef627b0, URL: <IGTypedURL: 0x130714840, URL: https://scontent-nrt1-1.cdninstagram.com/t51.2885-
15/s150x150/e15/18646180_1688608908108154_3180624454661177344_n.jpg?
ig_cache_key=MTUyNDMyODgwOTgwODEwOTIwOQ==.2-w150h150, width: 150.000000, height:150.000000>>, video:
<IGVideo: 0x12ef62d90, URLs: {(\n https://scontent-nrt1-1.cdninstagram.com/t50.2886-
16/18747154_149366562270857_2128634303553208320_n.mp4,\n https://scontent-nrt1-
1.cdninstagram.com/t50.2886-16/18809789_1930612557176396_3467211331512303616_
n.mp4\n)}>>"
cy# video = [videoItem video]
#"<IGVideo: 0x12ef62d90, URLs: {(\n https://scontent-nrt1-1.cdninstagram.com/t50.2886-
16/18747154_149366562270857_2128634303553208320_n.mp4,\n https://scontent-nrt1-
1.cdninstagram.com/t50.2886-16/18809789_1930612557176396_3467211331512303616_
n.mp4\n)}>"
cy# videoArray = [video videoVersions]
@[@{"height":640,"url":"https://scontent-nrt1-1.cdninstagram.com/t50.2886-
16/18747154_149366562270857_2128634303553208320_n.mp4","type":101,"width":640},@{"height":480,"url":"https://s
content-nrt1-1.cdninstagram.com/t50.2886-16/18809789_1930612557176396_3467211331512303616_
n.mp4","type":103,"width":480},@{"height":480,"url":"https://scontent-nrt1-1.cdninstagram.com/t50.2886-
16/18809789_1930612557176396_3467211331512303616_n.mp4","type":102,"width":480}]
cy# videoUrl = videoArray[0]
@{"height":640,"url":"https://scontent-nrt1-1.cdninstagram.com/t50.2886-16/18747154_149366562270857_2128634303553208320_n.mp4","type":101,"width":640}
同样我们找width和height最大的那个url:https://scontent-nrt1-1.cdninstagram.com/t50.2886-16/18747154_149366562270857_2128634303553208320_n.mp4
粘贴到浏览器:
2.�保存图片到相册
分析:
一开始我的思路是将app缓存在沙盒中的图片数据取出来,然后保存到相册。后来在实践中发现这种方法比较麻烦而且也不稳定,然后又想到一个更简单的方法:图片是显示在UIImageView上面,直接将UIImageView的image取出来存放在相册就可以了。
利用Reveal很容易找到显示图片的控件
查看IGFeedItemPhotoCell
头文件发现:
@property(readonly, nonatomic) IGFeedPhotoView *photoView;
接着往下找:
@interface IGFeedPhotoView : IGFeedMediaView <IGImageProgressViewDelegate, UIGestureRecognizerDelegate>
{
IGImageProgressView *_photoImageView;
}
@interface IGImageProgressView : UIView <IGImageViewDelegate>
@property(readonly, nonatomic) IGImageView *photoImageView;
@end
@interface IGImageView : UIImageView <IGImageLoadingDelegate, IGMediaRequestDelegate>
@end
拿到cell就可以拿到正在显示的UIImage然后保存到相册了。
3.�保存小视频到相册
这部分相对于前面没有那么直观和简单了。
刚才在寻找viideo的URL的时候我们发现了视频模型
IGVideo
类,那么我们先看看这个类头文件有什么有用的信息吧。 不过只发现NSData *dashManifestData
属性,�而且该属性一直为空,这并不是我们要找的。这时候思路断了,没有路可走只能发挥我们强大的猜测能力了(这里省略一万种猜测的过程)。通过观察app发现,如果完整看完一个小视频,然后将vpn断开,再把app重启,刚才看过的视频还可以播放,那么我们可以猜测它是将视频数据缓存到了本地,并且在没有网络连接的情况下会将本地缓存的数据取出来显示。那我们可以猜测一般开发者都会建立一个或者多个类来专门管理数据的缓存和提取。
在所有头文件中搜索
cache
, 发现IGCache
和IGDiskCache
两个类比较符合我们的猜测。来看看后者:
@interface IGDiskCache : NSObject
{
NSString *_cachePath;
unsigned long long _trimmingTask;
double _lastTrimTime;
NSString *_name;
unsigned long long _diskCapacity;
unsigned long long _maxFileCount;
}
- (void).cxx_destruct; // IMP=0x000000010111279c
- (void)_startBackgroundCacheTrimmingTask; // IMP=0x0000000101111c64
- (id)cachePathForKey:(id)arg1; // IMP=0x0000000101111c50
- (_Bool)containsDataForKey:(id)arg1; // IMP=0x000000010008cea0
- (id)dataForKey:(id)arg1; // IMP=0x0000000100024fec
- (void)dealloc; // IMP=0x00000001011119d4
@property(readonly, nonatomic) unsigned long long diskCapacity; // @synthesize diskCapacity=_diskCapacity;
- (id)initWithName:(id)arg1 diskCapacity:(unsigned long long)arg2 maxFileCount:(unsigned long long)arg3; // IMP=0x000000010000ea54
- (id)inputStreamForKey:(id)arg1; // IMP=0x0000000101111a50
@property(readonly, nonatomic) unsigned long long maxFileCount; // @synthesize maxFileCount=_maxFileCount;
@property(readonly, copy, nonatomic) NSString *name; // @synthesize name=_name;
- (id)outputStreamForKey:(id)arg1 append:(_Bool)arg2; // IMP=0x0000000101111b04
- (void)removeAllDataWithDeletionBlock:(CDUnknownBlockType)arg1; // IMP=0x0000000100031a18
- (void)removeDataForKey:(id)arg1; // IMP=0x0000000101111bc8
- (unsigned long long)removeExcessFiles; // IMP=0x00000001011125e4
- (void)setData:(id)arg1 forKey:(id)arg2; // IMP=0x0000000100040c04
@end
这应该就是我们要找的那个类。
- 上LLDB给
- (id)dataForKey:(id)arg1
下断点:
(lldb) image list -o -f | grep Instagram
[ 0] 0x000000000008c000 /var/containers/Bundle/Application/207B96EC-A857-42D5-BBD8-5CAB1FCBE716/Instagram.app/Instagram(0x000000010008c000)
(lldb) br s -a 0x000000000008c000+0x0000000100024fec
Breakpoint 1: where = Instagram`main + 129032, address = 0x00000001000b0fec
滑动屏幕,当要显示视频是断点断下:
(lldb) c
Process 26533 resuming
Process 26533 stopped
* thread #17, queue = 'com.instagram.videocache.videotask.serial', stop reason = breakpoint 1.1
frame #0: 0x00000001000b0fec Instagram`main + 129032
Instagram`main:
-> 0x1000b0fec <+129032>: stp x20, x19, [sp, #-0x20]!
0x1000b0ff0 <+129036>: stp x29, x30, [sp, #0x10]
0x1000b0ff4 <+129040>: add x29, sp, #0x10 ; =0x10
0x1000b0ff8 <+129044>: adrp x8, 7116
(lldb) po (char*)$x1
"dataForKey:"
(lldb) po $x2
d9ad4582db2cfc14ebbbee16f815ce29.meta
(lldb) po [$x0 dataForKey:$x2]
<OS_dispatch_data: data[0x13071b470] = { leaf, size = 370, buf = 0x105920000 }>
(lldb) po [$x0 cachePathForKey:$x2]
/var/mobile/Containers/Data/Application/F9FB71EF-2E12-4FAE-B70A-73128F4B85F3/Library/Caches/com.burbn.instagram.IGVideoCache/fd92ba891bf9245a802b6b5f7b5e49bd
(lldb) p/x $lr
(unsigned long) $15 = 0x0000000100bc4a9c
通过返回的数据类型,及缓存的路径我们可以初步判断,这应该就是视频数据。上Hopper回溯寻找IGDiskCache
及key
是怎么来的。
-[IGStreamingVideoTask loadMetaDataFromDiskCache]:
stp x22, x21, [sp, #-0x30]! ; Objective C Implementation defined at 0x101a81d30 (instance method), DATA XREF=0x101a81d30
stp x20, x19, [sp, #0x10]
stp x29, x30, [sp, #0x20]
add x29, sp, #0x20
adrp x8, #0x101bec000
ldrsw x8, [x8, #0x240] ; 0x101bec240
ldr x19, x0, x8
adrp x8, #0x101bec000
ldrsw x8, [x8, #0x23c] ; objc_ivar_offset_IGStreamingVideoTask__originalRequest
ldr x0, x0, x8
adrp x8, #0x101bbf000 ; @selector(processAddOperation:)
ldr x1, [x8, #0xd88] ; @selector(diskCacheKeyForMetaData),"diskCacheKeyForMetaData"
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x20, x0
adrp x8, #0x101b93000 ; @selector(ig_mainFeedCreationEnabled)
ldr x1, [x8, #0x38] ; @selector(dataForKey:),"dataForKey:"
mov x0, x19
mov x2, x20
bl imp___stubs__objc_msgSend
先寻找key的来源,从上面可看到它来源于IGStreamingVideoCacheRequest
的diskCacheKeyForMetaData
属性,在get方法上下断点:
(lldb) br s -a 0x0000000100b35430+0x000000000008c000
Breakpoint 2: where = Instagram`local_laplacian_bgra + 10920196, address = 0x0000000100bc1430
(lldb) c
Process 26533 resuming
Process 26533 stopped
* thread #53, queue = 'com.instagram.videocache.videotask.serial', activity = 'send gesture actions', stop reason = breakpoint 2.1
frame #0: 0x0000000100bc1430 Instagram`local_laplacian_bgra + 10920196
Instagram`local_laplacian_bgra:
-> 0x100bc1430 <+10920196>: b 0x1016adb6c ; symbol stub for: objc_getProperty
0x100bc1434 <+10920200>: adrp x8, 4279
0x100bc1438 <+10920204>: ldrsw x2, [x8, #0x1d0]
0x100bc143c <+10920208>: mov w3, #0x0
(lldb) po $x0
<IGStreamingVideoCacheRequest: 0x131c04900>
(lldb) po [$x0 diskCacheKeyForMetaData]
d9ad4582db2cfc14ebbbee16f815ce29.meta
(lldb) po [$x0 cacheKey]
d9ad4582db2cfc14ebbbee16f815ce29
(lldb) po [$x0 diskCacheKeyForStreamData]
d9ad4582db2cfc14ebbbee16f815ce29.stream
通过查看IGStreamingVideoCacheRequest
的头文件并没有发现明显生成key的方法,只有一个init开头的方法,去看看:
ldr x0, x23, x19
adrp x8, #0x101b8f000 ; @selector(intValue)
ldr x1, [x8, #0x580] ; @selector(absoluteString),"absoluteString"
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
str x28, sp
mov x28, x0
adrp x8, #0x101bbe000 ; @selector(registrationStepForSignUpViewController:)
ldr x1, [x8, #0x4b8] ; @selector(MD5),"MD5"
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
stp x20, x22, [sp, #0x10]
mov x22, x21
mov x20, x25
mov x25, x0
mov x1, x26
bl imp___stubs__objc_msgSend
mov x21, x27
mov x27, x0
mov x0, x25
bl imp___stubs__objc_release
mov x0, x28
bl imp___stubs__objc_release
adrp x8, #0x101738000
add x8, x8, #0x6b8 ; @""
cmp x27, #0x0
csel x1, x27, x8
adrp x8, #0x101bec000
ldrsw x8, [x8, #0x1d4] ; objc_ivar_offset_IGStreamingVideoCacheRequest__cacheKey
add x25, x23, x8
mov x0, x25
bl imp___stubs__objc_storeStrong
ldr x0, x25
adrp x8, #0x101b8d000 ; @selector(setStories:)
ldr x28, [x8, #0xfa8] ; @selector(stringByAppendingString:),"stringByAppendingString:"
adrp x2, #0x1017a0000
add x2, x2, #0xdd8 ; @".meta"
mov x1, x28
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x19, x0
mov x1, x26
bl imp___stubs__objc_msgSend
adrp x8, #0x101bec000
ldrsw x9, [x8, #0x1d8] ; 0x101bec1d8
ldr x8, x23, x9
str x0, x23, x9
mov x0, x8
bl imp___stubs__objc_release
mov x0, x19
bl imp___stubs__objc_release
ldr x0, x25
adrp x2, #0x1017a0000
add x2, x2, #0xdf8 ; @".stream"
mov x1, x28
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x19, x0
mov x1, x26
bl imp___stubs__objc_msgSend
adrp x8, #0x101bec000
ldrsw x9, [x8, #0x1dc] ; objc_ivar_offset_IGStreamingVideoCacheRequest__diskCacheKeyForStreamData
ldr x8, x23, x9
str x0, x23, x9
通过上面很容易看出,把url进行md5就是cacheKey
,然后后面分别拼接.meta
、.stream
就生成相应的diskCacheKeyForMetaData
和diskCacheKeyForStreamData
属性,那么url又是什么东西呢,下断点看下就知道了:
(lldb) br s -a 0x0000000100b35038+0x000000000008c000
Breakpoint 3: where = Instagram`local_laplacian_bgra + 10919180, address = 0x0000000100bc1038
(lldb) c
Process 26533 resuming
Process 26533 stopped
* thread #63, queue = 'com.instagram.video.proxyadaptor.serial', activity = 'send gesture actions', 1 messages, stop reason = breakpoint 3.1
frame #0: 0x0000000100bc1038 Instagram`local_laplacian_bgra + 10919180
Instagram`local_laplacian_bgra:
-> 0x100bc1038 <+10919180>: sub sp, sp, #0xa0 ; =0xa0
0x100bc103c <+10919184>: stp x28, x27, [sp, #0x40]
0x100bc1040 <+10919188>: stp x26, x25, [sp, #0x50]
0x100bc1044 <+10919192>: stp x24, x23, [sp, #0x60]
(lldb) po (char*)$x1
"initWithUrl:requestedRange:readAheadLimitInBytes:ignoreCache:isPrefetch:mediaInfoParsedBlock:progressBlock:successBlock:errorBlock:"
(lldb) po $x2
https://scontent-nrt1-1.cdninstagram.com/t50.2886-16/18747154_149366562270857_2128634303553208320_n.mp4
这个URL就是我们上面得到的视频的URL地址。就是简单的将URL的md5做为缓存的Key。
再来查看IGDiskCache
的来源。首先它是IGStreamingVideoTask
的一个属性,IGStreamingVideoTask
的一个初始化方法- (id)initWithRequest:(id)arg1 diskCache:(id)arg2 queue:(id)arg3 completionBlock:(CDUnknownBlockType)arg4
将diskCache对象传进来的,继续回溯:
(lldb) br s -a 0x0000000100b35fb8+0x000000000008c000
Breakpoint 4: where = Instagram`local_laplacian_bgra + 10923148, address = 0x0000000100bc1fb8
(lldb) c
Process 26533 resuming
Process 26533 stopped
* thread #63, queue = 'com.instagram.videocache.serial', activity = 'send gesture actions', 1 messages, stop reason = breakpoint 4.1
frame #0: 0x0000000100bc1fb8 Instagram`local_laplacian_bgra + 10923148
Instagram`local_laplacian_bgra:
-> 0x100bc1fb8 <+10923148>: sub sp, sp, #0x70 ; =0x70
0x100bc1fbc <+10923152>: stp x28, x27, [sp, #0x10]
0x100bc1fc0 <+10923156>: stp x26, x25, [sp, #0x20]
0x100bc1fc4 <+10923160>: stp x24, x23, [sp, #0x30]
(lldb) po (char*)$x1
"initWithRequest:diskCache:queue:completionBlock:"
(lldb) po $x3
<IGDiskCache: 0x130a33b00>
(lldb) po [$x3 name]
IGVideoCache
(lldb) p/x $lr
(unsigned long) $34 = 0x0000000100bc02b8
ldrsw x8, [x8, #0x1bc] ; objc_ivar_offset_IGStreamingVideoCache__diskCache
mov x27, x24
mov x24, x25
mov x25, x20
ldr x20, x19, x8
mov x2, x26
mov x3, x20
mov x4, x28
bl imp___stubs__objc_msgSend
来自于IGStreamingVideoCache
的属性, 那这个对象又是怎么来的呢?先看看其头文件:
@interface IGStreamingVideoCache : NSObject <IGCacheWithSnapshot>
{
IGDiskCache *_diskCache;
}
+ (id)sharedCache;
@end
有单例方法,令人惊喜,这样IGDiskCache
对象我们也得到了。这样就大功告成了吗?事实并没有那么简单。
再来回顾之前的那个调试:
(lldb) po [$x0 dataForKey:$x2]
<OS_dispatch_data: data[0x13071b470] = { leaf, size = 370, buf = 0x105920000 }>
(lldb) po [$x0 cachePathForKey:$x2]
/var/mobile/Containers/Data/Application/F9FB71EF-2E12-4FAE-B70A-73128F4B85F3/Library/Caches/com.burbn.instagram.IGVideoCache/fd92ba891bf9245a802b6b5f7b5e49bd
根据path找到对应目录:
将选中的那个最后一个文件复制到mac上面,修改后缀名有.mp4并不能播放,而将其它两个文件能正常播放,说明这个370b大小的文件不是我们要找的视频文件,而是同目录下的size很大的才是,那它们是怎么获取的呢?
在刚才lldb调试的过程中只是发现app会多次调用- (id)dataForKey:(id)arg1
方法,但并没有发现返回的data的size超过370b的情况,我猜测可能是利用其它方式提取的这些较大的视频数据。直接写个tweak, hook这个方法查看调用情况:
%hook IGDiskCache
- (id)cachePathForKey:(id)arg1 {
id result = %orig;
NSLog(@"cachePathForKey==name:%@=key:%@\n path:%@", [self name], arg1, result);
return result;
}
- (NSData *)dataForKey:(id)arg1 {
NSData* result = %orig;
NSLog(@"dataForKey==name:%@=key:%@\n dataSize:%lu", [self name], arg1, [result length]);
return result;
}
%end
部分打印:
May 29 23:51:32 iPhone Instagram[26985] <Warning>: dataForKey==name:IGVideoCache=key:d9ad4582db2cfc14ebbbee16f815ce29.meta
dataSize:370
May 29 23:51:32 iPhone Instagram[26985] <Warning>: cachePathForKey==name:IGVideoCache=key:d9ad4582db2cfc14ebbbee16f815ce29.stream
path:/var/mobile/Containers/Data/Application/F9FB71EF-2E12-4FAE-B70A-73128F4B85F3/Library/Caches/com.burbn.instagram.IGVideoCache/b7f795c5d8bb612a7d94ac8093576c1c
May 29 23:51:36 iPhone Instagram[26985] <Warning>: dataForKey==name:IGVideoCache=key:d9ad4582db2cfc14ebbbee16f815ce29.meta
dataSize:370
May 29 23:51:36 iPhone Instagram[26985] <Warning>: cachePathForKey==name:IGVideoCache=key:d9ad4582db2cfc14ebbbee16f815ce29.stream
path:/var/mobile/Containers/Data/Application/F9FB71EF-2E12-4FAE-B70A-73128F4B85F3/Library/Caches/com.burbn.instagram.IGVideoCache/b7f795c5d8bb612a7d94ac8093576c1c
May 29 23:51:39 iPhone Instagram[26985] <Warning>: dataForKey==name:IGVideoCache=key:d9ad4582db2cfc14ebbbee16f815ce29.meta
dataSize:370
May 29 23:51:39 iPhone Instagram[26985] <Warning>: cachePathForKey==name:IGVideoCache=key:d9ad4582db2cfc14ebbbee16f815ce29.stream
path:/var/mobile/Containers/Data/Application/F9FB71EF-2E12-4FAE-B70A-73128F4B85F3/Library/Caches/com.burbn.instagram.IGVideoCache/b7f795c5d8bb612a7d94ac8093576c1c
May 29 23:51:42 iPhone Instagram[26985] <Warning>: dataForKey==name:IGVideoCache=key:d9ad4582db2cfc14ebbbee16f815ce29.meta
dataSize:370
May 29 23:51:42 iPhone Instagram[26985] <Warning>: cachePathForKey==name:IGVideoCache=key:d9ad4582db2cfc14ebbbee16f815ce29.stream
path:/var/mobile/Containers/Data/Application/F9FB71EF-2E12-4FAE-B70A-73128F4B85F3/Library/Caches/com.burbn.instagram.IGVideoCache/b7f795c5d8bb612a7d94ac8093576c1c
从上面规律可以看出dataForKey
和cachePathForKey
成对出现,两个key的后缀不同,cachePathForKey
返回的路径相同,且就是整个视频流的本地缓存路径。
这样我们就可以先获取视频的URL地址,再md5成key,就能找到对应的本地缓存地址了。
另外: 由于app缓存到本地的数据不是mp4格式的,不能直接将其写入相册,需要将该路径下的数据复制到其它路径,并且修改后缀为.mp4才能将其保存到相册。
另外,最后完整代码已上传到github欢迎star