# iOS逆向工程-0x02－Hacking on B612

By [cp.codeplay](https://paragraph.com/@cp-codeplay) · 2022-03-26

---

前面两篇iOS逆向的文章（[iOS逆向工程-0x00－用途以及准备工作](http://cp0000.github.io/2015/11/21/iOS-reverse-engineering-01/),[iOS逆向工程-0x01－工具篇-Cycript](http://cp0000.github.io/2015/11/30/iOS-reverse-engineering-02/)）主要是介绍iOS逆向的相关基础环境的搭建，工具的使用。有了这些知识之后，我们可以进行简单的逆向工作了。

[B612](https://itunes.apple.com/us/app/b612-selfie-from-the-heart/id904209370?mt=8)是Line公司出品的一款非常棒的拍照软件。它的界面，交互，用起来非常顺手。app中的滤镜效果很赞，用户简单几步操作就可以生成一张很有质感的照片。本文我将会演示如何通过逆向来得知B612单个的滤镜的具体实现。

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

在使用B612的过程中，app会展列一个滤镜列表给用户进行选择（如下图）：

![B612_filter_list](https://storage.googleapis.com/papyrus_images/cf1b7b5c518b9e86b75630bcd0fb4981379677b65e611abdb2dace83fe399165.jpg)

B612\_filter\_list

使用的过程发现一个叫 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](https://github.com/KJCracks/Clutch),你可以clone一份repo到本地，然后编译得到一个clutch的程序，也可以直接下载release版本。然后把Clutch scp 到你的越狱机器上面就可以使用了。

*   首先我们查找一下b612的bundle
    
    iPhone:/usr root# ./Clutch -i|grep b61228)
    
*   然后进行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](https://github.com/conradev/dumpdecrypted)。由于在使用Clutch解密app的时候，消耗内存比较大，导致运行时会出现解密失败的情况，这个时候可以尝试使用[dumpdecrypted](https://github.com/conradev/dumpdecrypted)。

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

要分析解密之后的文件，我们可以使用[class-dump](https://github.com/nygard/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](https://www.hex-rays.com/products/ida/index.shtml)。IDA可以非常整洁的输出可执行文件的汇编代码，并且相互连接起来。不过这个软件非常之贵，但是它的免费版本已经够用了。

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

![reversebyte](https://storage.googleapis.com/papyrus_images/7a44de96c085f99cda1ca0ddf44d669e878ca61d6c262976111eceadf1862c24.jpg)

reversebyte

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

    - (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的图片：

![china](https://storage.googleapis.com/papyrus_images/c58f9b4a7c08707634d6fdefadf9cfe560d7cb74c1b949e20b014bc2e5eedaa8.png)

china

上述代码，请查阅[HackB612Image](https://github.com/cp0000/HackB612)

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

关于滤镜
----

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

> 根据头文件我们能发现B612是通过使用[GPUImage](https://github.com/BradLarson/GPUImage)框架来实现滤镜功能的。

Theos
-----

关于如何hook app 在运行时的函数，我们需要使用工具[Theos](http://iphonedevwiki.net/index.php/Theos).

*   关于如何安装Theos，请阅读[Theos Setup](http://iphonedevwiki.net/index.php/Theos/Setup)。
    
*   Logos相关语法，请参考[Theos Logos](http://iphonedevwiki.net/index.php/Logos)
    

安装好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](https://github.com/cp0000/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](https://github.com/BradLarson/GPUImage)。理论上我们下一步研究下GPUImage项目，应该就可以实现china滤镜的效果了，但GPUImage项目还是挺庞大，挺复杂的。

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

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

![原图](https://storage.googleapis.com/papyrus_images/4d971fbe79f892d2909550cb037599b7d3b0cce01ed67c8d8d36771e8ccb1719.jpg)

原图

![Demo效果图](https://storage.googleapis.com/papyrus_images/a5efd496ea41fc64c73997c52fa9e8a76dccae6a18c3b91102659d1e1702de24.png)

Demo效果图

---

*Originally published on [cp.codeplay](https://paragraph.com/@cp-codeplay/ios-0x02-hacking-on-b612)*
