REVERSING WITH IDA FROM SCRATCH (P10)

m4n0w4r
tradahacking
Published in
15 min readMar 29, 2019

Trong phần này tôi và các bạn sẽ tìm hiểu về tính năng Debugger của IDA. Bản thân IDA hỗ trợ nhiều kiểu Debuggers, để xem cách hoạt động của tính năng này chúng ta mở Crackme Cruehead gốc trong IDA.

Bên lề: Trường hợp IDA hiển thị như hình dưới có nghĩa là đã có một file database trước đó được lưu rồi, ta chọn Overwrite nếu muốn tạo ra một cơ sở dữ liệu mới và ghi đè lên file cũ, lúc đó IDA sẽ tiến hành phân tích toàn bộ file lại từ đầu và tạo một file .idb mới.

Tại màn hình Load a new file… của IDA bỏ chọn Manual load, nếu xuất hiện các cửa sổ khác thì cứ nhấn OK cho đến khi IDA load xong file. Sau khi IDA phân tích xong crackme, lựa chọn Debuggers bằng cách truy cập menu Debugger > Select Debugger như hình:

Các bạn sẽ thấy có nhiều tùy chọn Debugger khác nhau, ta sẽ tìm hiểu dần dần khi có điều kiện. Ở bài viết này chúng ta sẽ lựa chọn Local Win32 debugger để bắt đầu (có thể lựa chọn trình debugger mặc định mỗi khi load một file mới bằng các tích chọn Set as default debugger).

Qua đây, các bạn có thể thấy rằng, IDA không chỉ là một Loader với khả năng static disassembler rất tốt, cung cấp khả năng tương tác mạnh mẽ, mang lại hiệu qủa trong quá trình dịch ngược, mà còn hỗ trợ thêm cả việc debug nhằm hoàn thiện hơn. Tuy nhiên, cách hoạt động của debugger trong IDA sẽ khác một chút so với những gì các bạn đã làm quen với OllyDbg/ x64dbg.

Trong Debuggers > Debuggers options chúng ta có các thiết lập như sau:

Lựa chọn Suspend on process entry point để trình debugger dừng lại tại entry point của crackme. Chúng ta sẽ thực hiện những thay đổi như đã thực hiện trong phần trước. Trước tiên ta sẽ đổi màu các khối và đặt lại tên cho các sub_ như sau:

Sau đó, tại địa chỉ 0x401243 chúng ta đặt một Breakpoint tại lệnh nhảy (JZ) bằng cách nhấn F2 tại địa chỉ này. Tiếp theo, ta chuyển tới một lệnh nhảy khác mà chúng ta cũng đã phân tích trong phần trước, đặt một breakpoint tại lệnh nhảy này đồng thời đổi màu các khối tương tự như hình dưới đây:

Sau khi thiết lập xong, ta thực hiện debug bằng cách chọn Debugger > Start Process hoặc nhấn phím tắt là F9, cửa sổ cảnh báo sẽ xuất hiện như hình:

Cửa sổ cảnh báo trên sẽ luôn luôn xuất hiện khi chúng ta thực hiện debug một tệp thực thi trên máy local của mình. Bởi khi phân tích file bằng Loader của IDA, file đó sẽ không bao giờ được thực thi trên máy chúng ta, nhưng bây giờ nếu file được chạy để phục vụ debug, thì IDA sẽ đưa ra cảnh báo nhắc nhở phải cẩn thận khi thực hiện, vì có khả năng là đó một Virus hay cái gì đó có có thể gây nguy hiểm tới máy tính của chúng ta. Khi đó, ta cần sử dụng tính năng Remote Debugger và thực thi file trong một môi trường ảo hóa hoặc debug hoàn toàn bằng máy ảo. Việc sử dụng tính năng remote debugging như thế nào tôi sẽ cố gắng đề cập trong một bài viết khác.

Do ta đã biết Crackne này hoàn toàn sạch nên nhấn YES để tiếp tục quá trình debug:

Vì chúng ta lựa chọn để dừng lại tại Entry Point, do vậy debugger đã dừng lại tại EP của crackme, tại địa chỉ 0x401000. Nếu bạn nhấn space bar, IDA sẽ chuyển qua chế độ đồ họa như ta đã làm việc ở LOADER. Tiếp theo, các bạn có thể bố trí lại các cửa sổ theo ý thích, thu hẹp lại cửa sổ Output Window, mở rộng cửa số Stack view, thiết lập cửa sổ General registers để quan sát được các thanh ghi cũng như trạng thái của các cờ như hình dưới:

Khi có được một màn hình bố trí hợp lý và phù hợp, ta sẽ lưu nó lại như một thiết lập mặc định. Tại IDA, vào Windows > Save Desktop, tích chọn Default như hình dưới. Làm như thế này, bất cứ khi nào ta thực hiện Debugger, IDA sẽ luôn nạp các thiết lập ta đã đặt mặc định và nếu chúng ta muốn thay đổi nó một lần nữa, ta có thể làm lại mà không có vấn đề gì:

Bên dưới cửa sổ thanh ghi là cửa sổ chứa thông tin về Stack của chương trình:

Ta có màn hình IDA-View EIP là nơi hiển thị toàn bộ các lệnh của chương trình và bên dưới là cửa sổ Hex View hay Hex Dump, hiển thị thông tin của bộ nhớ ở chế độ xem hexa:

Quan sát tại phần dưới của màn hinh disassembly chúng ta thấy các thông tin sau:

Đây là các thông tin về File Offset (Offset của file thực thi khi lưu trên disk) và địa chỉ bộ nhớ (Virtual Address khi file được load vào memory). Nếu chúng ta mở crackme bằng một trình HexEditor, ví dụ như HxD, ta sẽ thấy tại File Offset là 0x600 có cùng opcode như đã thấy tại màn hình disassembly:

Chúng ta đã biết rằng, phím tắt G được sử dụng để đi đến một địa chỉ bộ nhớ bất kỳ, nếu bạn ấn G và nhập vào địa chỉ 0x401389:

Ta sẽ đi đến chỗ đã thiết lập breakpoint. Vào Options > General, cấu hình lại Number of opcode bytes (graph)0:

Trong cửa sổ View > Open Subview > Segments, chúng ta thấy có ba segments được nạp bởi Loader, trong đó CODE segment được nạp vào 0x401000, tiếp theo là DATA segment và .idata segment:

Bất kỳ sự thay đổi nào chúng ta thực hiện với ba segments này đều được lưu lại vì chúng được nạp bởi IDA Loader, nhưng các thay đổi bên ngoài ba segments này sẽ bị mất bởi vì chúng chỉ là các mô-đun nạp bởi Debugger và sẽ không được lưu vào cơ sở dữ liệu của IDA. Như vậy, các mô-đun mà chúng ta đang phân tích và muốn debug phải nằm trong Loader hoặc có xuất hiện chữ L ở đó, rõ ràng chúng cũng sẽ được tải trong Debugger, nhưng có L đồng nghĩa là chúng sẽ ở trong cả hai chế độ và ta vừa có thể phân tích tĩnh tại Loader và vừa debug được bằng Debugger mà không bị mất thông tin.

Một trong những thanh công cụ mà tôi thấy luôn luôn hữu ích là Jump:

IDA sẽ bổ sung thêm hai nút như hình dưới, tương tự như ta thấy ở các trình web browser. Sau khi cho hiển thị toolbar này, ta lưu lại màn hình Windows > Save Desktop.

Thanh công cụ này cho ta khả năng quay trở lại hoặc chuyển tới màn hình đã làm việc lúc trước một cách rất thuận tiện. Nhấn mũi tên

, ta sẽ quay lại màn hình entry point của crackme mà ta đã break trước đó:

Cũng ngay tại menu Debugger, ta có thể xem được danh sách các Breakpoints đã đặt thông qua Breakpoint list (Ctrl+Alt+B):

Và đi tới bất kỳ bp nào mà ta muốn bằng cách nhấp đúp vào bp đó, ví dụ:

Lúc này, ta đang dừng lại tại Entry Point và có hai Breakpoints đã thiết lập trước đó, do vậy ta có thể nhấn F9 để thực thi crackme:

Khi crackme đã thực thi hoàn toàn, vào Help > Register và nhập thông tin bất kì, ví dụ:

Sau đó nhấn OK, ta sẽ dừng lại ở bp tại địa chỉ 0x40138B:

Mũi tên bên trái lúc này đang nhấp nháy, nó báo hiệu cho ta biết nơi vùng code tiếp theo sẽ tiếp tục thực hiện. Quan sát tại cửa sổ Registers, chúng ta thấy rằng thanh ghi EAX đang có giá trị 0x6D:

Nếu tôi gõ chr (0x6D) tại thanh Python của IDA, kết quả trả về cho tôi chữ cái m trong chuỗi “manowar”.

Cũng tại thanh Python, làm tương tự như trên ta thấy với chr(0x41) sẽ cho kết quả là chữ A. Như vậy, kí tự này sẽ được so sánh xem liệu nó có thấp hơn giá trị 0x41 (‘A’) không?

Bên cạnh đó, IDA cũng cung cấp cách biểu diễn khác hiển thị rõ ràng hơn, bằng cách một nhấp chuột phải vào giá trị 41h tại màn hình disassembly, trong số các tùy chọn mà chúng ta có thể lựa chọn giống hình dưới, ta thấy có xuất hiện ‘A’:

Làm tương tự với 0x5A, ta có được chữ cái ‘Z’. Chúng ta thấy nó so sánh giá trị 0x6D với AZ. Tại thời điểm này, chưa phải lúc chúng ta tìm cách giải quyết hoàn toàn crackme này, nhưng các bạn thấy rằng 0x6D lớn hơn 0x41, do vậy ta sẽ không đi đến khối màu đỏ — thực hiện hiển thị thông báo lỗi. Rõ ràng, nó sẽ nhảy vào khối đỏ nếu nó thấp hơn, nhưng nó cũng có thể được đánh giá bằng cách quan sát trạng thái của các cờ.

Trong hình trên, chúng ta thấy lệnh nhảy JB nhảy theo mũi tên màu xanh lá cây trong IDA nếu nhỏ hơn. Khi thực hiện lệnh so sánh, cờ C (còn gọi là CF hoặc C) sẽ được kích hoạt, theo đó lệnh nhảy sẽ nhảy nếu cờ C = 1. Quan sát các cờ trong IDA:

Do cờ C = 0 nên lệnh nhảy sẽ không được thực hiện và nó sẽ theo hướng của mũi tên đỏ. Vậy điều kiện tính toán sẽ thế nào để cho cờ Carry Flag được kích hoạt?

Carry Flag cung cấp cho chúng ta thông tin rằng có gì đó đã sai trong một phép toán giữa các số nguyên unsigned. Nếu tôi làm phép trừ giống như cách thực hiện của lệnh CMP (không lưu lại kết quả), phép tính 0x6D-0x41 cho kết quả là 0x2C, đây là một số dương và sẽ không có vấn đề gì. Tuy nhiên, nếu ví dụ, giá trị của tôi là 0x30, bằng cách trừ đi 0x41, kết quả sẽ là -0x11:

Đây là một giá trị âm và không được chấp nhận là kết quả của một phép toán số dương, vì nếu bạn tiếp tục làm việc ở hệ thập lục phân:

Kết quả có được là 0xFFFFFFEF và đây sẽ là một số dương có giá trị rất lớn 4294967279 và không cách nào để thực hiện phép trừ 0x30–0x41 ra kết quả bằng 0xFFFFFFEF :)

Làm thế nào để chúng ta biết được cần phải quan tâm đến dấu của kết quả khi thực hiện một phép toán?

Điều này phụ thuộc vào lệnh nhảy, trong trường hợp này JB là một lệnh nhảy được sử dụng sau khi so sánh các số nguyên không dấu (unsigned). Đối với các phép toán giữa các số nguyên có dấu (signed) sẽ sử dụng lệnh JL. Ví dụ, nếu tôi so sánh số 0xFFFFFFFF với 0x40. Trong một lệnh nhảy không quan tâm đến dấu thì rõ ràng là số này lớn hơn, nhưng nếu nó là một bước nhảy nơi mà dấu cần được xem xét, lúc đó 0xFFFFFFFF sẽ là -1 và nó sẽ nhỏ hơn 0x40.

Vì vậy, để đánh giá liệu so sánh sử dụng dấu hay không, chúng ta phải xem xét lệnh nhảy tiếp theo để đưa ra quyết định.

Nếu lệnh nhảy là bất kỳ trong số trên, nó được xem xét là Without Sign, còn nếu thuộc danh sách trong bảng dưới thì được xem xét là With Sign.

Các bạn thấy lệnh nhảy JE (nhảy nếu hai toán hạng bằng nhau) đều xuất hiện ở cả hai bảng vì trong trường hợp đó dấu không còn quan trọng nữa. Nếu hai toán hạng bằng nhau, nó sẽ được kích hoạt bằng cách đặt cờ ZF là 1. Chúng ta cũng thấy rằng lệnh JG (nhảy nếu lớn hơn) trong bảng tổng hợp nhảy có dấu. Cùng một mục đích nhảy như thế là lệnh JA, nhưng trong bảng tổng hợp nhảy không dấu.

Trong quá trình phân tích hàng ngày chúng ta sẽ thường xuyên phải quan sát trạng thái của các cờ, và nếu thấy một lệnh nhảy JB thì ta sẽ biết rằng đó là một so sánh giữa các số dương hay các số nguyên không dấu, và nếu toán hạng đầu tiên là nhỏ hơn thì nó sẽ nhảy.

Nếu tiếp tục thực thi chương trình, bạn sẽ thấy nó liên tục dừng lại ở breakpoint đã đặt, từ đó sẽ thấy rằng ta đang ở một vòng lặp thực hiện đọc từng kí tự của chuỗi tên và so sánh với 0x41, nếu có một kí tự nhập vào thấp hơn thì crackme sẽ hiển thị thông báo lỗi. Tuy nhiên, vừa rồi tôi nhập đều là các chữ cái (manowar) trong ô Name nên sẽ không xảy ra việc hiển thị thông báo lỗi này. Nhưng thử dừng quá trình debug lại và tiến hành debug lại từ đầu, lúc này tôi nhập tên là 22ricnar và key là 98989898:

Nhấn OK, ta lại dừng lại tại breakpoint tại địa chỉ 0x40138B:

Bây giờ, ta thấy rằng kí tự đầu tiên là 0x32 tương ứng với số 2 trong chuỗi 22ricnar. Vì 0x32 nhỏ hơn 0x41, mũi tên màu xanh lá cây sẽ nhấp nháy, tức là lệnh nhảy sẽ được thực hiện, cờ C được kích hoạt bởi vì khi lấy 0x32 trừ đi 0x41 trong một phép trừ unsigned, kết quả có được là số âm, đó là một lỗi sẽ kích hoạt cờ C.

Nếu nhấn chuột phải tại cờ C, ta có thể thiết lập nó về 0:

Khi thiết lập xong thì đồng thời mũi tên đỏ cũng nhấp nháy vì chúng ta vừa mới thay đổi lại điều kiện nhảy.

Nếu chúng ta cho Run tiếp, nó sẽ lại dừng lại tại breakpoint khi kiểm tra kí tự tiếp theo cũng là 2 trong chuỗi 22ricnar, và mũi tên màu xanh lá cây sẽ lại nhấp nháy một lần nữa. Làm tương tự như trên, ta đảo ngược lại cờ CF, thiết lập nó là 0. Những lần break tiếp theo tại lệnh nhảy này tương ứng với chuỗi ricnar, các kí tự từ lúc này trở đi đều lớn hơn 0x41, do đó sau phép so sánh sẽ không kích hoạt cờ CF và crackme sẽ rẽ nhánh theo mũi tên đỏ.

Sau khi vượt qua được quá trình kiểm tra từng kí tự trong chuỗi tên chúng ta dừng lại tại lệnh nhảy thứ hai (đã đặt breakpoint):

Tại đây, ta thấy crackme thực hiện so sánh xem EAX và EBX có bằng nhau không? Chuyển qua cửa sổ Registers, quan sát thấy giá trị của hai thanh ghi là khác nhau, do đó mũi tên đỏ nhấp nháy để báo hiệu sẽ rẽ nhánh vào đoạn code thông báo lỗi:

Do giá trị của hai thanh ghi EAXEBX không bằng nhau cho nên cờ Z không được bật:

Nếu ta thay đổi giá trị của cờ ZF, crackme sẽ rẽ nhánh theo hướng mũi tên màu xanh lá cây để tới vùng code hiển thị Good Work. Nhấn chuột phải tại cờ ZF và chọn Increment Value:

Nhấn F9 để thực thi, ta có được kết quả như sau:

Toàn bộ quá trình thực hiện ở trên cùng đạt một mục đích tương tự như khi chúng ta thực hiện patch crackme này, chỉ khác ở chỗ là ta không thay đổi code của chương trình, hoàn toàn công việc chỉ là thay đổi trạng thái các cờ trong quá trình debug.

Ngoài ra, có một cách khác là thay đổi thanh ghi EIP để nó trỏ tới lệnh tiếp theo được thực hiện. Ví dụ, giả sử ta đang dừng tại lệnh nhảy tại địa chỉ 0x401243, lúc này lựa chọn địa chỉ 0x40124c và nhấn chuột phải và chọn Set IP (hay nhấn phím tắt là CTRL + N):

Lúc đó, chương trình sẽ tiếp tục thực hiện lệnh từ địa chỉ 0x40124c giống như ta đã làm khi thực hiện thay đổi các cờ.

Hẹn gặp lại các bạn ở phầ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

--

--