Task 22 [Day 17] Reverse Engineering ReverseELFneering
1. Câu chuyện.
McSkidy chưa bao giờ thực sự đụng đến các ngôn ngữ cấp thấp - đây là điều họ phải học trong hành trình đánh bại con quái vật Giáng sinh.
Follow along with Darkstar7474 and solve Day 17!
2. Giới thiệu về ASM x86-64
Máy tính thực thi mã máy, được mã hóa dưới dạng byte, để thực hiện các tác vụ trên máy tính. Vì các máy tính khác nhau có các bộ xử lý khác nhau, mã máy được thực thi trên các máy tính này là dành riêng cho bộ xử lý. Trong trường hợp này, chúng ta sẽ xem xét kiến trúc tập lệnh Intel x86-64 thường thấy nhất hiện nay. Mã máy thường được biểu diễn bằng một dạng mã dễ đọc hơn được gọi là mã lắp ráp. Mã máy này thường được tạo ra bởi một trình biên dịch, lấy mã nguồn của một tệp, và sau khi trải qua một số giai đoạn trung gian, tạo ra mã máy có thể được thực thi bởi máy tính.
Không đi sâu quá nhiều chi tiết, Intel bắt đầu bằng cách xây dựng tập lệnh 16 bit, tiếp theo là 32 bit, sau đó cuối cùng họ tạo ra 64 bit. Tất cả các tập lệnh này đã được tạo để tương thích ngược, vì vậy mã được biên dịch cho kiến trúc 32-bit sẽ chạy trên các máy 64-bit. Như đã đề cập trước đó, trước khi một tệp thực thi được tạo ra, mã nguồn đầu tiên được biên dịch thành assembly (tệp .s), sau đó trình hợp dịch chuyển đổi nó thành chương trình đối tượng (tệp .o) và các hoạt động với trình liên kết cuối cùng biến nó thành có thể thực thi.
Cách tốt nhất để thực sự bắt đầu giải thích lắp ráp là đi sâu vào. Chúng tôi sẽ sử dụng radare2 để làm điều này -
- 1. Nhấn nút "Triển khai" ở trên cùng bên phải của tác vụ này
- 2. Chờ địa chỉ IP của Phiên bản đích hiển thị
- 3. Đăng nhập vào Phiên bản của bạn bằng thông tin sau:
IP Address: MACHINE_IP
Username: elfmceager
Password: adventofcyber
Hãy tiếp tục chạy qua cách hoạt động chính xác của Radare2. Mặc dù bạn không nên làm điều này nếu chương trình không xác định, chúng tôi có thể an toàn để thực thi để xem điều gì sẽ xảy ra như vậy:
Chương trình trên cho thấy có 3 biến (a, b, c) trong đó c là tổng của a và b.
Đã đến lúc để xem điều gì đang xảy ra! Chạy lệnh r2 -d ./file1
Thao tác này sẽ mở tệp nhị phân trong chế độ gỡ lỗi. Khi hệ nhị phân được mở, một trong những điều đầu tiên cần làm là yêu cầu r2 phân tích chương trình và điều này có thể được thực hiện bằng cách nhập: aa
Lưu ý, khi sử dụng lệnh aa trong radare2, quá trình này có thể mất từ 5-10 phút tùy thuộc vào hệ thống của bạn.
Which is the most common analysis command. It analyses all symbols and entry points in the executable. The analysis, in this case, involves extracting function names, flow control information, and much more! r2 instructions are usually based on a single character, so it is easy to get more information about the commands.
Vd: Để được trợ giúp chung, tôi có thể chạy:? hoặc nếu tôi muốn hiểu thêm về một tính năng cụ thể, chúng tôi có thể chạy a?
3. Máy tính báo ... hoàn thành ?!
Khi quá trình phân tích hoàn tất, bạn sẽ muốn biết bắt đầu phân tích từ đâu - hầu hết các chương trình đều có điểm đầu vào được xác định là chính. Để tìm danh sách các hàm đã chạy: afl
Lưu ý rằng địa chỉ bộ nhớ có thể khác nhau trên máy tính của bạn.
Như đã thấy ở đây, thực sự có một chức năng ở chính. Chúng ta hãy kiểm tra mã lắp ráp tại main bằng cách chạy lệnh pdf @main. Trong đó pdf có nghĩa là chức năng tháo gỡ. Làm như vậy sẽ cho chúng ta cái nhìn sau:
- Truyền dữ liệu giữa bộ nhớ và thanh ghi, và ngược lại
- Thực hiện các phép toán số học trên sổ đăng ký và dữ liệu
- Chuyển điều khiển đến các phần khác của chương trình Vì kiến trúc là x86-64, các thanh ghi là 64 bit và Intel có danh sách 16 thanh ghi:
Initial Data Type | Suffix | Size (bytes) |
Byte | b | 1 |
Word | w | 2 |
Double Word | l | 4 |
Quad | q | 8 |
Single Precision | s | 4 |
Double Precision | l | 8 |
Khi xử lý thao tác bộ nhớ bằng cách sử dụng thanh ghi, có những trường hợp khác cần được xem xét:
- (Rb, Ri) = MemoryLocation[Rb + Ri]
- D(Rb, Ri) = MemoryLocation[Rb + Ri + D]
- (Rb, Ri, S) = MemoryLocation(Rb + S * Ri]
- D(Rb, Ri, S) = MemoryLocation[Rb + S * Ri + D]
5. Đọc hướng dẫn!
Một số hướng dẫn quan trọng khác là:
- leaq source, destination: hướng dẫn này đặt đích đến địa chỉ được biểu thị bằng biểu thức từ nguồn nào tới đích nào
- addq source, destination: destination = destination + source
- subq source, destination: destination = destination - source
- imulq source, destination: destination = destination * source
- salq source, destination: destination = destination << source where << là toán tử dịch chuyển bit bên trái
- sarq source, destination: destination = destination >> source where >> là toán tử dịch chuyển bit bên phải
- xorq source, destination: destination = destination XOR source
- andq source, destination: destination = destination & source
- orq source, destination: destination = destination | source
Bây giờ chúng ta hãy thực sự xem qua mã trả về để xem các hướng dẫn có ý nghĩa gì khi được kết hợp.
Dòng bắt đầu bằng sym.main cho biết chúng ta đang xem xét hàm chính. 3 dòng tiếp theo được sử dụng để biểu diễn các biến được lưu trữ trong hàm. Cột thứ hai chỉ ra rằng chúng là số nguyên (int), cột thứ 3 chỉ định tên mà r2 sử dụng để tham chiếu chúng và cột thứ 4 hiển thị vị trí bộ nhớ thực tế.
3 hướng dẫn đầu tiên được sử dụng để phân bổ không gian trên ngăn xếp đó (đảm bảo rằng có đủ chỗ cho các biến được phân bổ và hơn thế nữa). Chúng ta sẽ bắt đầu xem xét chương trình từ hướng dẫn thứ 4 (movl $4
). Chúng tôi muốn phân tích chương trình trong khi nó chạy và cách tốt nhất để làm điều này là sử dụng các điểm ngắt.
Một điểm ngắt chỉ định nơi chương trình sẽ ngừng thực thi. Điều này rất hữu ích vì nó cho phép chúng ta xem trạng thái của chương trình tại điểm cụ thể đó. Vì vậy, chúng ta hãy đặt điểm ngắt bằng lệnh db trong trường hợp này, nó sẽ là db 0x00400b55 Để đảm bảo điểm ngắt được đặt, chúng tôi chạy lại lệnh pdf @main và nhìn thấy dấu b nhỏ bên cạnh hướng dẫn mà chúng tôi muốn dừng lại.
Bây giờ chúng ta đã thiết lập một điểm dừng, hãy chạy chương trình bằng cách sử dụng dc
Chạy dc sẽ thực thi chương trình cho đến khi chúng ta chạm điểm ngắt. Khi chúng ta nhấn điểm ngắt và in ra hàm chính, đoạn trích dẫn là lệnh hiện tại sẽ hiển thị nơi thực thi đã dừng lại. Từ các ghi chú ở trên, chúng ta biết rằng lệnh mov được sử dụng để chuyển các giá trị. Câu lệnh này đang chuyển giá trị 4 vào biến thelocal_ch. Để xem nội dung của biến local_ch, chúng ta sử dụng lệnh sau px @ memory-address Trong trường hợp này, địa chỉ bộ nhớ tương ứng cho local_ch sẽ là rbp-0xc (từ vài dòng đầu tiên của @pdf main) Lệnh này in ra các giá trị của bộ nhớ trong hex:
Điều này cho thấy rằng biến hiện không có bất kỳ thứ gì được lưu trữ trong nó (nó chỉ là 0000). Hãy thực hiện lệnh này và chuyển sang lệnh tiếp theo bằng lệnh sau (chỉ chuyển đến lệnh tiếp theo) ds Nếu chúng ta xem vị trí bộ nhớ sau khi chạy lệnh này, chúng ta nhận được như sau:
Chúng ta có thể thấy rằng 2 byte đầu tiên có giá trị 4! Nếu chúng ta thực hiện quy trình tương tự cho hướng dẫn tiếp theo, chúng ta sẽ thấy rằng biến local_8h có giá trị 5.
Nếu chúng ta đi đến lệnh movl local_8h,% eax, chúng ta biết từ các ghi chú rằng điều này sẽ di chuyển giá trị từ local_8h sang thanh ghi %eax. Để xem giá trị của thanh ghi % eax, chúng ta có thể sử dụng lệnh:
Nếu chúng tôi thực hiện lệnh và chạy lại lệnh dr, tôi nhận được:
Về mặt kỹ thuật, quy trình này bỏ qua hướng dẫn trước đó movl local_ch, %edx
nhưng quy trình tương tự có thể được áp dụng cho nó. Hiển thị giá trị của rax (phiên bản 64 bit) là 5. Chúng ta có thể thực hiện tương tự đối với các hướng dẫn tương tự và xem giá trị của các thanh ghi đang thay đổi. Khi đến với movl local_ch, %edx
, chúng ta biết rằng điều này sẽ thêm các giá trị trong edx và eax và lưu trữ chúng trong eax. Chạy dr cho chúng ta thấy rax chứa 5 và rdx chứa 4, vì vậy tôi mong đợi rax chứa 9 sau khi lệnh được thực thi:
Việc thực thi ds để chuyển đến lệnh tiếp theo sau đó thực hiện dr để xem biến thanh ghi cho chúng ta thấy rằng chúng ta đã đúng:
Một số hướng dẫn tiếp theo liên quan đến việc di chuyển các giá trị trong thanh ghi sang các biến và ngược lại:
Sau đó, một chuỗi (là đầu ra được tải vào một thanh ghi và chức năng printf
được gọi ở dòng thứ 3. Dòng thứ hai xóa giá trị của eax vì eax đôi khi được sử dụng để lưu trữ kết quả từ các hàm. Dòng thứ 4 xóa giá trị của eax. Dòng thứ 5 và thứ 6 được sử dụng để thoát khỏi chức năng chính.
7. Để hoàn thiện quy trình làm việc...
Công thức chung để làm việc thông qua một cái gì đó như là:
- Đặt các điểm ngắt thích hợp
- Sử dụng ds để di chuyển qua các hướng dẫn và kiểm tra các giá trị của thanh ghi và bộ nhớ
- Nếu bạn mắc lỗi, bạn luôn có thể tải lại chương trình bằng lệnh ood
Bạn có thể thấy bảng radare2 này hữu ích trong cuộc phiêu lưu của mình ...
8. Thách thức
Sử dụng kiến thức mới tìm thấy của bạn về Radare2 để phân tích tệp "challenge1" trong Instance MACHINE_IP được đính kèm với nhiệm vụ này để trả lời các câu hỏi bên dưới.
0 Comments