Android 破解之道 (二)

前言

在这篇文章,我们来讨论一下基于Android系统多缓存文件方式截屏的一些事。《 破解之道(一)》开篇介绍了基于Root环境截屏的技术,使用这种方式获取屏幕数据是快捷而便捷的。然而,大家先不要开心太早,此中却有两个系统级问题,很少有文章涉猎讨论,在此向大家详细解说一下。

SurfaceFlinger 简述

下面这张截屏图片包含了较多信息,大家在往下阅读前,请稍微思考一下。

从截屏中读取的信息大概归纳如下,欢迎大家友情补充:

  1. 系统应该是分屏刷新的,能看到切分了三块区域
  2. 系统应该有个一刷新完成的标记
  3. 系统应该会派发刷新完成的状态量
  4. 这张图片是怎么捕获的
  5. 我是不是走火入魔了,研究这玩意

第一个问题解答:

首先,请大家查阅源码:
frameworks/base/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp

截取其中关键的两段:

渲染方式声明:

#ifdef EGL_ANDROID_swap_rectangle
    if (extensions.hasExtension("EGL_ANDROID_swap_rectangle")) {
        if (eglSetSwapRectangleANDROID(display, surface,
                0, 0, mWidth, mHeight) == EGL_TRUE) {
            // This could fail if this extension is not supported by this
            // specific surface (of config)
            mFlags |= SWAP_RECTANGLE;
        }
    }
    // when we have the choice between PARTIAL_UPDATES and SWAP_RECTANGLE
    // choose PARTIAL_UPDATES, which should be more efficient
    if (mFlags & PARTIAL_UPDATES)
        mFlags &= ~SWAP_RECTANGLE;
#endif

具体渲染操作:

void DisplayHardware::flip(const Region& dirty) const
{
    checkGLErrors();  

    EGLDisplay dpy = mDisplay;
    EGLSurface surface = mSurface;  

#ifdef EGL_ANDROID_swap_rectangle
    if (mFlags & SWAP_RECTANGLE) {
        const Region newDirty(dirty.intersect(bounds()));
        const Rect b(newDirty.getBounds());
        eglSetSwapRectangleANDROID(dpy, surface,
                b.left, b.top, b.width(), b.height());
    }
#endif  

    if (mFlags & PARTIAL_UPDATES) {
        mNativeWindow->setUpdateRectangle(dirty.getBounds());
    }  

    mPageFlipCount++;
    eglSwapBuffers(dpy, surface);
    checkEGLErrors("eglSwapBuffers");  

    // for debugging
    //glClearColor(1,0,0,0);
    //glClear(GL_COLOR_BUFFER_BIT);
}

这段代码主要用来检查系统的主绘图表面是否支持EGL_ANDROID_swap_rectangle扩展属性。如果支持的话,那么每次在调用函数eglSwapBuffers来渲染UI时,都会使用软件的方式来支持部分更新区域功能,即:先得到不在新脏区域里面的那部分旧脏区域的内容,然后再将得到的这部分旧脏区域的内容拷贝回到要渲染的新图形缓冲区中去,这要求每次在渲染UI时,都要将被渲染的图形缓冲区以及对应的脏区域保存下来。注意,如果系统的主绘图表面同时支持EGL_ANDROID_swap_rectangle扩展属性以及部分更新属性,那么将会优先使用部分更新属性,因为后者是直接在硬件上支持部分更新,因而性能会更好。

第二个问题解答:

在Android源码中有以下对framebuffer的结构定义:
hardware/libhardware/include/hardware/gralloc.h

typedef struct framebuffer_device_t {
    struct hw_device_t common;  

    / flags describing some attributes of the framebuffer /
    const uint32_t  flags;  

    / dimensions of the framebuffer in pixels /
    const uint32_t  width;
    const uint32_t  height;  

    / frambuffer stride in pixels /
    const int       stride;  

    / framebuffer pixel format /
    const int       format;  

    / resolution of the framebuffer's display panel in pixel per inch/
    const float     xdpi;
    const float     ydpi;  

    / framebuffer's display panel refresh rate in frames per second /
    const float     fps;  

    / min swap interval supported by this framebuffer /
    const int       minSwapInterval;  

    / max swap interval supported by this framebuffer /
    const int       maxSwapInterval;  

    int reserved[8];  

    int (setSwapInterval)(struct framebuffer_device_t window,
            int interval);  

    int (setUpdateRect)(struct framebuffer_device_t window,
            int left, int top, int width, int height);  

    int (post)(struct framebuffer_device_t dev, buffer_handle_t buffer);  

    int (compositionComplete)(struct framebuffer_device_t dev);  

    void* reserved_proc[8];  

} framebuffer_device_t;

以上声明中,成员函数compositionComplete用来通知fb设备device,图形缓冲区的组合工作已经完成。引用参考[2]的文章说明,此函数指针并没有被使用到。那么,我们就要找到在哪里能够获取得到屏幕渲染完成的信号量了。

第三个问题解答:

这个问题建议大家先行阅读所有引用参考文章。然后因为懒,这里就直接给出大家结论,过程需参考surfaceflinger的所有源码。

我们都知道Android在渲染屏幕的时候,一开始用到了double buffer技术,而后的4.0以上版本升级到triple buffer。buffer的缓存是以文件内存映射的方式存储在dev\graphics\fb0路径。每块buffer置换的时候,会有唯一的,一个,信号量(注意修饰语)抛给应用层,接收方是我们经常用到的SurfaceView控件。SurfaceView内的OnSurfaceChanged() API 即是当前屏幕更新的信号量,除此之外,程序无从通过任何其他官方API形式获取屏幕切换的时间点。这也是Android应用商场为何没有显示当前任意屏幕的FPS数值的软件(补充一下,有,需要Root,用到的就是本文后续介绍的技术。准确来说,是本文实现了一遍他们的技术)。

本文将在稍后的独立章节说明如何实现强行暴力获取埋在系统底层surfaceflinger service内的信号量。

第四个问题解答:

使用mmap MAP_SHARED方式读屏,就有可能出现此问题。因为屏幕是持续变换的,也就是fd指针指向的内存地址是持续变换的。那有同学就会问了,为什么在《 破解之道(一)》一文中所展示的截屏图片上没有此问题?答案很简单,其实是有的,只要同学细心分析里面的8张截屏图片,会发现有色差现象出现。只是在图像特征选取和识别上面规避了此影响。

第五个问题解答:

详见下一章节的问题。

Hooker 代码注入

考虑到文章已经很长,Hooker又不是什么善良的东西,具体实现方式的介绍会较为简单。大家感兴趣可以去看雪论坛逛逛。

系统屏幕切换所用到的函数是在surfaceflinger内的elfswapbuffer()函数,要获取得系统屏幕切换的信号量,需要劫持surfaceflinger service内的elfswapbuffer()函数,替换成我们自己的newelfswapbuffer()函数,并在系统每次调用newelfswapbuffer()函数时,此向JNI层抛出一个信号量,这样就能强行获得屏幕切换状态量。

而,这样做,需要用到hooker技能,向系统服务注入一段代码,勾住elfswapbuffer()函数的ELF表地址,然后把自己的newelfswapbuffer()函数地址替换入ELF表内。在程序结束后,需要逆向实现一遍以上操作,还原ELF表。

程序用到了以下两个核心文件:

一个文件负责注入系统服务,另一个负责感染系统程序。

Inject surfaceflinger

int main(int argc, char** argv) {

    pid_t target_pid;
    target_pid = find_pid_of("/system/bin/surfaceflinger");
    if (-1 == target_pid) {
        printf("Can't find the process\n");
        return -1;
    }

    //target_pid = find_pid_of("/data/test");
    inject_remote_process(target_pid, argv[1], "hook_entry",  argv[2], strlen(argv[2]));

    return 0;
}

Infect surfaceflinger

int hook_entry(char * argv) {

    LOGD("Hook success\n");

    LOGD("pipe path:%s", argv);

    if(mkfifo(argv, 0777) != 0 && errno != EEXIST) {
        LOGD("pipe create failed:%d",errno);
        return -1;
    } else {
        LOGD("pipe create successfully");
    }

    LOGD("Start injecting\n");

    elfHook(LIB_PATH, "eglSwapBuffers", (void )new_eglSwapBuffers, (void *)&old_eglSwapBuffers);

    while(1){

        int fPipe = open(argv, O_TRUNC, O_RDWR);
        if (fPipe == -1) {
            LOGD("pipe open failed");
            break;
        } else {
            LOGD("pipe open successfully");
        }

        char command[10];
        memset(command, 0x0, 10);

        int ret = read(fPipe, &command, 10);
        if(ret > 0 && strcmp(command, "done") == 0) {
            LOGD("ptrace detach successfully with %s", command);
            break;
        } else {
            LOGD("ret:%d received command: %s", ret, command);
        }

        // close the pipe
        close(fPipe);

        usleep(100);

    }

    elfHook(LIB_PATH, "eglSwapBuffers", (void )old_eglSwapBuffers, (void *)&new_eglSwapBuffers);

}

我们能看到以上代码还用到了pipe管道通讯,那是因为注入的是一段二进制可执行代码,而我们在退出程序时需要与此二进制代码通讯,以便正常退出。

详细的Log信息和具体细节因为安全原因,不便具体描述(其实已经有很多蛛丝马迹了),还是那句话,莫犯错。

技术验证

以下是基于一个游戏所做的技术验证:

图片是有序的:
| 1 | 5 |
| 2 | 6 |
| 3 | 7 |
| 4 | 8 |

这是没有使用Hooker之前的效果:

--------------------------------------------

使用Hooker之后的效果:

我们可以看到,使用了Hooker之后,截屏图片不再存在断层。剩下的坑,有机会再介绍。

后记

破解技术,是矛与盾的结合。这两篇文章,已有许多可延伸之处,再深入下去,到了汇编层,会越发枯燥了。后续,要不说说怎么防御吧,这也是一个很有趣的话题。

引用参考

  1. Android系统Surface机制的SurfaceFlinger服务对帧缓冲区(Frame Buffer)的管理分析
    http://www.cnblogs.com/mfryf/archive/2013/05/22/3092063.html
  2. Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析
    http://blog.csdn.net/luoshengyang/article/details/7747932
  3. android surfaceflinger研究----Surface机制
    http://blog.csdn.net/windskier/article/details/7041610
时间: 2024-09-16 02:56:03

Android 破解之道 (二)的相关文章

Android 破解视频App去除广告功能详解及解决办法总结

Android 破解视频App去除广告功能 作为一个屌丝程序猿也有追剧的时候,但是当打开视频app的时候,那些超长的广告已经让我这个屌丝无法忍受了,作为一个程序猿看视频还要出现广告那就是打我脸,但是我有没有钱买会员,只能靠着毕生技能去耍耍去除广告了.下面就来介绍一下如何进行视频广告的去除. 一.视频广告播放原理 首先我们需要了解的一个基本知识点那就是广告其实也是一段视频,那么他肯定有请求地址和播放地址.那么我们的思路就来了,如果能够得到这些地址的话,我们就可以去除广告了,为什么呢?因为我们知道所

Java的破解和反破解之道

破解 java字节码能够很容易被反编译大家都晓得啦, 今天下午我为了得到一个心仪已久的jbuilder opentools(昨天1.0 Released,新鲜出炉!但只能用14天,这怎么行~@@#!@#!#@!@#%%^@,少说也要140天嘛!),于是我不惜放下其他工作,研究了一把该软件加密方法的破解和反破解,结合以前的一些经验,作文一篇与大家共飨,并不是鼓励大家...破解之道:如今市面上的java obfuscator很多(可以从google分类中列出)比较著名的有4thpass的产品(呵呵,

fragment 二维码-android fragment 集成扫描二维码功能

问题描述 android fragment 集成扫描二维码功能 我在一个activity里放了一个viewpager,viewpager里填充两个fragment, 其中一个fragment是扫描二维码的功能,扫描完以后会跳到另外一个页面,请做过的人提供一点思路,感谢! 解决方案 你用ZXing这个第三方的扫描sdk就可以了 解决方案二: ZXing识别出url,传值给另一个页面,页面中用webview显示. 解决方案三: http://download.csdn.net/detail/zhuo

Android ROM开发(二)——ROM架构以及Updater-Script脚本分析,常见的Status错误解决办法

Android ROM开发(二)--ROM架构以及Updater-Script脚本分析,常见的Status错误解决办法 怪自己二了,写好的不小心弄没了,现在只好重新写一些了,上篇简单的配置了一下环境,这里呢,就来讲一下相关的只是点 我们先下载一个ROM,随便下,原理都是差不多的,这里我就下载一个红米Note的MIUI稳定版 1.ROM结构 ROM根据厂商的定制可能有所不同,但是大体是不变的 data 内置一些软件 META-INF 脚本文件 update-binary 二进制文件 updater

android 通过客户扫描二维码获得用户手机号

问题描述 android 通过客户扫描二维码获得用户手机号 android中怎样实现二维码扫描后获取客户的手机号?用的是4.0以上的版本,需要获得哪些用户扫描了二维码,并把它传入数据库,有没有办法获得用户在线扫描的手机号码 解决方案 首先你要根据你自己的生成规则生成一个含手机号的二维码,识别出来再根据你的规则解析出来 解决方案二: 想错了,我可以直接在应用中想办法使用户给我手机号就行了

Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解

Android绘图机制(二)--自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解 我们要想画好一些炫酷的View,首先我们得知道怎么去画一些基础的图案,比如矩形,圆形,三角形,多边形等-. 新建一个项目 然后我们创建一个listview,每个图案一个Activity,这样看起来是不是很顺眼 <ListView android:id="@+id/listview" android:layout_width="wrap_content&q

Android如何扫描获取二维码中的数据?

问题描述 Android如何扫描获取二维码中的数据? 我首先做了一个二维码名片,把信息输入进去以后生成了一个二维码,现在需要通过扫描这个二维码获取里面的信息中的电话号码信息,然后是一个发短信的过程.现在的问题就是如何扫描获得这个电话号码? 解决方案 参考:http://www.cnblogs.com/weixing/archive/2013/08/28/3287120.html 解决方案二: 建议你使用ZXing框架 解决方案三: android 二维码 扫描cocos2dx android 二

Xamarin.Android开发实践(二)

原文:Xamarin.Android开发实践(二) 一.准备 开始学习本教程前必须先完成该教程http://www.cnblogs.com/yaozhenfa/p/xamarin_android_quickstart.html 否则将无法继续.   二.界面 1.打开Resources/layout/Main.axml文件,并在Call Button下方继续加入一个按钮,并设置其id为@+id/CallHistoryButton同时设置Text为@string /callHistory(这个其实

混合云或许是破解之道

互联网的冲击从金融.制造.连锁型企业等向所有的行业蔓延."积极进行互联网变革是找死,等着互联网冲击来是等死",如何才能不死?传统企业需要回答这个问题,传统企业的信息化部门更需要回答这个问题,因为互联网对传统行业的冲击最先体现在这个部门,苏宁云商云技术开发中心总监金龙说,完成互联网转型难度并不亚于创业本身. 前不久,由浪潮集团主办.广东省网商协会协办的2014年互联网应用技术峰会(IATS)2014在广州举行.网易.搜狐.奇虎等互联网企业的技术负责人:互联网转型较为成功的传统企业,如苏宁