2013-08-27 26 views
1

獲得操作碼作者here並執行以下操作:如何獲取c程序的最低可執行操作碼?

[[email protected] testbed8]$ as testshell2.s -o testshell2.o 
[[email protected] testbed8]$ ld testshell2.o -o testshell2 
[[email protected] testbed8]$ objdump -d testshell2 

,然後他得到三段(或只提到這3個):

  • < _start>

  • <起動機>

  • <恩德>

我試圖以相同的方式得到十六進制操作碼,但不能正確地ld。當然,我可以生產.o和PROG文件例如用:

gcc main.o -o prog -g 
然而

objdump --prefix-addresses --show-raw-insn -Srl prog 

看到與註解和符號,I have many additional sections there完整的代碼,例如:

  • .init

  • .plt

  • 的.text(是的,我知道,主要是在這裏)這裏的許多部分:_start(),call_gmon_start(),__do_global_dtors_aux(),frame_dummy(),main()中,__libc_csu_init(),__libc_csu_fini() __do_global_ctors_aux()]

  • 調用.fini

我假定這些是與gcc鏈接到運行時庫中引入的。我想我不需要這些所有部分從c代碼調用操作碼(作者僅使用這3部分),但是我的問題是我不知道我可能會丟棄哪些部分以及哪些部分是必需的。我想用這樣的:

#include <unistd.h> 

char code[] = "\x31\xed\x49\x89\x...x00\x00"; 

int main(int argc, char **argv) 
{ 
/*creating a function pointer*/ 
int (*func)(); 
func = (int (*)()) code; 
(int)(*func)(); 

return 0; 
} 

,所以我創造了這個:

#include <unistd.h> 
/* 
* 
*/ 
int main() { 

    char *shell[2]; 

    shell[0] = "/bin/sh"; 
    shell[1] = NULL; 
    execve(shell[0], shell, NULL); 

    return 0; 
} 

,我做拆卸跟我描述。我嘗試使用.text main()的操作碼,這給了我分段錯誤,然後.text main()+另外.text _start(),具有相同的結果。

那麼,從上面的部分可以選擇什麼,或者如何只生成與三個部分一樣最小化的「prog」?

回答

2

char code [] =「\ x31 \ xed \ x49 \ x89 \ x ... x00 \ x00」;

這是行不通的。

原因:代碼必須包含地址。主要是函數execve()的地址和字符串常量「/ bin/sh」的地址。

使用「code []」方法的可執行文件根本不包含字符串常量「/ bin/sh」,函數execve()的地址將不同(如果該函數將鏈接到可執行文件完全)。

因此「呼叫」指令給「的execve()」函數將跳到在任何地方使用「代碼[]」的方法可執行文件。

一些理論關於可執行文件 - 只爲您的信息:

有兩種可能的可執行文件:

  • 靜態鏈接:這些可執行文件包含所有必要的代碼。因此他們不訪問動態庫,如「libc.so」
  • 動態鏈接:這些可執行文件不包含經常使用的代碼。這樣的代碼被存儲在共同所有的可執行文件:動態庫(例如,「libc.so」)

當相同的C代碼則使用靜態鏈接的可執行程序是比動態鏈接的可執行因爲所有大得多 C函數(例如「printf」,「execve」等)必須綁定到可執行文件中。

當不使用任何這些庫函數的靜態鏈接可執行更簡單,因此更容易理解。

靜態鏈接的可執行行爲

靜態鏈接的可執行程序加載到由操作系統的存儲器(當它被使用的execve啓動())。該可執行文件包含一個入口點地址。該地址存儲在可執行文件的文件頭中。你可以使用「objdump -h ...」來看它。

操作系統執行跳轉到該地址,以便程序在此地址開始執行。地址通常是「_start」函數,但是當使用「ld」進行鏈接時,可以使用命令行選項更改該地址。

該代碼在「_start」將準備的可執行文件(例如初始化變量,計算「的argc」和「argv的」的值,...),並調用「主()」函數。當「main()」返回「_start」函數時,會將「main()」返回的值傳遞給「_exit()」函數。

動態鏈接的可執行文件的行爲

這樣的可執行文件包含兩個額外的部分。第一部分包含動態鏈接器的文件名(可能是「/lib/ld-linux.so.1」)。然後操作系統將加載可執行文件和動態鏈接程序,並跳轉到動態鏈接程序的入口點(而不是可執行文件的入口點)。

動態鏈接程序將讀取的第二附加部分:它包含有關由可執行所需的動態庫(例如「libc.so」)的信息。它會加載所有這些庫並初始化很多變量。然後它調用所有庫和可執行文件的初始化函數(「_init()」)。

請注意,操作系統和動態鏈接程序都會忽略函數和段名稱!入口點的地址取自文件頭,「_init()」函數的地址取自附加部分 - 函數的命名可能不同!

當所有這些都完成後,動態鏈接器將跳轉到可執行文件的入口點(「_start」)。

關於「GOT」,「PLT」 ......部分:

這些章節包含有關在動態庫已經被加載鏈接器的地址信息。 「PLT」部分包含包含跳轉到動態庫的包裝代碼。這意味着:「PLT」部分將包含一個函數「printf()」,它實際上除了跳轉到「libc.so」中的「printf()」函數外什麼也不做。這是因爲直接從C代碼調用動態庫中的函數會使連接變得更加困難,因此C代碼不會直接調用動態庫中的函數。這種實現的另一個優點是「懶鏈接」是可能的。

有關Windows有些話

的Windows只知道動態鏈接的可執行文件。 Windows XP甚至拒絕加載不需要DLL的可執行文件。 「動態鏈接器」集成到操作系統中,而不是單獨的文件。還有一個相當於「PLT」部分。然而,許多編譯器支持從C代碼中直接調用DLL代碼,而無需首先調用PLT代碼中的代碼(理論上這在Linux下也是可行的)。不支持懶鏈接。

+0

實際上,有一種叫做「延遲加載導入」(http://msdn.microsoft.com/zh-cn/library/151kt790),儘管它在編譯器中實現,而不是OS加載器本身。 –

+0

@Martin Rosenau謝謝你,很好的回答,但是你沒有提到我的問題的一個解決方案:如果我做了我的shell程序的靜態鏈接並從_start中檢索操作碼,它會好嗎? (那裏沒有_init吧?) – 4pie0

相關問題