【使用工具】 Peid 0.94,OllyDbg(OllyIce),exeScope,Spy4Win
【運行平台】 WinXP
【軟件名稱】 IMMSG飛鴿傳書 (下載)
【軟件簡介】 一個用於內網交流和傳輸文件的小工具
前些日子到同事的同學哪里去拷《康熙大帝》。可惜網連好后,就是訪問不了。看來又是什么安全策略的問題了,本想重設一下。這時同事的同學就講可以使用 飛鴿傳書。
這個軟件用起來后感覺還不錯,很方便,不過也有些不足。界面如下
看上圖可知。首先是窗體太窄,尤其是 顯示用戶名和主機名的那個LISTVIEW。看起來很不爽。第二個問題是ICON(圖標)太難看。第三個呢在圖上看不出來,就是雙擊運行后,界面並不先出來,而只是在拖盤上顯示個圖標,雙擊后,主界面才能出來,非常不方便。於是就想改一改。
下面我們就一起來在沒有源碼的情況下把這些問題給改一改。
1. 改變窗體的大小
在未進行更改前,以為改變窗體大小應該是一件很容易的事情,不就是斷一下CreateWindowExA,然后看傳遞的參數(參數里包含寬度和高度的值),之后順藤摸瓜找到存放寬度和高度值的內存地址,更改成我想要的值不就行了嘛。如果幸運的話寬度和高度都是以立即數的形式給PUSH的話那就更美好了。當然事實往往和想象中的總是有差距的。動手之后才知道自己想的是那么簡單,當然這也和自己的知識不牢是分不開的。不過,這也不是什么壞事,雖然分析了很多無關緊要的東西,但也通過這些分析學到了不少東西。也不能完全算壞事。好了。廢話不多講了。下面開始分析過程吧
第一步當然是查殼,本來打算如果是猛殼(例如TMD之類)的話就直接放棄,不過一查之下,結果甚是讓人高興,不僅不是猛殼,甚至連殼都沒加。后來才得知其實這是一款國外人寫的免費軟件。國外人的免費軟件不加殼是很正常的,畢竟人家不象咱國人。把人家的源碼或者軟件拿來隨便加點東西,然后就開源的閉源,沒加殼的加殼,最后再把軟件名字和作者一改,這就堂而惶之的宣稱是××的作品了。用老羅的話說:就是真TM沒見過這么流氓的。PEID切圖如下。
從圖中可以看出這個軟件是用Microsoft Visual C++ 6.0 寫的。
我用的這個算是比較新的userdb(存放各種開發工具生成的EXE文件的特征碼的文本文件,PEID就是用他來識別開發工具的),而且這個軟件的這個版本年代也比較久遠了,理論上應該識別的准確率是很高的,當然我們還是不能完全相信PEID的,因為很多加殼的軟件,都可以將其加殼后的EXE偽裝成VC6編寫的。所以還是用OD(OLLYDBG)載進來看看先。載入OD代碼窗口顯示如下:
004183D7 >/$ 55 push ebp 004183D8 |. 8BEC mov ebp, esp 004183DA |. 6A FF push -1 004183DC |. 68 18CB4100 push 0041CB18 004183E1 |. 68 2C9D4100 push 00419D2C ; SE 處理程序安裝 004183E6 |. 64:A1 0000000>mov eax, dword ptr fs:[0] 004183EC |. 50 push eax 004183ED |. 64:8925 00000>mov dword ptr fs:[0], esp 004183F4 |. 83EC 58 sub esp, 58 004183F7 |. 53 push ebx 004183F8 |. 56 push esi 004183F9 |. 57 push edi 004183FA |. 8965 E8 mov dword ptr [ebp-18], esp 004183FD |. FF15 74B14100 call dword ptr [<&KERNEL32.GetVersion>; kernel32.GetVersion 00418403 |. 33D2 xor edx, edx 00418405 |. 8AD4 mov dl, ah 00418407 |. 8915 DC224200 mov dword ptr [4222DC], edx 0041840D |. 8BC8 mov ecx, eax 0041840F |. 81E1 FF000000 and ecx, 0FF 00418415 |. 890D D8224200 mov dword ptr [4222D8], ecx 0041841B |. C1E1 08 shl ecx, 8 0041841E |. 03CA add ecx, edx
看到注釋里的SE 處理和那個GetVersion沒有,有了這兩個家伙基本可以確定PEID的識別是准確的了。當然光確定沒加殼沒用,我們還得繼續前進,在OD的代碼區右鍵,查找->所有模塊中的名稱。在彈出的窗口上隨便選擇一行后,輸入 CreateWindowExA.會出現很多個,選擇模塊是USER32的那個,點右鍵->切換斷點。當然可能有些人會問,為什么要斷在USER32模塊里,為什么不直接查找->本模塊中的名稱,這樣不是能直接斷在本程序里,而不用再從USER32模塊里跟回本程序的領空了嗎。當然這個問題對於大部分人來說,都是異常簡單的,但對於新手還是有必要解釋一些的。OD的反匯編能力雖然強,但對於有些動態調用(使用LoadLibary GetProcAddress)就不一定能分析出來他的API名稱了,所以查找->本模塊中的名稱的時候,可能就會遺漏掉這些地方的調用。所以還是在斷在USER32里比較安全,無論靜態或動態調用,總得從我這里走,而且從API返回到程序領空也並不是一件困難的事情。懂點匯編的人就知道只要看棧頂就可以了(CALL一個函數的時候,在進行函數體之前,總要將代碼的下一個地址壓棧的,所以在函數的第一行的時候,棧頂就一定是調用函數下一個地址,當然這是在保護模式下,在實模式下,在棧頂和棧頂的下一位,分別存CS和偏移)。最重要的是OD里會直接提示你會返回到哪里,知道了返回的地址,那么他的上一行,一定就是調用的地方了,當然多重調用的話,可能第一次返回的並不你想要到的地方,不過不要緊,耐心慢慢一步步跟就是了。總能找到你想要的位置的:)。
設斷后F9,斷在這里,代碼區代碼如下:
77D2190B > 8BFF mov edi, edi 77D2190D 55 push ebp 77D2190E 8BEC mov ebp, esp 77D21910 68 01000040 push 40000001 77D21915 FF75 34 push dword ptr [ebp+34] 77D21918 FF75 30 push dword ptr [ebp+30] 77D2191B FF75 2C push dword ptr [ebp+2C] 77D2191E FF75 28 push dword ptr [ebp+28] 77D21921 FF75 24 push dword ptr [ebp+24] 77D21924 FF75 20 push dword ptr [ebp+20] 77D21927 FF75 1C push dword ptr [ebp+1C] 77D2192A FF75 18 push dword ptr [ebp+18] 77D2192D FF75 14 push dword ptr [ebp+14] 77D21930 FF75 10 push dword ptr [ebp+10] 77D21933 FF75 0C push dword ptr [ebp+C] 77D21936 FF75 08 push dword ptr [ebp+8] 77D21939 E8 B5FEFFFF call 77D217F3 77D2193E 5D pop ebp 77D2193F C2 3000 retn 30 77D21942 FFB5 F0FBFFFF push dword ptr [ebp-410] 77D21948 FF15 A810D177 call dword ptr [<&ntdll.RtlReleaseAct>; ntdll.RtlReleaseActivationContext
這個就是USER32里的CreateWindowExA 反匯編后的樣子,其中上面代碼的第一句就是,CreateWindowExA 的第一條語句了,當然上面的信息對我們來說是沒有用的。不用管那么多,關鍵是看堆棧 。
0012FCB0 00416876 /CALL 到 CreateWindowExA 來自 復件_IPM.00416870 0012FCB4 00000000 |ExtStyle = 0 0012FCB8 0012FDA0 |Class = "ipmsg_class" 0012FCBC 004207CC |WindowName = "IPMsg" 0012FCC0 20CF0000 |Style = WS_OVERLAPPED|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_MINIMIZE|WS_SYSMENU|WS_THICKFRAME|WS_CAPTION 0012FCC4 80000000 |X = 80000000 (-2147483648.) 0012FCC8 80000000 |Y = 80000000 (-2147483648.) 0012FCCC 80000000 |Width = 80000000 (-2147483648.) 0012FCD0 80000000 |Height = 80000000 (-2147483648.) 0012FCD4 00000000 |hParent = NULL 0012FCD8 00000000 |hMenu = NULL 0012FCDC 00400000 |hInst = 00400000 0012FCE0 00000000 /lParam = NULL
OD給出的解釋已經很詳細了,重要的是Class="ipmsg_class"(類名)和WindowName="ipmsg"(窗體名)。另外因為只要程序每次使用CreateWindowEx創建窗體的時候,這里都會被斷下來,所以這個地方是會被多次斷下來的,所以每次被斷下來的時候,都要記錄下Class和WindowsName,一會兒再拿來和用工具查出來的主窗體的類名和窗體名進行比較,如果類名和窗體名和主窗體的類名和窗體名能對應上的話,就可以確定當時被斷下來的那個就是主窗體了。一旦確實了主窗體被斷的位置,如此只要再運行一次程序,從那個地方開始返回到程序的領空,這樣就可以找到創建主窗體的CreateWindowExA的地方了,然后在他PUSH 寬度和高度的地方,設斷,刪除其它斷點,這樣就可以斷在我們想要的位置上,再順藤摸瓜就可以找到它的寬度和高度的地址了。當然你也完全可以先運行程序,然后用工具查出來主窗體信息后,在CreateWindowExA上設條件斷點,可能會更加的方便。不過在這里我們已經這樣了,就繼續順着這個思路繼續往下走吧。OKAY,記錄了類和窗體名后,在目前看來已經夠用了,就不用管其它了,繼續F9。發現此時拖盤上的圖標已經出來了。而且OD也顯示程序已經處於運行狀態。看來只截到這一個調用了,那么這個窗體是主窗體的可能性應該是比較大了。於是雙擊拖盤圖標,打開主窗體,發現沒有被截下來,想應該主窗體的確是已經被創建了,這里只是ShowWindow或者SetWindowPos一下而已.所以才不會被CreateWindowExA斷點給截下來。當然還是剛才那句,想的和現實總是有段距離的。打開妖哥的SPY4WIN.查看主窗體信息
頭大的了,不是吧.和剛才截到的窗口信息簡直風馬牛 不相及嘛。如果只是標題名不一樣,也許還能給點安慰,因為可以使用SetWindowText 重設下標題,而且也經常有人這樣干,但如果窗口類名不一樣,那就要另當別論了,雖說也可以使用GetClassInfo.重設類名。但一般情況下正常人類不會那樣做。難道我們辛辛苦苦斷下來的唯一一個窗口還是沒用的???頭都暈了,當然俺可也不是那種那么容易放棄的人,所以接着在剛才斷下來的那個窗口上又下了一番工夫,包括跟蹤窗口事件,因為我發現使用SPY4WIN將那個窗體(類名為img_class的那個)顯示后,雙擊就會出現主窗體。於是跟了事件,發現在打開主窗體之前,他發了一個WM_ACTIVEAPP**.事件。於是用OD在那個窗口上下了事件斷點,跟蹤了半天,沒發現什么東西,只好放棄了。看來還是得從頭理思路了,既然不是在啟動的時候創建的窗體。那么會不會是在雙擊拖盤圖標的時候創建的呢,不過剛才在雙擊后,也的確沒斷下來,於是又思考是不是使用了CreateWindowExW.於是在它上面設斷,的確斷了幾個下來,卻也都是和主窗體風馬牛不相及的。
看來還得再理一次,而且得換個思路,剛才忽略了一個很大的問題,我們一直假設他是在啟動的時候就創建了窗體,但這個卻是不一定的,因為他是在雙擊托盤里的圖標后,才顯示的窗體,那么會不會是雙擊托盤圖標后,才創建的窗體呢,這個得先確定一下。這個不難確認,先啟動,使用剛才記錄下來的主窗體信息在SPY4WIN中查找,沒發現窗體,雙擊拖盤圖標,查找,找到了。看來的確是在雙擊后才創建的,但為什么雙擊后,沒有截住他呢,難到不是使用CreateWindowExA創建窗體,而是使用的是DIALOG相關函數。一想到DIALOG,忽然想到了主窗體的類名,#32770,好象記的在那里看到過介紹,是特殊窗口的類,於是百度了一下,果然。這是DIALOG(對話框)的類名,看來應該使用DIALOG相關函數了,不過此時又犯嘀咕了。因為貌似在什么地方又見到過。講DIALOG相關函數最會還是會調用CreateWindowExA( W ).這樣看來,CreateWindowExA斷不下來。DIALOG相關函數應該也沒戲了,所以當時並沒有報多大希望。但事實又一次證明我錯了。DIALOG相關函數當然首選CreateDialogParamA(W)了,於是在它上面設斷,居然給斷下來了。倒掉了。不過具體什么原因就不追究了,目的達到就行了。畢竟只是改軟件,又不是分析軟件。
刪除原來所有的斷點,重新在CreateDialogParamA處設斷,既然已經知道了是雙擊備拖盤圖標后才創建的窗體,所以設斷的時機當然是選在程序完全啟動之后了。設斷,然后雙擊拖盤圖 標。斷在了這里
77D35EA0 > 8BFF mov edi, edi 77D35EA2 55 push ebp 77D35EA3 8BEC mov ebp, esp 77D35EA5 53 push ebx 77D35EA6 56 push esi 77D35EA7 8B75 08 mov esi, dword ptr [ebp+8] 77D35EAA 33DB xor ebx, ebx 77D35EAC 53 push ebx 77D35EAD FF75 0C push dword ptr [ebp+C] 77D35EB0 6A 05 push 5 77D35EB2 56 push esi 77D35EB3 FF15 2404D777 call dword ptr [77D70424] ; kernel32.FindResourceExA 77D35EB9 3BC3 cmp eax, ebx 77D35EBB 74 40 je short 77D35EFD 77D35EBD 57 push edi 77D35EBE 50 push eax 77D35EBF 56 push esi 77D35EC0 FF15 C402D777 call dword ptr [77D702C4] ; kernel32.LoadResource
當然這些信息仍然不重要,重要的是 堆 棧(話外音:我日,不重要你老帖出來干嘛 回答:了解一下沒有什么壞處)
0012FD5C 00415E68 /CALL 到 CreateDialogParamA 來自 復件_IPM.00415E62 0012FD60 00400000 |hInst = 00400000 0012FD64 00000065 |pTemplate = 65 0012FD68 00000000 |hOwner = NULL 0012FD6C 00415B5D |pDlgProc = 復件_IPM.00415B5D 0012FD70 00000000 /lParam = 0
因為在雙擊后,主窗體很快出來了,所以最大的可能性就是:第一個斷下來的地地方就是創建主窗體的地方。看堆 棧 信息第一排。那個 來自復件_IPM.00415E62。這個就是本程序調用此函數的地方了。再看看棧頂(這個第一行就是棧頂)。紅字那個地址00415E68 ,這個就是執行完這個函數后要返回到的地址。00415E68 - 00415E62 =6 。 CALL指令兩個字節,后面加一個地址(32位正好四字節)不正好是6嗎。所以,如果遇到OD沒有提示的情況下,可以使用這種方法確定調用地址,或者干脆不用算了,直接回到棧頂所指向的地址00415E68,然后往上看一行就行了,那個地方肯定是調用的地方。當然既然已經知道主窗體是使用CreateDialogParamA創建的,那其實已經很簡單了,根本連程序領空也不用回了,使用CreateDialogParamA,必然會使用 窗體模版,這個在資源里一定能找到。看堆 棧 信息第三行,|pTemplate = 65 這個就是模版的資源號了。當然這個是16進制的。因為下面我們要使用EXESCOPE,所以得轉成十進制101,因為EXESCOPE里的資源號是用十進制表示的。我勒個了去,原來啥都不懂的時候,還知道使用EXESCOPE打開文件,更改窗體的樣式。現在居然一時沒想起來,反而饒了這樣一個大圈子,倒掉了。當然郁悶歸郁悶。改還是要改。將程序載入EXESCOPE。找資源號為101的資源。肯定是個窗體,有了EXESCOPE,那就隨你想怎么樣就怎么樣了。具體就不講了。反正改就是了。到這里,調整窗體大小算是完成
調整后界面:
當然看到這里的朋友也不要覺得上當了,雖然上面做了N多無用功,但涉及的破解知識點以及逆向思路還是不少的。
2. 改變圖標
改變圖標是個很簡單的工作。用EXESCOPE直接就可以修改。需要注意的原來的圖標是32*32 16色的,你也必須找個同樣大小,同樣16色的圖標,否則替換不了。EXESCOPE是不能改變資源的大小的。ICON的格式有點類似於BMP,點陣式的。圖片文件的大小(占用的字節數)只和分辨率和位數(16色是4位)有關,和內容無關,不象JPEG和GIF等一些壓縮格式的圖片,文件的大小和圖片的內容有很大的關系。
使用EXESCOPE打開飛鴿傳書,當然備份一份是必不可少的。這是一個好習慣。
文件-》導入,選擇你要替換的那個ICON,點打開。我選擇的是自己處理的一個ICON,當然也是比較難看了。不過感覺還是比原來的好看一點。
圖標替換是完成了,但雙擊運行后。卻發現了一個問題,就是這個圖標顏色本身就顯得有些暗,而且大部分又是透明的,所以顯示在拖盤上就很不明顯()而且如果已經習慣了以前的飛鴿圖標,這突然一變,多少都會有些不習慣。所以我們有必要更改下拖盤圖標,把他換成原來的圖片。當然動手前還是要先確定思路,方法基本上和上篇一樣,就是截API調用。先看上圖,圖標資源105 下面還有一個108 .打開后看和原來的圖標差不多。所以就想可以把這個108 的圖標換成原來的圖標,然后將拖盤的圖標改成使用108的。這樣不就可以了嘛。
先做准備工作,將108 的圖標換成原來的105的圖標。這個方法和前面一樣,原來的那個圖標可以從備份的那個文件里導出來。具體過程就不講了。一切就緒后。就得開始 斷AP I。
提到拖盤圖標。我們第一時間想到的當然是 Shell_NotifyIconA(W).下面我們來看看他的聲明。
Shell_NotifyIconA(
DWORD dwMessage,
PNOTIFYICONDATAA lpData
)
dwMessage 有三個值 NIM_MODIFY,NIM_ADD,NIM_DELETE分別指修改,添加,刪除圖標。
LpData 是一個PNOTIFYICONDATAA。聲明如下
typedef struct _NOTIFYICONDATAA { DWORD cbSize; HWND hWnd; UINT uID; UINT uFlags; UINT uCallbackMessage; HICON hIcon; CHAR szTip[64]; } NOTIFYICONDATAA, *PNOTIFYICONDATAA;
cbSize指本結構的大小
hWnd,指接收消息的窗體(當你左鍵單擊,或者右鍵單擊圖標時,會自動發送一個消息(此消息由此結構后面的uCallbackMessage定義)給這個窗體))
UID 圖標的ID
uFlags用來設置以下三個參數uCallbackMessage、hIcon、szTip是否有效.
UCallbackMessage 當對拖盤圖標進行操作時(左鍵雙擊WM_LBUTTONDBCLICK)時,向窗體發送的消息號。目標窗體只須處理這個消息號,並通過lapram判斷所進行的操作(如左鍵單擊,雙擊,右鍵單擊等)然后做相應的處理就行了。
HIcon 圖標句柄,注意一下這個,這個是要重點分析的。
SzTip 拖盤提示語句.
具體怎么使用這個函數,在這里就不詳述了,大家可以查查相關的資料。在這里我們唯一要注意的就是那個HICON hIcon。這個就是指拖盤的圖標了,但不幸的是。HICON明顯是個圖標的句柄,而不是我們想要的圖標的資源號。如果是資源號的話,直接就可以在這個地方改了,但如果是句柄的話,可能就得煩一些了。
一開始的思路是。斷下這個API后,返回到程序領空,在PUSH 參數的時候,得到上述的那個結構的地址,然后找到 HICON 的位置,下內存寫入斷點。這樣就可以斷在 給HICON 賦值的地方,這個地方一般離得到HICON的地方不會很遠,最有可能的就是上一行。這樣就可以跟進到獲取HICON的函數里,這個函數里一定會使用到圖標資源號,這樣就可以找到圖標資源號 並修改它了。但是實際運行中。卻發現那塊內存不止被這一個結構使用,寫入的太過頻繁,所以放棄了。於是另尋思路。現在我們的目的是找到獲得HICON的函數,而且這個函數要和圖標資源號相關,那么有幾個,LoadIconA(W),LoadImageA(W )。最有可能的就是LOADICON。
LoadIconA 聲明如下
LoadIconA(
HINSTANCE hInstance,
LPCSTR lpIconName
);
hInstance.是指出資源所在的模塊(可能是EXE也可能是DLL)的實例。
lpIconName是指向NULL字符結尾的字符串的指針,它包含圖標名。當然在這里我們也可以傳一個資源號(資源標識)進去。所以這個函數正是我們需要的。當然在VC里傳資源標識
的時候。要使用MAKEINTRESOURCE 轉換一次。要不然是不可以通過的。當然這個和逆向沒關系。 思路確定了,下面就是要確認了
重新在OD里載入飛鴿傳書。斷在入口點。
菜單欄->查看->斷點 刪除所有斷點。在代碼區(反匯編區)右鍵->查找->所有模塊中的名稱,鍵入 LoadIconA,選擇模塊是user32 的那個,右鍵 切換斷點。
查一查斷點窗口,是不是多了一個斷點,是的話。F9,斷了下來,代碼區就不看了,沒什么用處,直接看 堆棧
0012FCFC 004041A4 /CALL 到 LoadIconA 來自 IPMSG.0040419E 0012FD00 00400000 |hInst = 00400000 0012FD04 00000069 /RsrcName = 105.
正好兩個參數全在這了。HInst = 00400000 這個一眼就看出來了,明顯是指本程序。WINDOWS用戶級 的程序默認的加載地址就是這個。RsrcName = 105 。105 不就是原來的那個圖標的資源號嘛。當然我們還不能高興的那么早,因為這個時候加載的圖標,不一定是給拖盤圖標使用的,有可能是設置窗體圖標的。所以還得走着看看。先回到程序領空里調用這個函數的地方。和原來一樣,看第一行。“來自 IPMSG.0040419E“
0040419E 就是調用此函數的地方了。在代碼區 右鍵->轉到->表達式,填入 0040419E。代碼區第一行是
0040419E |. FF15 8CB24100 call dword ptr [<&USER32.LoadIconA>] ; /LoadIconA
向上翻幾行。
00404192 |. 6A 69 push 69 ; /RsrcName = 105. 00404194 |. 50 push eax ; |hInst => 00400000 00404195 |. 8975 C4 mov dword ptr [ebp-3C], esi ; | 00404198 |. 8975 C8 mov dword ptr [ebp-38], esi ; | 0040419B |. 8945 CC mov dword ptr [ebp-34], eax ; | 0040419E |. FF15 8CB24100 call dword ptr [<&USER32.LoadIconA>] ; /LoadIconA
其中最后一排就是 我們剛才找到的調用的地方。不管它,看第一行。Push 69. 十六進制的 69 不就是十進的105(6*16+9)嘛。好的。在這一行F2設斷。CTRL+F2 重新載入程序。F9.斷在了00404192 處。雙擊這一行,在彈出的窗口里將69(105)改成 6C(108)。F9。斷在了LoadIconA處。看堆棧
0012FCFC 004041A4 /CALL 到 LoadIconA 來自 IPMSG.0040419E 0012FD00 00400000 |hInst = 00400000 0012FD04 00000069 /RsrcName = 108.
說明我們已經將圖標的資源號改過了。
先不用管它,繼續F9。又被斷了。看堆棧
0012FCD0 00408823 /CALL 到 LoadIconA 來自 IPMSG.00408821 0012FCD4 00400000 |hInst = 00400000 0012FCD8 00000069 /RsrcName = 105.
調用來自另外一個地方,傳過來的圖標資源號還是原來的105.再看拖盤,圖標沒有出來。看來我們第一次改的那個地方,八成是不對的了。先記下 00408821 這個地址。繼續F9。又被斷了。堆棧
0012FCD0 0040885B /CALL 到 LoadIconA 來自 IPMSG.00408859 0012FCD4 00400000 |hInst = 00400000 0012FCD8 0000006C /RsrcName = 108.
雖然這里RsrcName 是 108 但調用地址明顯和第一次斷的地方不一樣。所以這個地方應該就是加載真正的圖標108(現在的圖標108已經在前面被我們改過了)的地方。所以這個地方對我們來說,用處不大。繼續F9。再次被斷。堆棧
0012EA48 73658B1B /CALL 到 LoadIconA 來自 73658B15 0012EA4C 00000000 |hInst = NULL 0012EA50 00007F00 /RsrcName = IDI_APPLICATION
這個調用看地址就不在本程序的領空了,所以暫時先忽略掉它。繼續F9。拖盤圖標這時已經出來了。圖標還是原來的。OD顯示了程序處於運行狀態。
從上面來看,在程序處於運行前,有四個地方調用了LoadIconA.剛才第一個地方已經試過了。不是正確的地方。第三次傳入的參數是原來的108,所以應該關系也不大。至於第四個,返回地址根本不在程序領空,所以也不用管,那么剩下的只有第二個調用了。
Ctrl+F2重新載入,先不忙着F9。為了不讓其它的斷點影響到我們,先清除所有的斷點。另外剛才我們也已經記下了第二個調用LoadIcon的地方的地址,就是 00408821。代碼區,右鍵->轉到->表達式,填入這個地址 點確定
00408819 |> /6A 69 push 69 0040881B |. FF35 AC224200 push dword ptr [4222AC] 00408821 |. FFD3 call ebx
最后一排就是剛才找到的那個調用的地方,當然也不用管它。看第一行。又是一個Push 69.好的。在這一行F2設置斷點。F9。斷在了這里。和剛才一樣,改 65 為 6c. 沒有再被斷下來。程序也正常運行起來,圖標也已經出來了,而且已經變成了108對應的圖標,也就是我們先前導入到108里的那個原先程序的圖標了。至此。算是差不多已經完成了。但還存在一個問題,就是我們目前還只是在OD里改了代碼。此時改的也只是內存里的代碼而已。重新載入后不又沒有了嘛。不要緊。OD功能還是很強大的。
在代碼區 右鍵->復制到可執行程序->所有修改。彈出提示框。點 全部復制 ,彈出窗口,不用管,直接關閉,此時會提示你是否保存,點是,選擇我們要替換的文件,此時問你是否覆蓋。是。OK,一切搞定,關閉OD。雙擊我們修改后的飛鴿傳書。再看一看。拖盤圖標是不是又和原來一樣了哈J.
注:在實際當中,使用的分析方法和此略有不同。在改了第一個調用,發現不成功后。我就重新載入了 程序。然后在shell_notifyicon上設了斷點,找到了調用它的地方,又設了斷點。然后運行程序。記錄每個調用LoadIconA的地方的返回地址,直到斷點斷在了調用shell_notifyicon的地方為止,那么我記錄下來的最后一次調用LoadIconA的地方就是設置拖盤圖標的那個LoadIcon 了。事后,發現,其實沒這么麻煩。用上面的那個分析方法就已經能夠確定了。故在這里將較為簡單的方法作為主要介紹,而實際當中的分析方法只作為一個參考而已。
好的。到此。第二步總算也完成
3. 啟動就顯示窗體,而不是雙擊圖標后才顯示
下面我們來解決開篇里提到第三個問題:雙擊啟動后不彈出窗體,而只是在拖盤上有個圖標,需要雙擊拖盤圖標才能打開主窗體 的問題。
在正式開始之前,還得先回憶下在解決第一個問題時分析出來的“成果”:1. 主窗體是在雙擊拖盤圖標時創建的。2.點主窗體上的叉是關閉主窗體,而不是隱藏它。3. 創建主窗體的函數是CreateDialogParamA。 本節將在這些分析的基礎上繼續。
同樣的先確定思路。本來的思路是在Shell_NotifyIcon 后,隨便找個位置,將那個位置的代碼改成jmp A。A就是我們寫內存補丁的位置,在A位置調用CreateDialogParamA,然后顯示窗體,最后,再跳回到原程序的相應 位置繼續執行。這個方法是可行的,而且CreateDialogParamA本身就存在,不需要我們重建輸入表,省了不少的事。但卻也有些問題,例如CreateDialogParamA要指定窗體的處理函數,這個處理函數雖然很容易跟出來,在這里設置也不麻煩,但感覺上總有點不爽。另外CreateDialogParamA參數還有好幾個,都要處理一番。還是有些麻煩(其實也不是特別麻煩)的。於是就想有沒有更好一點的辦法,這時忽然想起,主窗體是在雙擊拖盤圖標后才創建的,而且每單擊一次就會創建一個主窗體。那么極有可能的情況是,創建主窗體的過程已經在原程序被寫在了一個方法里,這樣我們就不需要直接調用CreateDialogParamA然后再去處理因為調用他而引起的一系列后續操作了。只須簡單的調用下原程序里的那個創建窗體的函數即可。思路是確定了,剩下的就是證實和實現了。
將飛鴿傳書載入OD,代碼區->右鍵->查找->所有模塊里的名稱,輸入CreateDialogParamA,找到模塊是user32.dll的那個F2設斷,F9 直到OD顯示 運行.雙擊 拖盤圖標,斷了下來。
當然代碼區不是我們關心的,看堆棧
0012FD5C 00415E68 /CALL 到 CreateDialogParamA 來自 IPMSG.00415E62 0012FD60 00400000 |hInst = 00400000 0012FD64 00000065 |pTemplate = 65 0012FD68 00000000 |hOwner = NULL 0012FD6C 00415B5D |pDlgProc = IPMSG.00415B5D 0012FD70 00000000 /lParam = 0
代碼區->右鍵->轉到->表達式,填入00415E68 (看堆棧第一排,如果不知道為什么 寫它, 第一節有講)。代碼區來到這里。
00415E68 |. 85C0 test eax, eax 00415E6A |. 8946 20 mov dword ptr [esi+20], eax 00415E6D |. 75 0B jnz short 00415E7A 00415E6F |. 56 push esi ; /Arg1 00415E70 |. E8 CEFDFFFF call 00415C43 ; /IPMSG.00415C43 00415E75 |. 59 pop ecx 00415E76 |. 33C0 xor eax, eax 00415E78 |. EB 03 jmp short 00415E7D 00415E7A |> 6A 01 push 1 00415E7C |. 58 pop eax 00415E7D |> 5E pop esi 00415E7E /. C2 0400 retn 4
你可以向上翻一翻,這段代碼的第一行也就是00415E68 的上一行就是調用CreateDialogParamA的語句,當然在這里我們不管這些,在這一行,F2.設斷,F9運行到這里。此時窗體並沒出來。F8(單步步過)幾步走到00415E7E 處,此處是RETN。不用管,繼續。來到這里。
0040753F |. 8B07 mov eax, dword ptr [edi] ; IPMSG.0041C2A0 00407541 |. 6A 0A push 0A 00407543 |. 8BCF mov ecx, edi 00407545 |. FF50 04 call dword ptr [eax+4] 00407548 |. 6A 01 push 1 ; /Arg2 = 00000001 0040754A |. 57 push edi ; |Arg1 0040754B |. 8BCE mov ecx, esi ; | 0040754D |. E8 B8220000 call 0040980A ; /IPMSG.0040980A 00407552 |. 395E 6C cmp dword ptr [esi+6C], ebx
還是繼續F8,當走過00407545 處的那個CALL之后,窗體出來了(任務欄圖標已經出來) 看來再遇到一個retn就差不多要找到我們要找的函數了。繼續。來到00407592.此處是一個retn。繼續。來到這里。
00405590 . /E9 CB000000 jmp 00405660 00405595 > |8B06 mov eax, dword ptr [esi] ; Cases A1 (WM_NCLBUTTONDOWN),201 (WM_LBUTTONDOWN) of switch 00405540 00405597 . |8BCE mov ecx, esi
向上翻一翻。
00405587 . 55 push ebp 00405588 . 55 push ebp 00405589 . 8BCE mov ecx, esi 0040558B . E8 E71E0000 call 00407477 00405590 . E9 CB000000 jmp 00405660 00405595 > 8B06 mov eax, dword ptr [esi] ; Cases A1 (WM_NCLBUTTONDOWN),201 (WM_LBUTTONDOWN) of switch 00405540
上段中紅字的那行,就是剛才來到的那一行。看一下他的上一行 call 00407477.這個就是我們要找的函數了。他有兩個參數。PUSH EBP ,PUSH EBP。此時EBP是0.唯一感覺奇怪的是兩個PUSH下面的那個mov ecx,esi. 因為這個軟件是采用VC寫的。所以這個函數可能采用的是thiscall的調用方式,使用 ECX傳遞this指針,當然這只是猜測,未進一步分析。只是對程序在不同的時間進行了一下跟蹤,發現ESI的值一直保持不變,所以在補丁里加入這句也不會影響程序的正常運行。
好了。確定了調用函數。下一步,要確定調用的地方了。具體怎么找調用的地方,和上面的步驟差不多,只要斷一下Shell_NotifyIcon.然后不停的F8.找個合適的位置就是了。一開始的時候。找的是004080d5。后來發現在這里調用創建窗體的函數會在啟動時創建兩個主窗體。所以改在了004050AA處。當然在改跳轉之前,我們還得在程序里找一塊地方供我們寫補丁。此時PEID再次出場。
用PEID打開程序。找到 EP 段字樣,跟在他后面的有一個按鈕,按鈕的文本是”>”.點一下。隨便選擇一個(.text .rdata……).右鍵搜索全0處。彈出如下對話框
從圖中可以看出來。.text 節的偏移 0001A72A處有大小為 8D6 的空白位置。
8D6 = 2000多字節,差不多 2K了,絕對是夠用了。所以就確定在這里面寫了。當然我們不能使用RVA,而應該使用 基址+RVA 此程序的基址是 400000.所以我們寫內存補丁的地方,應該在41A72A- (41A72A+8D6)之間,我們就確定在41A730處吧,這個地址好記哈。至於具體為什么這樣確定,我想應該不要講了吧。從RVA的名字也猜出來了。RVA的意思:相對偏移地址。相對於什么呢,當然是程序的基址了,而且一般EXE的基址都是400000.當然也有不一樣的。不一樣也不難確定。看看OD就知道了,OD里反匯編出來的代碼的地址都是線性地址,也就是說都加了基址的,所以只須看看在OD里程序領空里的地址是多少就大致清楚了。當然這種方法確定的不是很准。准一點可以使用PE查看工具,查看NT頭部分。具體就不講了,可自已去查相關資料。
好的。現在確定了 調用 函數,確定了內存補丁位置,確定了跳轉至內存補丁的位置,那么下一步自然是實現從跑轉位置跳到補丁位置,然后寫內存補丁了。
重新回到OD。來到 004050AA 處。
004050AA處原代碼如下:
004050AA . /75 15 jnz short 004050C1
改成
004050AA . /75 15 jnz 41A730
F8 運行到 41A730 處。
依次寫入下列補丁程序。
0041A730 60 pushad // 保存現場 0041A731 6A 00 push 0 // 參見前面的分析 原來是PUSH EBP 0041A733 6A 00 push 0 // 同上 0041A735 8BCE mov ecx, esi // 這個可能是thiscall 0041A737 E8 3BCDFEFF call 00407477 // 調用創建窗體的函數 0041A73C 61 popad // 恢復現場。 0041A73D ^ 0F85 7EA9FEFF jnz 004050C1 // 回到正常軌道
以上的注釋,是我加上去的,可不用寫。
好。做完這些,在代碼區->右鍵->復制到可執行程序->所有修改。彈出對話框,選全部復制,彈出窗體,直接關閉,問是否保存,點是,選擇原來的文件替換。好了。關閉,OD。雙擊原來的程序。看看是不是已經可以,雙擊就彈出 窗體了哈。 :)
——