Android 破解之道 (二)
前言
在这篇文章,我们来讨论一下基于Android系统多缓存文件方式截屏的一些事。《 破解之道(一)》开篇介绍了基于Root环境截屏的技术,使用这种方式获取屏幕数据是快捷而便捷的。然而,大家先不要开心太早,此中却有两个系统级问题,很少有文章涉猎讨论,在此向大家详细解说一下。
SurfaceFlinger 简述
下面这张截屏图片包含了较多信息,大家在往下阅读前,请稍微思考一下。
从截屏中读取的信息大概归纳如下,欢迎大家友情补充:
- 系统应该是分屏刷新的,能看到切分了三块区域
- 系统应该有个一刷新完成的标记
- 系统应该会派发刷新完成的状态量
- 这张图片是怎么捕获的
- 我是不是走火入魔了,研究这玩意
第一个问题解答:
首先,请大家查阅源码:
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 pipeclose(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之后,截屏图片不再存在断层。剩下的坑,有机会再介绍。
后记
破解技术,是矛与盾的结合。这两篇文章,已有许多可延伸之处,再深入下去,到了汇编层,会越发枯燥了。后续,要不说说怎么防御吧,这也是一个很有趣的话题。
引用参考
Android系统Surface机制的SurfaceFlinger服务对帧缓冲区(Frame Buffer)的管理分析
http://www.cnblogs.com/mfryf/archive/2013/05/22/3092063.htmlAndroid帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析
http://blog.csdn.net/luoshengyang/article/details/7747932android surfaceflinger研究----Surface机制
http://blog.csdn.net/windskier/article/details/7041610
Android 破解之道 (二)相关推荐
- 【android】Android 破解实例(二)
接上一篇 分析一下这个apk的代码结构:google.gson肯定是google 解析json的gson库,nd.dianjin初步看一下,推断为 获取积分的第三方sdk,首先重点分析android. ...
- Java IO在Android中应用(二):APK加固
Java I/O在Android中应用(二):APK加固套壳 前言(废话) 我,有两把键盘,第一把是Poker III(黑轴),第二把是Poker II(红轴).工作的时候我常用的是红轴的Poker ...
- Android studio使用心得(二)— 打包签名apk发布
1.-–Android Studio菜单 Build->Generate Signed APK 2.--Create new.. 3.---跟eclipse里面一样,添加keystore 信 ...
- 碎片化学习的三大障碍及破解之道
-1- 周末参加线下活动,因为一直专注时间管理文章的分享,有幸作为嘉宾讲了一点自己的感悟. 主办方给的主题叫做:如何规划你的2018年. 接触时间管理越久,越清楚地认识到自我的局限,明白做计划也是一项 ...
- 用Python破解有道翻译反爬虫机制
破解有道翻译反爬虫机制 web端的有道翻译,在之前是直接可以爬的.也就是说只要获取到了他的接口,你就可以肆无忌惮的使用他的接口进行翻译而不需要支付任何费用.那么自从有道翻译推出他的API服务的时候,就 ...
- Android破解与防破解
Android 破解(仅用于学习参考,而不是恶意去破解别人的东西) 过程: 1.用apktool对apk进行解压 2.修改smali文件(注1) 3.用apktoo打包成apk文件 ...
- Python(4) 用Python破解有道翻译反爬虫机制
web端的有道翻译,在之前是直接可以爬的.也就是说只要获取到了他的接口,你就可以肆无忌惮的使用他的接口进行翻译而不需要支付任何费用.那么自从有道翻译推出他的API服务的时候,就对这个接口做一个反爬虫机 ...
- Android硬件加速(二)-RenderThread与OpenGL GPU渲染
Android4.0之后,系统默认开启硬件加速来渲染视图,之前,理解Android硬件加速的小白文简单的讲述了硬件加速的简单模型,不过主要针对前半阶段,并没怎么说是如何使用OpenGL.GPU处理数据 ...
- 《博弈论》之囚犯困境与破解之道
1.定义:(1)一般条件下的囚犯困境 1.双方都有占优策略,即: a1>a3,a2>a4,b1>b2,b3>b4 在a1>a3,a2>a4的条件下,张三选择背 ...
最新文章
- html语言文字闪烁,html+CSS3实现的文字闪烁特效
- JAVA-JSP内置对象
- nf_conntrack: table full, dropping packet 解决方案
- optee中TA的堆的分配
- 关于PIP 总结和记忆巩固
- Bootstrap 进度条堆叠
- Feature event receviers
- bzoj1835基站选址(dp+线段树)
- html跳转浏览器打开新页面打开新窗口,用JS控制打开新窗口
- Android自定义dialog对话框悬浮在界面上
- 提交健康信息服务器拥挤,python hpilo 监控 hp ilo 服务器健康信息
- 例题5-3安迪的第一个字典 UVa10815--C++STL库映射set的应用
- 小白学python-决策树和随机森林
- C#练习之判断字符串是否为回文
- 服装进销存管理软件哪个比较好用?
- 精英计算机主板,精英主板
- 空间分析工具:GIS
- 程序员花式吐槽:月入过万累成狗,还不如富士康技术员!
- java连接ftp工具类
- 用几何画板画七边形的方法