一:背景
1. 講故事
在高級調(diào)試的旅行中,發(fā)現(xiàn)有不少人對符號表不是很清楚,其實(shí)簡而言之符號表中記錄著一些程序的生物特征,比如哪個地址是函數(shù)(簽名信息),哪個地址是全局變量,靜態(tài)變量,行號是多少,數(shù)據(jù)類型是什么 等等,目的就是輔助我們可視化的調(diào)試,如果沒有這些輔助我們看到的都是一些無意義的匯編代碼,逆向起來會非常困難,這一篇我們就來系統(tǒng)的聊一聊。
二:程序編譯的四個階段
1. 案例代碼
要想理解符號表,首先需要理解 代碼文件
是如何變成 可執(zhí)行文件
的,即如下的四個階段。
- 預(yù)處理階段
- 編譯階段
- 匯編階段
- 鏈接階段
為了能夠看到每一個階段,用 gcc 的相關(guān)命令手工推進(jìn),并用 chatgpt 寫一段測試代碼,包含全局變量,靜態(tài)變量,函數(shù)等信息。
#include <stdio.h>
#define PI 3.1415926
int global_var = 10;
void func() {
static int static_var = 5;
printf("global_var = %d, static_var = %d PI=%f\n", global_var, static_var,PI);
global_var++;
static_var++;
}
int main() {
func();
func();
return 0;
}
接下來用 gcc --help
命令查看下需要使用的命令列表。
[root@localhost data]# gcc --help
Usage: gcc [options] file...
Options:
-E Preprocess only; do not compile, assemble or link
-S Compile only; do not assemble or link
-c Compile and assemble, but do not link
-o <file> Place the output into <file>
...
2. 預(yù)編譯階段
預(yù)處理主要做的就是代碼整合,比如將 #include
文件導(dǎo)入,將 #define
宏替換等等,接下來使用 gcc -E
進(jìn)行預(yù)處理。
[root@localhost data]# gcc main.c -E -o main.i
[root@localhost data]# ls
main.c main.i
可以看到這個 main.c 文件已經(jīng)膨脹到了 858 行了。
3. 編譯階段
前面階段是把代碼預(yù)處理好,接下來就是將C代碼
編譯成匯編代碼
了,使用 gcc -S
即可。
[root@localhost data]# gcc main.c -S -o main.s -masm=intel
[root@localhost data]# ls
main.c main.i main.s
從圖中可以看到匯編代碼中也有很多輔助信息,比如 global_var 是一個 @object 變量,類型為 int,在 .rodata
只讀數(shù)據(jù)段中,目的就是給匯編階段打輔助。
4. 匯編階段
有了匯編代碼之后,接下來就是將 匯編代碼
轉(zhuǎn)成 機(jī)器代碼
,這個階段會產(chǎn)生二進(jìn)制文件,并且會構(gòu)建 section 信息以及符號表信息,可以使用 gcc -c
即可。
[root@localhost data]# gcc main.c -c -o main.o -masm=intel
[root@localhost data]# ls
main.c main.i main.o main.s
二進(jìn)制文件模式默認(rèn)是不能可視化打開的,可以借助于 objdump 工具。
[root@localhost data]# objdump
-h, --[section-]headers Display the contents of the section headers
-t, --syms Display the contents of the symbol table(s)
[root@localhost data]# objdump -t main.o
main.o: file format elf64-x86-64
SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000004 l O .data 0000000000000004 static_var.2179
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g O .data 0000000000000004 global_var
0000000000000000 g F .text 0000000000000058 func
0000000000000000 *UND* 0000000000000000 printf
0000000000000058 g F .text 000000000000001f main
在上面的符號表中看到了 func
函數(shù)以及 static_var
和 global_var
以及所屬的 section。
5. 鏈接階段
這個階段主要是將多個二進(jìn)制代碼文件進(jìn)一步整合變成可在操作系統(tǒng)上運(yùn)行的可執(zhí)行文件,可以使用 gcc -o
。
[root@localhost data]# gcc main.c -o main
[root@localhost data]# ls
main main.c main.i main.o main.s
[root@localhost data]# ./main
global_var = 10, static_var = 5 PI=3.141593
global_var = 11, static_var = 6 PI=3.141593
[root@localhost data]# objdump -t main
main: file format elf64-x86-64
SYMBOL TABLE:
...
0000000000601034 g O .data 0000000000000004 global_var
0000000000601034 g O .data 0000000000000004 global_var
...
000000000040052d g F .text 0000000000000058 func
...
相比匯編階段,這個階段的 符號表
中的第一列都是有地址值的,是相對模塊的偏移值,比如說: module+0x000000000040052d
標(biāo)記的是 func 函數(shù)。
上面是 linux 上的可執(zhí)行文件的符號表信息,有些朋友說我是 windows 平臺上的,怎么看符號表信息呢?
三:Windows 上的 pdb 解析
1. 觀察 pdb 文件
上一節(jié)我們看到的是 linux 上 elf格式
的可執(zhí)行文件,這一節(jié)看下 windows 平臺上的PE文件 的符號表信息是什么樣的呢?有了前面四階段編譯的理論基礎(chǔ),再聊就比較簡單了。
在 windows 平臺上 符號表信息 是藏在 pdb 文件中的,這種拆開的方式是有很大好處的,如果需要調(diào)試代碼,windbg 會自動加載 pdb 文件,無調(diào)試的情況下就不需要加載 pdb 了,減少了可執(zhí)行文件的大小,也提升了性能。
接下來用 SymView.exe
這種工具去打開 pdb 文件,截圖如下:
從圖中可以看到,符號表信息高達(dá) 10968 個,并且 func 函數(shù)的入口地址是在 module +0x11870
處,相當(dāng)于做了一個標(biāo)記,接下來我們拿這個func做一個測試。
2. 有 pdb 的 func 函數(shù)
首先說一下為什么通過 exe 可以找到 pdb,這是因?yàn)?PE 頭的 DIRECTORY_ENTRY_DEBUG 節(jié)中記錄了 pdb 的地址。
只要這個路徑有 pdb 就可以在 windbg 運(yùn)行中按需加載了,然后通過 u MySample.exe+0x11870
觀察,截圖如下:
圖中顯示的非常清楚,地址 00fd1870
就是 func
的入口地址,讓一個無意義的地址馬上有意義起來了,哈哈~~~
3. 無 pdb 的 func 函數(shù)
這一小節(jié)是提供給好奇的朋友的,如果沒有 pdb,那匯編上又是一個什么模樣,為了找到 func 的入口地址,我們內(nèi)嵌一個 int 3 ,然后把 pdb 給刪掉,代碼如下:
int main() {
__asm {
int 3;
}
func();
func();
return 0;
}
從圖中可以看到,func 標(biāo)記已經(jīng)沒有了,取而代之的都是 module+0xxx
,這就會給我們逆向調(diào)試帶來巨大的障礙。文章來源:http://www.zghlxwxcb.cn/news/detail-750747.html
三: 總結(jié)
總而言之,符號表就是對茫茫內(nèi)存進(jìn)行標(biāo)記,就像百度地圖一樣,讓我們知道某個經(jīng)緯度上有什么建筑,讓無情的地理坐標(biāo)更加有溫度,讓世界更美好。文章來源地址http://www.zghlxwxcb.cn/news/detail-750747.html

到了這里,關(guān)于聊一聊 .NET高級調(diào)試 中必知的符號表的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!