REVERSING WITH IDA FROM SCRATCH (P3)

Posted: February 28, 2019 in IDA Tutorials, REVERSING WITH IDA FROM SCRATCH (P3)
Tags: ,

IDA Loader

Các bạn đã thấy rằng khi mở một file thực thi trong IDA, nó sẽ sử dụng bộ phân tích tĩnh để phân tích file hay còn được gọi là Loader. Ở chế độ Loader này, chương trình sẽ không được thực thi, nhưng nó được IDA phân tích và sau cùng sẽ tạo ra một file .idblà cơ sở dữ liệu lưu các thông tin trong quá trình phân tích, bao gồm đổi tên biến, tên hàm, các chú thích…. Trên thực tế, file .idb sẽ là tổng hợp của 5 files(.id0, .id1, .nam, .id2, and .til) được sinh ra trong quá trình phân tích:

Bên lề: IDA không có tính năng Undo như các bạn hay làm việc với các trình soạn thảo văn bản, cho nên bất kì những thay đổi nào mà bạn thực hiện trong quá trình phân tích sẽ không quay lại được và sẽ lưu thẳng vào database. Tuy nhiên, những thay đổi này chỉ là ở phía database mà thôi, nó sẽ không tác động trực tiếp lên binary gốc mà bạn đang phân tích.

Ở chế độ Loader đương nhiên sẽ không xuất hiện các cửa sổ Registers, cửa sổ Stack và danh sách các Module được nạp vào bộ nhớ mà chương trình sử dụng. Các thông tin này chỉ xuất hiện khi ta cho thực thi và debug chương trình ở chế độ Debugger. Chúng ta sẽ làm quen với tính năng debug của IDA ở các phần sau.

Sau khi nạp crackme của Cruehead vào IDA, quan sát trong danh sách các tiến trình (process) thông qua trình Task Manager, ta thấy không xuất hiện process của crackme này. Như vậy, có thể hiểu crackme không được thực thi trừ khi chúng ta sử dụng Debugger của IDA. Việc này cực kì hữu ích cho một số công việc nhất định như phân tích mã độc… Ở chế độ Loader, chúng ta có thể phân tích bất kỳ hàm nào của chương trình, tuy nhiên không phải lúc nào chúng ta cũng có thể truy xuất vào hàm mà ta cần tìm hiểu, lúc đó ta phải học cách để debug. Tất nhiên, để tìm hiểu cách phân tích các hàm, chúng ta cần phải trang bị kiến thức cơ bản về các thanh ghi và các câu lệnh asm cơ bản. Bởi vì mặc dù không debug, không có cửa sổ thanh ghi với các giá trị tại từng thời điểm, các câu lệnh sử dụng chúng thì dựa vào các kiến thức cơ bản này, ta có thể hiểu mục đích của hàm hoặc chương trình làm gì.

Các thanh ghi là gì và chúng được sử dụng cho những mục đích nào?

Nôm na các bạn có thể hiểu rằng, bộ vi xử lý khi thực thi các chương trình cần có “trợ lý” phục vụ cho nó. Các thanh ghi lúc này sẽ hỗ trợ bộ vi xử lý trong quá trình thực thi chương trình. Chúng được xem như các vùng lưu trữ nhỏ được tích hợp sẵn trong bộ xử lý (volatile memory – chỉ giữ được dữ liệu khi máy tính còn hoạt động). Khi CPU thực thi một lệnh, nó phải lấy lệnh từ bộ nhớ, giải mã lệnh, và sau đó thực hiện hành động tương ứng với mục đích của lệnh. Các hành động mà CPU thực hiện có thể thao tác thông tin trong các thanh ghi hoặc trong bộ nhớ.

Khi tìm hiểu về các lệnh ASM, các bạn sẽ biết được nội dung của hai vị trí bộ nhớ không thể cộng trực tiếp với nhau. Bộ vi xử lý sẽ phải chuyển một trong số chúng vào thanh ghi và sau đó cộng nó với vị trí bộ nhớ còn lại. Đây chỉ là một ví dụ, dĩ nhiên các thanh ghi có thể sử dụng cho những mục đích nhất định, cụ thể hơn chúng ta sẽ đi chi tiết bên dưới. Trong kiến trúc 32 bit các thanh ghi được sử dụng là EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI và EIP. Vào cuối bài, sẽ có một phần nhỏ dành cho 64 bit.

Các thanh ghi dùng chung

Chúng ta có 8 thanh ghi dùng chung, gồm:

EAX (thanh ghi chứa – accumulator): được sử dụng nhiều nhất trong các lệnh số học, logic, và chuyển dữ liệu. Các thao tác nhân, chia sử dụng thanh ghi này. Với các hàm API của Windows, kết quả trả về của hàm thường sẽ lưu vào thanh ghi EAX.

EBX (thanh ghi cơ sở – base): thanh ghi EBX có thể truy cập trực tiếp dữ liệu bộ nhớ và nó cũng là một thanh ghi dùng chung.

ECX (thanh ghi đếm – count): ECX là một thanh ghi dùng chung có thể được sử dụng như là một bộ đếm cho các lệnh khác nhau. Nó cũng có thể chứa địa chỉ lệch của dữ liệu trong bộ nhớ. Các lệnh sử dụng bộ đếm là các lệnh liên quan lặp chuỗi, các lệnh chuyển, xoay và LOOP / LOOPD.

EDX (thanh ghi dữ liệu – data): là một thanh ghi dùng chung dùng để chứa một phần kết quả của phép nhân hoặc một phần của phép chia. Nó cũng có thể truy cập địa chỉ dữ liệu trong bộ nhớ trực tiếp.

EDI (chỉ số đích – destination): EDI thường được sử dụng trong các thao tác làm việc với chuỗi hoặc mảng. Thanh ghi này sẽ trỏ tới chuỗi đích. Bên cạnh đó nó cũng là một thanh ghi dùng chung.

ESI (chỉ số nguồn – source): Giống như EDI, ESI cũng thường được sử dụng trong các thao tác làm việc với chuỗi hoặc mảng. Thanh ghi này sẽ trỏ tới chuỗi nguồn.

EBP (con trỏ cơ sở – base): EBP trỏ tới vị trí bộ nhớ, bên cạnh mục đích dùng chung thì nó được sử dụng làm frame pointer để truy xuất các tham số và các biến cục bộ trong ngăn xếp của một hàm.

ESP (con trỏ ngăn xếp – stack): thanh ghi này luôn trỏ đến đỉnh hiện thời của Stack. Theo nguyên tắc làm việc của Stack thì thanh ghi này sẽ hướng về phía địa chỉ thấp hơn.

Như vậy, có tổng cộng 8 thanh ghi 32 bit dùng chung là EAX, EBX, ECX, EDX, ESP, EBP, ESI và EDI. Ngoài ra, các thanh ghi này còn có thể chia nhỏ thành các thanh ghi 16-bit và 8-bit như hình dưới đây:

Ví dụ, nếu thanh ghi EAX có giá trị là 0x12345678 thì AX là thanh ghi 16 bit chứa bốn chữ số cuối cùng:

Thanh ghi AX có thể được tách thành 2 thanh ghi 8 bit, đó là cặp thanh ghi: AH chứa hai số 5 và 6 và AL chứa hai số cuối cùng là 7 và 8:

Như vậy các bạn có thể hình dung, thanh ghi 32 bit EAX được tách thành một thanh ghi 16 bit gọi là AX; AX được tách thành hai thanh ghi 8 bit được gọi là AHAL. Tương tự đối với các thanh ghi EBX (BX, BH và BL), ECX (CX, CH và CL) và EDX (DX, DH và DL), riêng các thanh ghi còn lại chỉ được tách thành một thanh ghi 16 bit, không chia nhỏ thêm thành các thanh ghi 8 bit:

Các thanh ghi đặc biệt

Bên cạnh các thanh ghi dùng chung ở trên, các bạn sẽ gặp các thanh ghi đặc biệt khác nữa, bao gồm:

EIP (con trỏ lệnh – instruction): đây là một thanh ghi đặc biệt, nó luôn trỏ đến lệnh tiếp theo sẽ được thực hiện. Khác với các thanh ghi khác, EIP không thể bị tác động trực tiếp bởi các lệnh.

Một thanh ghi quan trọng khác là EFLAGS (thanh ghi cờ), mỗi bit của nó được dùng để phản ánh một trạng thái nhất định của phép toán. Dựa theo kết quả tính toán mà các cờ sẽ được bật và căn cứ trên các cờ này để thực hiện rẽ nhánh thực thi của chương trình, chúng ta sẽ tìm hiểu thêm sau.

Bên lề: Zero Flag (ZF) là cờ phổ biến nhất được sử dụng trong reversing. Chủ yếu được sử dụng trong các lệnh rẽ nhánh có điều kiện, làm thay đổi luồng thực thi dựa trên các kết quả lệnh trước đó.

Tiếp theo là các thanh ghi đoạn, các thanh ghi này trỏ tới các phần khác nhau của file thực thi như CS = CODE, DS = DATA v..v…

Trong quá trình làm việc với thanh ghi và bộ nhớ thì có một chi tiết quan trọng khác là kích thước của các kiểu dữ liệu thường được sử dụng nhiều nhất:

IDA hỗ trợ xử lý nhiều loại dữ liệu mà chúng ta sẽ thấy qua từng phần một. Điều quan trọng là phải ghi nhớ rằng BYTE là 1 byte, WORD là 2 bytes và DWORD 4 bytes trong bộ nhớ.

Các lệnh ASM cơ bản

Lý do các bạn cần biết về Assembly language đó là bởi nó nằm ở tầng thấp nhất trong Software chain. Khi một phần mềm thực hiện bất kỳ hành động nào, nó cũng sẽ được biểu diễn bằng các lệnh ASM. Người ta xem Assembly là ngôn ngữ của “Reverse Engineering”, do đó để có thể bước vào con đường của một người làm về dịch ngược, bạn phải tự mình rèn luyện để có kiến thức vững về ngôn ngữ assembly trên nền tảng mà bạn muốn nghiên cứu, bởi mỗi hệ thống/ nền tảng khác nhau sẽ có tâp lệnh Asm riêng.

IDA phân rã các lệnh ASM với một số cú pháp khác so với các trình debugger như OllyDbg/x64dbg, cho nên bạn nào đang quen với cách đọc lệnh trên OllyDbg sẽ cảm thấy hơi rối một chút.

Các lệnh chuyển dữ liệu

MOV

MOV dest, srcSao chép nội dung của toán hạng nguồn (src) tới đích (dest). Thao tác: dest <- src. Lệnh này được sử dụng để chuyển dữ liệu giữa các thanh ghi, giữa một thanh ghi và một ô nhớ hoặc chuyển trực tiếp một số vào một thanh ghi hay ô nhớ. Hiểu cơ bản thì lệnh mov này có thể tương ứng với lệnh gán ở ngôn ngữ bậc cao.

Lấy một số ví dụ, đầu tiên là chuyển giá trị từ một thanh ghi này vào thanh ghi khác.

Câu lệnh như sau:MOV EAX, EDI; EAX nhận giá trị của EDI; còn EDI giữ nguyên giá trị, không bị thay đổi.

Nói chung, chỉ có thể chuyển trực tiếp dữ liệu từ hoặc đến thanh ghi, ngoại trừ thanh ghi EIP không thể là Destination hoặc Source của bất kỳ hoạt động nào. Chúng ta không thể thực hiện câu lệnh sau:

MOV EIP, EAX; Câu lệnh hày hoàn toàn không hợp lệ.

Ví dụ tiếp theo thực hiện chuyển một hằng số vào một thanh ghi như sau:

MOV EAX, 1; chuyển số 1 vào thanh ghi EAX, giá trị trước đó của thanh ghi EAX bị ghi đè lên (thay bằng giá trị mới).

Tiếp theo là câu lệnh thực hiện chuyển giá trị của một địa chỉ ô nhớ không phải là nội dung của ô nhớ đó. Các hình minh họa dưới đây là lệnh trong file VEViewer.exe (download tại đây: https://mega.nz/#!CLIgmS5S!s5qrzoxbRP5W9xblRf8bgVz5UWRcR9yGoICF-PpJbR4 )

Ở ví dụ trên, thanh ghi EAX lúc này sẽ nhận giá trị là một địa chỉ bộ nhớ. Tiền tố offset ở phía trước chỉ ra rằng phải lấy địa chỉ chứ không phải nội dung của ô nhớ đó. Vì vậy, nếu tôi nhấn Q, IDA sẽ chuyển đối câu lệnh này thành dạng:

MOV EAX, 46f038h; đây là một lệnh giống như ở OllyDbg, nhưng không cung cấp cho ta bất kỳ thông tin gì về nội dung của địa chỉ đó. Nếu nhấp chuột phải vào địa chỉ 46f038, ta có thể quay về lệnh gốc ban đầu mà IDA đã hiển thị:

Có một câu hỏi đặt ra: Liệu IDA có thể cho tôi biết về các thông tin bổ sung liên quan tới địa chỉ bộ nhớ đó không?

Nếu tôi chuyển qua cửa sổ Hex View và tìm địa chỉ trên bằng cách nhấn phím tắt G và nhập vào địa chỉ cần đến:

Ta thấy rằng, tại địa chỉ này đang lưu các bytes có giá trị 0x00. Theo mô tả địa chỉ trong IDA, tôi biết đó là một DWORD:

Nếu trở lại màn hình disassembly và nhấp đúp vào địa chỉ này, ta sẽ tới đây:

Tiền tố dword đứng đằng trước một địa chỉ, có nghĩa là nội dung của địa chỉ đó là một DWORD, sau đó có kiểu dữ liệu dd tương ứng với DWORD và tiếp theo là giá trị 0 lưu tại vị trí ô nhớ đó. Như vậy, IDA đang nói với tôi rằng chương trình sử dụng địa chỉ đó để lưu DWORD và hơn thế nữa, ở bên phải tôi thấy các tham chiếu đến vùng code mà DWORD này sẽ được sử dụng.

Như trên hình, ta thấy có hai chỗ tham chiếu. Mỗi mũi tên là một vị trí và khi đặt con trỏ chuột tại đó ta có thể xem trước được mã lệnh tại từng vị trí này.

Nếu tôi nhấn phím X trên đầu của địa chỉ, IDA sẽ hiển thị cho ta thấy các lệnh sử dụng tới địa chỉ đó:

Lệnh đầu tiên trong hình sẽ thực hiện đọc địa chỉ mà chúng ta đã thấy ở trên và lưu vào thanh ghi eax. Câu lệnh thứ hai sẽ ghi một DWORD (phụ thuộc vào giá trị của eax) vào nội dung bộ nhớ tại địa chỉ 0x46F038.

Vì vậy, trong IDA lệnh đầu tiên không chỉ thông báo cho ta rằng nó sẽ chuyển một địa chỉ vào một thanh ghi mà nó còn cho biết địa chỉ đó chứa một DWORD, đó chính là thông tin bổ sung thêm mà IDA cung cấp. Một điểm cộng cho IDA. Vậy nên, chúng ta thấy rằng khi đề cập đến các địa chỉ, IDA sẽ kèm theo tiền tố là offset và khi chúng ta đi tìm nội dung của địa chỉ đó, như trong trường hợp này sẽ có giá trị là 0. IDA mặc định không sử dụng dấu ngoặc [] như OllyDbg nếu đó là một địa chỉ.

Tổng kết lại những gì đã viết dài dòng ở trên 🙂 :

mov     eax, offset dword_46F908

Chuyển địa chỉ 0x46F908 vào thanh ghi EAX, tức là EAX = 0x46F908

mov     eax, dword_46F908

Chuyển nội dung hoặc giá trị tại địa chỉ đó vào thanh ghi EAX, tức là EAX = 0x0

Với những ai đã quen với việc sử dụng OllyDbg thì lệnh này sẽ được hiển thị trong OLLY với cặp ngoặc vuông: MOV EAX, DWORD PTR DS:[46f908]. Còn trong IDA, khi một địa chỉ có tiền tố offset ở phía trước thì có nghĩa là đang nói đến giá trị số của địa chỉ, và khi thay bằng tiền tố dword, thì có nghĩa là sử dụng đến nội dung / giá trị tại địa chỉ đó. Điều này chỉ xảy ra khi đề cập đến các địa chỉ là số, nếu ta làm việc với các thanh ghi thì sẽ thế nào? Quan sát hình dưới đây:

Lệnh trong hình sử dụng dấu ngoặc [] vì rõ ràng bạn không biết giá trị tĩnh mà thanh ghi có thể có được tại thời điểm đó và bạn không thể biết hướng nào bạn sẽ trỏ đến để lấy thêm thông tin từ đó. Tất nhiên trong trường hợp này, ví dụ nếu thanh ghi EDI trỏ tới 0x10000, lệnh này sẽ tìm nội dung trong địa chỉ bộ nhớ đó và sao chép nó vào thanh ghi ECX.

Vì vậy, điều quan trọng là phải hiểu rằng khi IDA sử dụng tiền tố offset ở phía trước của một địa chỉ, nó hàm ý đề cập đến địa chỉ ô nhớ mà không liên quan đến nội dung tại địa chỉ ô nhớ đó.

Lấy một ví dụ khác:

Trong hình trên, chúng ta thấy rằng EAX sẽ nhận giá trị 0x45f4d0 vì nó có tiền tố offset ở phía trước và nó cho ta biết rằng IDA không thể nhận biết được kiểu dữ liệu là gì nên có thêm tiền tố unk (unknown).

Trong lệnh được đánh dấu trên hình, lệnh đầu tiên thực hiện chuyển nội dung của 0x46fc50 là một giá trị DWORD và với lệnh ở dưới nó sẽ chuyển địa chỉ chính nó, nghĩa là giá trị số 0x46FC50.

Chúng ta có thể thấy giá trị đang lưu tại địa chỉ để xem giá trị nào sẽ được chuyển vào EAX tại địa chỉ 0x42f302, nhấp đúp tại 0x46fc50 ta sẽ thấy như sau:

Chúng ta thấy rằng lệnh sẽ thực hiện gán giá trị 0 cho thanh ghi EAX, nếu như một lệnh khác trong danh sách các tham chiếu không được thực hiện và lưu giá trị khác vào địa chỉ này.

Câu lệnh được đánh dấu ở trong hình sẽ lưu một giá trị DWORD vào địa chỉ bộ nhớ, trong khi những lệnh khác chỉ đọc địa chỉ với tiền tố offset hoặc đọc ra giá trị tại địa chỉ.

Ngoài các ví dụ trên, tất nhiên ta cũng hoàn toàn có thể gán hằng số vào thanh ghi 16-bit và 8-bit như chúng ta đã thấy trước đó:

Lệnh trên thực hiện gán giá trị 1 vào thanh ghi AL và giữ nguyên giá trị ban đầu đã có trước đó của thanh ghi EAX. Trường hợp này chỉ có các byte thấp của thanh ghi là bị thay đổi.

Lệnh trên gán nội dung của địa chỉ bộ nhớ 0x459c24 vào thanh ghi AX và cho chúng ta biết giá trị lấy được có kích thước là một WORD.

Và như trong hình chúng ta thấy rằng giá trị ban đầu đang là 0, có thể sau đó khi thực thi chương trình giá trị này sẽ bị thay đổi.

Trong hình minh họa trên, giá trị của thanh ghi AX được gán vào nội dung của địa chỉ đang trỏ bởi thanh ghi EBX. Vì nó là một thanh ghi và không biết đang lưu giá trị nào vào thời điểm đó, nó sử dụng cặp ngoặc [ ] để chỉ ra rằng nó ghi vào nội dung của EBX.

Thêm một ví dụ khác, lệnh trên sẽ thực hiện sẽ gán giá trị của thanh ghi AX vào nội dung của ESI + 8. Tiếp tục, tôi có lệnh như sau:

Nếu tôi nhấp đúp vào tên dài loằng ngoằng như trong hình, IDA sẽ dẫn tới đây:

Như đã biết, bảng IAT là bảng lưu thông tin địa chỉ của các hàm được import khi thực thi file, hầu như luôn luôn nằm tại section .idata hoặc .rdata. Nếu tôi quan sát địa chỉ đó tại màn hình Hex View, nó vẫn chưa có giá trị của hàm bởi vì IAT chỉ được điền đầy đủ khi thực thi chương trình, còn hiện tại thì vẫn chưa.

Có thể IDA của các bạn sẽ không hiển thị tên giống như ở màn hình của tôi. Để chuyển đổi, bạn vào menu Options – Demangle Names và chọn Names:

Để biết hàm này được lấy từ thư viện nào ta cuộn chuột lên trên một chút, kết quả là các hàm được import từ thư viện QtCoreX.dll và ở trên còn có nhiều dlls khác nữa:

Như vậy, qua bài viết này, các bạn đã thấy được rất nhiều ví dụ khác nhau của lệnh MOV. Bạn có thể thực hành và xem tại IDA với tập tin thực thi tôi đã gửi kèm. Trong phần 4, chúng ta sẽ tiếp tục với các câu lệnh khác.

Bên lề: kiến trúc x64 được thiết kế như một phần mở rộng cho x86 và có sự tương đồng mạnh mẽ với các tập lệnh x86. Có một vài sự khác biệt từ góc độ phân tích mã lệnh:

  • Các thanh ghi dùng chung 32 bit (4 byte) eax, ebx, ecx, edx, esi, edi, ebp và esp được mở rộng thành 64 bit (8 byte); các thanh ghi này được đặt tên là rax, rbx, rcx, rdx, rsi, rdi, rbp và rsp.
  • 8 thanh ghi mới được bổ sung thêm là r8, r9, r10, r11, r12, r13, r14, và r15.
  • Một chương trình có thể truy cập vào thanh ghi dưới dạng 64 bit (rax, rbx, v.v.), 32 bit (eax, ebx, v.v.), 16 bit (ax, bx, v.v.) hoặc 8 bit (al, bl, …).
  • Truy cập các thanh ghi r8 – r15 dưới dạng byte, word, dword hoặc qword bằng cách bổ sung thêm b, w, d hoặc q vào sau tên thanh ghi.
  • Trong kiến trúc x86, các tham số của hàm sẽ được đẩy vào ngăn xếp trước khi gọi hàm, trong khi ở kiến trúc x64, bốn tham số đầu tiên được truyền vào các thanh ghi rcx, rdx, r8 và r9 và nếu chương trình còn các tham số khác nữa, chúng sẽ được lưu vào stack. Điều này sẽ khiến cho khó xác định được xem địa chỉ bộ nhớ nào là biến cục bộ hay tham số của hàm.

Hẹn gặp lại các bạn!

Xin gửi lời cảm ơn chân thành tới thầy Ricardo Narvaja!

m4n0w4r

Ủng hộ tác giả

Nếu bạn cảm thấy những gì tôi chia sẻ trong bài viết là hữu ích, bạn có thể ủng hộ bằng “bỉm sữa” hoặc “quân huy” qua địa chỉ:

Tên tài khoản: TRAN TRUNG KIEN
Số tài khoản: 0021001560963
Ngân hàng: Vietcombank

Comments
  1. concavang says:

    Em xin góp ý ở đoạn này:
    “`
    Ví dụ, nếu thanh ghi EAX có giá trị là 0x12345678 thì AX là thanh ghi 16 bit chứa bốn chữ số cuối cùng:

    Thanh ghi AX có thể được tách thành 2 thanh ghi 8 bit, đó là cặp thanh ghi: AH chứa hai số 5 và 6 và AL chứa hai số cuối cùng là 7 và 8:
    “`
    Trên hình minh họa anh phải để là EAX=0x12345678 (thay vì EAX=12345678) mới đúng.

  2. kienmanowar says:

    À cảm ơn em đã góp ý!
    Trên thực tế thanh ghi thì không có 0x ở đầu, mình viết ra giấy là 0x để hiểu rằng đó là kí pháp biểu diễn cho số ở dạng hexa.

    Regards,

  3. Cảm ơn anh. Bài viết rất bổ ích và chi tiết

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.