最近一直研究一個對個人而言很有價值的一個LIB庫的逆向。在今天下班後突然靈感閃現,這個斷斷續續逆了接近一周的核心管理類。終於在今天給逆完了。在最後一個函數裡,碰到了之前基本沒有用過的一條指令。當然光看單句的匯編指令,是沒有辦法看出具體的作用的,而且還很可能會認為原作者本來就是用匯編來實現的!呵呵,先不廢話,先貼出反匯編代碼一睹為快:
mov dword ptr [b],64h // int b
xor eax,eax
cmp dword ptr [b],0
setg al
sub eax,1
and eax,64h
add eax,0C8h
mov dword ptr [a],eax // int a
今天的LIB裡面的那段迷惑的代碼就跟這段代碼一致,唯獨a、b變量不一樣。當然這個不影響結果。一開始可能會對setg這條指令的用途不了解。二是看下面藍色的三條指令,什麼又是減,又是and,又是add一些莫名奇妙的立即數。還真讓人迷惑這段代碼翻譯成C 將怎麼寫。難道就一句一句的翻譯?這樣的話恐怕一條匯編就是一句C 。而且到了setg這條指令時還真不知道怎麼單獨的將其翻譯成C 的什麼語句。呵呵!這可能也就是逆向所帶來的樂趣之一吧(個人觀點)!
好了。不廢話,先分析下。首先b是一個變量,首先被賦值成0x64(100)。然後將b與0進行比較,如果有心的朋友會覺得奇怪,這個cmp的下面一條語句怎麼不是跳轉語句,一般都是比較後,然後根據比較結果進行跳轉。否則cmp有什麼意義呢?到這裡的話誤導我們的就是setg這條指令了。要了解它,首先得知道cmp會影響到標志寄存器的標志位。cmp是執行的減法操作,將前面的操作數減去後面的操作數。與sub的區別就是它不將減後的值放到目的操作數中。所以cmp有可能減溢出等,從而影響到了標志位。由此一來我們就算猜測都能知道setg應該與標志位有關系。然後通過資料或者奔騰X86指令集查找表(我使用的平台是INTEL X86 CPU)。就可以知道setg指令乃是大於零為真,setg al 就是如果cmp比較後大於零,al裡的值將是0x01。setg的判斷表達式就是(ZF=0 and SF=OF),還有個setle( ZF = 1 or SF <> OF ),等等還有幾個,這裡就不一一說明。有興趣的朋友可以去查閱。
之後藍色的三條指令,仔細分析會發現,如果eax為0的話,sub eax, 1後eax將是0xffffffff。之後再and eax, 64h結果eax為64h,之後再是加C8h。之後就給a變量了。然後再分析另外一種情況,假如eax為1, sub eax, 1後將是0,再and等於沒運算,之後才是加上C8h,後面就一樣了。前面的xor eax, eax就不說了,就是把eax清零。這樣一分析,可以有個大概的C 語句的錐形。那就是三目運算符:(?:)。
好了,這句C 語句很短,那就是:
int b = 100;
int a = ( b <= 0 ) ? 300 : 200;
哈哈,很簡單吧!其實逆向就是這樣,分析一大段很可能翻譯過來的C 代碼就一句而已。這篇文章說是巧妙原理解析。這裡的巧妙之處就在於編譯器很聰明(MS很厲害),這些細節的技巧可以給我們很多的啟發。這個三目運算在匯編層面上,首先編譯器會把後面冒號兩邊的數字求差,差將用於and運算。b <= 100後eax要麼為1要麼為0。因此後面的sub eax, 1後eax要麼為0xffffffff,要麼為0,為0xffffffff表示b小於等於0。b小於等於0之後的and eax, 64h也就將300 - 200的差100賦值給了eax,之後再add C8h(200),便得到了300。反之,sub eax, 1後。 eax將為0。and運算後等於就沒有算上差值,之後加上冒號後面的數。就是小的200了。呵呵!這些細節,MS的程序很細心啊!
還有就是一點,這裡記錄的差值是有符號的,而且是固定的冒號前面的數減去後面的數。如果前面的數小於後面的數,那麼這裡記錄的將是一個負值,原理一樣。
再舉一個例子吧:
mov dword ptr [b],1
xor eax,eax
cmp dword ptr [b],0
setg al
lea eax,[eax eax-1]
mov dword ptr [a],eax
這個例子就不用說了,直接貼C 代碼:
int b = 1;
int a = ( b > 0 )? 1 : -1;
或者是BOOL類型的。注意看紅色指令的巧用,它直接代替了and和add指令。大家慢慢體會!
如果後面冒號兩邊任意一邊是變量的話,就不會被編譯成這樣子了,就會被編譯成普通的跳轉類似於if語句了。