本節內容 - 使用nm查看符號 - 使用readelf -s輸出符號信息 - 刪除符號表對反彙編的影響 - 使用strip刪除符號和調試信息 - 使用UPX壓縮並保護可執行文件 ...
本節內容
- 使用nm查看符號
- 使用readelf -s輸出符號信息
- 刪除符號表對反彙編的影響
- 使用strip刪除符號和調試信息
- 使用UPX壓縮並保護可執行文件
我們提到的所有的東西,編譯、鏈接、可執行文件。這裡面都會涉及到一個東西叫符號,什麼叫符號。符號(symbol)用來表示一個地址(函數、變數、段名、行號信息等)
使用nm查看符號
類型通常對應段:T .text;D .data;B .bss;R .rodata
$ cat hello.c
文件
#include <stdio.h>
#include <stdlib.h>
int x = 0x1234;
int y;
char *s = "x = %x, y = %x\n";
int main()
{
printf(s, x, y);
return 0;
}
$ gcc -o test hello.c
$ nm test
輸出符號信息
0000000000601048 B __bss_start
0000000000601048 b completed.7585
0000000000601028 D __data_start
0000000000601028 W data_start
0000000000400460 t deregister_tm_clones
00000000004004e0 t __do_global_dtors_aux
0000000000600e18 t __do_global_dtors_aux_fini_array_entry
0000000000601030 D __dso_handle
0000000000600e28 d _DYNAMIC
0000000000601048 D _edata
0000000000601050 B _end
00000000004005d4 T _fini
0000000000400500 t frame_dummy
0000000000600e10 t __frame_dummy_init_array_entry
0000000000400718 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
00000000004005f4 r __GNU_EH_FRAME_HDR
00000000004003c8 T _init
0000000000600e18 t __init_array_end
0000000000600e10 t __init_array_start
00000000004005e0 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000600e20 d __JCR_END__
0000000000600e20 d __JCR_LIST__
w _Jv_RegisterClasses
00000000004005d0 T __libc_csu_fini
0000000000400560 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
0000000000400526 T main
U printf@@GLIBC_2.2.5
00000000004004a0 t register_tm_clones
0000000000601040 D s
0000000000400430 T _start
0000000000601048 D __TMC_END__
0000000000601038 D x
000000000060104c B y
可以看到s,x,y,main,printf@@GLIBC_2.2.5
是符號,每個符號都有唯一地址。符號就是這個地址的別稱。因為反彙編時候,符號可以很快知道這個地址乾什麼用的。
反彙編
$ objdump -d -M intel test | grep -A15 "<main>:"
0000000000400526 <main>:
400526: 55 push rbp
400527: 48 89 e5 mov rbp,rsp
40052a: 8b 15 1c 0b 20 00 mov edx,DWORD PTR [rip+0x200b1c] # 60104c <y>
400530: 8b 0d 02 0b 20 00 mov ecx,DWORD PTR [rip+0x200b02] # 601038 <x>
400536: 48 8b 05 03 0b 20 00 mov rax,QWORD PTR [rip+0x200b03] # 601040 <s>
40053d: 89 ce mov esi,ecx
40053f: 48 89 c7 mov rdi,rax
400542: b8 00 00 00 00 mov eax,0x0
400547: e8 b4 fe ff ff call 400400 <printf@plt>
40054c: b8 00 00 00 00 mov eax,0x0
400551: 5d pop rbp
400552: c3 ret
400553: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
40055a: 00 00 00
40055d: 0f 1f 00 nop DWORD PTR [rax]
符號表對程式執行沒有用。反彙編,找到.main段,可以看到mov彙編指令最後面可以看到根據地址信息可以查到符號名,這樣我們反彙編時候很容易知道是在操作哪行數據。
符號也知道從哪一行開始執行哪個函數,例如0000000000400526 <main>:
。
使用readelf -s輸出符號信息
查看包括.dynsym在內的所有符號
$ readelf -S test
刪除符號表對反彙編的影響
$ ls -lh #文件大小8.5k
$ readelf -S test #有30個段
$ strip test #刪除符號表和調試信息,運行期不需要的信息都刪除了
$ ls -lh #文件大小6.2k
$ readelf -S test #有28個段
$ nm test #輸出符號信息,沒有符號
$ ./test #執行程式不會有影響
$ objdump -d -M intel test #反彙編,整個.text段沒有區分函數
使用strip刪除符號和調試信息
使用strip刪除符號和調試信息,不包括動態符號
刪除符號表對程式有一定的保護作用,刪除符號表不一定是安全的
$ cat panic.go
package main
func main() {
panic("abc")
}
註意和go build -ldflags "-w -s"的差異
$ go build -o xxx panic.go
$ strip xxx
$ ./xxx #在mac環境下執行
$ go build -ldflags "-w -s" -o xxx2 panic.go #建議使用官方自帶的刪除功能
$ ./xxx2
使用UPX壓縮並保護可執行文件
UPX是一個可執行文件壓縮工具,一個可執行文件a和可執行文件b,a作為b一個段存儲在b中,當b執行時,把a釋放出來執行。既然a作為數據文件存儲在b中,我們使用壓縮後存儲到b中,再解壓縮執行。b就成了可執行文件的殼。
$ upx -9 test
$ readelf -S test #沒有段信息
$ objdump -d -M intel test #反彙編不能執行
UPX是常見的一種殼,把真實的目標壓縮以後存到殼的內部,由殼在執行時把內部的數據解壓縮釋放出來。殼起到了一定的保護作用,當然這個保護實際上對高手來說沒有多大效果的,因為既然釋放出來,真實的目標肯定在記憶體當中,我直接把記憶體那段數據拷到硬碟上就可以了。