在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ 產(chǎn)品經(jīng)理/ 運(yùn)行時(shí)的掛鉤 C 函數(shù)
5 個(gè)提示助你設(shè)計(jì)出精妙的 Apple Watch 應(yīng)用軟件
如何使用安卓密鑰庫存儲(chǔ)密碼和其他敏感信息
從 HDFS 中使用分布式的 MAP REDUCE JOB 寫入 CASSANDRA
現(xiàn)代 Javascript 工具漫游指南
理解 Cassandra 壓縮儲(chǔ)存的作用
不懂 JavaScript?那你就不是一個(gè) Web 開發(fā)者
如何開發(fā)一個(gè)簡單的 Android Wear 應(yīng)用程序
Angular 與 React 的比拼
谷歌加入 OpenStack 基金會(huì)的 4 個(gè)理由
15個(gè)很有用的面向設(shè)計(jì)師的 UI 和 UX 設(shè)計(jì)工具及資源
DevTools 摘要: 處理?xiàng)l帶化數(shù)據(jù)時(shí)給條帶化數(shù)據(jù)的一個(gè)新家
為什么是 Node.js ? 什么時(shí)候使用 Node.js ?
玩轉(zhuǎn) Dcoker:Hello World, 開發(fā)環(huán)境和你的應(yīng)用
運(yùn)行時(shí)的掛鉤 C 函數(shù)
在游戲開發(fā)中獲得成功
在 iOS 開發(fā)中使用 TWITTERKIT & DIGITS
使用 ionic 將數(shù)據(jù)保存到本地存儲(chǔ)中
20 個(gè)有用的 Angular.js 工具
如何成為一個(gè)超級(jí)軟件開發(fā)者
用 Go 語言來看 Android! 出發(fā), Android, 出發(fā)!

運(yùn)行時(shí)的掛鉤 C 函數(shù)

文章翻譯:邵凱陽
發(fā)表時(shí)間:2015 年 7 月 27 日
原文作者:Thomas Finch
文章分類:移動(dòng)應(yīng)用開發(fā)

關(guān)于文本

鉤子(Hook)是 Windows 消息處理機(jī)制的一個(gè)平臺(tái),該技術(shù)可以實(shí)現(xiàn)對(duì)消息的監(jiān)視,具有很強(qiáng)大的功能。本文就是基于鉤子的主要功能實(shí)現(xiàn)鉤子在 C 中的應(yīng)用,主要介紹了運(yùn)行時(shí)掛鉤 C 函數(shù)的基本步驟、相關(guān)代碼和一些局限性。

文章內(nèi)容

這是一份我最近嘗試的關(guān)于在 C 中運(yùn)行時(shí)函數(shù)掛鉤的快速記錄。對(duì)于鉤入一個(gè)函數(shù)最基本的思想是用您自己的代碼替換函數(shù)的代碼,所以在調(diào)用該函數(shù)時(shí),您的代碼將被執(zhí)行。運(yùn)行時(shí)掛鉤允許您在被執(zhí)行的程序沒有自己的代碼或者沒有以任何方式對(duì)其文件進(jìn)行實(shí)際修改的時(shí)候更改程序的運(yùn)行方式。運(yùn)行時(shí)函數(shù)掛鉤并不少見,并且用于 iOS 越獄調(diào)整(通過 Cydia SubstrateSubstitute 平臺(tái)提供技術(shù)支持)以及 Xposed 框架 在 Android 程序中的使用。

如果您想在您自己的計(jì)算機(jī)上了解這篇文章,您需要使用 Xcode 和 Xcode 命令行工具安裝的 Mac。這些代碼能夠在 Github 上找到。

示例程序

//testProgram.c
#include <stdio.h>

int hookTargetFunction() {
    printf("Calling original function!\n");
    return 5;
}

int main() {
    printf("The number is: %d\n", hookTargetFunction());
    return 0;
} 

編譯和運(yùn)行該程序可以得到下面的輸出:

Calling original function!
The number is: 5

我們的目標(biāo)是鉤入 hookTargetFunction 這個(gè)函數(shù)并且更改該函數(shù)的返回值,使返回值不再是 5。

掛鉤目標(biāo)函數(shù)

我們掛鉤目標(biāo)函數(shù)的方法是通過創(chuàng)建一個(gè)動(dòng)態(tài)庫,當(dāng)程序運(yùn)行時(shí)加載它。這個(gè)動(dòng)態(tài)庫的構(gòu)造函數(shù)會(huì)在 main 函數(shù)的目標(biāo)可執(zhí)行文件前執(zhí)行,所以我們就可以在目標(biāo)可執(zhí)行文件運(yùn)行之前在內(nèi)存中修改它。若要運(yùn)行我們替換的代碼,我們需要在掛鉤函數(shù)的開頭插入跳轉(zhuǎn)指令的機(jī)器代碼。換句話說,當(dāng)計(jì)算機(jī)嘗試運(yùn)行目標(biāo)函數(shù)時(shí),他將跳轉(zhuǎn)到我們替換函數(shù)所在的位置并運(yùn)行我們的代碼。

這個(gè)過程的第一步就是創(chuàng)建包含一個(gè)構(gòu)造函數(shù)和一個(gè)替換函數(shù)的動(dòng)態(tài)庫。

//inject.c
#include <stdio.h>

int hookReplacementFunction() {
    printf("Calling replacement function!\n");
    return 3;
}

__attribute__((constructor))
static void ctor(void) {
    printf("Dylib constructor called!\n");
}

當(dāng)他使用了 DYLD_INSERT_LIBRARIES 環(huán)境變量的目標(biāo)程序被編譯和加載后,我們能夠看到他的構(gòu)造函數(shù)在主程序之前被執(zhí)行。

$ ls
inject.c    testProgram testProgram.c
$ clang -dynamiclib inject.c -o inject.dylib
$ DYLD_INSERT_LIBRARIES=inject.dylib ./testProgram
Dylib constructor called!
Calling original function!
The number is: 5

為了鉤入目標(biāo)函數(shù),現(xiàn)在我們可以開始向構(gòu)造函數(shù)中添加代碼。由于 x86 跳轉(zhuǎn)指令使用相對(duì)尋址,所以我們不能簡單的在內(nèi)存中給計(jì)算機(jī)一個(gè)地址讓其跳轉(zhuǎn)。首先,我們需要從目標(biāo)函數(shù)中找到替換函數(shù)的抵消函數(shù),這些可以通過獲得進(jìn)入每個(gè)函數(shù)的指針,然后從另一個(gè)指針中減去一個(gè)函數(shù)的指針。

void *mainProgramHandle = dlopen(NULL, RTLD_NOW);
int64_t *origFunc = dlsym(mainProgramHandle , "hookTargetFunction");
int64_t *newFunc = (int64_t*)&hookReplacementFunction;
int32_t offset = (int64_t)newFunc - ((int64_t)origFunc + 5 * sizeof(char));

在這個(gè)示例代碼中有一些值得關(guān)注的事情。首先是使用 dlopen 來獲得進(jìn)入目標(biāo)可執(zhí)行文件的指針。dlopen 通常被用來加載共享庫,但是 根據(jù)其文檔,如果傳遞 NULL 作為文件名,它也可以用于訪問主可執(zhí)行文件。其次應(yīng)該注意的是,跳轉(zhuǎn)的偏移量實(shí)際采取的是下一條指令的地址,在這種情況下目標(biāo)函數(shù)將增加 5 bytes,因?yàn)椴迦胩D(zhuǎn)指令的大小為 5 bytes。

在這篇文章中我省略了一小步,那就是使目標(biāo)函數(shù)所在的內(nèi)存是可寫的,因?yàn)樘幱诎踩目紤],在默認(rèn)情況下內(nèi)存僅僅是可讀的和可執(zhí)行的。一旦這些被完成,最后一步就是創(chuàng)建和插入跳轉(zhuǎn)指令。x86 操作碼是 E9,他與立即數(shù)偏移尋址一起是無條件跳轉(zhuǎn),因此我們將這作為指令的第一個(gè)字節(jié),緊跟的是偏移。

int64_t instruction = 0xE9 | offset << 8;
*origFunc = instruction;

這里是完成的 inject.c 文件:

#include <stdio.h>
#include <dlfcn.h>
#include <stdint.h>
#include <sys/mman.h>
#include <unistd.h>

int hookReplacementFunction() {
    printf("Calling replacement function!\n");
    return 3;
}

__attribute__((constructor))
static void ctor(void) {
    //Get pointers to the original and new functions and calculate the jump offset
    void *mainProgramHandle = dlopen(NULL, RTLD_NOW);
    int64_t *origFunc = dlsym(mainProgramHandle , "hookTargetFunction");
    int64_t *newFunc = (int64_t*)&hookReplacementFunction;
    int32_t offset = (int64_t)newFunc - ((int64_t)origFunc + 5 * sizeof(char));

    //Make the memory containing the original funcion writable
    //Code from http://stackoverflow.com/questions/20381812/mprotect-always-returns-invalid-arguments
    size_t pageSize = sysconf(_SC_PAGESIZE);
    uintptr_t start = (uintptr_t)origFunc;
    uintptr_t end = start + 1;
    uintptr_t pageStart = start & -pageSize;
    mprotect((void *)pageStart, end - pageStart, PROT_READ | PROT_WRITE | PROT_EXEC);

    //Insert the jump instruction at the beginning of the original function
    int64_t instruction = 0xe9 | offset << 8;
    *origFunc = instruction;
}

當(dāng)他編譯和執(zhí)行完后,他確實(shí)改變了主程序的輸出!

$ ls
inject.c    testProgram testProgram.c
$ ./testProgram 
Calling original function!
The number is: 5
$ clang -dynamiclib inject.c -o inject.dylib
$ DYLD_INSERT_LIBRARIES=inject.dylib ./testProgram
Calling replacement function!
The number is: 3

這里是另外一個(gè)執(zhí)行過程和一些調(diào)試輸出,顯示了跳轉(zhuǎn)指令插入目標(biāo)函數(shù)的開始:

$ DYLD_INSERT_LIBRARIES=inject.dylib ./testProgram
Original function address: 0x1078abee0
Replacement function address: 0x1078b4c40
Offset: 0x8d5b
Before replacement: 
*(origFunc+0):  554889e5
*(origFunc+4):  488d3d73
*(origFunc+8):  00e84c00
*(origFunc+12): 00000089
After replacement: 
*(origFunc+0):  e95b8d00
*(origFunc+4):  488d3d73
*(origFunc+8):  00e84c00
*(origFunc+12): 00000089
Calling replacement function!
The number is: 3

局限性

掛鉤這種方法的一個(gè)局限性是他要求目標(biāo)函數(shù)至少是 5 bytes,用于插入跳轉(zhuǎn)指令。這看起來似乎是一個(gè)愚蠢的限制,但創(chuàng)建這樣小的函數(shù)也肯定是可能的(例如,只有單字節(jié)大小的 ret 指令)。我想不出解決這一問題的方式,畢竟對(duì)單字節(jié)進(jìn)行操作時(shí)很艱難的。最直截了當(dāng)?shù)慕鉀Q方法就是不掛鉤小于 5 bytes的指令。

我遇到的另外一個(gè)問題是讓這些代碼運(yùn)行在 Linux 上。出于某些原因,Linux 始終在一個(gè)高地址加載動(dòng)態(tài)庫,地址如此之高以至于偏移量溢出了可用的 32 bits。我不認(rèn)為這是可以修復(fù)的,盡管也使用了跳轉(zhuǎn)指令,因?yàn)槠屏康淖畲蟪叽缡?32 bits。但是,這個(gè)函數(shù)能夠被掛鉤通過另外一種方法——例如,將替換函數(shù)的地址壓入堆棧,然后通過 ret 指令跳轉(zhuǎn)到改地址。這種方法將會(huì)比簡單的跳轉(zhuǎn)花費(fèi)更多的空間,但是這是我現(xiàn)在僅能想到的方法。

我希望您能夠喜歡這篇文章!再一次,自由下載并且在您自己的計(jì)算機(jī)上測(cè)試這些代碼。當(dāng)您自己動(dòng)手嘗試時(shí),您會(huì)體會(huì)到更多的樂趣!

更多IT技術(shù)干貨: wiki.jikexueyuan.com
加入極客星球翻譯團(tuán)隊(duì): http://wiki.jikexueyuan.com/project/wiki-editors-guidelines/translators.html

版權(quán)聲明:
本譯文僅用于學(xué)習(xí)和交流目的。非商業(yè)轉(zhuǎn)載請(qǐng)注明譯者、出處,并保留文章在極客學(xué)院的完整鏈接
商業(yè)合作請(qǐng)聯(lián)系 wiki@jikexueyuan.com
原文地址:[http://thomasfinch.me/blog/2015/07/24/Hooking-C-Functions-At-Runtime.html ](http://thomasfinch.me/blog/2015/07/24/Hooking-C-Functions-At-Runtime.html )