linux 我有2個c程序 a,b 是否可以通過獲得a的物理地址之後,在b中調用a的函數?

被這個鳥問題困擾了1天...

鼓起勇氣向各位大神請教一下...

不要噴我...

a.c

#include &

void sayHello()

{

printf("hello zhihu...
");

}

int main(int argc, char const *argv[])

{

while(1);

return 0;

}

nm a.out

拿到sayHello的虛擬地址 0x1234

問題1:

是否可以通過pid 和 0x1234 算出sayHello 函數真正的物理地址addr

這裡我百度了,可以通過proc下面的某個結點算?現在沒有linux環境沒辦法測試

引申的另外一個問題就是,在用戶態是否有許可權?

問題2:

是否可以另起一個進程

b.c, 通過addr來訪問a中的sayHello函數

謝謝各位大神...我真的不是一個伸手黨...

不要讓我滾去百度知道...

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

提出這個問題是由於在項目中,有一個服務在跑,經常會加一些功能,想在服務運行的時候測試一下對應函數調用的效果

正常的方法應該是寫一個client通過進程間通信,發送字元串給服務,然後通過映射表來調用.

如果不能通過物理地址的方法來實現,只能按照正常的方法來寫了

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

過節比較忙,這幾天一直沒上來...

首先非常感謝大家的回答!!

經過幾位大神的提醒,用了ptrace這個函數

今天晚上的測試 從12點搞到現在(我太弱了)

在b.c裡面用ptrace 可以實現跨進程的訪問

這個貌似就是GDB的原理

可以先通過PTRACE_GETREGS 拿到一個a的寄存器信息 struct user_regs_struct

(這裡比較有搞笑的地方是 結構體的頭文件裡面的注釋 哈哈)

/* The whole purpose of this file is for GDB and GDB only. Don"t read

too much into it. Don"t use it for anything other than GDB unless

you know what you are doing. */

通過b設置好各項寄存器只會, PTRACE_SETREGSS回去,改變a的調用流程

比較經典的流程是,創建一個so,通過ptrace 注入到a裡面,流程比較複雜 網上也有一些文章可以參考

寫一個最簡單的一個例子就是 通過b來直接改a的全局變數

a:

#include&

#include&

int var;

int main(int argc, char const *argv[])

{

printf("pid : %d
",getpid());

var=0;

while(var!=1);

printf("end...%d",var);

}

gcc 編譯之後 nm a.out |grep var

拿到虛擬地址 var的0804a028

運行./a拿到pid 6498

b:

#include &

#include &

#include &

#include &

#include &

int main(int argc,char *argv[])

{

pid_t traced_process;

int data;

traced_process=atoi(argv[1]);

ptrace(PTRACE_ATTACH,traced_process,NULL,NULL);

wait(NULL);

data=ptrace(PTRACE_PEEKDATA,traced_process,0x0804a028,0);

printf("data=%d
",data);

data=1;

ptrace(PTRACE_POKEDATA,traced_process,0x0804a028,data);

ptrace(PTRACE_DETACH,traced_process,0,0);

return 0;

}

sudo ./b 6498

a進程退出


第一,我先告訴你為什麼不能。

第二,我再告訴你怎樣才能。

第三,你現在明白很多程序員都是精神病患者了吧。

不能,因為如果可以的話,任何一個程序,都可以闖入其他程序去調用一把,如同任何人都可以闖入他人的卧室和人家老婆來一發一樣,少年郎,這樣會亂套的!另外程序運行在虛擬地址空間,物理地址可能是支離破碎的。沒鳥用。多任務操作系統用盡一切手段就是來隔離各個進程使其不受影響。

怎樣才能,大概有兩種辦法,第一種是合法手段獲得對方程序授權,比如你說的寫個client是最簡單的一種。第二種是強行闖入,通過檢查對方程序的錯誤,使用「緩衝區溢出」的方式,向對方程序注入代碼(請自行百度)。但是第二種不一定能成功。請注意兩種辦法都是在對方的進程空間執行。

作為一個精神病患者,能還是不能,其實並不是個問題,關鍵是為什麼要這樣?對於樓主的問題可能有另外一個解決辦法是,把服務變成一個程序集即可。

正常情況下

a服務程序,有時會調用一下b程序(就是直接運行那種)

測試的時候,c程序也來調用一下b程序。

這樣b就是一個公共的單身女青年了。


1.可以

/proc/[pid]/maps
/proc/[pid]/pagemap

可以看到映射關係

2.不行

以下內容摘自《深入理解計算機系統》

目標文件有三種形式:

  • 可重定位目標文件。包含二進位代碼和數據,其形式可以在編譯時與其他可重定位目標文件合併起來,創建一個可執行目標文件。
  • 可執行目標文件。包含二進位代碼和數據,其形式可以被直接拷貝到存儲器並執行。
  • 共享目標文件。一種特殊類型的可重定位目標文件,可以在載入或運行時被動態的載入到存儲器並鏈接。

編譯器和彙編器生成可重定位目標文件(包括共享目標文件)。鏈接器生成可執行目標文件。

簡單來說,生成的 .o 文件就是可重定位目標文件。

可以直接運行的自然是可執行目標文件。

而 .so .ko 則都是共享目標文件。

要得到共享目標文件,必須在編譯時使用額外的參數來生成與位置無關的代碼( Position-Independent Code , PIC)。所謂位置無關代碼,就是說生成的二進位代碼中的地址都是相對位置,這些相對位置在實際載入時會被加上一個全局偏移量產生絕對地址。

所以, a.out 已經是鏈接過的可執行目標文件,不再具有重定位能力,二進位代碼中的地址已經被硬編碼成固定的值,即使你得到了函數的物理地址,你也無法通過這個物理地址來調用這個函數。


不能。

如果我沒記錯的話,一個程序里的地址是系統分配給它特有的。也就是說,對於進程A里的地址0x1234這個物理存儲空間來說,從另一個進程看它的話就不是0x1234了。

題主可以考慮把你想共享的東西做成鏈接文件,然後在你需要的地方導入鏈接文件即可。

另外,題主可以百度:進程間共享內存。這或許是你想要的。我只能讓你百度看看了因為我也沒做過。


Windows在十幾年前就有自動化技術(Automation)可以完美地從一個進程里調用到另一個自動化伺服器進程里的函數,通過RPC。

linux並沒有這樣的通信,通常進程間交互通過unix socket, pipe, shared memory完成。相比於Windows的做法,需要程序員自己考慮的東西多一些。


不是非常明白你的需求。如果是指一個已經載入內存運行的程序 a.c ,你想在 b.c 中調用它的某個函數,這有辦法做到,但非常 tricky 也非常危險。RPC之類的解決方案,其它答主都提過了,我就不啰嗦了。

如果不是這種需求,那雙方配合一下,也行的:把程序當作動態庫載入就是了。有一點小困難就是,這種手法在 Windows 下是沒有任何問題的,只要函數本身被導出(用 DEF 文件或 __declspec(dllexport) 都行),那麼就可以用 LoadLibrary 載入程序,用 GetProcAddr 取得函數入口。

但是,在 Linux 是有點困難的——因為,ELF 文件格式對它的虛擬地址是有特定要求的,所以即使載入了也無法正常運行。不過,我們可以用 PIE 格式,所以,就有了下面的方法。

不好意思我習慣用 C++,不過這個程序這麼簡單,用啥都一樣。舉個栗子:

a.cpp 的代碼

------------------------- 以下是代碼 --------------------------

#include &

// 注意用 extern "C" 來阻止名稱 mangled。

// 如果不這樣,可以通過 nm 命令來找到確切的名稱。(作死么?)

extern "C" int __attribute__((dllexport)) addFunc(int a) {

return a + 4;

}

int main() {

std::cout &<&< "result : " &<&< addFunc(5);

}

------------------------- 以上是代碼 --------------------------

b.cpp 的代碼

------------------------- 以下是代碼 --------------------------

include &

#include &

typedef int __attribute__((dllimport)) (*AddFunc)(int a);

using namespace std;

int main() {

void* hdl = dlopen("./dlhost", RTLD_NOW);

if(!hdl) {

cerr &<&< "Load module fail : " &<&< dlerror() &<&< endl;

return -1;

}

AddFunc addFunc = reinterpret_cast&(dlsym(hdl, "addFunc"));

if(!addFunc) {

cerr &<&< "Load symbol fail" &<&< dlerror() &<&< endl;

return -2;

}

cout &<&< "result : " &<&< addFunc(5) &<&< endl;

dlclose(hdl);

}

------------------------- 以上是代碼 --------------------------

下面是關鍵,我們要把 a.cpp 編譯為 PIE 格式,並引出動態符號:

g++ -o dlhost -fPIE a.cpp -pie -rdynamic

至於 b.cpp ,就隨便了,記得把 dl 庫引入就行:

g++ -o dlclient b.cpp -ldl

然後運行驗證一下:

./dlclient

result : 9

正確。

不過:必須警告一下,PIE格式雖然允許這樣載入,但作為一個可執行程序,如果這個函數對於 main 中的某些初始化操作有依賴,那就危險了:因為此時 a.cpp 中的 main 沒有運行。


RPC


只要做過外掛的人都知道,這當然是可行的,至少windows平台下是可以的 !!

只需要用上 ReadProcessMemory()這個api和他的兄弟Write**

比如植物大戰殭屍,對其進行分析就會發現,其中的陽光數目保存在一個固定的位置,我們只需要修改這個位置的值就可以做到無線陽光。

也就是說,我們要寫一個程序,這個程序可以修改另一個程序中的變數的值。

而windows恰巧提供了這麼一個api!!!!!!!!!感謝微軟的工程師為我們提供如此棒的api

微軟簡直喪心病狂?!!!!!!!有了這個api,很多不加殼的單機遊戲很容易製造出外掛,只需要用ce分析其中的變數,找到變數的地址,然後我們寫一個程序 用wpm這個api修改這個值就能無線金幣無線金錢有木有!!!!


Using Remote Procedure Calls (RPCs) in C using XDR

可以考慮參考上面的教程用RPC方式來實現。

一個進程用地址訪問另一個進程的內存,現代操作系統是不允許的。


兩個進程之間的頁表不一樣,即使得到虛擬地址,也得經過段機製得到線性地址之後根據進程頁表得到物理地址,這樣的話得到的物理地址也不是你想訪問的地址


CreateRemoteThread,Windows上最簡單的代碼注入方式,Linux上自己加個syscall也不是什麼難事吧


用現成的調試軟體啊,可以做到attach to process


用ptrace可以 去修改進程的IP


在IBM as400系統上做過一個實驗:

a程序功能為對變數a循環自增100次。

在session A 中以debug方式啟動a程序,在循環體內用斷點中斷程序,獲取a變數的地址和值並記錄下來。(比如是F0208,值是1)

在session B 再次debug方式啟動a程序,同樣中斷在循環體內,然後手動將session A記錄下的a程序的a變數地址覆蓋掉session B啟動的a程序的a變數地址。繼續session B的a程序運行一次,查看a變數的地址為F0208,值為2。進一步查看session A啟動的a程序的a變數值,也是2。讓session A中的a程序循環繼續執行一次,變數值變為3,同時去觀察session B的a程序的a變數的值,也是3。

以上測試能成功的前提是兩個程序都啟動在一個usergroup中。


注入進去是可以的


Windows下是可以的,直接遠程call 或者 切cr3靠加進程然後call,或者注入之後call,有各種辦法,linux下不清楚,你可以找一下有木有類似跨進程內存操作的api。


不熟悉 Linux 平台。

給個 OS X 平台上解決問題的思路供參考。

首先需要明確一點就是調用的結果可以通過 Log 等看出來,而不需要再回傳 b.out,否則直接用 RPC 就可以了。在這個前提下,問題就是能否通過 b.out 在 a.out 中實現代碼注入。1、只要進程 b 對進程 a 有控制權,通過進程 b 向進程 a 注入代碼就可以實現。2、故意在 a 程序中留個漏洞(後門),b 利用這個漏洞實現代碼執行。

在正常的開發中沒必要用這些,基於 RPC 建立個相應的機制挺好的。

另,問題中的物理地址讓強迫症患者看起來很彆扭,那只是載入後的地址,並不是什麼物理地址。


注入,或者 動態載入。


是這樣的張總:你在家裡的電腦上按了CTRL+C,然後在公司的電腦上再按CTRL+V是肯定不行的。即使同一篇文章也不行。不不不,多貴的電腦都不行!


不能。因為這個內存依然是假的,是系統分配的。


去搜搜內存定址的機制。

1.每個進程定址空間獨立:32/64位的定址空間

2.進程定址空間是虛擬的,不代表物理內存地址

3.虛地址分成兩部分:前半部分訪問虛表得到段號或者頁號,後半部分是偏移量

4.通過段號和偏移量查段表得到物理地址

5.物理地址是操作系統在調度


推薦閱讀:

在Windows上安裝Oracle VM VirtualBox Linux系統,共享目錄的原理是什麼?
Linux文件系統中/bin、/sbin、/usr/bin、/usr/sbin、/usr/local/bin、/usr/local/sbin文件夾的區別是什麼?
怎麼搭建學習Linux內核的運行、調試環境?
在現代的Unix操作系統上執行sudo rm -rf /會發生什麼?
有什麼理由選擇收費的RHEL而不用免費的Centos?

TAG:Linux | C編程語言 | 內存管理 |