Các công cụ sử dụng trong bài hướng dẫn này gồm có:
– VicOlly (Cần có hai plug-in OllyScript và Poison).
– Exe Info PE, PEiD
– ChimpREC
– CFF Explorer
Tải đầy đủ (.doc + unpackme) tại đây.
Xin chào các bạn! Sau tut phân tích, hướng dẫn chi tiết về “MUP” (Manual UnPack) Asprotect phiên bản 1.xx cách đây khá lâu, hôm nay mình viết tut thứ hai về phân tích và cách thức unpack trình ASProtect phiên bản 2.xx.
Chú ý: Trong quá trình viết bài mình luôn có những đoạn dừng lại suy nghĩ để đặt câu hỏi và trả lời cho những câu hỏi như tại sao, như thế nào, là gì, etc mà mình highlight màu xanh để các bạn có thể hiểu rõ bản chất mọi vấn đề.
Ở đây mình đã dùng một unpackme và nén bằng ASprotect SKE 2.56 build 03.17 với tất cả những tùy chọn của nó như sau:
– Bảo vệ tài nguyên (resource .rsrc).
– Duy trì dữ liệu phụ.
– Bảo vệ, chống gỡ rối, dịch ngược.
– Bảo vệ bằng checksum.
– Bảo vệ Original Entry Point (OEP).
– Mô phỏng các hàm chuẩn của hệ thống.
– Bảo vệ bảng IAT (Import Address Table).
Bây giờ chúng ta scan lại unpackme đã pack bằng Exe Info PE nào:
Exe Info PE đã phát hiện đúng unpackme của chúng ta bảo vệ bằng ASProtect 2.xx rồi. Việc tiếp theo của chúng ta là cài đặt OllyDbg. Như đã nói là chúng ta cần thêm hai plug-in nữa OllyScript để giúp chúng ta đỡ mỏi tay hơn ở công đoạn fix IAT hồi sau. Còn plug-in Poison các bạn cài đặt như sau để vượt qua anti-debug của ASProtect:
Câu hỏi đầu tiên: Một packer/protector “thường làm việc” như thế nào? Trả lời: Những việc mà một packer/protector thường làm là mã hóa bảng IAT, mã hóa code thực thi, anti-debug. Nhưng ko phải là tất cả các packer/protector đều làm thế mà phụ thuộc từng loại packer/protector như UPX thì chỉ có nén code thực thi…hay tùy vào những tùy chọn mà packer/protector có để mình chọn như ASProtect mình đang mần dưới đây. Ở unpackme mình lấy làm mục tiêu cho tut này là một unpackme có đầy đủ các kiểu mà một packer/protector thường hay xài kể trên là mã hóa bảng IAT, mã hóa code thực thi, anti-debug.
Câu hỏi hai: Làm sao để biết được một chương trình bị bảo vệ bằng những chức năng nào? Như làm sao biết nó có mã hóa IAT hay ko, có bảo vệ Original Entry Point hay ko, có bị anti-debug hay ko? Trả lời: Câu hỏi này khá đơn giản đúng ko. Muốn xem nó có bị anti-debug hay ko thì hãy load nó vào một bản OllyDbg gốc ko chỉnh sửa, ko xài plug-in và run nó coi có run đc ko, nếu status là Terminated hay có bất kì thông báo gì báo là bị phát hiện là biết ngay là có rồi. Còn xem có bị mã hóa IAT hay ko thì sau khi run nó rồi tìm bảng IAT của nó coi bảng IAT đó có bị trống hàm nào ko, nếu một bảng Import Descriptor ứng với một dll đã import nào đó mà thấy có một hay một số chỗ thay vì có địa chỉ hàm và có mô tả bằng tên của hàm bằng một số vô nghĩa nào đó mà ko có mô tả nào đó thì chính nó đã bị má hóa bảng IAT rồi đó. Còn Original Entry Point thì load vô OllyDbg mà nhìn code xung quanh của nó xấu xấu, có mấy hàm ko có nghĩa gì…là bạn sẽ biết ngay thôi.
Mấy câu hỏi chung chung đó đã xog, bây giờ quay lại ASProtect của mình coi. Bây giờ cứ coi như mình là người gặp nó lần đầu, chưa biết làm gì với nó. Bây giờ phải làm như thế nào? Như mình đã trả lời ở câu hỏi một kia, mình cứ xoay vào nó để dần dần vẽ ra các bước để thực hiện việc unpack/unprotect.
Việc đầu tiên chúng ta cần làm là phải làm sao cho OllyDbg của chúng ta làm việc đc với unpackme, cụ thể là ko bị phát hiện ra chúng ta đang debug nó. Công việc này chúng ta đã có plug-in Poison lo rồi các bạn cài đặt tùy chọn cho plug-in Poison như hình ở trên. Còn một điều nữa là bạn nên chỉnh lại Debugging Options cho bản OllyDbg của chúng ta chút để trong lúc làm việc ko gặp cản trở của bất kì Exceptions nào. Các bạn vào Options/Debugging Options và qua tab Exceptions và cài đặt như sau:
Cái này mình cài đặt hay ko cũng ko sao nhưg nên cài đặt như thế này để OllyDbg tự vượt qua các Exception đó đỡ mất công chúng ta phải Shift + F7/F8/F9 để vượt qua mỗi lần gặp nó thôi. OK! Vượt qua anti-debug và unpackme đã run tốt trong OllyDbg rồi. Bây giờ chúng ta xử lí tiếp hai cái còn lại là mã hóa IAT và mã code thực thi. Dừng lại suy nghĩ chút nào. Một chương trình bình thường khi load vào OllyDbg trước khi đến đc OEP (Original Entry Point) nó cũng đã sử dụng một số hàm API để khởi tạo dữ liệu này nọ…rồi. Mà mấy hàm đó lại là import từ mấy dll hệ thống thông qua bảng IAT, vậy có nghĩa là trước khi đến đc OEP, trước khi giải mã mã thực thi thì nó cũng đã sử dụng một số hàm trong IAT đã giải mã, vậy thì từ những hàm đã giải mã này chúng ta sẽ dần dần tới đc OEP. Vậy thì bây giờ chúng ta đi tìm bảng IAT đã nào. Nhưng làm như thế nào để tìm đc bảng IAT đây? Đơn giản thôi, mình cứ run (F9) cho nó unpackme chạy đi, nó chạy rồi có nghĩa là bảng IAT và mã thực thi nó mã hóa đã đc giải mã có thể ko hoàn toàn nhưng cũng một phần ra bộ nhớ rồi, mình dựa vào nó để tìm. Cụ thể như thế nào thì mình sẽ vừa suy nghĩ vừa làm dưới đây.
Chú ý các bạn một chút: Những địa chỉ trên máy mình có thể khác trên máy các bạn vì trong quá trình làm việc các trình packer/protector thường xử dụng các hàm như VirtualAlloc,…để chứa code của nó nên mỗi lần re-load, run, máy này máy kia sẽ có địa chỉ khác nhau. Nên các bạn cố gắng đọc tut để hiểu rõ rồi tự làm nhé, mục đích của mình ở tut này là làm sao để các bạn “hiểu rõ” ASProtect nó làm gì và cách giải mã, giải nén nó như thế nào chứ ko phải đơn thuần là nhấn nút & làm theo để nhận đc một file đã giải nén, giải mã.
Nào, bây giờ load unpackme vào OllyDbg rồi nhấn F9 đến khi status là running nào.
Bây giờ các bạn nhấn chuột phải vào cửa sổ CPU vào chọn thực đơn Search for | All imtermodular calls để OllyDbg tìm tất cả những nơi gọi hàm API mà unpackme này đang sử dụng, chúng ta có như sau:
Các bạn nhìn hình trên, đó là tất cả những nơi trong mã thực thi gọi hàm API lên sử dụng. Những địa chỉ nào mà có mô tả bên phải kia là những hàm API mà ASProtect đã giải mã còn những địa chỉ gọi hàm mà địa chỉ của hàm đó rất lớn, nằm ngoài phạm vi địa chỉ của tệp tin, nằm trong bộ nhớ kia là những hàm ASProtect chưa giải mã, chỉ khi nào những hàm đó thực thi mới tự giải mã. Bây giờ các bạn double-click chọn đại một hàm nào đó đã đc giải mã, mình chọn ngay hàm đầu tiên kia VirtualAlloc, các bạn đến đây trong cửa sổ CPU:
Tiếp đến bạn chọn dòng 004029CA nơi gọi hàm VirtualAlloc kia vào nhấn Enter để đi tới nơi nó gọi và chúng ta đến đây:
Tiếp nữa chúng ta đi tới bảng IAT, các bạn làm như sau, chọn dòng 004025BC vừa đi tới và chọn như sau:
Và bây giờ các bạn nhìn xuống cửa sổ Dump bạn sẽ thấy bảng IAT (Chú ý: cửa sổ Dump hiển thị ở chế độ Address mới thấy đc như của mình, bạn nhấn chuột phải vào cửa sổ Dump chọn Long sau đó đó chọn Address ở bản 1.xx còn ở bản 2.xx thì các bạn chọn Integer thay Long) .
Các bạn kéo lên một chút để lên đầu bảng IAT nào. Chúng ta sẽ thấy đầu bảng IAT tại địa chỉ 004B38C8 là hàm SysFreeString, nhưng có điều bảng IAT này vẫn còn một số hàm bị mã hóa, chưa đc giải mã hoàn toàn:
Việc chúng ta bây giờ là đặt một điểm ngắt cứng vào file bằng một Hardware breakpoints. Các bạn chọn dòng 004B38C8 và nhấn chuột phải chọn Breakpoint | Hardware, on write | Dword. Việc này sẽ làm OllyDbg break khi có một lệnh nào đó thực hiện hành vi thay đổi 4 byte giá trí tại địa chỉ 004B38C8. Từ lệnh làm thay đổi địa chỉ này chúng ta sẽ dần tìm đc nơi giải mã bảng IAT.
Ok, sau khi đặt Hardware breakpoint (HBP) rồi các bạn re-load (Ctrl + F2) lại OllyDbg. Sau khi re-load, tại cửa sổ Dump các bạn đi đến địa chỉ vừa đặt HBP (004B38C8 ) kia để mình xem giá trị của nó sau khi nhấn F9.
Bây giờ bạn nhấn F9 cho đến khi nhìn thấy hàm SysFreeString hiện lên tại địa chỉ đã đặt HBP.
Bây giờ các bạn nhìn lên cửa sổ CPU và FPU các bạn thấy như sau:
OllyDbg dừng tại địa chỉ 014362EB vậy nên lệnh vừa thực thi ở ngay trên đó MOV DWORD PTR DS:[EDX],EAX chính nó đã làm thay đổi 4 byte tại địa chỉ mình đặt HBP hay đầu bảng IAT. Lệnh này thay đổi giá trị lung tung kia bằng địa chỉ của hàm API đã giải mã. Các bạn nhìn cửa sổ FPU kìa, EDX vẫn còn giữ địa chỉ 004B38C8 và EAX chứa địa chỉ của hàm SysFreeString. Vậy đoạn code này chính là đoạn code giải mã cho bảng IAT của chúng ta nhưng nó giải mã ko hoàn toàn bảng IAT của chúng ta, công việc bây giờ của chúng ta là phân tích đoạn code giải mà này để tìm hiểu qua cách nó giải mã và tìm cách để cho nó giải mã hoàn toàn bảng IAT của chúng ta. Kéo lên trên một chút nào, chúng ta đến đây:
Ta nhìn thấy đoạn code này ko đc đẹp cho lắm, có chỗ OllyDbg còn ko nhận dạng đc lệnh kìa (??? Unknown command), dòm thấy đoạn code dưới lệnh đó còn đẹp đẹp chút nên chúng ta đặt một Toggle break point tại địa chỉ 0143621F kia và trace dần xuống để phân tích. Công đoạn phân tích code này cũng phải trace đi trace lại 2, 3 lần mới hiểu đc nhưng mình cũg đã phân tích qa và tóm tắt lại những gì cần chú ý cho nhanh nhé, các bạn coi kĩ để hiểu rõ hơn nhé, đừng nhìn qua đoạn này:
Các bạn nhìn đoạn này, đây chính là đoạn giải mã để lấy tên của hàm API đã mã hóa. Các bạn để ý hàm CALL tại địa chỉ 0143628E kia, nó chính là hàm giải mã lấy tên của hàm API với tham số đầu vào là hai biến chứa giá trị đã đc mã hóa tên của hàm API. Hàm này sẽ giải mã lấy tên của hàm bằng thuật toán Xor, các bạn có thể coi code giải mã chính của nó sau đây:
01415E9B 21C9 and ecx,ecx
01415E9D 74 46 je short 01415EE5
01415E9F 56 push esi
01415EA0 57 push edi
01415EA1 89C6 mov esi,eax ; oleaut32.SysFreeString
01415EA3 8B7D 08 mov edi,dword ptr ss:[ebp+8]
01415EA6 F7C1 01000000 test ecx,1
01415EAC 74 0E je short 01415EBC
01415EAE 49 dec ecx
01415EAF 8A040E mov al,byte ptr ds:[esi+ecx]
01415EB2 32040A xor al,byte ptr ds:[edx+ecx]
01415EB5 88040F mov byte ptr ds:[edi+ecx],al ; AL chứa 1 byte là mã ascii của 1 kí tự trong tên hàm API.
01415EB8 21C9 and ecx,ecx
01415EBA 74 27 je short 01415EE3
01415EBC D1E9 shr ecx,1
01415EBE F7C1 01000000 test ecx,1
01415EC4 74 0D je short 01415ED3
01415EC6 49 dec ecx
01415EC7 66:8B044E mov ax,word ptr ds:[esi+ecx*2]
01415ECB 66:33044A xor ax,word ptr ds:[edx+ecx*2]
01415ECF 66:89044F mov word ptr ds:[edi+ecx*2],ax ; AX chứa 2 byte là mã ascii của 2 kí tự trong tên hàm API.
01415ED3 D1E9 shr ecx,1
01415ED5 49 dec ecx
01415ED6 7C 0B jl short 01415EE3
01415ED8 8B048E mov eax,dword ptr ds:[esi+ecx*4]
01415EDB 33048A xor eax,dword ptr ds:[edx+ecx*4]
01415EDE 89048F mov dword ptr ds:[edi+ecx*4],eax ; EAX chứa 4 byte là mã ascii của 4 kí tự trong tên hàm API.
01415EE1 ^ EB F2 jmp short 01415ED5
Sau khi Xor để giải mã đc từng kí tự của tên hàm thì nó sẽ lưu từng kí tự đã giải mã vào địa chỉ là kết quả của biểu thức của edi và ecx.
Các bạn nhìn các lệnh nhảy ở trên hình trên. Lệnh nhảy JNZ tại địa chỉ 01436253 sẽ nhảy qua hàm giải mã tên hàm API, như vậy đồng nghĩa với việc là hàm API ứng với tên đó sẽ không đc giải mã vậy nên ta ko đc cho lệnh JNZ này nhảy, bạn nhìn lệnh JE ở ngay trên đó nó nhảy qua lệnh JNZ này đúng ko? Vậy để ko thực thi đến lệnh JNZ này thì mình sẽ thay lệnh JE này bằng lệnh JMP luôn nhảy qua lệnh JNZ.
Đoạn tiếp nào, kéo xuống dưới một chút ta thấy đc đoạn như sau:
Bạn nhìn hàm CALL tại địa chỉ 014362DF. Hàm này chính là hàm lấy địa chỉ của hàm API có tên hàm vừa đc giải mã ở trên. Hàm này có hai tham số chính là một biến có địa chỉ chứa trong EAX là tên của module dll chứa hàm đó và biến thứ hai có địa chỉ chứa trong EDI chứa tên của hàm API. Hàm CALL này có chức năng tương tự như hàm GetProcAddress. Sau khi có địa chỉ của hàm tại EAX tại địa chỉ 014362E9 nó sẽ thay thế giá trị lung tung trong bảng IAT bằng địa chỉ thực sự của hàm. Nhưng các bạn nhìn lệnh JE ở trên, nó có thể nhảy qua đoạn lấy địa chỉ của hàm, thay đổi bảng IAT vậy nên ta phải NOP lệnh này lại để theo đúng ý của chúng ta.
OK. Những gì cần làm đã làm bây giờ các bạn nhấn F9 và nhìn bảng IAT coi:
Bảng IAT đầy đủ rồi chứ? Các bạn so sánh với bảng IAT lúc đầu coi có khác nhiều ko nào:
Vậy là công việc giải mã bảng IAT của chúng ta đã xong. Chúng ta hãy copy lại bảng IAT này để sau sử dụng. Ở cửa sổ Dump bạn chọn từ địa chỉ đầu của bảng IAT 004B38C8, kéo thanh cuộn đến cuối bảng IAT 004B4024 và chọn, nhấn chuột phải vào vùng đã chọn chọn thực đơn Binary | Binary copy và sau đó lưu lại và mở notepad ra dán vào đó và lưu lại.
Xong phần giải mã IAT giờ mình đến phần tìm đoạn giải mã mã thực thi. Dừng lại suy nghĩ chút nào: Như ở trên mình đã nói, sau khi nó giải mã IAT xong nó sẽ giải mã code thực thi của chương trình ra bộ nhớ để thực thi khi run, mà muốn code thực thi được thì section chứa nó phải mang thêm flags “Executeable” vậy nên sau khi chúng ta thực thi qua đoạn giải mã IAT chúng ta sẽ tìm những section nào của file có section flags là “Executeable” và đặt một break point lên section đó với điều kiện set break khi truy cập đến (access), như vậy chúng ta sẽ dần dần tìm tới đc OEP. Nào, thử những gì chúng ra vừa suy luận coi. Re-load lại OllyDbg và run (F9) đến đoạn giải mã IAT nào.
Đến đây chúng ta xóa HBP đã đặt trước đó đi nào.
Bây giờ nhấn tổ hợp phím Alt + M để mở cửa sổ Memory map và nhìn coi:
Nhìn qua thì thấy tất cả các section của file đã đc map lên bộ nhớ & đều đc khởi tạo với section flags có đầy đủ các thuộc tính Read/Write/Execute nên chúng ta ko thể xác định chính xác luôn đc section nào sẽ chứa mã thực thi vậy nên giờ chúng ta phải đi tìm. Chúng ta loại trừ dần như sau, đầu tiên loại trừ section PE Header ra, đây là section chứa tất cả thông tin về định dạng của một file PE nên ko thể chứa code thực thi đc, vậy thì chúng ta chọn 2, 3 section đầu rồi đặt một Break point on access lên đó để OllyDbg sẽ break ngay khi truy cập đến nó. Có ai hỏi là tại sao lại chọn 2, 3 section đầu ko? Vì code thực thi của chương trình thường là nằm các section đầu như .CODE, .text,…nên ta cứ chọn đại 2, 3 section đầu để đặt break point.
Ở đây mình chọn 3 section đầu (trừ section PE Header), section thứ nhất bắt đầu từ 00401000 đến 004A9000, section thứ hai bắt đầu từ 004A9000 đến 004AA000 và section thứ ba bắt đầu từ 004AA000 đến 004AD000. Đặt break point đã xong bây giờ run (F9) để chạy nào, chúng ta đến đây:
Đây chính là đoạn giải mã mã thực thi của ASProtect. Các bạn để ý 2 lệnh ở cuối hình CMP DWORD PTR SS:[ESP] và JA. Thanh ghi ESP chứa địa chỉ của mã đoạn mã code bị mã hóa, đoạn mã trên sẽ lấy các giá trị này để giải mã ra mã thực thi gốc của chương trình. Để đỡ mất thời gian chúng ta nên thoát nhanh khỏi vòng lặp giải mã này để đến ngay OEP, chúng ta đặt một Toggle break point (F2) ở ngay dưới lệnh nhảy JA kia và nhấn F9.
Khi chúng ta thoát khỏi vòng lặp giải mã code thực thi này thì code thực thi gốc của chương trình đã được giải mã hoàn toàn ra bộ nhớ rồi. Sau khi giải mã code thực thi rồi thì việc cuối cùng ASProtect sẽ làm là nhảy đến OEP. Vì trước đó chúng ta đã đặt break point on access lên các section mà một trong số 3 section đó sẽ chứa code thực thi và OEP vậy nên bây giờ chúng ta nhấn F9 một lần nữa sẽ thấy:
Đây chính là OEP mà chúng ta cần tìm. Nếu bạn nào dịch ngược nhiều thì nhìn đoạn code này sẽ cảm thấy rất quen. Các bạn có nhận ra đây là OEP của trình biên dịch ngôn ngữ nào ko? Đây chính là code tạo ra bởi trình biên dịch của ngôn ngữ Borland Delphi. Chúng ta nên đặt một HBP On Execution tại OEP để sau có re-load lại thì ko cần phải tìm lại OEP, đỡ mất thời gian.
Vậy là OEP đã tìm ra, bạn còn nhớ file text chứa IAT đầy đủ mà mình đã nói lưu lại bằng notepad trc đó rồi chứ, đây chính là lúc chúng ta dùng đến nó. Bây giờ các bạn qua cửa sổ Dump và sử dụng tổ hợp phím Ctrl + G (Go to expression) để đi đến điểm đầu của bảng IAT tại địa chỉ 004B38C8.
Bây giờ các bạn mở file text chứa IAT đầy đủ ra và copy tất cả các byte hexan đó vào clipboard. Sau đó qua cửa sổ Dump của OllyDbg và kéo chọn từ đầu bảng IAT (tại địa chỉ 004B38C8) đến cuối bảng IAT (tại địa chỉ 004B4024), nhấn chuột phải vào đó và chọn thực đơn Binary | Binary paste. Chúng ta đc bảng IAT đầy đủ như sau:
Những dòng màu đỏ kia chính là những hàm mà IAT cũ bị sai, mình vừa dán bảng IAT đầy đủ vào nên nhưng thay đổi giữa bảng IAT cũ và bảng IAT mới này sẽ có màu đỏ.
Vậy là chúng ta làm từ đầu đến giờ chúng ta đã tìm ra đc bảng IAT đầy đủ và mã thực thi gốc của chương trình. Đến đây các bạn nghĩ đã xong chưa? Giờ dump ra đã chạy đc chưa? Chưa đâu, còn một chút này nữa. Các bạn nhấn chuột phải vào cửa sổ CPU và chọn thực đơn Search for | All intermodular calls để xem tất cả những nơi mà unpackme gọi các hàm đã API của unpackme đã import vào lên sử dụng.
Đây là danh sách tất cả những lời gọi đến Jump Thunk Table. Có ai hỏi Jump Thunk Table là gì ko? Jump Thunk Table là một bảng chứa những lệnh nhảy trực tiếp và vô điều kiện (JMP) đến bảng IAT. Để sử dụng những hàm API đã import thì một số ngôn ngữ xây dựng mã, liên kết theo kiểu gọi gián tiếp những hàm API thôg qua bảng Jump Thunk Table đến IAT chứ ko gọi trực tiếp đến bảng IAT. Một số trình biên dịch khác ko sử dụng bằng lệnh JMP để làm việc này thay vào đó thì chúnh lại sử dụng lệnh CALL. Nhưng trình biên dịch của unpackme này đã sử dụng lệnh JMP. Đây là một bảng Jump Thunk trong unpackme của chúng ta:
Mình ví dụ: Để sử dụng hàm SysAllocStringLen thì khi sử dụng chúng ta gọi như sau: CALL 00402550. Sau khi gọi hàm này thì lệnh JMP tại địa chỉ 00402550 kia sẽ nhảy đến địa chỉ 004B38D0 trong bảng IAT. Các bạn dòm qua bảng IAT coi, địa chỉ 004B38D0 chính là hàm SysAllocStringLen:
Các bạn hiểu rõ rồi chứ? Ok, bây giờ quay lại vấn đề chính. Các bạn nhìn hình cửa sổ Found intermodular calls trên, những hàm CALL nào gọi những hàm có địa chỉ nằm trong section đc ánh xạ của file là những hàm CALL (gọi hàm API sử dụng thông qua Jump Thunk Table) đã được giải mã trong quá trình giải mã code thực thi. Nhưng nó đã ko giải mã tất cả những hàm CALL gọi hàm API mà nó vẫn còn mã hóa một số trên bộ nhớ ảo (có địa chỉ khá lớn 028B0000) và gọi lên trên đó. Công việc của chúng ta bây giờ là làm cách nào để tìm đc xem những hàm CALL gọi hàm API trên bộ nhớ ảo kia, từng hàm nó gọi đến hàm nào và mình thay thế lại hàm đó cho gọi đến hàm API tương ứng trong bảng Jump Thunk. Bây giờ chúng ta bắt đầu đi tìm nào. Các bạn chọn hàm CALL đầu tiên đi. Các bạn click vào đó và chúng ta đến đây:
Dừng lại suy nghĩ một chút nào. Mình chưa biết nó mã hóa hay giải mã như thế nào nhưng dù như thế nào đi nữa thì để sử dụng hàm cũng cần phải có địa chỉ của hàm đúng ko? Mà để có đc địa chỉ của hàm thì làm sao? Đương nhiên là sử dụng hai hàm LoadLibraryA/W và GetProcAddress rồi. Đây chỉ là suy nghĩ tư duy của mình thôi, ASProtect nó cũg thể có nhiều cách để lấy đc địa chỉ của hàm để sử dụng mà. Nhưng ko sao, phải thử lần lượt mới biết đc chứ. Nếu đúng thì tốt còn ko thì chúng ta đi vào phân tích code của nó, ko sao cả. Trước hết chúng ta trỏ EIP tới địa chỉ hàm CALL 028B0000 này đã. Bạn làm như sau:
Bạn làm như trên có nghĩa là bạn thực hiện từ địa chỉ 00402438. Bây giờ bạn đặt Toggle break point ở 3 hàm LoadLibraryA, LoadLibraryW, GetProcAddress. Các bạn dùng cách nào để đặt Toggle break point cũng đc, mình sử dụng CommandBar để đặt:
Đặt Toggle break point lên 3 hàm xong bây giờ các bạn run (F9) thử một phát coi có break ko nào. Nếu OllyDbg có break thì có nghĩa là ASProtect đã sử dụng cách mình suy đoán trên để lấy địa chỉ hàm của hàm đã giấu, còn nếu ko thì mình suy luận sai. Nào thử nào:
Và nó đã break. Tại đia chỉ đó nó đã gọi đến hàm GetStdHandle trong module kernel32.dll. Chúng ta đã biết tên hàm rồi bây giờ chúng ta tìm trong bảng IAT coi hàm này trong bảng đó ở địa chỉ bao nhiêu:
Và đây, hàm GetStdHandle nằm tại địa chỉ 004B3998. Bây giờ chúng ta quay lại hàm CALL 028B0000 tại địa chỉ 00402438:
Bây giờ chúng ta sửa lại lệnh CALL 028B0000 thành JMP DWORD PTR DS:[004B3998]
OK, vậy là chúng ta đã sửa xong một hàm. Các bạn search lại danh sách các nơi sử dụng những hàm import từ dll coi sao, Search for | All intermodular calls.
OK rồi, đúng ko. (Nghĩ chút coi, các bạn thấy ở trên nó đã ko sử dụng hàm GetProcAddress để lấy địa chỉ hàm đúng ko, các bạn còn nhớ ở đoạn giải mã IAT chứ, ASProtect cũng có một hàm riêng của nó để lấy địa chỉ của một hàm API, vậy là nó sử dụng luôn hàm đó của nó hay vì sử dụng hàm GetProcAddress của Windows).
Đây mới chỉ là tìm lại một hàm thôi, nó có rất nhiều hàm như vậy mà, để giúp cho việc này đỡ mất thời gian chúng ta hãy sử dụng OllyScript để tự động làm việc này, chúng ta sẽ sử dụng script ASProtect 2.xx IAT Recovery, mình nói qua về cách thức làm việc của script này. Script này sẽ quét từ đầu section chứa code thực thi, cụ thể là từ địa chỉ 00401000 đến hết section này và dựa vào opcode của lệnh CALL để tìm các lời gọi như ở hình trên, nếu hàm CALL nào gọi tới địa chỉ nào mà có địa chỉ nằm ở trên bộ nhớ thì nó sẽ làm công việc như mình đã trình bày qua ví dụ trên tìm địa chỉ thực sự của nó và thay thế lại.
Bây giờ chúng ta re-load lại OllyDbg. Vì chúng ta đã đặt HBP On Execution lên OEP nên bây giờ chúng ta chỉ cần nhấn F9 để đến luôn OEP. Sau khi đến đc OEP chúng ta làm lại bước paste IAT đầy đủ đã lưu ở text file vào bảng IAT. Sau đó mở script ra để chạy:
Sau khi run script, script yêu cần nhập địa chỉ bắt đầu của IAT và kết thúc của IAT các bạn nhìn xuống cửa sổ Dump coi địa chỉ của nó là bao nhiêu thì nhập vô. Mình thấy IAT Start = 004B38C8 còn IAT End = 004B4024. Sau khi nhập xong IAT Start, End có một bảng thông báo hỏi bạn muốn phục hồi theo cách nào? Nếu theo lệnh CALL (mã là FF 15) thì chọn Yes còn nếu là lệnh JMP (mã là FF 25) thì chọn No.
Như các bạn thấy ở unpackme của chúng ta đã sử dụng bảng Jump Thunk (JMP op code là FF 25) nên chúng ta chọn là No. Chờ một chút đến khi script chạy xong sẽ hiện lên thông báo sau:
Script thông báo với chúng ta Script chạy xong và có 27h (39 Decan) hàm đã đc phục hồi. Bây giờ các bạn mở lại All intermodular calls coi nào:
Các bạn nhìn coi, tất cả những hàm gọi API lên sử dụng gián tiếp qua Jump Thunk Table đã đc chỉnh sửa xong hết rồi. Bây giờ công việc còn lại của chúng ta chỉ là dump & fix kết quả làm từ đầu đến giờ ra thôi.
Mình xài ChimpREC để dump tiến trình và chỉnh sửa lại file dump. Mình khuyên các bạn xài ChimpREC để fix dump. Vì theo kinh nghiệm mình thấy ChimpREC tìm, fix IAT tốt hơn là ImportREC rất nhiều, ImportREC đôi lúc tìm bảng IAT và fix hàm sai. Tuy nhiên ChimpREC ko hỗ trợ plug-in như ImportREC.
Sau khi chọn tiến trình của unpackme các bạn nhập RVA của OEP, RVA của Start IAT và Size của IAT. Để tính RVA bạn lấy VA – ImageBase. Vậy RVA của OEP = 004A9C48 – 00400000 = 000A9C48, RVA của IAT Start = 004B38C8 – 00400000 = 000B38C8. Size của IAT = 004B4024 – 004B38C8 = 75C. Làm theo các bước như trong hình. Sau khi nhấn Fix Dump bạn chọn file dump và nhấn OK
Như vậy là coi như công việc unpacking/unprotecting của chúng ta đã xong. Bây giờ chúng ta chạy thử coi run ok ko nào:
Good! Chạy rất tốt rồi nha. Bây giờ scan file vừa unpack bằng Exe Info PE coi sao:
Exe Info PE đã phát hiện ra unpackme này đã được code trong Delphi hoặc C++. Scan lại thử bằng PeiD cho rõ ràng coi:
Vậy là quá rõ rồi. Unpackme đc viết trong Delphi. Bây giờ các bạn coi lại kích thước của file đã unpack coi. Có vẻ khá nặng 1.409.024 byte. Bây giờ chúng ta re-built lại và loại bỏ mấy section thừa đi coi dung lượng giảm đi đc chút nào ko. Mình xài CFF Explorer. Qua tab Section Header [x] coi thi file có tới 13 sections lận. Bây giờ chúng ta tìm những section nào toàn byte 00 thì chúng ta xóa nó đi. Ở đây mình thấy có section thứ 4, 7, 9, 12 là toàn byte 00 và ko xài đến vậy nên xóa đi cho nhẹ.
Tiếp đó bạn qua tab Rebuilder. Tích chọn Rebuild PE Header, Update Checksum, Bind Import Table và nhấn Rebuilt và save lại.
Bây giờ chúng ta coi lại dung lượng của file coi giảm đc nhiêu. Coi lại dung lượng còn 1.310.720 byte. Giảm đc gần 100 Kbyte. Ít quá.
Đến đây là đã hoàn thành hết quá trình “MUP” ASProtect phiên bản 2.xx. Tut đến đây là hết. Mong sau khi đọc xong tut này mọi ng có thể hiểu hết tất cả những gì mình đã viết. Nếu có chỗ nào ko hiểu hay ko rõ ràng mọi ng có thể post câu hỏi của mình trên diễn đàn CiN1 hoặc có thể gửi câu hỏi về mail của mình!! Hẹn gặp lại trong tut lần sau. Bye!!!
Hà Nội, 11/2012