本节内容
- 使用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是常见的一种壳,把真实的目标压缩以后存到壳的内部,由壳在执行时把内部的数据解压缩释放出来。壳起到了一定的保护作用,当然这个保护实际上对高手来说没有多大效果的,因为既然释放出来,真实的目标肯定在内存当中,我直接把内存那段数据拷到硬盘上就可以了。