[英]How to make an executable ELF file in Linux using a hex editor?
只是好奇。 對於實際編程來說,這顯然不是一個很好的解決方案,但假設我想在 Bless(一個十六進制編輯器)中制作一個可執行文件。
我的架構是 x86。 我可以制作什么非常簡單的程序? 一個你好世界? 無限循環? 與此問題類似,但在 Linux 中。
這個答案的版本有一個很好的TOC和更多內容: http : //www.cirosantilli.com/elf-hello-world (在這里達到30k char限制)
ELF由LSB指定:
LSB基本上鏈接到具有次要擴展的其他標准,特別是:
通用(由SCO提供):
特定架構:
可以在以下位置找到方便的摘要:
man elf
它的結構可以通過readelf
和objdump
等實用程序以人類可讀的方式進行檢查。
讓我們分解一個最小的可運行Linux x86-64示例:
section .data
hello_world db "Hello world!", 10
hello_world_len equ $ - hello_world
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, hello_world_len
syscall
mov rax, 60
mov rdi, 0
syscall
編譯:
nasm -w+all -f elf64 -o 'hello_world.o' 'hello_world.asm'
ld -o 'hello_world.out' 'hello_world.o'
版本:
ld
) 我們不使用C程序,因為這會使分析復雜化,這將是第2級:-)
hd hello_world.o
hd hello_world.out
輸出地址: https : //gist.github.com/cirosantilli/7b03f6df2d404c0862c6
ELF文件包含以下部分:
ELF標題。 指向節頭表和程序頭表的位置。
節頭表(可執行文件可選)。 每個都有e_shnum
節標題,每個標題指向一個節的位置。
N個部分, N <= e_shnum
(可執行文件中可選)
程序頭表(僅適用於可執行文件)。 每個都有e_phnum
程序頭,每個頭都指向一個段的位置。
N個段, N <= e_phnum
(可執行文件中可選)
這些部分的順序並不固定:唯一固定的是ELF標題必須是文件上的第一個東西:通用文檔說:
觀察標題的最簡單方法是:
readelf -h hello_world.o
readelf -h hello_world.out
輸出地址: https : //gist.github.com/cirosantilli/7b03f6df2d404c0862c6
目標文件中的字節:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 |..>.............|
00000020 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |....@.....@.....|
可執行文件:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 b0 00 40 00 00 00 00 00 |..>.......@.....|
00000020 40 00 00 00 00 00 00 00 10 01 00 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 02 00 40 00 06 00 03 00 |....@.8...@.....|
結構代表:
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
手動分解:
0 0: EI_MAG
= 7f 45 4c 46
= EI_MAG
0x7f 'E', 'L', 'F'
:ELF幻數
0 4: EI_CLASS
= 02
= ELFCLASS64
:64位精靈
0 5: EI_DATA
= 01
= ELFDATA2LSB
:大端數據
0 6: EI_VERSION
= 01
:格式版本
0 7: EI_OSABI
(僅在2003年更新)= 00
= ELFOSABI_NONE
:沒有擴展名。
0 8: EI_PAD
= 8x 00
:保留字節。 必須設置為0。
1 0: e_type
= 01 00
= 1(大端)= ET_REl
:可重定位格式
在可執行文件上, ET_EXEC
為02 00
。
1 2: e_machine
= 3e 00
= 62
= EM_X86_64
:AMD64架構
1 4: e_version
= 01 00 00 00
:必須為1
1 8: e_entry
= 8x 00
:執行地址入口點,如果不適用,則為0,如對象文件,因為沒有入口點。
在可執行文件上,它是b0 00 40 00 00 00 00 00
。 TODO:我們還能將其設置為什么? 內核似乎直接將IP放在該值上,它不是硬編碼的。
2 0: e_phoff
= 8x 00
:程序頭表偏移量,如果不存在則為0。
40 00 00 00
在可執行文件上,即它在ELF標題之后立即啟動。
2 8: e_shoff
= 40
7x 00
= 0x40
:節頭表文件偏移量,如果不存在則為0。
3 0: e_flags
= 00 00 00 00
TODO。 Arch具體。
3 4: e_ehsize
= 40 00
:此精靈標題的大小。 TODO為什么要這個領域? 它怎么變化?
3 6: e_phentsize
= 00 00
:每個程序頭的大小,如果不存在,則為0。
38 00
on executable:它長56個字節
3 8: e_phnum
= 00 00
:程序頭條目數,如果不存在,則為0。
02 00
on executable:有2個條目。
3 A: e_shentsize
和e_shnum
= 40 00 07 00
:節標題大小和條目數
3 E: e_shstrndx
( Section Header STRing iNDeX
)= 03 00
: .shstrtab
部分的索引。
Elf64_Shdr
結構數組。
每個條目都包含有關給定部分的元數據。
ELF頭的e_shoff
給出了起始位置0x40。
ELF頭中的e_shentsize
和e_shnum
表示我們有7個條目,每個條目長0x40
個字節。
因此該表從0x40到0x40 + 7 + 0x40 - 1
= 0x1FF獲取字節。
某些部分名稱保留用於某些部分類型: http : //www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections例如.text
需要SHT_PROGBITS
類型和SHF_ALLOC
+ SHF_EXECINSTR
readelf -S hello_world.o
:
There are 7 section headers, starting at offset 0x40:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .data PROGBITS 0000000000000000 00000200
000000000000000d 0000000000000000 WA 0 0 4
[ 2] .text PROGBITS 0000000000000000 00000210
0000000000000027 0000000000000000 AX 0 0 16
[ 3] .shstrtab STRTAB 0000000000000000 00000240
0000000000000032 0000000000000000 0 0 1
[ 4] .symtab SYMTAB 0000000000000000 00000280
00000000000000a8 0000000000000018 5 6 4
[ 5] .strtab STRTAB 0000000000000000 00000330
0000000000000034 0000000000000000 0 0 1
[ 6] .rela.text RELA 0000000000000000 00000370
0000000000000018 0000000000000018 4 2 4
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
每個條目代表的struct
:
typedef struct {
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
包含在字節0x40到0x7F中。
第一部分總是很神奇: http : //www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html說:
如果節的數量大於或等於SHN_LORESERVE(0xff00),則e_shnum的值為SHN_UNDEF(0),並且節頭表條目的實際數量包含在索引0的節頭的sh_size字段中(否則,初始條目的sh_size成員包含0)。
還有其他魔術部分詳見Figure 4-7: Special Section Indexes
。
在索引0中, SHT_NULL
是必需的。 是否還有其他用途: ELF中SHT_NULL部分的用途是什么? ?
.data
是第1部分:
00000080 01 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00 |................| 00000090 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 |................| 000000a0 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000b0 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
80 0: sh_name
= 01 00 00 00
: .shstrtab
字符串表中的索引1
這里, 1
表示該部分的名稱從該部分的第一個字符開始,並以第一個NUL字符結束,構成字符串.data
。
.data
是具有預定義含義的部分名稱之一http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html
這些部分包含有助於程序內存映像的初始化數據。
80 4: sh_type
= 01 00 00 00
: SHT_PROGBITS
:ELF未指定節內容,僅由程序如何解釋。 正常,因為.data
部分。
80 8: sh_flags
= 03
7x 00
: SHF_ALLOC
和SHF_EXECINSTR
: http : //www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#sh_flags ,根據.data
部分的要求
90 0: sh_addr
= 8x 00
:在執行期間將放置該部分的虛擬地址,如果未放置,則為0
90 8: sh_offset
= 00 02 00 00 00 00 00 00
= 0x200
:從程序開始到本節第一個字節的字節數
a0 0: sh_size
= 0d 00 00 00 00 00 00 00
如果我們從sh_offset
200開始采用0xD字節,我們會看到:
00000200 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a 00 |Hello world!.. |
AHA! 所以我們的"Hello world!"
字符串在數據部分,就像我們告訴它在NASM上一樣。
一旦我們畢業的hd
,我們將看到這個像:
readelf -x .data hello_world.o
哪個輸出:
Hex dump of section '.data': 0x00000000 48656c6c 6f20776f 726c6421 0a Hello world!.
NASM為該部分設置了不錯的屬性,因為它神奇地處理.data
: http : //www.nasm.us/doc/nasmdoc7.html#section-7.9.2
另請注意,這是一個糟糕的部分選擇:一個好的C編譯器會將字符串放在.rodata
,因為它是只讀的,它將允許進一步的OS優化。
A0 8: sh_link
和sh_info
= 8X 0:不適用於本部分的類型。 http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections
b0 0: sh_addralign
= 04
= TODO:為什么需要這種對齊? 它只適用於sh_addr
,還適用於sh_addr
符號?
b0 8: sh_entsize
= 00
=該部分不包含表格。 如果!= 0,則表示該部分包含固定大小條目的表。 在這個文件中,我們從readelf
輸出中看到.symtab
和.rela.text
部分就是這種情況。
現在我們已經手動完成了一個部分,讓我們畢業並使用其他部分的readelf -S
。
objdump -d hello_world.o
.text
是可執行的但不可寫:如果我們嘗試寫入Linux段錯誤。 讓我們看看我們是否真的有一些代碼:
hello_world.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_start>:
0: b8 01 00 00 00 mov $0x1,%eax
5: bf 01 00 00 00 mov $0x1,%edi
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
14: ba 0d 00 00 00 mov $0xd,%edx
19: 0f 05 syscall
1b: b8 3c 00 00 00 mov $0x3c,%eax
20: bf 00 00 00 00 mov $0x0,%edi
25: 0f 05 syscall
得到:
hello_world.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <_start>: 0: b8 01 00 00 00 mov $0x1,%eax 5: bf 01 00 00 00 mov $0x1,%edi a: 48 be 00 00 00 00 00 movabs $0x0,%rsi 11: 00 00 00 14: ba 0d 00 00 00 mov $0xd,%edx 19: 0f 05 syscall 1b: b8 3c 00 00 00 mov $0x3c,%eax 20: bf 00 00 00 00 mov $0x0,%edi 25: 0f 05 syscall
如果我們在hd
上grep b8 01 00 00
,我們看到這只發生在00000210
,這就是該部分所說的。 大小是27,也匹配。 所以我們必須談論正確的部分。
這看起來像正確的代碼: write
后exit
。
最有趣的部分是a
行:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
將字符串的地址傳遞給系統調用。 目前, 0x0
只是一個占位符。 鏈接發生后,它將被修改為包含:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
由於.rela.text
部分的數據,這種修改是可能的。
帶有sh_type == SHT_STRTAB
節被稱為字符串表 。
它們包含一個空分隔的字符串數組。
當使用字符串名稱時,其他部分將使用這些部分。 使用部分說:
例如,我們可以有一個字符串表包含:TODO:它必須以\\0
開頭嗎?
Data: \\0 abc \\0 def \\0 Index: 0 1 2 3 4 5 6 7 8
如果另一部分想要使用字符串def
,則必須指向本節的索引5
(字母d
)。
值得注意的字符串表部分:
.shstrtab
.strtab
節類型: sh_type == SHT_STRTAB
。
通用名稱: 節頭字符串表 。
節名稱.shstrtab
是保留的。 標准說:
此部分包含部分名稱。
ELF標題本身的e_shstrnd
字段指向此部分。
此節的字符串索引由節標題的sh_name
字段指向,表示字符串。
此部分沒有標記SHF_ALLOC
,因此它不會出現在執行程序中。
Hex dump of section '.shstrtab':
0x00000000 002e6461 7461002e 74657874 002e7368 ..data..text..sh
0x00000010 73747274 6162002e 73796d74 6162002e strtab..symtab..
0x00000020 73747274 6162002e 72656c61 2e746578 strtab..rela.tex
0x00000030 7400 t.
得到:
Hex dump of section '.shstrtab': 0x00000000 002e6461 7461002e 74657874 002e7368 ..data..text..sh 0x00000010 73747274 6162002e 73796d74 6162002e strtab..symtab.. 0x00000020 73747274 6162002e 72656c61 2e746578 strtab..rela.tex 0x00000030 7400 t.
本節中的數據具有固定格式: http : //www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html
如果我們查看其他部分的名稱,我們會看到它們都包含數字,例如.text
部分是數字7
。
然后每個字符串在找到第一個NUL字符時結束,例如字符12
是\\0
, .text\\0
在.text\\0
。
節類型: sh_type == SHT_SYMTAB
。
通用名稱: 符號表 。
首先,我們注意到:
sh_link
= 5
sh_info
= 6
對於SHT_SYMTAB
部分,這些數字表示:
.strtab
.rela.text
拆卸該部分的一個很好的高級工具是:
0000000000000000 T _start
0000000000000000 d hello_world
000000000000000d a hello_world_len
這使:
readelf -s hello_world.o
然而,這是一個高級視圖,省略了某些類型的符號以及符號類型。 可以通過以下方式獲得更詳細的反匯編:
Symbol table '.symtab' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello_world.asm
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world
5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len
6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start
這使:
Symbol table '.symtab' contains 7 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello_world.asm 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 2 4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len 6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start
該表的二進制格式記錄在http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html
數據是:
Hex dump of section '.symtab':
0x00000000 00000000 00000000 00000000 00000000 ................
0x00000010 00000000 00000000 01000000 0400f1ff ................
0x00000020 00000000 00000000 00000000 00000000 ................
0x00000030 00000000 03000100 00000000 00000000 ................
0x00000040 00000000 00000000 00000000 03000200 ................
0x00000050 00000000 00000000 00000000 00000000 ................
0x00000060 11000000 00000100 00000000 00000000 ................
0x00000070 00000000 00000000 1d000000 0000f1ff ................
0x00000080 0d000000 00000000 00000000 00000000 ................
0x00000090 2d000000 10000200 00000000 00000000 -...............
0x000000a0 00000000 00000000 ........
這使:
typedef struct {
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;
條目類型:
typedef struct { Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Half st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; } Elf64_Sym;
就像在section表中一樣,第一個條目是神奇的,並設置為固定的無意義值。
STT_FILE 條目1具有ELF64_R_TYPE == STT_FILE
。 ELF64_R_TYPE
持續的內部st_info
。
字節分析:
10 8: st_name
= 01000000
=在字符1 .strtab
,它直到下面\\0
使得hello_world.asm
鏈接器可以使用這條信息文件來決定哪些段部分。
10 12: st_info
= 04
位0-3 = ELF64_R_TYPE
= Type = 4
= STT_FILE
:此條目的主要目的是使用st_name
指示生成此目標文件的文件的名稱。
位4-7 = ELF64_ST_BIND
= Binding = 0
= STB_LOCAL
。 STT_FILE
必需值。
10 13: st_shndx
=符號表部分標題索引= f1ff
= SHN_ABS
。 STT_FILE
必需。
20 0: st_value
= 8x 00
: STT_FILE
值需要
20 8: st_size
= 8x 00
:沒有分配的大小
現在,從readelf
,我們很快解釋其他人。
有兩個這樣的條目,一個指向.data
,另一個指向.text
(段索引1
和2
)。
global _start
TODO他們的目的是什么?
STT_NOTYPE然后是最重要的符號:
Num: Value Size Type Bind Vis Ndx Name 4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len 6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start
hello_world
字符串位於.data
部分(索引1)。 它的值為0:它指向該部分的第一個字節。
自從我們寫道以來, _start
標有GLOBAL
可見性:
global _start
在NASM。 這是必要的,因為它必須被視為切入點。 與C不同,默認NASM標簽是本地的。
SHN_ABS hello_world_len
指向特殊的st_shndx == SHN_ABS == 0xF1FF
。
選擇0xF1FF
以便不與其他部分沖突。
st_value == 0xD == 13
這是我們在程序集中存儲的值:字符串Hello World!
的長度Hello World!
。
這意味着重定位不會影響此值:它是一個常量。
這是我們的匯編程序為我們做的小優化,並且具有ELF支持。
如果我們在任何地方使用過hello_world_len
的地址,那么匯編程序就無法將其標記為SHN_ABS
,並且鏈接器稍后會對其進行額外的重定位工作。
默認情況下,NASM .symtab
在可執行文件上放置.symtab
。
這僅用於調試。 沒有符號,我們完全失明,必須對所有事情進行逆向工程。
您可以使用objcopy
刪除它,並且可執行文件仍將運行。 此類可執行文件稱為剝離的可執行文件 。
保存符號表的字符串。
此部分有sh_type == SHT_STRTAB
。
它由.symtab
部分的sh_link == 5
.symtab
。
readelf -x .strtab hello_world.o
得到:
Relocation section '.rela.text' at offset 0x3b0 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0
這意味着全局變量不能包含NUL字符是ELF級別限制。
節類型: sh_type == SHT_RELA
。
通用名稱: 重定位部分 。
.rela.text
保存重定位數據,該數據說明在鏈接最終可執行文件時應如何修改地址。 這指向文本區域的字節,當鏈接發生指向正確的內存位置時必須修改。
基本上,它會轉換包含占位符0x0地址的對象文本:
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi 11: 00 00 00
到包含最終0x6000d8的實際可執行代碼:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi 4000c1: 00 00 00
有人指出由sh_info
= 6
所述的.symtab
部分。
readelf -r hello_world.o
給出:
Relocation section '.rela.text' at offset 0x3b0 contains 1 entries: Offset Info Type Sym. Value Sym. Name + Addend 00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0
該部分在可執行文件中不存在。
實際字節是:
Elf file type is EXEC (Executable file)
Entry point 0x4000b0
There are 2 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000d7 0x00000000000000d7 R E 200000
LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
0x000000000000000d 0x000000000000000d RW 200000
Section to Segment mapping:
Segment Sections...
00 .text
01 .data
表示的struct
是:
00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....|
00000060 d7 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 |................|
00000070 00 00 20 00 00 00 00 00 |.. ..... |
所以:
370 0: r_offset
= 0xC:進入.text
的地址,其重定位將修改其地址
370 8: r_info
= 0x200000001。 包含2個字段:
ELF64_R_TYPE
= 0x1:含義取決於確切的體系結構。 ELF64_R_SYM
= 0x2:地址所指向的段的索引,因此.data
位於索引2處。 AMD64 ABI表示類型1
稱為R_X86_64_64
,它表示操作S + A
,其中:
S
:目標文件上符號的值,這里為0
因為我們指向00 00 00 00 00 00 00 00
movabs $0x0,%rsi
A
:加數,存在於字段r_added
此地址將添加到重定位操作的部分。
此重定位操作總共占用8個字節。
380 0: r_addend
= 0
因此,在我們的示例中,我們得出結論,新地址將是: S + A
= .data + 0
,因此數據部分中的第一件事。
僅出現在可執行文件中。
包含有關如何將可執行文件放入進程虛擬內存的信息。
可執行文件由鏈接器從目標文件生成。 鏈接器的主要工作是:
確定目標文件的哪些部分將進入可執行文件的哪些部分。
在Binutils中,這歸結為解析鏈接器腳本,並處理一堆默認值。
您可以獲取與ld --verbose
一起使用的鏈接描述文件,並使用ld -T
設置自定義腳本。
對文本部分進行重定位。 這取決於多個部分如何放入內存。
readelf -l hello_world.out
給出:
Elf file type is EXEC (Executable file) Entry point 0x4000b0 There are 2 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000000d7 0x00000000000000d7 RE 200000 LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8 0x000000000000000d 0x000000000000000d RW 200000 Section to Segment mapping: Segment Sections... 00 .text 01 .data
在ELF頭文件中, e_phoff
, e_phnum
和e_phentsize
告訴我們有2個程序頭,它們從0x40
開始,每個長度為0x38
字節,所以它們是:
00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................| 00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....| 00000060 d7 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 |................| 00000070 00 00 20 00 00 00 00 00 |.. ..... |
和:
00000070 01 00 00 00 06 00 00 00 | ........| 00000080 d8 00 00 00 00 00 00 00 d8 00 60 00 00 00 00 00 |..........`.....| 00000090 d8 00 60 00 00 00 00 00 0d 00 00 00 00 00 00 00 |..`.............| 000000a0 0d 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 |.......... .....|
結構代表http://www.sco.com/developers/gabi/2003-12-17/ch5.pheader.html :
typedef struct { Elf64_Word p_type; Elf64_Word p_flags; Elf64_Off p_offset; Elf64_Addr p_vaddr; Elf64_Addr p_paddr; Elf64_Xword p_filesz; Elf64_Xword p_memsz; Elf64_Xword p_align; } Elf64_Phdr;
細分第一個:
p_type
= 01 00 00 00
= PT_LOAD
:TODO。 我認為這意味着它將實際加載到內存中。 其他類型可能不一定。 p_flags
= 05 00 00 00
=執行和讀取權限,不寫TODO p_offset
= 8x 00
TODO:這是什么? 看起來像段開頭的偏移量。 但這意味着某些細分市場交織在一起? 可以使用它來玩它: gcc -Wl,-Ttext-segment=0x400030 hello_world.c
p_vaddr
= 00 00 40 00 00 00 00 00
:要加載此段的初始虛擬內存地址 p_paddr
= 00 00 40 00 00 00 00 00
:要在內存中加載的初始物理地址。 僅適用於程序可以設置其物理地址的系統。 否則,就像在System V系統中一樣,可以是任何東西。 NASM似乎只是復制p_vaddrr
p_filesz
= d7 00 00 00 00 00 00 00
:TODO vs p_memsz
p_memsz
= d7 00 00 00 00 00 00 00
:TODO p_align
= 00 00 20 00 00 00 00 00
:0或1表示無需對齊TODO是什么意思? 否則與其他領域重復 第二個是類似的。
那么:
Section to Segment mapping:
readelf
部分告訴我們:
.text
段。 啊哈,所以這就是為什么它是可執行的,而不是可寫的 .data
段。 正如我的評論中所提到的,你將基本上為可執行文件編寫自己的elf-header,從而消除不需要的部分。 仍有幾個必修部分。 Muppetlabs-TinyPrograms的文檔可以很好地解釋這個過程。 為了好玩,這里有幾個例子:
相當於/ bin / true(45字節):
00000000 7F 45 4C 46 01 00 00 00 00 00 00 00 00 00 49 25 |.ELF..........I%|
00000010 02 00 03 00 1A 00 49 25 1A 00 49 25 04 00 00 00 |......I%..I%....|
00000020 5B 5F F2 AE 40 22 5F FB CD 80 20 00 01 |[_..@"_... ..|
0000002d
你的經典'Hello World!' (160字節):
00000000 7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 03 00 01 00 00 00 74 80 04 08 34 00 00 00 |........t...4...|
00000020 00 00 00 00 00 00 00 00 34 00 20 00 02 00 28 00 |........4. ...(.|
00000030 00 00 00 00 01 00 00 00 74 00 00 00 74 80 04 08 |........t...t...|
00000040 74 80 04 08 1f 00 00 00 1f 00 00 00 05 00 00 00 |t...............|
00000050 00 10 00 00 01 00 00 00 93 00 00 00 93 90 04 08 |................|
00000060 93 90 04 08 0d 00 00 00 0d 00 00 00 06 00 00 00 |................|
00000070 00 10 00 00 b8 04 00 00 00 bb 01 00 00 00 b9 93 |................|
00000080 90 04 08 ba 0d 00 00 00 cd 80 b8 01 00 00 00 31 |...............1|
00000090 db cd 80 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a |...Hello world!.|
000000a0
別忘了讓它們可執行......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.