Packer là một kiểu chương trình nén hoặc che dấu file thực thi (executable file). Các chương trình này ra đời bắt nguồn từ mục đích giảm kích thước của file, làm cho việc tải file nhanh hơn.
Rất nhiều các coder hay các hãng phần mềm sử dụng Packer nhằm mục đích khiến cracker/reverser khó khăn và tốn thời gian hơn trong việc bẻ khỏa hoặc đảo ngược phần mềm của họ. Đối với những kẻ chuyên phát tán các phần mềm độc hại (malicious software) thì Packer còn có tác dụng giúp kéo dài thời gian tồn tại của phần mềm càng lâu bị phát hiện càng tốt.
Cụ thể khi sử dụng, Packer nén, mã hóa, lưu hoặc dấu code gốc của chương trình, tự động bổ sung một hoặc nhiều section, sau đó sẽ thêm đoạn code Unpacking Stub và chuyển hướng Entry Point (EP) tới vùng code này. Bình thường một file không đóng gói (Nonpacked) sẽ được tải bởi OS. Với file đã đóng gói thì Unpacking Stub sẽ được tải bởi OS, sau đó chương trình gốc sẽ tải Unpacking Stub. Lúc này code EP của file thực thi sẽ trỏ tới Unpacking Stub thay vì trỏ vào mã gốc. Để hiểu được các phần trên thì các bạn cần hiểu cấu trúc PE file và cách thực thi của nó. Bạn có thể xem bài viết Tìm hiểu cấu trúc PE file.
Cấu trúc đầy đủ của một PE file như sau:
Trong môi trường DOS, chương trình sẽ kiểm tra DOS header và nếu hợp lệ sẽ thực thi DOS Stub. Còn bình thường file của chúng ta hay chạy trên Windows thì 2 trường này có thể bỏ qua. Trường này là một cấu trúc có 19 thành phần.
Cuối trường DOS header sẽ có một thành phần là: e_lfanew sẽ chứa offset của PE header so với vị trí đầu file. Chương trình sẽ thực thi đến PE header. Tiếp đến là đọc Section Table để xem trong chương trình có những section nào. Sau đó chương trình sẽ nhảy đến từng section và thực thi. Entry Point thường nằm ở các section đầu nếu file chưa bị đóng gói.
2. Nguyên lí hoạt động của Packer
Cấu trúc của file PE ban đầu sẽ có dạng như sau:
File này sẽ bị nén và mã hóa rồi được thêm vào một hay nhiều section do packer tự tạo ra.
Khi chạy file thì chương trình sẽ được thực thi như sau:
Tiếp theo chương trình sẽ thực thi PE header:
Sau khi nhảy đến Entry Point của section UMPACKER thì toàn bộ giá trị của các thanh ghi sẽ được lưu lại nhờ lệnh PUSHAD. Sau đó Import Table sẽ được tính toán lại. Trong một file bị pack, Import Table sẽ bị thay đổi và các dữ liệu sẽ bị mã hóa.
Tiếp theo chương trình sẽ khôi phục lại giá trị các thanh ghi đã được lưu trong Stack bằng lệnh POPAD. Cuối cùng chương trình sẽ nhảy đến Origin EntryPoint và thực thi như file lúc chưa bị đóng gói.
3.Nguyên lí Unpack
Đầu tiên ta phải nhận biết được xem một file có bị đóng gói hay không bằng cách dùng các công cụ như PEiD :
Ở đây mình dùng file UnPackMe_ASPack2.12 làm đối tượng để test thử. File trên đã bị đóng gói bằng ASPack. Thông thường, các bước cơ bản để thực hiện unpack như sau:
- Tìm OEP (Origin Entry Point): Orginal entry point là nơi mà chương trình gốc thực sự bắt đầu thực thi. Ta sẽ dùng một số công cụ debug file như OllyDbg và IDA để tìm lại OEP.
- Kết xuất file (Dump): Sau khi nhảy đến OEP ta sẽ tiến hành dump file. Mục đích của việc này là fix lại các section và import table như file ban đầu trước khi bị đóng gói. Sử dụng Plugin OllyDumpEx để dump file.
- Sửa lại IAT (Repair Import Address Table): Sử dụng công cụ ImportREC.
- Kiểm tra file xem còn các cơ chế Anti hoặc ngăn chặn việc thực thi hay không rồi tiến hành chỉnh sửa. Đảm bảo file sau unpack thực thi bình thường (Phần này sẽ được giới thiệu trong các phần sau).
một số kĩ thuật để tìm OEP(Origin Entry Point) cơ bản. Đây là quá trình khó nhất trong khi unpack.
Như đã giới thiệu trong phần trước thì các bước để unpack một file như sau:
- Tìm OEP
- Kết xuất file (Dump)
- Sửa lại IAT (Repair IAT)
- Kiểm tra file xem còn các cơ chế Anti hoặc ngăn chặn không cho thực thi không và tiến hành chỉnh sửa. Đảm bảo file sau unpack thực thi bình thường .
Bài viết sử dụng UnPackMe_ASPack2.12.exe và OllyDbg để minh họa.
1.1 Tìm opcode
Cách này đơn giản và hơi thô, chỉ có thể áp dụng với các trình packer đơn giản, không thể áp dụng nó cho các trình packer khó nhằn như Asprotect.
Ý tưởng cơ bản là tìm opcode của lệnh LONG JMP (0xE9) hoặc LONG CALL (0xE8). Các trình packer sau khi thực hiện đóng gói file thì có khả năng có một lệnh nhảy hoặc một lời gọi hàm (call) tới section đầu tiên, mục đích là để tới được OEP. Với phương pháp này, ta hi vọng các lệnh JUMP hoặc CALL sẽ xuất hiện ngay đầu tiên trong quá trình tìm kiếm hoặc trình packer không có cơ chế tự động chỉnh sửa.
Ta chỉ cần tìm kiếm “Binary String” các chuỗi E9,E8. Sau đó xem câu lệnh nào có opcode. Ngoài ra câu lệnh đó phải nằm trong section đầu tiên của chương trình.
Chú ý , đôi khi ta xem code của các trình packer sẽ thấy có các lệnh kiểu như CALL EAX, CALL EBX, JMP EAX. Rất nhiều trình packer thường sử dụng các thanh ghi nhằm mục đích để che dấu việc nhảy tới OEP.
1.2 Sử dụng tính năng tìm kiếm OEP của OllyDBG
Mở đối tượng bị đóng gói trong OllyDBG, chọn Options > Debugging options hoặc nhấn phím tắt Alt+O, chuyển tới tab SFX:
Trong thẻ SFX có 2 tùy chọn: “blockwise” cho phép tìm kiếm nhanh nhưng độ chính xác có thể không cao; “bytewise” cho phép tìm kiếm với độ chính xác cao hơn nhưng tốc độ chậm hơn.
Chuyển qua thẻ Exceptions, lựa chọn tương tự như hình:
Sau khi cấu hình xong các bạn khởi động lại OllyDbg thì sẽ được như sau :
404000 chính là địa chỉ OEP của chương trình.Bạn có thể thử với tùy chọn khác trong SFX.
Sau khi thực hiện xong phương pháp này thì phải thiết lập tab SFX trở về tùy chọn ban đầu, nếu không thì OllyDBG sẽ không dừng lại tại EP như bình thường nữa mà luôn luôn thực hiện việc tìm kiếm OEP.
1.3. Sử dụng BPM on access với OllyDBG đã patch để tìm OEP
Load file vào OllyDBG, chuyển tới cửa sổ Memory map, lựa chọn section đầu tiên để thiết lập BPM:
Chuyển về cửa sổ code, nhấn F9 để chạy, OllyDBG sẽ break và dừng lại tại đây:
Ta thấy khi trace qua lệnh ret thì chương trình không nằm trong phân vùng đầu tiên nên ta cứ nhấn F9.Khi đó có thông báo hiện ra:
Khi đó chương trình đã kết thúc.Vậy chỗ OEP không nằm trong section đầu tiên. Ta thử với các section tiếp theo.
Với section thứ 2 tại địa chỉ 402000 cũng tương tự section thứ nhất. Ta thử tiếp với section thứ 3 tại địa chỉ 404000.Khi ta nhấn F9 thì chương trình sẽ dừng tại 404000 với câu lệnh WAIT.
Vì đây không nằm ngoài section ta đặt break point nên nó là địa chỉ câu lệnh đầu tiên của OEP.
1.4. PUSHAD Method
Nhiều trình packer sau khi được load vào OllyDBG ta thấy lệnh đầu tiên được thực thi là lệnh PUSHAD. Lệnh này nhằm mục đích bảo toàn các thanh ghi, nó sẽ thực hiện lưu toàn bộ giá trị khởi tạo của tất cả các thành ghi vào stack, sau đó thực hiện quá trình unpack, cuối cùng trước khi nhảy tới OEP, packer sẽ thực hiện việc khôi phục lại giá trị của các thanh ghi đã lưu trên stack bằng lệnh POPAD. Cặp lệnh PUSHAD và POPAD thường đi cùng với nhau. Nếu như ta thấy ở đâu đó trong mã của chương trình xuất hiện lệnh PUSHAD thì bên dưới chắc chắn sẽ có câu lệnh POPAD. Đây là phương pháp được áp dụng thành công cho khá nhiều trình packer.
Đầu tiên load UnPackMe_ASPack2.12 vào OllyDBG và nhấn F9:
Thực hiện trace qua lệnh PUSHAD và quan sát trên cửa sổ Stack:
Như các bạn thấy tại cửa sổ Stack, giá trị ban đầu của các thanh ghi đã được lưu lại.Do vậy, trước khi nhảy tới OEP thì chắc chắn packer phải thực hiện việc khôi phục lại các giá trị đã lưu này. Căn cứ vào đó, ta có thể tiến hành đặt một Hardware BPX On Access trong OllyDBG nhằm break lại khi packer thực hiện lệnh khôi phục giá trị, và khi break ta sẽ dừng lại tại lệnh nhảy tới OEP.
Lựa chọn thanh ghi ESP, nhấn chuột phải và chọn Follow in Dump:
Sau đó ta đặt breakpoint hardware vào 0018FF6C.
Sau khi đặt BP xong, nhấn F9 để chạy, ta sẽ dừng lại tại đây:
Dưới lệnh POPAD sẽ là câu lệnh nhảy đến 004113BA. Nhưng nếu nhìn kĩ hơn một chút ta sẽ thấy có lệnh push 00404000 và ret. Như vậy đó chính là địa chỉ của OEP.
Trên đây mình đã trình bày 4 cách để unpack một file .exe cơ bản. Các bạn có thể tự tìm thêm các ví dụ để thực hành thêm và tham khảo tài liệu có sẵn trên mạng.
theo SD.net
Không có nhận xét nào:
Đăng nhận xét