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的快取是以檔案記憶體對映的方式儲存在devgraphicsfb0路徑。每塊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
");
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
");
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
");
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之後,截圖圖片不再存在斷層。剩下的坑,有機會再介紹。
後記
破解技術,是矛與盾的結合。這兩篇文章,已有許多可延伸之處,再深入下去,到了彙編層,會越發枯燥了。後續,要不說說怎麼防禦吧,這也是一個很有趣的話題。
引用參考
-
Android系統Surface機制的SurfaceFlinger服務對幀緩衝區(Frame Buffer)的管理分析
http://www.cnblogs.com/mfryf/archive/2013/05/22/3092063.html
-
Android幀緩衝區(Frame Buffer)硬體抽象層(HAL)模組Gralloc的實現原理分析
http://blog.csdn.net/luoshengyang/article/details/7747932
-
android surfaceflinger研究—-Surface機制
http://blog.csdn.net/windskier/article/details/7041610
相關文章
- Andriod破解之道(一)
- Java的破解和反破解之道 (轉)Java
- “桌遊深度”先手的優勢以及破解之道
- 位置不可用資料夾?的破解之道!
- 「iOS 面試之道」勘誤(二)iOS面試
- 資料夾拒絕訪問的原因與破解之道
- 俞敏洪:破解組建核心創業團隊之道創業團隊
- 第二章 程式設計之道程式設計
- HT圖形元件設計之道(二)元件
- 原始碼防洩密解決之道(二)原始碼
- 從全球視野破解中國工業軟體產業發展之道產業
- Android開發之道(4)程式框架基礎Android框架
- 架構整潔之道二(設計原則)架構
- Android開發之道(7)響應鍵盤事件Android事件
- 軟體測試架構師修煉之道 (二)架構
- Android破解實戰:遊戲蜂窩3.21版本破解記錄Android遊戲
- Android開發之道(3)系統演進歷史Android
- Android手機:破解鎖屏密碼Android密碼
- Android逆向之旅---動態方式破解apk終極篇(加固apk破解方式)AndroidAPK
- 藥廠潔淨車間網路升級挑戰多?解析銳捷的破解之道
- 大模型如何破解資料困局,WAIC產學研專家共話突圍之道大模型AI
- Android開發之道(8)幾個常用的文字WidgetAndroid
- Android開發之道(2)系統體系結構概要Android
- Android 二維碼相關(二)Android
- 《Go 語言併發之道》讀後感 - 第二章Go
- 第二個CrackMe的破解 (6千字)
- 繞過身份檢測,破解Android SU(android靜默安裝)Android
- android 動畫原理二Android動畫
- Android開發之道(9)RadioBox、CheckBox和SpinnerAndroid
- Android APIs (Class Index - Android SDK)(二)AndroidAPIIndex
- 架構師修煉之道(二)——架構?設計?架構師?架構
- Mono for Android 安裝配置方法 附破解版MonoAndroid
- ReGet Junior 2.0破解手記(二) (4千字)
- 演算法之道:形而之上謂之道演算法
- Android開發之道(10)Handler本質簡析與使用例項Android
- 程式碼整潔之道 讀書筆記(二)第12章 迭進筆記
- Android Service詳解(二)Android
- Android 屬性動畫(二)Android動畫