OllyDbg_tut32

m4n0w4r
m4n0w4r
Published in
13 min readJan 21, 2018

--

I. Giới thiệu chung

Ở các phần trước, tôi đã hướng dẫn chi tiết quá trình unpack một unpackme được packed bởi tElock có áp dụng kĩ thuật chuyển hướng IAT (IAT redirected). Trong các phần tới đây, nếu có thời gian tôi sẽ tiếp tục nâng dần độ khó của packer lên. Với phần này, tôi sẽ dành thời gian để thực hành với một unpackme khác là UnPackMe_YodasCrypter1.3.e.exe.

Để đảm bảo cho quá trình làm việc với các packers, OllyDbg cần được trang bị các plugins cần thiết để tránh bị phát hiện bởi các cơ chế anti-debug. Các plugins thì các bạn có thể tìm đọc trong các phần tôi viết về Anti-Debug hoặc tìm hiểu thêm thông qua các trang khác như tuts4you.com, v.v…

II. Phân tích và xử lý target

Load unpackme vào OllyDbg, ta dừng lại tại Entry Point:

Quan sát các lệnh bên dưới, chúng ta thấy có lệnh PUSHAD. Thử áp dụng phương pháp PUSHAD xem có được không? Tiến hành trace code và trace qua lệnh PUSHAD bằng F8/F7. Sau đó chuyển qua cửa sổ Registers, chọn thanh ghi ESP, nhấn chuột phải và chọn Follow in dump. Đánh dấu 4 bytes đầu tiên và đặt một breakpoint: Hardware, on access > Dword.

Sau khi đặt xong bp, nhấn F9 để run, ta sẽ dừng tại đây:

Nhìn vào đoạn code tại đây có thể thấy nó tạo ra một xử lý ngoại lệ (exception handler) và sau đó bằng lệnh JMP để nhảy đến một vùng code chứa toàn các bytes 0 bên dưới nhằm gây ra lỗi. Do vậy, nếu chúng ta trace code và dừng tại lệnh JMP.

Tại đó, ta thấy lệnh nhảy này sẽ nhảy tới vùng bytes 0, gây ra exception và exception này sẽ được xử lý bởi exception handler đã thiết lập. Để xem danh các SEH, chọn View > SEH chain:

Mục tiêu chính của chúng ta là tới OEP, do đó để không làm phức tạp thêm, tôi chuyển qua cửa sổ Memory và đặt một Memory breakpoint on access tại section đầu tiên:

Sau đó, nhấn F9 để vượt qua exception, ta sẽ đến OEP:

Vẫn là địa chỉ OEP 4271B0 quen thuộc như các phần trước, đơn giản là vì được packed cho cùng một crackme nhằm tiện cho việc thực hành.

Quan sát code bên dưới ta thấy xuất hiện lời gọi tới hàm API GetVersion() (hàm này nếu ở tElock thì đã bị redirect).

Ngoài lề: Dành cho những bạn muốn đào sâu hơn một chút thì có thể thấy rằng trong exception handler tại 0046590B, code xử lý ở đó thực hiện thao tác để thay đổi EIP và tại đó địa chỉ của exception là 465982 được ghi đè bằng giá trị OEP hay 4271B0, để trở về trực tiếp từ exception tới OEP.

Sau khi tới được OEP, tiến hành dump thử bằng OllyDump:

Nhớ bỏ tùy trọn Rebuild Import để tránh việc OllyDump tự sửa IAT. Nhấn nút Dump và lưu lại với tên file tùy ý:

Tất nhiên, khi chúng ta chạy file yoda_dump.exe thì sẽ nhận được thông báo lỗi (còn nếu chạy được thì có thể bạn là người may mắn hơn tôi).

Nếu quan sát kĩ ta sẽ thấy rằng IAT cũng đã bị chuyển hướng, có nghĩa là nó không thể thực thi được bình thường khi cố gắng truy cập những địa chỉ không tồn tại trong file dump.

Tiến hành phân tích IAT bằng cách tìm một lệnh call đến một hàm API bất kỳ. Ở đây, tôi chọn luôn lệnh call tới API GetVersion() vốn đã quen thuộc ở các phần trước:

Follow in Dump tại địa chỉ bộ nhớ 0x460ADC:

OK, các giá trị này của IAT là địa chỉ chính xác của các hàm API bởi vì tất cả đều có định dạng chung là 7C8XXXXX. Nếu ta kiểm tra tại cửa sổ Memory thì những địa chỉ này nằm trong phần section code của thư viện kernel32.dll. Do vậy, ta khẳng định các IAT entry này là chuẩn.

Một cách khác để kiểm chứng, tìm kiếm lệnh tham chiếu tới bất kỳ một giá trị ngẫu nhiên bằng cách nhấn chuột phải và chọn Find references (Ctrl+R).

Tiếp tục cuộn chuột xuống dưới để tìm dấu hiệu phân tách giữa các import:

Tôi thấy rằng nhóm IAT tiếp theo được chuyển tới một vùng nhớ có định dạng 15XXXX. Ta xem thử ở đó có thông tin gì:

Theo như trên hình, ta thấy đây là một section không thuộc dll nào, do vậy chắc chắn section này đã được tạo ra bởi packer. Khởi động lại OllyDbg để kiểm tra section này:

Chúng ta thấy rằng ban đầu section này được tạo ra bởi hệ thống và nó có kích thước là 4000 bytes (trên máy tôi), nhưng sau đó nó được sử dụng bởi packer và packer đã mở rộng section này lên 28000 bytes (trên máy tôi) để sử dụng cho mục đích riêng của mình. Vì vậy, có thể đây là những IAT entry đã bị chuyển hướng bởi packer tới section đó. Ta kiểm tra thử một entry để xem nó hoạt động như thế nào. Chọn 4 bytes tại địa chỉ 0x460BAC:

Nhấn chuột phải tại 4 bytes đã chọn và chọn Find references. Kết quả như sau:

Ta thấy trên hình xuất hiện hai lệnh Call nhận giá trị tại địa chỉ ta đã chọn. Tới lệnh call đầu tiên bằng cách nhấp đúp vào nó, ta sẽ tới vùng code sau:

Ta đang dừng lại tại lệnh CALL, nhấn chuột phải và chọn Follow (Enter) để xem code của lệnh Call này sẽ làm những gì trong section được chuyển hướng.

Ta thấy rằng, tại đây là một lệnh nhảy trực tiếp tới hàm API SysStringLen() thuộc oelaut32.dll. Dưới đó cũng là các lệnh nhảy trực tiếp tới các hàm API tương ứng như trên hình. Nếu ta follow tại từng lệnh này ta sẽ tới được các hàm API mà unpackme sử dụng.

Như vậy, quá trình thực hiện chuyển hướng dường như là rất đơn giản. Liệu rằng nếu dùng ImpREC với các tính năng trace code thì nó có fix file dump mà không cần phải tìm ra magic jump hay không? Mở ImpREC và attach process của unpackme:

Đừng quên để ImpREC có thể fix được file ta phải cung cấp cho nó 3 thông tin là OEP, IAT START và IAT Size. Ta đã có được OEP là 4271B0, chuyển đổi sang RVA là 271B0. Giờ tìm IAT Start, cách tìm tôi sẽ không nhắc lại nữa.

Khi quan sát tại cửa sổ Dump, ta nhận thấy các IAT entry tại đây: một là địa chỉ của hàm API cụ thể, hai là được chuyển hướng tới section 15xxxxx. Do đó, tìm ngược lên trên theo dấu hiệu này cho tới khi thấy có các giá trị khác:

Chọn một địa chỉ bất kỳ và thực hiện tìm kiếm, kết quả sẽ không thấy có lệnh Call nào:

Do đó, IAT Start của chúng ta sẽ là 460818, sau khi trừ đi giá trị ImageBase, ta có giá trị RVA = 60818.

Thực hiện tương tự để tìm IAT End:

Thông tin về IAT Start và IAT End đã có, giờ tính IAT Size theo công thức: Size = IAT End — IAT Start = 460f28–460818 = 710

Tổng hợp lại ta có:

· OEP= 271B0

· RVA = 60818

· SIZE = 710

Điền các giá trị trên vào phần IAT Infos Needed:

Sau đó nhấn Get Imports:

Ta thấy rằng có 296 entry chưa xử lý được. Thử dùng mấy tính năng Trace của ImpREC xem có làm được gì không? Đầu tiên, thử với Auto Trace thì ImpREC treo cứng luôn:

Khởi động lại ImpREC, nhấn Show Invalids, chuột phải và chọn Trace Level 1:

ImpREC thông báo đã lấy được toàn bộ bảng IAT và không còn bất kỳ một invalid entry nào nữa. Tin tưởng được không, lolz? Nhấn Show Invalid một lần nữa (hơi thừa, nhưng để chắc chắn), ta thấy toàn bộ đều là YES.

Quá đẹp! Giờ nhấn Fix Dump để ImpREC tiến hành fix file yoda_dump.exe:

ImpREC thực hiện quá trình fix file và tạo một file mới là yoda_dump_.exe. Chạy thử file đã fix:

Hehe với unpackme này tính năng Trace của IMP REC đã làm việc quá tốt, giúp ta tiết kiệm rất nhiều thời gian. Ngoài việc áp dụng cách như trên, tôi vẫn đặt câu hỏi liệu packer này có sử dụng magic jump hay không?

Tiến hành thực hiện từ đầu và tới OEP, tìm kiếm một bad entry trong IAT:

Đặt một Breakpoint: Hardware, on write > Dword tại 4 bytes đã được đánh dấu ở trên trước khi khởi động lại OllyDbg:

Sau đó khởi động lại OllyDbg, tại cửa số Dump, nhấn Ctrl+G tới địa chỉ 460ECC để kiểm tra xem giá trị ban đầu của nó là gì:

Nhấn F9 để RUN, ta dừng lại tại đây:

Như trên hình, ta thấy có lệnh tại 00465730 8902 mov dword ptr [edx], eax. Quan sát cửa sổ Registers, thanh ghi EDX đang lưu địa chỉ 00460ECC, thanh ghi EAX đang lưu địa chỉ của một hàm API. Đó là lý do vì sao OllyDbg dừng lại tại vùng code này:

Ta thấy địa chỉ của hàm API được lưu vào vùng nhớ trỏ bởi EDX, điều đó có nghĩa là trong trường hợp này, lần đầu tiên sẽ lưu địa chỉ chính xác của hàm API và sau đó sửa đổi nó bằng giá trị khác (tôi hay gọi là bad entry). Tiếp tục theo dõi để xem khi nào điều này xảy ra.

Tại vùng code như trên hình, quan sát cửa số Register ta thấy ESI đang lưu giá trị xấu và sẽ ghi đè lên địa chỉ của hàm API đã được lưu trước đó:

OK nếu tiếp tục thì unpackme sẽ thực thi một cách bình thường. Giờ ta tiến hành thực hiện lại công việc trên nhưng với một hàm API chuẩn. Tôi vẫn lựa chọn hàm API là GetVersion(). Follow in Dump tại 0x460ADC và đặt một HW BP on write tại đó. Sau đó, khởi động lại OllyDbg, nhấn F9 để run ta sẽ dừng tại đây:

Tương tự, quan sát tại cửa sổ Registers ta thấy gì nào:

Thanh ghi EAX đang có địa chỉ của hàm API GetVersion(). Tiếp tục trace xuống một chút ta tới đây:

Ta dừng ở lệnh JMP tại địa chỉ 0046577F. Lệnh JMP này sẽ nhảy tránh qua vùng code ở bên dưới (đánh dấu bằng mũi tên đỏ), mà vùng code bên dưới lại chính là nơi thực hiện thay đổi địa chỉ API chuẩn bằng giá trị xấu (bad entry). Vì vậy, mục tiêu là tìm cách sao cho đối với bad entry thì ta cũng cho nhảy để tránh vùng code bên dưới luôn. Trước lệnh JMP ta thấy có rất nhiều nhảy có điều kiện, lựa chọn một lệnh nhảy phù hợp, đó chính là magic jump cần tìm.

Sau khi quan sát, tôi lựa chọn lệnh nhảy tại địa chỉ 00465760 /74 4F je short 004657B1. Đặt một BP HW on execution tại địa chỉ này. Sau đó, khởi động lại OllyDbg, nhấn F9 ta sẽ dừng lại tại lệnh trên. Patch lệnh này thành lệnh JMP luôn để cho dù có thế nào thì ta cũng bypass luôn chỗ ghi đè bad entry lên địa chỉ API chuẩn:

Sau đó bỏ HW bp tại đây đi và nhấn F9 để run. Tuy nhiên, unpackme sau khi ta patch như trên sẽ bị crash khi run trong OllyDbg:

Kệ nó thôi, giữ nguyên như thế. Mở ImpREC, điền các thông tin cần thiết và nhấn Get Imports, ta sẽ có được bảng IAT hoàn chỉnh dùng để fix dump:

Với IAT như trên thì ta hoàn toàn có thể fix dump được rồi.

Ngoài cách fix bằng magic jump ở trên, ta có thể thực hiện một cách khác nữa như sau. Tôi tìm tới lệnh trước lệnh thực hiện việc lưu bad entry và đặt một HBP on execution tại đây:

Sau đó, khởi động lại OllyDbg và thực thi bằng F9 cho tới khi dừng sự thực thi tại câu lệnh này:

Như đã biết, câu lệnh bên dưới 0046579D 8932 mov dword ptr [edx], esi chính là lệnh thực hiện lưu giá trị bad entry. Giờ tôi không muốn cho lưu nữa bằng cách là nop luôn lênh này:

Sau khi patch như trên, bỏ HW bp đi và đến OEP xem thế nào. Cũng bị crash như phương pháp magic jump ở trên:

Oh! Damn it…

Vậy là trình packer này có áp dụng phương pháp nào đó để phát hiện việc ta can thiệp patch chương trình. Nhưng mà crash thì cũng kệ thôi vì mục tiêu cuối cùng là đã có được bảng IAT full rồi:

Với bảng IAT hoàn chỉnh không lỗi như trên, tôi có thể mở một Ollydbg khác, load unpackme vào và tới OEP mà không thực hiện việc chỉnh sửa gì, sau đó tôi quay trở lại OllyDbg đã có đầy đủ bảng IAT, thực hiện sao chép và dán đúng bảng IAT này sang OllyDbg mới bằng tính năng Bynary Copy and Binary Paste, rất đơn giản .. bạn có thể tự thực hiện. Sau đó, từ OllyDbg mới tiến hành Dump file và dùng ImpREC để Get Imports và fix dump một cách bình thường.

III. Kết luận

Toàn bộ phần 32 đến đây là kết thúc! Tôi có đính kèm thêm một unpackme unpackme- FSG 1.31 — dulek.exe như là một bài tập để các bạn thực hành. Nó rất đơn giản .

Tôi hy vọng các bạn có thể unpack được nó mà không gặp trở ngại nào, và OEP các bạn tìm được sẽ là pass để giải nén cho phần tiếp theo.

Cảm ơn các bạn đã dành thời gian để theo dõi. Hẹn gặp lại các bạn ở phần tiếp theo!

PS: Tài liệu này chỉ mang tính tham khảo, tác giả không chịu trách nhiệm nếu người đọc sử dụng nó vào bất kì mục đích nào.

End!

m4n0w4r

--

--