我在2014年7月1日參加了獵豹移動(原金山網絡)反病毒工程師的電話面試,但是很遺憾,由于我當時準備不足,加上自身水平不夠,面試官向我提出的很多技術問題我都沒能答出來(這里面既有基礎類的問題,也有比較高深的問題),結果沒能通過那次的面試。痛定思痛,我認認真真總結出了當時向我提出的所有問題,一共是十多條,我會分為幾篇文章來進行剖析。并且根據問題的難易程度,由淺入深進行排序。參考了大量的相關資料,給出了業界較為權威的書籍上的答案,可能對于某些問題,會同時摘錄幾本書的內容,并且給出知識擴展,以更為全面地看待這個問題。
獵豹移動的反病毒工程師(珠海)職位分為社會招聘和校園招聘,其中社會招聘要求如下:
**工作職責:**
1、樣本鑒定;? ? ?
2、分析病毒樣本并提供解決方案;? ?
3、信息安全技術研究。? ?
**任職要求:**
1、熱愛底層工作,對反病毒、逆向工程、系統漏洞等有強烈興趣;
2、熟悉x86系列匯編語言,能熟練讀懂匯編代碼;
3、熟練使用IDA、OD等工具進行反匯編;
4、有一定C/C++程序功底;
5、有良好的團隊合作意識、善于溝通、有耐心、責任心強;
6、工作細心、積極主動、推動力強、有較快、較好的學習新技術能力。
校園招聘的要求如下:
**工作職責:**
1、樣本鑒定;? ? ?
2、分析病毒樣本并提供解決方案;? ?
3、信息安全技術研究。? ?
**任職要求:** ?
1、熱愛底層開發工作,對反病毒、逆向工程、系統漏洞等有強烈興趣;
2、熟悉x86系列匯編語言,能熟練讀懂匯編代碼;
3、具有C/C++程序開發經驗者優先。
這次所討論的是三個基礎問題,主要關于匯編一些指令的用法。那么接下來就是我所總結的技術面試問題:**1、請解釋一下匯編中的ADC和REP指令。**
答:(以下內容均選自軟件“**匯編指令助手 V1.1**”)
帶進位加法指令ADC(Addition Carry)
格式:ADC OPRD1,OPRD2
功能:OPRD1←OPRD1+OPRD2+CF
說明:
(1)OPRD1為任一通用寄存器或存儲器操作數,可以是任意一個通用寄存器,而且還可以是任意一個存儲器操作數。OPRD2為立即數,也可以是任意一個通用寄存器操作數。立即數只能用于源操作數。
(2)OPRD1和OPRD2均為寄存器是允許的,一個為寄存器而另一個為存儲器也是允許的,但不允許兩個都是存儲器操作數。
(3)加法指令運算的結果對CF、SF、OF、PF、ZF、AF都會有影響。以上標志也稱為結果標志。
(4)該指令對標志位的影響同ADD指令。
重復前綴的說明
格式:REP ? ? ? ? ? ?; CX<>0重復執行字符串指令
? ? ? ? REPZ/REPE ? ? ?; CX<>0且ZF=1重復執行字符串指令
? ? ? ? REPNZ/REPNE ? ?; CX<>0且ZF=0重復執行字符串指令
功能:在串操作指令前加上重復前綴,可以對字符串進行重復處理。由于加上重復前綴后,對應的指令代碼是不同的,所以指令的功能便具有重復處理的功能,重復的次數存放在CX寄存器中。
說明:
(1)REP與MOVS或STOS串操作指令相結合使用,完成一組字符的傳送或建立一組相同數據的字符串。
(2)REPZ/REPE常用與CMPS串操作指令結合使用,可以完成兩組字符串的比較。
(3)REPZ/REPE常與SCAS指令結合使用,可以完成在一個字符串中搜索一個關鍵字。
(4)REPNZ/REPNE與CMPS指令結合使用,表示當串未結束(CX=1)且當對應串元素不相同(ZF=0)時,繼續重復執行串比較指令。
**知識擴展:**
帶借位減去指令SBB(SuBtraction with Borrow)
格式:SBB OPRD1,OPRD2
功能:是進行兩個操作數的相減再減去CF進位標志位,即從OPRD1←OPRD1-OPRD2-CF,其結果放在OPRD1中。
循環控制指令LOOP
格式:LOOP 標號
功能:(CX)←(CX)?1,(CX)<>0,則轉移至標號處循環執行,直至(CX)=0,繼續執行后繼指令。
說明:
(1)本指令是用CX寄存器作為計數器,來控制程序的循環。
(2)它屬于段內SHORT短類型轉移,目的地址必須距本指令在-128到+127個字節的范圍內。
(以下內容選自**《IDA Pro代碼破解揭秘》**第2.4節)
EFLAGS寄存器是32位寄存器,包含一組狀態、系統標志及控制標志。每個標志由寄存器里一位代表,從0位到31位我們有下面這些標志。
CF:進位標志,指示在算術運算中是否帶有進位或借位。用于無符號算術運算。
PF:奇偶標志,為機器中傳送信息時可能出錯提供校驗。當目的操作數中1的個數為偶數時置1(PE),否則置0(PO)。
AF:輔助進位標志,記錄運算時低4位(半個字節)產生的進位值。有進位時置1(AC),否則置0(NA)。
ZF:零標志。運算結果為0時置1(ZR),否則置0(NZ)。
SF:符號標志,記錄運算結果的符號。結果為負時置1(NG),否則置0(PL)。
TF:陷阱標志,用于單步方式操作。當TF為1時,每條指令執行完后產生陷阱,由系統控制計算機;當TF為0時,CPU正常工作,不產生陷阱。
IF:允許中斷標志。當IF為1(EI)時,允許中斷;IF為0(DI)時關閉中斷。
DF:方向標志,在串處理指令中控制處理信息的方向。當DF置1(DN)時每次操作后,變址寄存器SI和DI減量,這樣就使串處理從高地址向低地址方向處理;當DF置0(UP)時,則反之。
OF:溢出標志。在運算過程中,若操作數超出了機器能表示的范圍則稱為溢出,此時OF標志位為1(OV);否則置0(NV)。
IOPL(12位到13位):I/O特權級標志。指出當前運行任務的I/O端口的特權級。
NF:嵌套任務標志。只在當前任務是前一任務的子任務時設置。
RF:回復標志。控制處理器對調試異常的響應。
VM:虛擬8086標志。控制是否啟用虛擬8086模式。
AC:對齊檢查標志。設置為啟用存儲器的對齊檢查的參考。
VIF:虛擬中斷標志。IF的虛擬映像,與VIP標志聯合使用。
VIP:虛擬中斷標志。確定是否有中斷被掛起。
ID:標識標志,確定CPU是否支持CPUID指令。
第22到31位當前被保留。
(以下內容選自**《黑客免殺攻防》**第9.3節)
~~~
013A13CC lea edi,dword ptr ss:[ebp-0xC0]
013A13D2 mov ecx,0x30
013A13D7 mov eax,0xCCCCCCCC
013A13DC rep stos dword ptr es:[edi]
;向EDI指向的地址處依次填入EAX中的內容,循環ECX次(也就是填0xCC操作)。這是因為匯編指令
;“int 3”的OpCode就是0xCC,很顯然這樣做會大大提高程序的排錯能力,如果由于某些不可知的原
;因導致代碼跑到了不該去的地方,那么這些成排的“int 3”指令會馬上將其斷下來。
~~~
**2、請說明寄存器EBP與ESP的功能,并解釋在大多數函數入口點處的反匯編代碼中,這兩個寄存器的作用。**
答:(以下內容選自**《0day安全:軟件漏洞分析技術 第2版》**第2.1.4節)
每一個函數都獨占自己的棧幀空間。當前正在運行的函數的棧幀總是在棧頂。Win32系統提供兩個特殊的寄存器用于標識位于系統頂端的棧幀。
(1)ESP:棧指針寄存器(extended stack pointer),其內存放著一個指針,該指針永遠指向系統棧最上面一個棧幀的棧頂。
(2)EBP:基址指針寄存器(extended base pointer),其內存放著一個指針,該指針永遠指向系統棧最上面一個棧幀的底部。
注意:EBP指向當前位于系統棧最上邊一個棧幀的底部,而不是系統棧的底部。嚴格說來,“棧幀底部”和“棧底”是不同的概念,本書在敘述中將堅持使用“棧幀底部”這一提法以示區別;ESP所指的棧幀頂部和系統棧的頂部是同一個位置,所以后面的敘述中并不嚴格區分“棧幀頂部”和“棧頂”的概念。請您注意這里的差異,不要產生概念混淆。
函數棧幀:ESP和EBP之間的內存空間為當前棧幀,EBP標識了當前棧幀的底部,ESP標識了當前棧幀的頂部。
(以下內容選自**《0day安全:軟件漏洞分析技術 第2版》**第2.1.5節)
函數調用大致包括以下幾個步驟
……
(4)棧幀調整:具體包括。
保存當前棧幀狀態值,以備后面恢復本棧幀時使用(EBP入棧);
將當前棧幀切換到新棧(將ESP值裝入EBP,更新棧幀底部);
給新棧幀分配空間(把ESP減去所需空間的大小,抬高棧幀);
……
~~~
push ebp ;保存舊棧幀的底部
mov ebp,esp ;設置新棧幀的底部(棧幀切換)
sub esp,xxx ;設置新棧幀的頂部(抬高棧幀,為新棧幀開辟空間)
~~~
(以下內容選自**《黑客免殺攻防》**第9.3節)
~~~
013A13C0 >push ebp ;EBP入棧保存(一般情況下將某個寄存器入棧保存的目的只有兩個,一個
013A13C0 ;是需要將其通過棧傳遞給某個函數或代碼使用,另外一種情況就是后面的
013A13C0 ;代碼要使用到這個寄存器,因此要將其原始的值保存起來,
013A13C0 ;以備恢復)
013A13C1 mov ebp,esp ;然后將堆棧指針ESP的值傳遞給EBP,如此一來在這個函數內只需要使用
013A13C1 ;EBP就可以對棧進行操作了。這樣做的好處是不需要對ESP做過多的操
013A13C1 ;作,從而更好地保證了程序的健壯性(也增加了易讀性)
013A13C3 sub esp,0xC0 ;將ESP減0xC0,也就是將棧頂抬高0xC0。這里有一個專業名詞叫做
013A13C3 ;“打開棧幀”。但是通過源代碼我們知道根本用不了這么大的空間,這
013A13C3 ;是編譯器在編譯Debug版本時為了增強程序的健壯性與可調試性而做
013A13C3 ;的一件事
~~~
**3、請說明CALL與RET指令的實現原理。**
答:(以下內容選自**《匯編語言 第二版》**王爽著,第10章,僅針對16位系統,而32位及64位系統也可參考)
call和ret指令都是轉移指令,它們都修改IP,或同時修改CS和IP。它們經常被共同用來實現子程序的設計。
ret指令用棧中數據,修改IP的內容,從而實現近轉移;
retf指令用棧中的數據,修改CS和IP的內容,從而實現遠轉移。
CPU執行ret指令時,進行下面兩步操作:
(1)(IP)=((ss)*16+(sp))
(2)(sp)=(sp)+2
CPU執行retf指令時,進行下面4步操作:
(1)(IP)= ((ss)*16+(sp))
(2)(sp)=(sp)+2
(3)(CS)= ((ss)*16+(sp))
(4)(sp)=(sp)+2
可以看出,如果我們用匯編語法來解釋ret和retf指令,則:
CPU執行ret指令時,相當于進行:
~~~
pop IP
~~~
CPU執行retf指令時,相當于進行:
~~~
pop IP
pop CS
~~~
CPU執行call指令時,進行兩步操作:
(1)將當前的IP或CS和IP壓入棧中;
(2)轉移。
call指令不能實現短轉移,除此之外,call指令實現轉移的方法和jmp指令的原理相同。
(以下內容選自**《0day安全:軟件漏洞分析技術第2版》**第2.1.5節)
函數調用大致包括以下幾個步驟。
(1)參數入棧:將參數從右向左依次壓入系統棧中。
(2)返回地址入棧:將當前代碼區調用指令的下一條指令地址壓入棧中,供函數返回時繼續執行。
(3)代碼區跳轉:處理器從當前代碼區跳轉到被調用函數的入口處。
……
~~~
call函數地址 ;call指令將同時完成兩項工作:a)向棧中壓入當前指令在內存中的位置即保存返回
;地址。b)跳轉到所調用函數的入口地址函數入口處
~~~
……
類似地,函數返回的步驟如下。
(1)保存返回值:通常將函數的返回值保存在寄存器EAX中。
(2)彈出當前棧幀,恢復上一個棧幀。
具體包括:
● 在堆棧平衡的基礎上,給ESP加上棧幀的大小,降低棧幀,回收當前棧幀的空間。
● 將當前棧幀底部保存的前棧幀EBP值彈入EBP寄存器,恢復出上一個棧幀。
● 將函數返回地址彈給EIP寄存器。
(3)跳轉:按照函數返回地址跳回母函數中繼續執行。
還是以C語言和Win32平臺為例,函數返回時的相關的指令序列如下。
~~~
add esp,xxx ;降低棧幀,回收當前的棧幀
pop ebp ;將上一個棧幀底部恢復到ebp
retn ;這條指令有兩個功能:a)彈出當前棧頂元素,即彈出棧幀中的返回地址。
;至此,棧幀恢復工作完成。b)讓處理器跳轉到彈出的返回地址,恢復調用前
;的代碼區
~~~
**本篇文章參考資料:**
1、林文龍,“**匯編指令助手 V1.1**”,小龍軟件工作室。
2、[美]DanKaminsky、Justin Ferguson、Jason Larsen、Luis Miras、Walter Pearce(著),看雪論壇翻譯小組(譯),**《IDA Pro代碼破解揭秘》**,人民郵電出版社。
3、任曉琿,**《黑客免殺攻防》**,機械工業出版社。
4、王清(主編),張東輝、周浩、王繼剛、趙雙(編著),**《0day安全:軟件漏洞分析技術(第2版)》**,電子工業出版社。
5、王爽,**《匯編語言(第2版)》**,清華大學出版社。