前面两篇iOS逆向的文章(iOS逆向工程-0x00-用途以及准备工作,iOS逆向工程-0x01-工具篇-Cycript)主要是介绍iOS逆向的相关基础环境的搭建,工具的使用。有了这些知识之后,我们可以进行简单的逆向工作了。

B612是Line公司出品的一款非常棒的拍照软件。它的界面,交互,用起来非常顺手。app中的滤镜效果很赞,用户简单几步操作就可以生成一张很有质感的照片。本文我将会演示如何通过逆向来得知B612单个的滤镜的具体实现。

从AppStore上面下载B612的IPA文件,把文件的后缀名从 .IPA 修改成 .zip。解压zip文件之后,在Payload中有一个b612.app的文件,这里我们把.app的后缀去掉,让它变成一个文件夹,方便后面查阅。经过以上几步操作我们就拿到了 B612 app的 bundle 文件信息。

在使用B612的过程中,app会展列一个滤镜列表给用户进行选择(如下图):

使用的过程发现一个叫 China 的滤镜,China,中国?还是瓷器?叫China的滤镜会呈现出什么样子的效果,根据效果我们是否能推测出这里的China是要翻译成中国,还是瓷器咧?很有意思的样子。我们就拿这个滤镜当做目标吧。

一般情况下,我的常用做法是在先前解压的文件夹中搜索一下相关的信息,碰碰运气。

find . -name "*china*"                 
./FilterThumb.bundle/filterthumb_china.jpg
./ObfuscateImages.bundle/lut/china.dat

运气不错。在ObfuscateImages.bundle发现了一个叫china.dat的文件。不过尝试用各种编辑器都没法打开,使用修改后缀名等之类的方法也没能成功打开。看来要获取这个文件的内容没有那么简单,开发者对这个文件做了相关加密工作。要想解密该文件,我们可能需要到汇编代码中寻找答案了。

这里考虑到程序在使用这个滤镜的时候,会加载该文件,并进行相关解密工作。所以下一步是要找到解密dat文件的地方,不过在此之前,我们还有一些工作要做。

解密可执行文件 - clutch 演示

接下来我们要去可执行文件中查询相关信息了。但是从苹果商店上面下载下来的IPA里面的可执行文件是被苹果加密过的,我们解谜它。我常用的工具是 clutch,你可以clone一份repo到本地,然后编译得到一个clutch的程序,也可以直接下载release版本。然后把Clutch scp 到你的越狱机器上面就可以使用了。

  • 首先我们查找一下b612的bundle

    iPhone:/usr root# ./Clutch -i|grep b612
    28)

  • 然后进行dump工作

    iPhone:/usr root# ./Clutch -d com.linecorp.b612 …. //程序输出相关log …. DONE: /private/var/mobile/Documents/Dumped/com.linecorp.b612-iOS7.0-(Clutch-2.0 RC2).ipa

最后会生成一个解密了的ipa文件,我们把它scp到电脑上来,进行分析。

还有另外一个解密苹果商店加密IPA的工具叫dumpdecrypted。由于在使用Clutch解密app的时候,消耗内存比较大,导致运行时会出现解密失败的情况,这个时候可以尝试使用dumpdecrypted

分析可执行文件 - class-dump 以及 IDA

要分析解密之后的文件,我们可以使用class-dump来导出程序的头文件。有了头文件列表之后,我们可以利用它来了解app的架构,使用的类库,以及探索需要逆向的那一块功能所属的类名。

class-dump-z/mac_x86/class-dump-z b612 -H -o headers
cd headers
total 10728
drwxr-xr-x  1295 cp  wheel  44030 Apr  4 12:46 .
drwxr-xr-x   455 cp  wheel  15470 Apr  4 12:46 ..
-rw-r--r--     1 cp  wheel    785 Apr  4 12:46 ACTApplication.h
-rw-r--r--     1 cp  wheel    370 Apr  4 12:46 ACTAutomatedUsageReporter.h
-rw-r--r--     1 cp  wheel    391 Apr  4 12:46 ACTAutomatedUsageReporterPrivate.h
-rw-r--r--     1 cp  wheel    264 Apr  4 12:46 ACTAutomatedUsageReporting.h
-rw-r--r--     1 cp  wheel   1830 Apr  4 12:46 ACTAutomatedUsageTracker.h

要找到加密 .dat 文件的相关代码,我们需要使用另外一个非常强大的工具IDA。IDA可以非常整洁的输出可执行文件的汇编代码,并且相互连接起来。不过这个软件非常之贵,但是它的免费版本已经够用了。

把B612的可执行文件加载到IDA之后,我们在IDA中执行一个搜索,关键字是dat;幸运的是,搜索结果指向了一下的代码。虽然是汇编代码,但是不难看出两点信息,(1)dat后缀的文件是一张Image (2)程序中需要把文件的数据字节进行反向操作,达到混淆的目的。

接下来验证一下线索结果:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSData * data = [[NSData alloc] initWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"china" ofType:@"dat"]];
    data = [self reverseData: data];
    UIImage * image  = [UIImage imageWithData: data];
    UIImageView * imageView = [[UIImageView alloc] initWithImage: image];
    imageView.frame = self.view.bounds;
    [self.view addSubview: imageView];
}
- (NSData *)reverseData:(NSData *)data {
    const char *bytes = [data bytes];
    int idx = [data length] - 1;
    char *reversedBytes = calloc(sizeof(char),[data length]);
    for (int i = 0; i < [data length]; i++) {
        reversedBytes[idx--] = bytes[i];
    }
    NSData *reversedData = [NSData dataWithBytes:reversedBytes length:[data length]];
    free(reversedBytes);
    return reversedData;
}

上述代码让我们获得一张china的图片:

上述代码,请查阅HackB612Image

OK,到此为止,我们解密了china.dat 这种类型的文件,它跟我们最终要挖掘的china滤镜是如何实现的之间肯定有着联系,但我们现在还不得知。

关于滤镜

在iOS app 中给图片加滤镜是要通过OpenGL ES 去实现的,而现在app中普遍使用的是OpenGL ES 2.0。这种情况下,滤镜一般都是通过OpenGL的shader来实现的。此时,前面dump出来的header就有用了,分析headers之后,我们发现了两个相关类 GPUImageFilter以及GLProgram。又由于shader在使用之前一定要进行link,compile操作。所以我们可以通过尝试hook这两个类的相关函数,来获得滤镜的shader。

根据头文件我们能发现B612是通过使用GPUImage框架来实现滤镜功能的。

Theos

关于如何hook app 在运行时的函数,我们需要使用工具Theos.

安装好Theos之后,我们为B612创建一个tweak;

$THEOS/bin/nic.pl 
NIC 2.0 - New Instance Creator
------------------------------
  [1.] iphone/application
  [2.] iphone/library
  [3.] iphone/preference_bundle
  [4.] iphone/tool
  [5.] iphone/tweak
Choose a Template (required): 5
Project Name (required): bsix
Package Name [com.yourcompany.bsix]: com.cp.bsix
Author/Maintainer Name [cp]: 
[iphone/tweak] MobileSubstrate Bundle filter [com.apple.springboard]: com.linecorp.b612            
[iphone/tweak] List of applications to terminate upon installation (space-separated, '-' for none) [SpringBoard]: b612
Instantiating iphone/tweak in bsix/...
Done.

然后在Tweak.xm文件中编辑需要hook的函数:

%hook GLProgram
-(id)initWithVertexShaderString:(id)vertexShaderString fragmentShaderString:(id)string
{
    [Logger log: [NSString stringWithFormat:@"%@ %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]];
    NSString * msg = [NSString stringWithFormat: @"vertexShaderString: %@ fragmentShaderString: %@ ", vertexShaderString, string];
    [Logger log: msg];
    return %orig;
}
%end

B612 tweak 的项目地址B612Tweak.成功编译之后,把生成的.deb文件scp到越狱机器上,利用dpkg安装tweak;杀掉B612进程重启app之后,tweak就可以hook相关函数了。以下在点击China滤镜的时候,tweak打印出来的log信息:

GLProgram initWithVertexShaderString:fragmentShaderString:
vertexShaderString: 
attribute vec4 position; 
attribute vec4 inputTextureCoordinate; 
attribute vec4 inputTextureCoordinate2; 
varying vec2 textureCoordinate; 
varying vec2 textureCoordinate2; 
void main() { 
    gl_Position = position; 
    textureCoordinate = inputTextureCoordinate.xy; 
    textureCoordinate2 = inputTextureCoordinate2.xy; 
} 
fragmentShaderString: 
varying highp vec2 textureCoordinate; 
varying highp vec2 textureCoordinate2; 
uniform sampler2D inputImageTexture; 
uniform sampler2D inputImageTexture2; 
void main() { 
    highp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate); 
    highp float blueColor = textureColor.b * 63.0; 
    highp vec2 quad1; 
    quad1.y = floor(floor(blueColor) / 8.0); 
    quad1.x = floor(blueColor) - (quad1.y * 8.0); 
    highp vec2 quad2; 
    quad2.y = floor(ceil(blueColor) / 8.0); 
    quad2.x = ceil(blueColor) - (quad2.y * 8.0); 
    highp vec2 texPos1; 
    texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); 
    texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); 
    highp vec2 texPos2; 
    texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r); 
    texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g); 
    lowp vec4 newColor1 = texture2D(inputImageTexture2, texPos1); 
    lowp vec4 newColor2 = texture2D(inputImageTexture2, texPos2); 
    lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor)); 
    gl_FragColor = vec4(newColor.rgb, textureColor.w); 
}

关于使用OpenGL实现滤镜

前文提到过,根据class-dump出来的头文件,得知B612使用了GPUImage。理论上我们下一步研究下GPUImage项目,应该就可以实现china滤镜的效果了,但GPUImage项目还是挺庞大,挺复杂的。

GPUImage 提供了GPUImageLookupFilter类来实现颜色查找类的滤镜

这里我们直接使用OpenGL来实现China滤镜。阅读前面的shader代码,得知需要传递给shader两个texture,可以想象出一个是需要原图片,一个是china.dat转换出来的图片,经过顶点着色器和片段着色器,最终给目标图片加上滤镜。下面第一张图是原图,第二张是我用OpenGL实现的Demo效果图片,第三张是B612加china滤镜出来的结果。对比第二张和第三章,效果非常相似。利用OpenGL实现滤镜的代码,请查阅HackB612