最近在調試一些問題,發現幾個命令很實用,記錄一下。
一 背景
也許大家都遇到過這種場景,就是有二進制代碼,比如深度分析下此文件到底是什么格式的圖片等,這篇文章就記錄我分析下二進制可執行文件的過程,已經自己讀寫二進制文件的一些坑。分析的二進制執行文件為linux下的可執行文件。
二 常用二進制文件靜態分析命令
2.1 file基本信息查看
Linux下有個最常用的通用命令,來分析任何文件的基本格式,那就是file,來看下:
可以看到基本信息,比如是什么類型文件,只是概述,還有些其他選項,可以用 -h 查看。
2.2 ldd動態鏈接庫信息
動態鏈接庫,即沒有在編譯鏈接的時候直接打入到程序中的,而是運行時候動態加載了,從而節省內存,通過動態鏈接庫,我們可以知道這個可執行文件用了哪些動態庫,方法也比較簡單。
這是我寫的一個小程序的動態鏈接庫信息,通過鏈接庫分析的信息也同樣比較少,用這個命令多查看依賴鏈接庫找不到的問題。
2.3 nm符號查看
nm可以列出二進制可執行文件,動態庫,靜態庫中的符號信息,包括符號的類型,符號名稱,比如函數名,全局變量等,通過這些信息可以看到不少有用的信息,通過函數名猜函數功能,使用的幫助如下:
配合grep信息可以很方便的進行符號搜索:
對於一些動態庫,直接nm可能查不到信息,可以通過 nm -D 命令查看。
2.4 strings 查看二進制文件中的字符串
strings信息可以打印二進制文件中的字符串信息,結合grep進行搜索,用grep
命令其實可以直接在二進制文件中搜索內容,但是不夠直觀,用strings看起來的更直觀些:
strings 會把任何可打印字符串都顯示出來,比nm的內容更多,截取部分如下:
2.5 objdump 將二進制代碼轉匯編指令
objdump是個值得深入學習的指令,不光可以還原匯編指令,還可以讀取二進制中特定段的信息,更可怕的是,如果我們的程序是以-g -o0等調試不優化的情況下,用objdump -S指令可能盡可能地還原源代碼信息(沒看錯,是還原出源代碼信息),其實也可以理解這些信息是完整的在可執行文件中的,要不然gdb調試的時候沒辦法單步追蹤了,測試如下:
參數選項:
1 --archive-headers 2 -a 3 顯示檔案庫的成員信息,類似ls -l將lib*.a的信息列出。 4 5 -b bfdname 6 --target=bfdname 7 指定目標碼格式。這不是必須的,objdump能自動識別許多格式,比如: 8 9 objdump -b oasys -m vax -h fu.o 10 顯示fu.o的頭部摘要信息,明確指出該文件是Vax系統下用Oasys編譯器生成的目標文件。objdump -i將給出這里可以指定的目標碼格式列表。 11 12 -C 13 --demangle 14 將底層的符號名解碼成用戶級名字,除了去掉所開頭的下划線之外,還使得C++函數名以可理解的方式顯示出來。 15 16 --debugging 17 -g 18 顯示調試信息。企圖解析保存在文件中的調試信息並以C語言的語法顯示出來。僅僅支持某些類型的調試信息。有些其他的格式被readelf -w支持。 19 20 -e 21 --debugging-tags 22 類似-g選項,但是生成的信息是和ctags工具相兼容的格式。 23 24 --disassemble 25 -d 26 從objfile中反匯編那些特定指令機器碼的section。 27 28 -D 29 --disassemble-all 30 與 -d 類似,但反匯編所有section. 31 32 --prefix-addresses 33 反匯編的時候,顯示每一行的完整地址。這是一種比較老的反匯編格式。 34 35 -EB 36 -EL 37 --endian={big|little} 38 指定目標文件的小端。這個項將影響反匯編出來的指令。在反匯編的文件沒描述小端信息的時候用。例如S-records. 39 40 -f 41 --file-headers 42 顯示objfile中每個文件的整體頭部摘要信息。 43 44 -h 45 --section-headers 46 --headers 47 顯示目標文件各個section的頭部摘要信息。 48 49 -H 50 --help 51 簡短的幫助信息。 52 53 -i 54 --info 55 顯示對於 -b 或者 -m 選項可用的架構和目標格式列表。 56 57 -j name 58 --section=name 59 僅僅顯示指定名稱為name的section的信息 60 61 -l 62 --line-numbers 63 用文件名和行號標注相應的目標代碼,僅僅和-d、-D或者-r一起使用使用-ld和使用-d的區別不是很大,在源碼級調試的時候有用,要求編譯時使用了-g之類的調試編譯選項。 64 65 -m machine 66 --architecture=machine 67 指定反匯編目標文件時使用的架構,當待反匯編文件本身沒描述架構信息的時候(比如S-records),這個選項很有用。可以用-i選項列出這里能夠指定的架構. 68 69 --reloc 70 -r 71 顯示文件的重定位入口。如果和-d或者-D一起使用,重定位部分以反匯編后的格式顯示出來。 72 73 --dynamic-reloc 74 -R 75 顯示文件的動態重定位入口,僅僅對於動態目標文件意義,比如某些共享庫。 76 77 -s 78 --full-contents 79 顯示指定section的完整內容。默認所有的非空section都會被顯示。 80 81 -S 82 --source 83 盡可能反匯編出源代碼,尤其當編譯的時候指定了-g這種調試參數時,效果比較明顯。隱含了-d參數。 84 85 --show-raw-insn 86 反匯編的時候,顯示每條匯編指令對應的機器碼,如不指定--prefix-addresses,這將是缺省選項。 87 88 --no-show-raw-insn 89 反匯編時,不顯示匯編指令的機器碼,如不指定--prefix-addresses,這將是缺省選項。 90 91 --start-address=address 92 從指定地址開始顯示數據,該選項影響-d、-r和-s選項的輸出。 93 94 --stop-address=address 95 顯示數據直到指定地址為止,該項影響-d、-r和-s選項的輸出。 96 97 -t 98 --syms 99 顯示文件的符號表入口。類似於nm -s提供的信息 100 101 -T 102 --dynamic-syms 103 顯示文件的動態符號表入口,僅僅對動態目標文件意義,比如某些共享庫。它顯示的信息類似於 nm -D|--dynamic 顯示的信息。 104 105 -V 106 --version 107 版本信息 108 109 --all-headers 110 -x 111 顯示所可用的頭信息,包括符號表、重定位入口。-x 等價於-a -f -h -r -t 同時指定。 112 113 -z 114 --disassemble-zeroes 115 一般反匯編輸出將省略大塊的零,該選項使得這些零塊也被反匯編。 116 117 @file 118 可以將選項集中到一個文件中,然后使用這個@file選項載入。
關於符號表字段下面直接只介紹部分常用的:
1 .text:已編譯程序的機器代碼。 2 .rodata:只讀數據,比如printf語句中的格式串和開關(switch)語句的跳轉表。 3 .data:已初始化的全局C變量。局部C變量在運行時被保存在棧中,既不出現在.data中,也不出現在.bss節中。 4 .bss:未初始化的全局C變量。在目標文件中這個節不占據實際的空間,它僅僅是一個占位符。目標文件格式區分初始化和未初始化變量是為了空間效率在:在目標文件中,未初始化變量不需要占據任何實際的磁盤空間。 5 .symtab:一個符號表(symbol table),它存放在程序中被定義和引用的函數和全局變量的信息。一些程序員錯誤地認為必須通過-g選項來編譯一個程序,得到符號表信息。實際上,每個可重定位目標文件在.symtab中都有一張符號表。然而,和編譯器中的符號表不同,.symtab符號表不包含局部變量的表目。 6 .rel.text:當鏈接噐把這個目標文件和其他文件結合時,.text節中的許多位置都需要修改。一般而言,任何調用外部函數或者引用全局變量的指令都需要修改。另一方面調用本地函數的指令則不需要修改。注意,可執行目標文件中並不需要重定位信息,因此通常省略,除非使用者顯式地指示鏈接器包含這些信息。 7 .rel.data:被模塊定義或引用的任何全局變量的信息。一般而言,任何已初始化全局變量的初始值是全局變量或者外部定義函數的地址都需要被修改。 8 .debug:一個調試符號表,其有些表目是程序中定義的局部變量和類型定義,有些表目是程序中定義和引用的全局變量,有些是原始的C源文件。只有以-g選項調用編譯驅動程序時,才會得到這張表。 9 .line:原始C源程序中的行號和.text節中機器指令之間的映射。只有以-g選項調用編譯驅動程序時,才會得到這張表。 10 .strtab:一個字符串表,其內容包括.symtab和.debug節中的符號表,以及節頭部中的節名字。字符串表就是以null結尾的字符串序列。
2.6 readelf 讀取ELF文件格式
如果二進制文件是ELF格式的,通過 file 文件可以查看文件格式.使用 readelf
指令可以方便分析ELF文件的結構,比如節信息,elf頭文件信息,比如我們在分析文件是否為病毒文件的時候,需要讀取 elf 文件頭信息,做一些特征的判斷,或作為特征參與機器學習的判斷。
readelf讀取文件頭
還有些其他命令,有興趣的小伙伴,可以通過-h
命令還原看下。
三 動態查看文件結構
3.1 ltrace 跟蹤進程調用庫函數過程
這也是一個很棒的命令,我們可以查看程序執行的時候調用庫函數信息,還可以在線查看執行的進程的庫函數調用情況,找幾個比較典型的命令,測試的代碼比較簡單如下:
1 #include <stdlib.h> 2 #include <stdio.h> 3 4 int main(void) 5 { 6 short shs[5] ={1,234,567,789,890}; 7 int ins[5] ={890,88111,23333,7777,6666}; 8 FILE * fp = fopen("a.bin","wb"); 9 for (int i = 0; i < 5; i++) { 10 fwrite(&shs[i],sizeof(short),1,fp); 11 fwrite(&ins[i],sizeof(int),1,fp); 12 } 13 printf("read....\n"); 14 fclose(fp); 15 fp = fopen("a.bin","rb"); 16 short a; 17 int b; 18 for (int i = 0; i <5;i++) { 19 fread(&a,sizeof(short),1,fp); 20 fread(&b,sizeof(int),1,fp); 21 printf("i:%d a:%d,b:%d\n", i,a,b); 22 } 23 fclose(fp); 24 return 0; 25 }
3.1.1 ltrace 查看庫函數調用情況
ltrace 查看庫函數調用情況
3.1.2 ltrace 查看庫函數調用占用時間
這在查看系統調用耗時很有用。
1 # -T 是查看調用時間開銷 2 ltrace -T 3 #-t -tt -ttt 是查看調用絕對時間,t越多越精確 4 ltrace -t
ltrace 查看絕對時間。
3.1.3 ltrace 查看系統調用信息
1 ltrace -S
系統調用信息顯然比庫函數顯示更多,追蹤更復雜的情況可以使用。
還有 -p pid 追蹤具體的進行id的調用情況也很有用,這里面就不舉例子了。如果沒有這個命令,如果是centos環境可以通過yum install -y ltrace
安裝。
3.2 strace
strace和ltrace的命令差不多,strace更偏向於系統調用的追蹤或信號產生的情況。安裝命令yum -y install strace
強大地方在於可以指定系統調用的類型:
1 -e trace=set 2 只跟蹤指定的系統 調用.例如:-e trace=open,close,rean,write表示只跟蹤這四個系統調用.默認的為set=all. 3 -e trace=file 4 只跟蹤有關文件操作的系統調用. 5 -e trace=process 6 只跟蹤有關進程控制的系統調用. 7 -e trace=network 8 跟蹤與網絡有關的所有系統調用. 9 -e strace=signal 10 跟蹤所有與系統信號有關的 系統調用 11 -e trace=ipc 12 跟蹤所有與進程通訊有關的系統調用 13 -e abbrev=set 14 設定 strace輸出的系統調用的結果集.-v 等與 abbrev=none.默認為abbrev=all. 15 -e raw=set 16 將指 定的系統調用的參數以十六進制顯示. 17 -e signal=set 18 指定跟蹤的系統信號.默認為all.如 signal=!SIGIO(或者signal=!io),表示不跟蹤SIGIO信號. 19 -e read=set 20 輸出從指定文件中讀出 的數據.例如: 21 -e read=3,5 22 -e write=set
比如以下命令:
還有很多有用的選項,有興趣的可以嘗試下。
四 圖形化界面分析二進制執行文件
網上找到一個圖形化界面分析二進制程序的,名字叫Relyze
雖然是收費的,但是可以正常用一段時間,一段時間后才提示,界面如下,強大之處在於可以顯示調用關系信息等。其實原理都類似,沒有比命令行更多的功能,只是看起來更方便而已。
4.1 基本文件信息
可執行文件基本信息
4.2 頭和段信息查看
頭和段信息
4.3 搜索
字符信息搜索
4.4 調用關系圖
雙擊可以看到調用關系圖信息,便於做進一步分析。
其他的也沒啥特殊點了,有興趣的朋友可以下載試試。
參考文章:
https://blog.csdn.net/mseaspring/article/details/110359312
objdump 命令解析:
https://blog.csdn.net/q2519008/article/details/82349869
簡書--使用 readelf 和 objdump 解析目標文件:
https://www.jianshu.com/p/863b279c941e