CTF--PWN必備技能--理解c程序從編譯開始到運行結(jié)束的過程-創(chuàng)新互聯(lián)

重溫c語言

我們在linux平臺下建立一個a.c文件,程序很簡單,顯示輸出Please input your name:,然后讓我們輸入名字,最后調(diào)用了一個子函數(shù)輸出hello,我們的名字

成都創(chuàng)新互聯(lián)公司作為成都網(wǎng)站建設(shè)公司,專注成都網(wǎng)站建設(shè)公司、網(wǎng)站設(shè)計,有關(guān)成都定制網(wǎng)站方案、改版、費用等問題,行業(yè)涉及不銹鋼雕塑等多個領(lǐng)域,已為上千家企業(yè)服務(wù),得到了客戶的尊重與認可。
#includevoid hello(char * name);
	int main()
	{char name[16]={0};
	        printf("Please input your name:");
	        gets(name);
	        hello(name);
	        return 0;
	}
	void hello(char * name)
	{printf("hello,%s\n",name);
	}
從.c文件到可執(zhí)行文件 gcc的編譯過程
預(yù)處理(Preprocessing),
編譯(Compilation),
匯編(Assemble),
鏈接(Linking)

gcc編譯C語言主要用到以下幾個程序:C編譯器gcc、匯編器as、鏈接器ld和二進制轉(zhuǎn)換工具objcopy

gcc選項總結(jié):
-E 只激活預(yù)處理,這個不生成文件,你需要把它重定向到一個輸出文件里面
-S 編譯到匯編語言不進行匯編和鏈接
-c 編譯到目標(biāo)代碼
-o 文件輸出到 文件
-static 此選項對生成的文件采用靜態(tài)鏈接
-g 生成調(diào)試信息。GNU 調(diào)試器可利用該信息
-shared 此選項將盡量使用動態(tài)庫,所以生成文件比較小,但是需要系統(tǒng)由動態(tài)庫
-O0
-O1
-O2
-O3 編譯器的優(yōu)化選項的4個級別,-O0表示沒有優(yōu)化,-O1為缺省值,-O3優(yōu)化級別最高
-w 不生成任何警告信息
-Wall 生成所有警告信息(默認生成)
1.預(yù)處理(Preprocessing)

由編譯器完成,將所有的#include頭文件以及宏定義替換成其真正的內(nèi)容
gcc的預(yù)處理是預(yù)處理器cpp來完成的

我們可以用以下指令對.c文件進行預(yù)處理:

gcc -E a.c -o a.i

或者

cpp a.c -o a.i

-o:代表輸出到指定文件

在這里插入圖片描述
可以看到文件預(yù)處理后變大了很多
打開a.i文件發(fā)現(xiàn)是把我們用的頭文件stdio.h所涉及的其他頭文件以及宏定義,變量都引入了進來
在這里插入圖片描述

在這里插入圖片描述
可以看到文件最后才是我們寫的c代碼,實際上我們能直接使用printf等函數(shù)是因為我們引用了一個頭文件,使得我們可以少寫很多代碼,就相當(dāng)于自己事先把一個自定義的函數(shù)寫到一個文件里,下次再使用直接引用這個文件就可以了,而不用自己再把以前定義好的函數(shù)再抄一遍了,我們用的gcc編譯器的預(yù)處理就幫助我們省略了抄定義好的函數(shù),變量等東西這一步

2.編譯(Compilation)

由編譯器完成,將經(jīng)過預(yù)處理之后的程序轉(zhuǎn)換成特定匯編代碼的過程, 編譯的命令如下:

gcc -S a.i -o a.s

在這里插入圖片描述
執(zhí)行這一步程序會出現(xiàn)一個warning,警告:函數(shù)gets的隱式聲明;你是說“fgets”嗎?
為什么會出現(xiàn)這個呢,這是因為gcc編譯器太強了,檢測出我們使用了gets函數(shù),然后說gets函數(shù)很危險,建議用fgets函數(shù),這個問題在后面會講

我們先來查看一下編譯好的匯編代碼

.file	"a.c"
	.text
	.section	.rodata
.LC0:
	.string	"Please input your name:"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$32, %rsp
	movq	%fs:40, %rax
	movq	%rax, -8(%rbp)
	xorl	%eax, %eax
	movq	$0, -32(%rbp)
	movq	$0, -24(%rbp)
	leaq	.LC0(%rip), %rax
	movq	%rax, %rdi
	movl	$0, %eax
	call	printf@PLT
	leaq	-32(%rbp), %rax
	movq	%rax, %rdi
	movl	$0, %eax
	call	gets@PLT
	leaq	-32(%rbp), %rax
	movq	%rax, %rdi
	call	hello
	movl	$0, %eax
	movq	-8(%rbp), %rdx
	subq	%fs:40, %rdx
	je	.L3
	call	__stack_chk_fail@PLT
.L3:
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.section	.rodata
.LC1:
	.string	"hello,%s\n"
	.text
	.globl	hello
	.type	hello, @function
hello:
.LFB1:
	.cfi_startproc
	endbr64
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movq	%rdi, -8(%rbp)
	movq	-8(%rbp), %rax
	movq	%rax, %rsi
	leaq	.LC1(%rip), %rax
	movq	%rax, %rdi
	movl	$0, %eax
	call	printf@PLT
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE1:
	.size	hello, .-hello
	.ident	"GCC: (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	1f - 0f
	.long	4f - 1f
	.long	5
0:
	.string	"GNU"
1:
	.align 8
	.long	0xc0000002
	.long	3f - 2f
2:
	.long	0x3
3:
	.align 8
4:

這是AT&T匯編語言,初學(xué)者可能看上去很復(fù)雜,暫且不看
接著往下走

3.匯編(Assemble)

由匯編器完成,匯編過程將上一步的匯編代碼轉(zhuǎn)換成機器碼(machine code),這一步產(chǎn)生了二進制的目標(biāo)文件(.o文件)(ELF文件), gcc匯編過程通過as命令完成

gcc -c a.s -o a.o

或者

as a.s -o a.o

查看一下a.o
在這里插入圖片描述
這已經(jīng)把我們的匯編語言變成二進制目標(biāo)代碼(.o文件)了
.o文件又稱對象文件,是可執(zhí)行文件,是可重定向文件的一種,通常以ELF格式保存,里面包含了對各個函數(shù)的入口標(biāo)記,描述,當(dāng)程序要執(zhí)行時還需要鏈接(link).鏈接就是把多個.o文件鏈成一個可執(zhí)行文件。
中間插一段elf文件的內(nèi)容

番外篇1–ELF文件

ELF文件格式是linux下可執(zhí)行文件的一種格式

在這里插入圖片描述

幾種類型的ELF文件:
  • 可重定位文件(Relocatable File):用戶和其他目標(biāo)文件一起創(chuàng)建可執(zhí)行文件或者共享目標(biāo)文件,例如lib*.a文件。
  • 可執(zhí)行文件(Executable File):用于生成進程映像,載入內(nèi)存執(zhí)行,例如編譯好的可執(zhí)行文件a.out。
  • 共享目標(biāo)文件(Shared Object File):用于和其他共享目標(biāo)文件或者可重定位文件一起生成elf目標(biāo)文件或者和執(zhí)行文件一起創(chuàng)建進程映像,例如lib*.so文件。
  • 核心轉(zhuǎn)儲文件(Core Dump File),當(dāng)進程意外終止時,系統(tǒng)可以將該進程的地址空間內(nèi)容及終止時的一些其他信息轉(zhuǎn)儲到核心轉(zhuǎn)儲文件,如linux下的coredump文件。
ELF文件作用:

ELF文件參與程序的連接(建立一個程序)和程序的執(zhí)行(運行一個程序),所以可以從不同的角度來看待elf格式的文件:

  • 如果用于編譯和鏈接(可重定位文件),則編譯器和鏈接器將把elf文件看作是節(jié)頭表描述的節(jié)的集合,程序頭表可選。
  • 如果用于加載執(zhí)行(可執(zhí)行文件),則加載器則將把elf文件看作是程序頭表描述的段的集合,一個段可能包含多個節(jié),節(jié)頭表可選。
  • 如果是共享文件,則兩者都含有。
ELF文件總體組成:

elf文件頭描述elf文件的總體信息。包括:系統(tǒng)相關(guān),類型相關(guān),加載相關(guān),鏈接相關(guān)。

  • 系統(tǒng)相關(guān)表示:elf文件標(biāo)識的魔術(shù)數(shù),以及硬件和平臺等相關(guān)信息,增加了elf文件的移植性,使交叉編譯成為可能。
  • 類型相關(guān)就是前面說的那個類型。
  • 加載相關(guān):包括程序頭表相關(guān)信息。
  • 鏈接相關(guān):節(jié)頭表相關(guān)信息。

可以用readelf命令來查看elf文件信息
如查看elf文件頭信息

readelf -h a.o

在這里插入圖片描述

readelf命令解析

-a 
--all 顯示全部信息,等價于 -h -l -S -s -r -d -V -A -I. 

-h 
--file-header 顯示elf文件開始的文件頭信息. 

-l 
--program-headers  
--segments 顯示程序頭(段頭)信息(如果有的話)。 

-S 
--section-headers  
--sections 顯示節(jié)頭信息(如果有的話)。 

-g 
--section-groups 顯示節(jié)組信息(如果有的話)。 

-t 
--section-details 顯示節(jié)的詳細信息(-S的)。 

-s 
--syms        
--symbols 顯示符號表段中的項(如果有的話)。 

-e 
--headers 顯示全部頭信息,等價于: -h -l -S 

-n 
--notes 顯示note段(內(nèi)核注釋)的信息。 

-r 
--relocs 顯示可重定位段的信息。 

-u 
--unwind 顯示unwind段信息。當(dāng)前只支持IA64 ELF的unwind段信息。 

-d 
--dynamic 顯示動態(tài)段的信息。 

-V 
--version-info 顯示版本段的信息。 

-A 
--arch-specific 顯示CPU構(gòu)架信息。 

-D 
--use-dynamic 使用動態(tài)段中的符號表顯示符號,而不是使用符號段。 

-x--hex-dump=以16進制方式顯示指定段內(nèi)內(nèi)容。number指定段表中段的索引,或字符串指定文件中的段名。 

-w[liaprmfFsoR] or 
--debug-dump[=line,=info,=abbrev,=pubnames,=aranges,=macro,=frames,=frames-interp,=str,=loc,=Ranges] 顯示調(diào)試段中指定的內(nèi)容。 

-I 
--histogram 顯示符號的時候,顯示bucket list長度的柱狀圖。 

-v 
--version 顯示readelf的版本信息。 

-H 
--help 顯示readelf所支持的命令行選項。 

-W 
--wide 寬行輸出。

elf文件格式先講這些,接著往下看

4.鏈接(Linking)

鏈接過程實際上是把多個可重定位文件合并成一個可執(zhí)行文件的過程。這個過程中最重要的兩個步驟是符號解析和重定位。所謂的符號解析,就是將符號的定義和引用關(guān)聯(lián)起來,而重定位就是給所有符號和指令添加運行時的地址的過程。

過程

  • 確定符號引用關(guān)系(符號解析)
  • 合并相關(guān) .o 文件(重定位)
  • 確定每個符號的地址(重定位)
  • 在指令中填入新的地址(重定位)

我們的程序最終是要裝載到運行內(nèi)存中才可以執(zhí)行的,所以需要規(guī)定一種文件格式來確定裝載到內(nèi)存后對應(yīng)的地址

由鏈接器完成,鏈接分為兩種:動態(tài)鏈接和靜態(tài)鏈接
GCC默認情況下以動態(tài)庫方式link

靜態(tài)鏈接

命令

gcc -static a.o -o a

實際是gcc調(diào)用了ld鏈接器,因為程序需要鏈接很多系統(tǒng)文件,所以這里不推薦使用ld命令鏈接
在這里插入圖片描述

這里出現(xiàn)gets函數(shù)比較危險,跟編譯成匯編時一樣的意思,先不管這里,后面再講

對于靜態(tài)鏈接而言,程序鏈接后的地址其實就是在裝載進內(nèi)存后,程序運行的地址

在這里插入圖片描述
在這里插入圖片描述
可以看到符號表已經(jīng)有了地址,符號表在鏈接時已經(jīng)重定位,當(dāng)程序被裝載進內(nèi)存時,運行時所用的虛擬地址就是這個地址

動態(tài)鏈接

GCC默認情況下以動態(tài)庫方式link

gcc a.o -o a

在這里插入圖片描述
用readelf命令查看一下生成的ELF文件的信息
在這里插入圖片描述
在這里插入圖片描述
這里符號表的地址只是偏移地址(相對于ELF文件首地址的偏移),而ELF文件的首地址在程序裝載進內(nèi)存時才會分配,而動態(tài)符號表(.dynsym)的地址也沒有分配,只有在程序運行時才會分配地址,這是動態(tài)鏈接的一個延遲綁定機制
查看程序節(jié)頭信息會發(fā)現(xiàn)動態(tài)鏈接比靜態(tài)鏈接多出幾個符號表

readelf -S a
  • 動態(tài)鏈接

    There are 31 section headers, starting at offset 0x3710:

    節(jié)頭:
    [號] 名稱 類型 地址 偏移量
    大小 全體大小 旗標(biāo) 鏈接 信息 對齊
    [ 0] NULL 0000000000000000 00000000
    0000000000000000 0000000000000000 0 0 0
    [ 1] .interp PROGBITS 0000000000000318 00000318
    000000000000001c 0000000000000000 A 0 0 1
    [ 2] .note.gnu.pr[…] NOTE 0000000000000338 00000338
    0000000000000030 0000000000000000 A 0 0 8
    [ 3] .note.gnu.bu[…] NOTE 0000000000000368 00000368
    0000000000000024 0000000000000000 A 0 0 4
    [ 4] .note.ABI-tag NOTE 000000000000038c 0000038c
    0000000000000020 0000000000000000 A 0 0 4
    [ 5] .gnu.hash GNU_HASH 00000000000003b0 000003b0
    0000000000000024 0000000000000000 A 6 0 8
    [ 6] .dynsym DYNSYM 00000000000003d8 000003d8
    00000000000000d8 0000000000000018 A 7 1 8
    [ 7] .dynstr STRTAB 00000000000004b0 000004b0
    00000000000000af 0000000000000000 A 0 0 1
    [ 8] .gnu.version VERSYM 0000000000000560 00000560
    0000000000000012 0000000000000002 A 6 0 2
    [ 9] .gnu.version_r VERNEED 0000000000000578 00000578
    0000000000000040 0000000000000000 A 7 1 8
    [10] .rela.dyn RELA 00000000000005b8 000005b8
    00000000000000c0 0000000000000018 A 6 0 8
    [11] .rela.plt RELA 0000000000000678 00000678
    0000000000000048 0000000000000018 AI 6 24 8
    [12] .init PROGBITS 0000000000001000 00001000
    000000000000001b 0000000000000000 AX 0 0 4
    [13] .plt PROGBITS 0000000000001020 00001020
    0000000000000040 0000000000000010 AX 0 0 16
    [14] .plt.got PROGBITS 0000000000001060 00001060
    0000000000000010 0000000000000010 AX 0 0 16
    [15] .plt.sec PROGBITS 0000000000001070 00001070
    0000000000000030 0000000000000010 AX 0 0 16
    [16] .text PROGBITS 00000000000010a0 000010a0
    000000000000018e 0000000000000000 AX 0 0 16
    [17] .fini PROGBITS 0000000000001230 00001230
    000000000000000d 0000000000000000 AX 0 0 4
    [18] .rodata PROGBITS 0000000000002000 00002000
    0000000000000026 0000000000000000 A 0 0 4
    [19] .eh_frame_hdr PROGBITS 0000000000002028 00002028
    000000000000003c 0000000000000000 A 0 0 4
    [20] .eh_frame PROGBITS 0000000000002068 00002068
    00000000000000cc 0000000000000000 A 0 0 8
    [21] .init_array INIT_ARRAY 0000000000003da8 00002da8
    0000000000000008 0000000000000008 WA 0 0 8
    [22] .fini_array FINI_ARRAY 0000000000003db0 00002db0
    0000000000000008 0000000000000008 WA 0 0 8
    [23] .dynamic DYNAMIC 0000000000003db8 00002db8
    00000000000001f0 0000000000000010 WA 7 0 8
    [24] .got PROGBITS 0000000000003fa8 00002fa8
    0000000000000058 0000000000000008 WA 0 0 8
    [25] .data PROGBITS 0000000000004000 00003000
    0000000000000010 0000000000000000 WA 0 0 8
    [26] .bss NOBITS 0000000000004010 00003010
    0000000000000008 0000000000000000 WA 0 0 1
    [27] .comment PROGBITS 0000000000000000 00003010
    000000000000002b 0000000000000001 MS 0 0 1
    [28] .symtab SYMTAB 0000000000000000 00003040
    00000000000003a8 0000000000000018 29 18 8
    [29] .strtab STRTAB 0000000000000000 000033e8
    000000000000020b 0000000000000000 0 0 1
    [30] .shstrtab STRTAB 0000000000000000 000035f3
    000000000000011a 0000000000000000 0 0 1
    Key to Flags:
    W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
    L (link order), O (extra OS processing required), G (group), T (TLS),
    C (compressed), x (unknown), o (OS specific), E (exclude),
    D (mbind), l (large), p (processor specific)

  • 靜態(tài)鏈接
    There are 32 section headers, starting at offset 0xdb548:
    節(jié)頭:
    [號] 名稱 類型 地址 偏移量
    大小 全體大小 旗標(biāo) 鏈接 信息 對齊
    [ 0] NULL 0000000000000000 00000000
    0000000000000000 0000000000000000 0 0 0
    [ 1] .note.gnu.pr[…] NOTE 0000000000400270 00000270
    0000000000000030 0000000000000000 A 0 0 8
    [ 2] .note.gnu.bu[…] NOTE 00000000004002a0 000002a0
    0000000000000024 0000000000000000 A 0 0 4
    [ 3] .note.ABI-tag NOTE 00000000004002c4 000002c4
    0000000000000020 0000000000000000 A 0 0 4
    [ 4] .rela.plt RELA 00000000004002e8 000002e8
    0000000000000240 0000000000000018 AI 29 20 8
    [ 5] .init PROGBITS 0000000000401000 00001000
    000000000000001b 0000000000000000 AX 0 0 4
    [ 6] .plt PROGBITS 0000000000401020 00001020
    0000000000000180 0000000000000000 AX 0 0 16
    [ 7] .text PROGBITS 00000000004011c0 000011c0
    0000000000095138 0000000000000000 AX 0 0 64
    [ 8] __libc_freeres_fn PROGBITS 0000000000496300 00096300
    00000000000014cd 0000000000000000 AX 0 0 16
    [ 9] .fini PROGBITS 00000000004977d0 000977d0
    000000000000000d 0000000000000000 AX 0 0 4
    [10] .rodata PROGBITS 0000000000498000 00098000
    000000000001cb2c 0000000000000000 A 0 0 32
    [11] .stapsdt.base PROGBITS 00000000004b4b2c 000b4b2c
    0000000000000001 0000000000000000 A 0 0 1
    [12] .eh_frame PROGBITS 00000000004b4b30 000b4b30
    000000000000b928 0000000000000000 A 0 0 8
    [13] .gcc_except_table PROGBITS 00000000004c0458 000c0458
    0000000000000122 0000000000000000 A 0 0 1
    [14] .tdata PROGBITS 00000000004c17b0 000c07b0
    0000000000000020 0000000000000000 WAT 0 0 8
    [15] .tbss NOBITS 00000000004c17d0 000c07d0
    0000000000000048 0000000000000000 WAT 0 0 8
    [16] .init_array INIT_ARRAY 00000000004c17d0 000c07d0
    0000000000000008 0000000000000008 WA 0 0 8
    [17] .fini_array FINI_ARRAY 00000000004c17d8 000c07d8
    0000000000000008 0000000000000008 WA 0 0 8
    [18] .data.rel.ro PROGBITS 00000000004c17e0 000c07e0
    0000000000003788 0000000000000000 WA 0 0 32
    [19] .got PROGBITS 00000000004c4f68 000c3f68
    0000000000000098 0000000000000000 WA 0 0 8
    [20] .got.plt PROGBITS 00000000004c5000 000c4000
    00000000000000d8 0000000000000008 WA 0 0 8
    [21] .data PROGBITS 00000000004c50e0 000c40e0
    00000000000019e0 0000000000000000 WA 0 0 32
    [22] __libc_subfreeres PROGBITS 00000000004c6ac0 000c5ac0
    0000000000000048 0000000000000000 WAR 0 0 8
    [23] __libc_IO_vtables PROGBITS 00000000004c6b20 000c5b20
    0000000000000768 0000000000000000 WA 0 0 32
    [24] __libc_atexit PROGBITS 00000000004c7288 000c6288
    0000000000000008 0000000000000000 WAR 0 0 8
    [25] .bss NOBITS 00000000004c72a0 000c6290
    0000000000005980 0000000000000000 WA 0 0 32
    [26] __libc_freer[…] NOBITS 00000000004ccc20 000c6290
    0000000000000020 0000000000000000 WA 0 0 8
    [27] .comment PROGBITS 0000000000000000 000c6290
    000000000000002b 0000000000000001 MS 0 0 1
    [28] .note.stapsdt NOTE 0000000000000000 000c62bc
    0000000000001648 0000000000000000 0 0 4
    [29] .symtab SYMTAB 0000000000000000 000c7908
    000000000000c480 0000000000000018 30 770 8
    [30] .strtab STRTAB 0000000000000000 000d3d88
    0000000000007668 0000000000000000 0 0 1
    [31] .shstrtab STRTAB 0000000000000000 000db3f0
    0000000000000157 0000000000000000 0 0 1

這是因為它們的重定向機制不同
先來說一下兩個符號表

  • 1.全局偏移表(GOT):存放外部函數(shù)地址的數(shù)據(jù)段。

  • 2.程序連接表(PLT):用來獲取數(shù)據(jù)段記錄的外部函數(shù)地址的代碼。

    print_banner: printf@plt: printf@got: 0xf7e835f0 :
    … jmp *printf@got 0xf7e835f0 …
    call printf@plt ret
    … …

    可執(zhí)行文件 PLT表 GOT表 glibc中的printf函數(shù)

    GOT是一個存儲外部庫函數(shù)的表

    PLT則是由代碼片段組成的,每個代碼片段都跳轉(zhuǎn)到GOT表中的一個具體的函數(shù)調(diào)用

這里講一下重定位

番外篇2–重定位 鏈接時重定位–靜態(tài)鏈接

鏈接階段是將一個或多個中間文件(.o文件)通過鏈接器將它們鏈接成一個可執(zhí)行文件,主要做的事情有

對各個中間文件的同名section進行合并

對代碼段,數(shù)據(jù)段等進行地址分配

進行鏈接時重定位

兩種情況:

如果是在其他中間文件中已經(jīng)定義了的函數(shù),鏈接階段可以直接重定位到函數(shù)地址

如果是在動態(tài)庫中定義了的函數(shù),鏈接階段無法直接重定位到函數(shù)地址,只能生成額外的小片段代碼,也就是PLT表,然后重定位到該代碼片段
運行時重定位–動態(tài)鏈接

運行后加載動態(tài)庫,把動態(tài)庫中的相應(yīng)函數(shù)地址填入GOT表,由于PLT表是跳轉(zhuǎn)到GOT表的,這就構(gòu)成了運行時重定位

延遲重定位–動態(tài)鏈接

只有動態(tài)庫函數(shù)在被調(diào)用時,才會進行地址解析和重定位工作,這時候動態(tài)庫函數(shù)的地址才會被寫入到GOT表項中

請?zhí)砑訄D片描述
第一步由函數(shù)調(diào)用跳入到PLT表中,然后第二步PLT表跳到GOT表中,可以看到第三步由GOT表回跳到PLT表中,這時候進行壓棧,把代表函數(shù)的ID壓棧,接著第四步跳轉(zhuǎn)到公共的PLT表項中,第5步進入到GOT表中,然后_dl_runtime_resolve對動態(tài)函數(shù)進行地址解析和重定位,第七步把動態(tài)函數(shù)真實的地址寫入到GOT表項中,然后執(zhí)行函數(shù)并返回。

解釋下dynamic段,link_map和_dl_runtime_resolve

dynamic段:提供動態(tài)鏈接的信息,例如動態(tài)鏈接中各個表的位置
link_map:已加載庫的鏈表,由動態(tài)庫函數(shù)的地址構(gòu)成的鏈表
_dl_runtime_resolve:在第一次運行時進行地址解析和重定位工作

請?zhí)砑訄D片描述

可以看到,第一步還是由函數(shù)調(diào)用跳入到PLT表,但是第二步跳入到GOT表中時,由于這個時候該表項已經(jīng)是動態(tài)函數(shù)的真實地址了,所以可以直接執(zhí)行然后返回。

對于動態(tài)函數(shù)的調(diào)用,第一次要經(jīng)過地址解析和回寫到GOT表項中,第二次直接調(diào)用即可

運行可執(zhí)行文件 裝載進內(nèi)存

程序編譯鏈接完成后是保存在硬盤中的,當(dāng)用戶執(zhí)行該程序的時候,該程序(ELF可執(zhí)行文件)會被加載器按照program header table(可執(zhí)行文件頭)的描述將程序的代碼段和數(shù)據(jù)段從硬盤加載到內(nèi)存中。
在這里插入圖片描述
如果是靜態(tài)鏈接就是直接將各個段的地址寫入內(nèi)存,如果是動態(tài)鏈接就先隨機分配一個地址給ELF文件頭,作為首地址,然后ELF文件其他數(shù)據(jù)根據(jù)偏移量確定地址。
這里的地址都是虛擬地址,在運行內(nèi)存中有著許多進程,每個進程又包含至少一個線程
進程
這里講一下進程與線程

番外篇3–進程與線程

gcc編譯好的ELF可執(zhí)行文件叫程序
那么運行時的程序就叫做進程,進程之間通過 TCP/IP 端口實現(xiàn)交互

進程是申請一塊內(nèi)存空間,將數(shù)據(jù)放到內(nèi)存空間中去, 是申請數(shù)據(jù)的過程
是最小的資源管理單元

而線程就是進程的子集,多個線程共享同一塊內(nèi)存(由進程向操作系統(tǒng)申請),通過共享的內(nèi)存空間來進行交互

是進程的一條流水線, 只用來執(zhí)行程序,而不涉及到申請資源, 是程序的實際執(zhí)行者
最小的執(zhí)行單元

在這里插入圖片描述

總的來說,程序要運行,需要先向操作系統(tǒng)申請一段內(nèi)存作為進程

進程空間的內(nèi)存分配

在這里插入圖片描述
對于linux來說,從 Linux 內(nèi)核的角度來看,進程和線程都是一樣的。
系統(tǒng)調(diào)用fork()可以新建一個子進程,函數(shù)pthread()可以新建一個線程。但無論線程還是進程,都是用task_struct結(jié)構(gòu)表示的,,唯一的區(qū)別就是共享的數(shù)據(jù)區(qū)域不同。
Linux 系統(tǒng)將線程看做共享數(shù)據(jù)的進程

番外篇4–函數(shù)調(diào)用棧(線程棧)

函數(shù)調(diào)用經(jīng)常是嵌套的,在同一時刻,堆棧中會有多個函數(shù)的信息。每個未完成運行的函數(shù)占用一個獨立的連續(xù)區(qū)域,稱作棧幀(Stack Frame)。棧幀是堆棧的邏輯片段,當(dāng)調(diào)用函數(shù)時邏輯棧幀被壓入堆棧, 當(dāng)函數(shù)返回時邏輯棧幀被從堆棧中彈出。棧幀存放著函數(shù)參數(shù),局部變量及恢復(fù)前一棧幀所需要的數(shù)據(jù)等。
棧幀的邊界由棧幀基地址指針EBP和堆棧指針ESP界定(指針存放在相應(yīng)寄存器中)。EBP指向當(dāng)前棧幀底部(高地址),在當(dāng)前棧幀內(nèi)位置固定;ESP指向當(dāng)前棧幀頂部(低地址),當(dāng)程序執(zhí)行時ESP會隨著數(shù)據(jù)的入棧和出棧而移動。因此函數(shù)中對大部分數(shù)據(jù)的訪問都基于EBP進行。

函數(shù)調(diào)用時入棧順序為

實參N~1 → 主調(diào)函數(shù)返回地址→主調(diào)函數(shù)幀基指針EBP → 被調(diào)函數(shù)局部變量1~N

請?zhí)砑訄D片描述
對于我們剛寫的的hello.c來說,大概是這樣的

在這里插入圖片描述

CPU開始運行程序

上面說進程向系統(tǒng)申請了一段內(nèi)存,gcc編譯生成的ELF可執(zhí)行文件會將自己數(shù)據(jù)映射到這段內(nèi)存上,而線程作為進程的執(zhí)行單位擁有自己的堆棧,棧上有一個個棧幀來臨時存儲數(shù)據(jù)。
CPU運行程序的過程就是執(zhí)行一條條指令的過程,這些指令就是gcc編譯鏈接成的ELF可執(zhí)行文件里的指令(也就是.text段里的代碼段的機器代碼)。

  • cpu的程序寄存器存放著要執(zhí)行的下一條指令的地址,通過這個地址去訪問下一條指令,
  • 指令寄存器讀取下一條指令,
  • 邏輯運算單元通過分析各個寄存器中的數(shù)據(jù)去執(zhí)行指令,
  • 之后程序寄存器中的值進行自加,執(zhí)行下一指令的地址,如此循環(huán)下去,執(zhí)行完整個程序。

棧幀的創(chuàng)建和銷毀也是通過CPU執(zhí)行ELF文件里的代碼段中的指令實現(xiàn)的。

請?zhí)砑訄D片描述

番外篇5–虛擬內(nèi)存與內(nèi)存尋址
  • 操作系統(tǒng)會提供一種機制,將不同進程的虛擬地址和不同內(nèi)存的物理地址映射起來。我們上面看到的地址其實都是虛擬地址,這一段虛擬地址對應(yīng)的物理地址可能是不連續(xù)的。
  • CPU訪問內(nèi)存時,進程持有的虛擬地址會通過 CPU 芯片中的內(nèi)存管理單元(MMU)的映射關(guān)系,來轉(zhuǎn)換變成物理地址,然后再通過物理地址訪問內(nèi)存。
  • MMU的轉(zhuǎn)換分兩個階段,分段機制和分頁機制,分段把虛擬地址轉(zhuǎn)換為線性地址,分頁把線性地址轉(zhuǎn)換為物理地址。
結(jié)語

至此,c程序從編譯開始到運行結(jié)束的過程就結(jié)束了。
到這里就可以解釋gcc進行第二步操作編譯時和靜態(tài)鏈接時的警告了。
我們再看一下我們的a.c文件

#includevoid hello(char * name);
int main()
{char name[16]={0};
	printf("Please input your name:");
	gets(name);
	hello(name);
	return 0;
}
void hello(char * name)
{printf("hello,%s\n",name);
}

我們的name數(shù)組只有16字節(jié)大小,gets函數(shù)可以輸入超過16字節(jié)的數(shù)據(jù),根據(jù)函數(shù)調(diào)用棧的原理,name數(shù)組存放的位置與main返回地址是挨著的,如果輸入過長的數(shù)據(jù)可能會破壞返回地址的數(shù)據(jù),致使程序返回到一個錯誤的地址,如果這個地址無意義,程序不能正常退出,就會報錯,如果這個地址被我們設(shè)置成一個特定的有惡意代碼的地址,程序就會去執(zhí)行我們的惡意代碼,造成危險。所以gcc向我們發(fā)送警告,讓我們使用fgets函數(shù)來限制輸入字節(jié)為16字節(jié)以內(nèi)。

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧

網(wǎng)站題目:CTF--PWN必備技能--理解c程序從編譯開始到運行結(jié)束的過程-創(chuàng)新互聯(lián)
文章轉(zhuǎn)載:http://muchs.cn/article46/egieg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化、定制網(wǎng)站營銷型網(wǎng)站建設(shè)、面包屑導(dǎo)航、網(wǎng)站設(shè)計公司、動態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

微信小程序開發(fā)