iOS 逆向-将Instagram图片和视频保存到本地

效果图:

1.jpeg
3.jpeg
4.jpeg
6.jpeg

需求:

长按图片/视频view可复制其URL或者直接保存到本地相册

环境

  • iPhone6s iOS9.3.1 (已越狱)
  • Instagram v10.21.0

1.复制URL

  • 利用Reveal查看界面信息,可以看到展示图片的cell是IGFeedItemPhotoCell,它的地址为0x1301ae200
Reveal
  • 利用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粘贴到浏览器:

video

2.�保存图片到相册

分析:

一开始我的思路是将app缓存在沙盒中的图片数据取出来,然后保存到相册。后来在实践中发现这种方法比较麻烦而且也不稳定,然后又想到一个更简单的方法:图片是显示在UIImageView上面,直接将UIImageView的image取出来存放在相册就可以了。

利用Reveal很容易找到显示图片的控件

image

查看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, 发现IGCacheIGDiskCache两个类比较符合我们的猜测。来看看后者:

@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回溯寻找IGDiskCachekey是怎么来的。

 -[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的来源,从上面可看到它来源于IGStreamingVideoCacheRequestdiskCacheKeyForMetaData属性,在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就生成相应的diskCacheKeyForMetaDatadiskCacheKeyForStreamData属性,那么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找到对应目录:

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

从上面规律可以看出dataForKeycachePathForKey成对出现,两个key的后缀不同,cachePathForKey返回的路径相同,且就是整个视频流的本地缓存路径。

path

这样我们就可以先获取视频的URL地址,再md5成key,就能找到对应的本地缓存地址了。

另外: 由于app缓存到本地的数据不是mp4格式的,不能直接将其写入相册,需要将该路径下的数据复制到其它路径,并且修改后缀为.mp4才能将其保存到相册。

另外,最后完整代码已上传到github欢迎star

本教程及代码仅供技术学习交流,如用于非法活动与作者无关

参考:
https://github.com/GitterYang/YQSystemPhotoOperation

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容